In an earlier post, I described how I was parsing commands input (via serial) into the Zolatron 64 8-bit homebrew computer in assembly language. That parsing process matched an input command with an internal list and the result was a ‘token’, a single integer value of $80 or more representing that command that is placed in a memory location.
Or not. If the system failed to get a match, it would return a suitable error code.
Now let’s look at what we do with those tokens. There’s really not much to this, but I thought it worth describing as it’s a good use of a jump table.
First, we set up our table – all done with the cunning use of labels. The code to execute each command – commands such as ‘LM’ or ‘PRT’ – is in it’s own section of code at addresses defined by suitable labels. I’ve given these labels names such as cmdprcLM and cmdprcPRT.
I then use these labels to create a table of addresses starting at the address defined by the label ‘.cmdprcptrs’.
.cmdprcptrs ; these entries need to be in the same order as equw cmdprcSTAR ; the CMD_TKN_* definitions equw cmdprcLM equw cmdprcLP equw cmdprcPRT equw cmdprcVERBOSE equw cmdprcVERS
As you can see from the use of the keyword ‘equw’, each of these entries is two bytes – as you’d expect for an address on a 6502-based machine.
So, to clarify, what we have above is a chunk of memory, starting at the address defined by ‘cmdprcptrs’. The first two bytes of this memory contain the address of the subroutine that handles the ‘*’ command, the second two bytes provide the memory of the code for the ‘LM’ command ,and so on.
Finding the address
As I mentioned, the parsing routine puts the relevant token for the command in a memory location, which I refer to using the constant FUNC_RESULT. This location can be in any part of memory. Jumping to the appropriate subroutine is now a simple matter although, like a crafty chef, there’s one other bit of preparation that makes things easier.
I’ve defined two bytes in zero-page memory (ie, the first 256 bytes of RAM) with the constants TBL_VEC_L and TBL_VEC_H. (They are at addresses $70 and $71, as it happens, although that’s arbitrary so long as they are contiguous, both in the zero-page area and the ‘L’ byte – the low byte – comes first.)
Here’s the code for jumping to the command subroutines:
lda FUNC_RESULT sec sbc #$80 ; this turns the token into an offset for our asl A ; cmdprcptrs table, once it's multiplied by 2 tay ; transfer to Y to use as an offset lda cmdprcptrs,Y ; load LSB of pointer sta TBL_VEC_L ; and store in our table vector lda cmdprcptrs+1,Y ; load MSB of pointer sta TBL_VEC_H ; also store in table vector jmp (TBL_VEC_L) ; now jump to location indicated by pointer
We load the token value into A and then subtract $80 from it. I’ve used the SBC operator, but ANDing with binary value #01111111 would do the trick too. Actually, that might be better. Anyhow, the token values start at $80, so by subtracting that value we now turn the token value into an offset – essentially the position that the command appears in the command table (numbering, as always, from 0).
The address pointers in the table are each two bytes, so to find the address of the command we’re seeking, we take this offset, multiply it by two and then add that to the address of the start of the pointer table (cmdprcptrs).
Thanks to the marvels of binary, the multiplying by two is done simply by shifting the value of the offset one bit to the left, using ASL. This new value is placed in the Y register to use as an offset. Using absolute indexed addressing, ‘cmdprcptrs,Y’ gives the first byte of the address we need for the command subroutine. Because the 6502 is little-endian, this is the low byte, so we put this in TBL_VEC_L. The address cmdprcptrs+1,Y then provides the high byte of the address, which goes into TBL_VEC_H (although TBL_VEC_L+1 would have worked just as well).
And now we find out why the TBL_VEC_* addresses need to be in zero page. We can jump easily to the address using indirect addressing, With this addressing mode you can provide a single byte as an operand (in this case TBL_VEC_L) but the processor knows to read two bytes starting at that zero-page address for the actual location in memory to which it needs to jump.
And that’s it. Couldn’t be easier, really.
As usual, all the code is on GitHub and it’s fairly heavily commented.