There are a few projects I think everyone should undertake at least once:
- A compiler
- An operating system
- An emulator
I’m not really interested in embedded or systems development, but these projects have taught me immensely about the inner workings of the computer and the tools that programmers use. One of my issues with emulator development is that if you want to emulate something more modern, say the Gameboy, you’re going to have to implement every single opcode that the CPU handles. For the Gameboy, that’s 256 functions, and 512 if you want to include the Colorboy extension. But, there are many patterns in the layout of these opcodes, and I wonder if we can make use of those patterns to simplify our code a bit.
Parsing LD and math operations ¶
If we take a look at that opcode table, we’ll notice that there is a huge segment of LD operations and math operations in the middle of the table. We can actually simplify the parsing of these opcodes and write 128 of the Gameboy opcodes in just a few lines of code.
The loads are especially easy. If we look at the operations, we can notice that they cycle through the registers in this order: B, C, D, E, H, L, (HL), ACC. If you look at the binary representation of these opcodes, we’ll notice that the lowest 3 bits and the next 3 bits represent the index of these registers. We can create a table like this:
and then we can implement all of these loads in a few lines of pseudo-code:
def step(opcode): ... if opcode in 0x40 ... 0x80: src = opcode & 0b111 dst = (opcode >> 3) & 0b111 set_registers(dst, src)
set_registers will index that array and handle the case of the (HL) register. Note that the opcode that is supposed to be
LD (HL), (HL) is actually the
HALT opcode, so you might want to handle that case somewhere.
You can notice that the math operations in the opcodes from 0x80 to 0xB0 are also cyclical. Specifically, the lower 3 bits also index the table of registers that we developed above, and they cycle through the various math operations in a consistent manner. So these can also be implemented rather easily, although with a little more work.
def step(opcode): ... if opcode in 0x80 ... 0xB0: reg = opcode & 0b111 operation = (opcode >> 3) & 0b111 if operation == 0: registers.acc += get_value(reg) elif operation == 1: registers.acc += get_value(reg) + carry_flag ...
You can keep going to implement every other operation, but it’s just 1 or 2 lines per operation, rather than the 128 you would have to write by hand. These patterns also pop up in the Colorboy opcodes, which can be implemented just as easily.
I hope this short overview was helpful to you. I know that when I was writing my C++ Gameboy emulator, noticing this pattern simplified my work significantly and allowed me to focus on the more interesting aspects of emulation.