codeslinger.co.uk

The chip8 instruction emulation.

Fetch Opcodes:

Opcodes are the instructions that the cpu executes. We get the opcodes from the memory which we initialized in Step1. As stated in step1 the beginning of the game is located at address 0x200 which is where the program counter has been initialized to. So it is this address in memory that we will get the first opcode from. As you have all read the Wikipedia chip8 article you will know that opcodes are 2 bytes long (1 word), however our memory is of size 1 byte. So how do we create 2 byte opcode from an address that is only 1 byte long? The answer is simple. We have to combine the byte located in memory with the byte the next element along in memory. So for instance if byte m_Memory[0x200] is 0xAB and byte m_Memory[0x201] is 0xCB we combine them to form 1 word, which is the opcode. So the opcode would be 0xABCD

In binary the value 0xAB is 10101011 and the value 0xCD is 11001101 so we need to combine them to form 0xABCD which is 1010101111001101. As you can see the first byte (0xAB) has been shifted across 8 places to make room for the second byte(0xCD). So what we do is shift 0xAB left 8 times which will give 1010101100000000 (0xAB00)we then logically or this result with 0xCB which will give 1010101111001101. So to form an opcode from memory you do the following:

WORD GetNextOpcode( )
{
   WORD res = 0 ;
   res = m_GameMemory[m_ProgramCounter] ; // in example res is 0xAB
   res <<= 8 ; // shift 8 bits left. In our example res is 0xAB00
   res |= m_GameMemory[m_ProgramCounter+1] ; In example res is 0xABCD
   m_ProgramCounter+=2 ;
   return res ;
}

As you can see from the code the program counter gets incremented twice. The reason for this is that we now know what this opcode is (res) so we move the program counter (also called the instruction pointer) onto the next instruction. The reason why it is incremented twice remember is because an opcode is 2 bytes long.

Decode Opcode:

So now we have a function to return the next opcode but what do we do with it? Well we have to decode it to see which instruction we have to execute. Load up wikipedia again and look at the opcode table. The first number of the opcode will dictate what instruction needs to be executed. For example if you took the first number of the opcode and it was number 1, we can see from the opcode table we should jump to position NNN. So opcode 0x1234 would mean change program counter to 0x234. However around 50% of the opcodes share the same first number. For example if the first number was 0 then it could be opcode 00E0 or 00EE, so that means we have to then split the opcode up further by looking at number 4. If number 4 is a 0 then it is opcode 00E0 which means clear the screen. Otherwise if number 4 is an E it must be opcode 00EE which returns from a subroutine. But how do we get the specific numbers of the opcode? Thats simple, we just logical AND the opcode with 0xF for the position we want:

WORD opcode = GetNextOpcode( ) ; // assume this returns 0x1234
WORD firstNumber = opcode & 0xF000 ; // would give 0x1000
WORD secondNumber = opcode & 0x0F00 ; // would give 0x0200
WORD secondAndLast = opcode & 0x0F0F ; // would give 0x0204
WORD lastTwoNumbers = opcode & 0x00FF ; // would give 0x0034

So now we have a way of decoding the opcode. All we have to do now is call the function that is linked to that opcode. There are three ways we can do this, we can either use a big if else if statement. Or a switch statement. Or an array of function pointers. You should never use the first option which is the if else if statement. This is because it can be quite slow to execute. The most common option is the switch statement. So this will give us something that looks like this:

WORD opcode = GetNextOpcode( ) ; // assume this returns 0x1234

// decode the opcode
switch (opcode & 0xF000)
{
   case 0x1000: Opcode1NNN(opcode); break ; // jump opcode
   case 0x0000: // need to break this opcode down further
   {
     switch(opcode & 0x000F)
     {
       case 0x0000: 0pcode00E0(opcode) ; break // clear screen opcode
       case 0x000E: Opcode00EE(opcode) ; break // return subroutine
     }
   }
   break ;
   default : break ; // opcode yet to be handled
}

Just to round off this section I'll show you how to implement 1NNN. This opcode is responsible to jumping address NNN like so:

void Opcode1NNN(WORD opcode)
{
   m_ProgramCounter = opcode & 0x0FFF ; // remember we're only interested in NNN of opcode 1NNN
}

Well thats about it for this section. You should now how complete understanding of the fetch and decode loop. Obviously as this is a loop you have to keep fetching and decoding until the player quits. On modern day computers the fetch and decode loop will execute so quick that the chip8 game will need to be slowed down. We'll handle this in step4.