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 ;
}
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
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
}
void Opcode1NNN(WORD opcode)
{
m_ProgramCounter = opcode & 0x0FFF ; // remember we're only interested in NNN of opcode 1NNN
}