20/02/2024
Continuing the series of coding tricks - in my ongoing quest to produce content for the most niche audience on the entire Internet - the second trick I’m going to explain is this little nugget:
STRA, r0 $1EC9, r0-
STRA, r0 $1FC9
The purpose of these two lines of code is to blank the left and right Score values at the beginning of the game. To do so, they each need to be set to a number greater than 9, as explained in the Signetics 2636 Programmable Video Interface (PVI) manual (see 2nd pic).
There are a few things to know in advance:
Firstly, the locations of the Score values are 1FC8 (left) and 1FC9 (right).
Secondly, each score value is two digits, with each digit encoded in one of the two four bit 'nibbles' in those bytes.
(See the relevant bit of the PVI manual in pic 3)
Finally, r0 is known to be clear (set to zero) when these operations are executed (in fact we will have placed these operations here *because* we know r0 is 0, probably because we just used it to set some other things to zero during program initialisation).
We could, of course, just set r0 to a number greater than $AA (186 in decimal) and just store that in $1FC8 and $1FC9 - but why do something so obvious when we can take advantage of a little feature called "Absolute Indexed Addressing"?!
Absolute addressing means loading a value from, or storing a value in, a specific memory location - for instance "Store the value contained in register X in the memory address at Y".
Indexed addressing means you can declare an offset in addition to the memory address - so "Store the value contained in register X in the memory address at Y plus the value contained in register Z".
This can be very useful indeed, but it gets even better:
Not only can we offset by a fixed amount, but we can also tell the processor to increment or decrement the offset every time we execute the operation!
So we can say "Store the value contained in register X in the memory address at Y plus the value contained in register Z, but first take one away from the value in register Z".
The word "first" is very important - the processor will always do the required subtraction or incrementation *first* before evaluating the rest of the instruction.
So let's look back at that first line:
STRA, r0 $1EC9, r0-
This means "Store the value contained in register 0 in memory location $1EC9 plus the value contained in register zero, but take one away from the value in register 0 first".
So the first thing the processor does is to decrement r0, as that's specified in the index. But we know that the value of r0 is zero, so what happens when we subtract one from zero? Well, in standard 'logical' binary arithmetic there are no negative numbers and the result will be all 1s, or 255 in decimal / $FF in hexadecimal. The value effectively 'wraps' round to the end of the 8 bit number space.
(The Signetics 2650 can also handle signed binary arithmetic, and compare so called "twos complement" numbers in which bit 7 is used to indicate the sign, but that's a whole other story...)
Anyway, by evaluating the index first, r0 is now set to $FF, which is definitely greater than the $AA required to blank the scores. We just need to put it in the correct place, which will be the memory location for the left score digits *minus* the offset which is now $FF.
As we saw earlier, the left score location is $1FC8, and when we subtract $FF we get $1EC9 - and that indeed is the initial absolute address in the instruction.
Now that r0 is $FF, we can just store that in the right hand Score location, and that's what the second line of code does.
STRA,r0 $1FC9
So, hopefully you can see how this bit of code blanks the score values, but why do it this way instead of just setting r0 to $FF (or just $AA) and storing it in those two locations?
Well, once again we are saving space: two bytes, to be exact, as we don't need to load a value into r0, and Store Absolute (STRA) is a 3 byte instruction whether we use an index or not.
We also save two clock cycles, but as we blank the score during initiatization, (i.e. before we enter the main game loop), we don't really care about speed here.
For completeness' sake we could also subtract 1 from r0 (SUBI 1), but this would also cost two bytes, or we could subtract the value of another register from r0 using SUBZ,rx, (assuming we know the value of that register at the point the instruction is executed and it's between $1 and $55), which is a smaller instruction but would still cost one unnecessary byte.
So using Absolute, Indexed Addressing to blank the scores, as with just about all code optimisations, is a bit more complicated and harder to follow but also quite elegant when you understand it properly. We're basically using a 'free' memory addressing feature to turn a zero into a big number and store it in a specific place 🎉