There are a couple of opcodes that have to do with sprites.
The Wikipedia article says this about opcode Dxxx:
Draws a sprite at coordinate (VX, VY) that has a width of 8 pixels and a height of N pixels. Each row of 8 pixels is read as bit-coded (with the most significant bit of each byte displayed on the left) starting from memory location I; I value doesn't change after the execution of this instruction. As described above, VF is set to 1 if any screen pixels are flipped from set to unset when the sprite is drawn, and to 0 if that doesn't happen.
A CHIP-8 sprite is 8-pixels wide and up to 15 lines high. Since it is 8 pixels wide, each line is in a single byte. An encoded sprite for a Space Invaders alien would look like this:
Sprite map Binary Hex X.XXX.X. 0b10111010 $BA .XXXXX.. 0b01111100 $7C XX.X.XX. 0b11010110 $D6 XXXXXXX. 0b11111110 $FE .X.X.X.. 0b01010100 $54 X.X.X.X. 0b10101010 $AA
The hex codes for the sprite are put in memory just like that. This program sequence would draw this little guy on the display at 10, 12:
PC Opcodes Assembly 0210 620A MOV V2,#$0A 0212 630C MOV V3,#$0C 0214 A220 MOV I,#$220 0216 D236 SPRITE V2, V3, #$6 0218 1240 JUMP $240 0220 BA7C Sprite data for 6 bytes 0222 D6FE 0224 54AA
Now for the drawing itself, the easiest implementation is to do it bit by bit. The algorithm is something like this:
For each line in the sprite
Starting with MSB, For each bit in the sprite (that is still on the screen)
If the bit is set
Determine the address of the effected byte on the screen
Determine the effected bit in the byte
Check to see if the screen's bit is set and set VF appropriately
XOR the source bit and screen bit
Write the effected bit to the screen
It took me a while to determine that the sprite is clipped at the screen edge instead of wrapping around. If the bit in the sprite is not set, then no action is taken.
Implementation of opcode FX29 SPRITECHAR VX
requires that we make a 4x5 font somewhere in the address space that is accessible by the program. Since the CHIP-8 program starts at 0x200 in memory, the bytes starting at zero are unused. We'll put the font there after we read the program into the memory.
I'll hand draw the font out. Since the characters are 4 pixels wide but sprites are 8 pixels wide, I'll pad the other 4 pixels with zeros. I'll show the corresponding hex number, but I'm actually just going to leave the sprite in binary in my code to make it easier to modify by hand.
/* Sprite Binary Hex .xx..... 0b01100000 0x60 x..x.... 0b10010000 0x90 x..x.... 0b10010000 0x90 x..x.... 0b10010000 0x90 .xx..... 0b01100000 0x60 /* uint8_t font4x5[] = { //0 0b01100000, 0b10010000, 0b10010000, 0b10010000, 0b01100000, //1 0b01100000, 0b00100000, 0b00100000, 0b00100000, 0b01110000, /* etc...*/
I'll modify my InitChip8() routine to copy the font into memory starting at zero, and give an implementation of FX29.
#define FONT_BASE 0 #define FONT_SIZE 5*16 //5 bytes each * 16 characterss Chip8State* InitChip8(void) { Chip8State* s = calloc(sizeof(Chip8State), 1); s->memory = calloc(1024*4, 1); s->screen = &s->memory[0xf00]; s->SP = 0xfa0; s->PC = 0x200; //Put font into lowmem memcpy(&s->memory[FONT_BASE], font4x5, FONT_SIZE); return s; } static void OpFX29(Chip8State *state, uint8_t *code) { int reg = code[0]&0xf; state->I = FONT_BASE+(state->V[reg] * 5); }
My program is going to use the MOVBCD, SPRITECHAR, and SPRITE instructions together to draw the player's score on the screen. I might write it like this:
;assume score is in register 3 ; we want to draw it at 16, 5 MOV V5, #$10 MOV V6, #$5 ;Change the score into base-10 ;Use scratch memory at $300 MOV I,$300 MOVBCD V3 ;Read them back into registers MOVM V0-V2,(I) ;load address of sprite for hundreds digit into I SPRITECHAR V0 ;Draw sprite (pointed to by I) at V5, V6 for 5 lines SPRITE V5,V6,#$5 ; Increment x by 6 pixels ADDI V5,#$06 ; Do same for tens and ones digits SPRITECHAR V1 SPRITE V5,V6,#$05 ADDI V5,#$06 SPRITECHAR V2 SPRITE V5,V6,#$05
All CHIP-8 programs I've seen are visual and pretty short. I'm not going to be able to test the emulator until I can see the results. So I'll start with a platform port next.
← Prev: chip-8-emulator Next: chip-8-cocoa-port-part-1 →Post questions or comments on Twitter @realemulator101, or if you find issues in the code, file them on the github repository.