codeslinger.co.uk

The chip8 opcode emulation.

Opcode Examples:

Below is a set of example opcode for the chip8 system. The idea is to give examples of how to emulate a CPU and how to decode an opcode.

Opcode 2NNN - Call subroutine at NNN:

This opcode calls a function inside the game code and once it has finished executing the function it returns to where it previously was. The NNN part of the 2NNN opcode is the beginning of the subroutine. Our emulated function of opcode 2NNN simply saves the current program counter so we know where to return to when the subroutine finshes and jumps to address NNN.

void Opcode2NNN( WORD opcode )
{
     m_Stack.push_back(m_ProgramCounter) ; // save the program counter
     m_ProgramCounter = opcode & 0x0FFF ; // jump to address NNN
}

Remember that before this function is called m_ProgramCounter points to the next opcode in memory,not the 2NNN. This is because our GetNextOpcode function returns opcode 2NNN and then moves the m_ProgramCounter to the next instruction. If you didnt move the m_ProgramCounter onto the next instruction then this function would cause serious problems as you will recursively call subroutine NNN upon returning from it.

Opcode 5XY0: Skips the next instruction if VX equals VY

There are a few opcodes like this and they are all really easy to implement. All the emulated function has to do is compare the contents of registerx to registery and if they are equal the program counter gets moved onto the next instruction. First of all we have to decode the opcode to find which is registerx. Then we do the same for registery and compare the two.

void Opcode5XY0( WORD opcode )
{
     int regx = opcode & 0x0F00 ; // mask off reg x
     regx = regx >> 8 ; // shift x across
     int regy = opcode & 0x00F0 ; // mask off reg y
     regy = regy >> 4 ; // shift y across
     if (m_Registers[regx] == m_Registers[regy])
         m_ProgramCounter+=2 ; // skip next instruction
}

If you dont understand bit shifting then this instruction can seem confusing at first, however it's really quite simple. If the opcode we were processing was 5120 then obviously the 5 part of this opcode is the opcode identifier. The 1 part of this opcode is registerX and the 2 part is registerY (the 0 isnt used in this opcode). So in order to get the registerX value from 5120 we logical and it with 0x0F00 which will return 0x100. However 0x100 is no good to us we need the value 0x1. So the code has to shift this value across 8 places to turn 0x100 to 0x1. But why do we shift it 8 places? Each hexedecimal digit (0-F) can be represented as 4 binary bits so if we need to move one digit across we have to shift it right by 4. However as 0x100 needs to be shifted two digits right to become 0x1 the code must shift it 8 times. This explains why the code only shifts right the registerY by 4 places because 0x20 is only one digit away from 0x2. If you still dont understand I recommend you google bit shifting as I seem to of found a very difficult way of explaining something quite simple... Anyway, moving swiftly on. The program counter gets incremented twice if the two registers are equal to skip the next instruction. If you remember that one opcode is two bytes in memory which is why it gets incremented twice.

Opcode 8XY5- RegisterY is subtracted from RegisterX:

As explained in one of the other sections the register array is 16 elements in range and each element stores 1 byte of memory. As we are dealing with unsigned bytes each element of the registers can contain a value between 0 and 255. However what happens when an addition or subtraction results in a value outside that range(i.e. 254+8 or 4-7)? This is what register F aka the carry flag is used for. Whenever an equation results in a value outside of the 0-255 range then the carry flag is set according to what the instruction expects. For a subraction instruction the carry flag is set to 0 if the result is less than 0, otherwise it is set to 1.

void Opcode8XY5( WORD opcode )
{
     m_Register[0xF] = 1;
     int regx = opcode & 0x0F00 ; // mask off reg x
     regx = regx >> 8 ; // shift x across
     int regy = opcode & 0x00F0 ; // mask off reg y
     regy = regy >> 4 ; // shift y across
     int xval = m_Register[regx] ;
     int yval = m_Register[regy] ;
     if (yval > xval) // if this is true will result in a value < 0
          m_Register[0xF] = 0 ;
     m_Register[regx] = xval-yval ;
}

The first thing this function does is set the carry flag to 1 which means that the equation did not wrap around itself. It then simply gets the values from the X and Y registers and checks to see if Y is bigger. If it is bigger than obviously X-Y will result in a negative number which means it will wrap round so the carry flag must be set to 0. It finishes by actually doing the substitution.

Opcode DXYN - Draws a sprite at coordinate (VX, VY) that has a width of 8 pixels and a height of N pixels:

This is the opcode which draws a sprite to the screen. The sprite will always be 8 pixels wide and you need to decode the opcode for the height of the sprite. When you draw each pixel of the sprite if the pixel was already on then you must turn it off. So you are actually just toggling the visibility of each pixel in the sprite. If a pixel is turned from on to off then the carry flag must be set to 1. If none of the pixels are turned from on to off then the carry flag is set to 0.

void OpcodeDXYN( WORD opcode )
{
     int regx = opcode & 0x0F00 ;
     regx = regx >> 8 ;
     int regy = opcode & 0x00F0 ;
     regy = regy >> 4 ;

     int height = opcode & 0x000F
     int coordx = m_Registers[regx] ;
     int coordy = m_Registers[regy] ;

     m_Registers[0xf] = 0 ;

     // loop for the amount of vertical lines needed to draw
     for (int yline = 0; yline < height; yline++)
     {
          BYTE data = m_GameMemory[m_AddressI+yline];
          int xpixelinv = 7 ;
          int xpixel = 0 ;
          for(xpixel = 0;xpixel < 8; xpixel++,xpixelinv--)
          {
               int mask = 1 << xpixelinv ;
               if (data & mask)
               {
                    int x = coordx + xpixel;
                    int y = coordy + yline ;
                    if ( m_ScreenData[x][y] == 1 )
                         m_Registers[0xF]=1; //collision
                    m_ScreenData[x][y]^=1 ;
               }
          }
     }
}

This is the most difficult the emulator gets. The variables coordx and coordy specify the top left coordinates where the sprite will be drawn. The code then decodes the opcode to get the height of the sprite because this varies from sprite to sprite. However all sprites are 8 pixels wide. The data needed to draw the sprite is located at memory address m_GameMemory[m_AddressI]. It is stored as sequential bytes of data from this address. Each byte represents one line of the sprite. Each bit of the 1 byte sprite line instructs whether the pixel represented by this bit needs to have its state toggled. If the bit is 0 then this pixel is ignored. If it is a 1 then it must be toggled. If a pixel to be toggled is being toggled from on to off (1 to 0) then the collision flag must be set. The line m_ScreenData[x][y] ^=1 is responsible for toggling the pixel state. Perhaps the only really confusing part of this function is the xpixelinv variable. The purpose for this is to translate the xpixel variable which increments from 0 to 7 each loop to a value between 7 and 0. So if xpixel is 0 then xpixelinv is 7. When xpixel is 1 xpixelinv is 6 etc. The reason for this is the way that the sprite data is encoded. Pixel 0 is bit 7 in the sprite data whereas pixel 7 is bit 0. The data & mask code is simply to determine if the bit xpixelinv refers to is 1 or 0. If it is 1 then it toggles the pixel state, otherwise it ignores the pixel.

Opcode FX33 - Binary-coded decimal :

When I first coded my Chip8 emulator this was the instruction that caused me the most headaches. It maybe simple to emulate but I just couldn't understand what the opcode was designed to do. Binary coded decimal is storing each digit of a number as its own sequence. For example number 123 is represented as 1 then 2 and then 3. This opcode takes the value stored in registerx and stores each digit in game memory starting from m_AddressI. For expample: If the value stored in registerx was 45 then we would put the following values in game memory. m_GameMemory[m_AddressI] = 0; m_GameMemory[m_AddressI+1] = 4 ; m_GameMemory[m_AddressI+2] = 5 ; However we need a generic algorithm for doing this:

void OpcodeFX33( WORD opcode )
{
     int regx = opcode & 0x0F00 ;
     regx >>= 8 ;

     int value = m_Registers[regx] ;

     int hundreds = value / 100 ;
     int tens = (value / 10) % 10 ;
     int units = value % 10 ;

     m_GameMemory[m_AddressI] = hundreds ;
     m_GameMemory[m_AddressI+1] = tens ;
     m_GameMemory[m_AddressI+2] = units ;
}

This code is very self explanatory. If you are having problems understanding it then look up how the modulus operator % works.

Opcode FX55 - Stores V0 to VX in memory starting at address I:

This opcode simply loops through registers VX to V0 storing their values from m_GameMemory[m_AddressI]. It is also important to note that after this operation the m_AddressI variable is set to m_AddressI + X + 1.

void OpcodeFX55( WORD opcode )
{
     int regx = opcode & 0x0F00 ;
     regx >>= 8 ;
     for (int i = 0 ; i <= regx; i++)
     {
          m_GameMemory[m_AddressI+i] = m_Registers[i] ;
     }
     m_AddressI= m_AddressI+regx+1 ;
}

The one thing I remain unsure of with this opcode is whether the for loop uses a "<=" operator or a "<". My current emulator seems to work fine "<=" so thats what im going with. Feel free to test this and change it if necessary. Opcode FX65 is just the reverse of this opcode (stores game memory in registers) but it still changes the m_AddressI in the same way as this function does.