The only bit of interest implementation-wise is how we concatenate the operands to form a memory address:
PC-related - 0xbnnn
This really isn’t much:
And the implementation is equally straight-forward:
Storing/loading registers - 0xfx55, 0xf65
As the latter is the inverse of the former, it makes for a nice test:
The implementation was surprisingly straight-forward:
Random number - 0xcxnn
Note that in the test we use srand to set the seed for further calls to the rand function - this makes our tests reproducible. And actually once we start debugging actual ROMs, we may want this as an option to make things a little more deterministic.
Nothing fancy for the implementation - by default rand will generate a number between 0 and RAND_MAX, so we % 256 to ensure we get something in the 0-255 range.
Setting sprites - 0xfx29
The CHIP-8 specification stores a pre-defined set of “sprites” in memory. Those represent an 5x8 bit array where a 1 is a pixel turned on, and 0 turned off - one for each hex character. See here for a visual representation. Note each hex character occupies only half the size (so 5x4).
I’m not entirely sure which offset those should start at so let’s keep all the sprites in a single block starting at SPRITES_OFFSET which we’ll set to 0x0 for now.
This will need to be called during the ROM’s initialisation routine but we can handle that later.
Here’s the test:
And the implementation:
Note we expose copySpritesToMemory in mylib.h so it’s available in the test.
Displaying sprites - 0xdxyn
This is the instruction we need to start displaying things! very excited
It essentially takes (x,y) coordinates and a value n representing how many “rows” we write. The data is what the register I is point to. So for instance if we wanted to display the character 0x0 in the top-left corner, we would set i to the 0x0 sprite address (SPRITES_OFFSET) and as the sprite is 5-rows high, n would be 5.
The second part is that if we flip any pixels whilst doing so (so turning one off or on), we set the register 0xf to 1.
As always, let’s start with a test!
And here’s the implementation:
Since the width of a sprite is always 8 bites we need to move in (SCREEN_WIDTH/8)*(y+row) increments.
From a state-perspective, we set the draw boolean if any bits have been flipped - this will trigger a screen refresh if there has been any changes.
Keyboard - 0xex9e, 0xexa1, 0xfx0a
The first 2 are essentially conditionals on key presses. We can mimic that behaviour in our test by setting the appropriate key registers. I originally misread the spec as this being a conditional on whether key x was pressed - it’s actually whether the key corresponding to the value in rX has been pressed. Subtle, but without it nothing will behave as expected :)
Unlike most of our previous tests, we’ll run this one twice - once with a key depressed, and one with it pressed:
The implementation of one is the opposite of the other:
Adding this to processOp covers the whole 0xe block:
The last one is a bit trickier as it’s a blocking operation - that is, we need to wait until a key is pressed before we proceed - and whilst we wait, nothing happens. The easiest way to implement this is to simply not increment the program counter until a key is pressed.
Note that the implementation of this function is stateless - if key 1 is already being pressed, this function will store key 1 into the relevant register and proceed. I don’t know if hardware implementations of CHIP-8 support multiple key presses at the same time but for simplicity, we’ll assume not for now.
One way I could see working around this is to store some sort of timestamp along with each key press, which would mean making the evaluation loop time-aware. But let’s kick this down the road.
Binary-coded decimal - 0xfx33
I didn’t expect to come across such an instruction. If I=0x300 and register 1 has 0x81 (which is 129 in decimal), we then have the following:
0x300 = 1
0x301 = 2
0x302 = 9
A small test should suffice:
And the implementation:
Timers - 0xfx07, 0xfx15, 0xfx18
Mmm - so timing is something we didn’t really cover in our main event loop. Right now our interpreter is simply processing instructions as quickly as it can without any notion of “wall clock time”. What this really means is that if our CPU is slow instructions will be processed slowly, and if it’s super fast your game of Snake will be Game Over before you can even blink.
We’ll sort this out in a separate post. Right now we’ll simply implement stubs that set the registers and do nothing else.
Conclusion
OK - I think that means we’re ready to go back to our test ROM and start debugging. Hopefully we have implemented all the instructions required with minimal issues (but I know better than thinking it’ll work flawlessly from the get-go :-/)