Thursday, 10 September 2026

GameBoy Pixmap Demo (Part 2)

 In the my most recent post, I started to discuss a recent interest in the Nintendo GameBoy (original version), and how to use its Tile-based graphics to implement a full frame buffer.

This post discusses some more intriguing aspects of the hardware architecture before moving on to the development process.

GameBoy CPU (SM83)

The GameBoy CPU is a fairly strange design. Most of the references to it describe it as a variant of a Z80, mostly I guess because the assembly-based development environments use Z80 mnemonics.

However, the CPU is, in my opinion, much closer to the 8080 than a Z80, because it lacks a number of features forcing clumsy solutions to programming problems and often far less efficiency.

Deficiencies

Registers

Amongst the most significant is the lack of a second bank of registers. The Z80 is register-rich for an 8-bit CPU, whereas the 8080 is somewhat meh, borrowing its register set from the original 8008. Like the 8080 there are just 7 main registers; of which 6 can be paired up as 3x 16 bit address registers.

A small register set would be fairly usable if the CPU provided indexed addressing modes, but the 8080, Z80 and SM83 only support register-pair indirection from these registers. The SM83 also lacks some instructions available on the 8080, namely being able to load HL from the contents of a 16-bit address: absolute addressing is only possible for loading and storing A.

The 8080 and Z80 also have the ability to exchange DE with HL and HL with (SP) (i.e. the top of stack). The SM83 lacks these which often forces two LD instructions to copy a 16-bit register and major limitations on using the stack for operands.

Jumps

The SM83 also lacks a number of jump conditions present on the 8080 and Z80, namely JP P (Jump positive), JP M (Jump Negative), JP PO (Jump Parity Odd), JP PE (Jump Parity Even). And this is because the SM83 lacks the parity and negative flags present on the others. However, having said that, most jumps are based on the zero and carry flags, which the SM83, 8080 and Z80 all have.

The Z80 has a very helpful looping instruction, DJNZ which isn't present on the SM83.

16-bit Operations

The SM83 lacks 16-bit operations found on the Z80, namely 16-bit SBC and ADC instructions.

Timing

All of the Z80, 8080 and SM83 have variable length timings with a minimum of 4 cycles per instruction. However, the SM83's timings are all multiples of 4 cycles. This means that all the memory operations that take 7 cycles on a Z80 and 8080 take 8 cycles on an SM83. Loads which take 10 cycles on a Z80 and 8080, take 12 cycles on an SM83. Long jumps which take 10 cycles on the other two take 16 on the SM83. Call/Returns which take 17/10 cycles on the 8080 (16/10 on the Z80) take a stunning 24/16 cycles on the SM83 (it's really not good for call/return). 16-bit INCs and DECs take 6 cycles on a Z80 (5 on the 8080), but 8 on the SM83.

Z80 Features

As per many discussions, the SM83 includes the full set of the Z80's bit manipulation instructions as well as introducing the SWAP nybble instructions.

Advantages

However, the SM83 does have a few advantages over the other CPUs:

  1. Because all the instructions are a multiple of 4 cycles, it's easier to count cycles, which is often important on gaming devices of this era.
  2.  16-bit ADD instructions are actually 27% faster on the SM83 than the Z80 (20% faster than an 8080).
  3. ADD SP,n8 allows you to allocate and deallocate stack frames quickly.
  4. Even though loading data from the stack is slow, LD HLSP+d8 can be used to point HL to locals or parameters.
  5. There's a whole set of what amounts to zero-page memory you can access with LDH A,[n8] and LDH [n8],A instructions as well as LDH A,[C] and LDH [C],A, in place of the IN/OUT instructions on the 8080 and Z80. These access the I/O registers too.
  6. LDI [HL],A and LDI A,[HL] with both post-increment and post-decrement addressing modes provide for very fast FIFO or buffer operations as they're only 8 cycles each.
  7. LD [A16],SP goes some way to making context switching easier.
  8. Finally, the 8-cycle SWAP r instructions help significantly with bit shifting. For example, SWAP A, AND 15 performs a >>4 in 16 cycles (it would take 23 on a Z80, RRCA * 4, then AND 15).

Microarchitecture

At first I thought that the SM83 must be highly microcoded, due to the multiple of 4 instruction timings, but it turns out that it's all hard-wired.

Conclusion

The SM83 is quite an interesting 8080 / Z80 hybrid-variant with architectural and instruction level features missing from both; some Z80 features and a few features not found in either.

My Demo program was written entirely in SM83 assembly via the very helpful https://gbdev.io/rgbds-live/ IDE and emulator. It's quite a strange experience, because of the different timing and instructions that aren't available.

If I'd been designing it I would have made a few different choices. Firstly, I would have removed all the conditional long jump instructions, because the short +/-128, relative jumps cover nearly all cases. I would have kept the unconditional jump.

I would have included the EX DE,HL instruction, because it's just so much more useful.

I would have added LD BC/DE,[SP+d8] and LD [SP+d8],DE/HL instructions as they would have been really useful for managing stack frames (though this probably isn't very important to games developers) and making up for the lack of alternate registers on the Z80. That's 4 more instructions. I would also have added LD HL/DE, [a16] and LD [a16],HL/DE instructions, because loading globals is important. That's another 2 instructions, a net gain of 1, but I would have removed LD [a16],SP, because LD HL,SP+0 then LD [a16],HL is equivalent (so is LD HL,0; ADD HL,SP; LD [a16],HL). INC SP and DEC SP are also redundant.

I would have replaced the redundant prefixed, RL A, RLC A, RR A, RLC A, and SLA A instructions with ADC HL, HL; ADC HL,DE; SBC HL,DE; RR DE and ADC HL,HL instructions to make other 16-bit operations quicker.




No comments: