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
}
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
}
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 ;
}
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 ;
}
}
}
}
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 ;
}
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 ;
}