| name | cpu-gbz80 |
| description | Game Boy CPU (LR35902 / gbz80) for z88dk: Zilog mnemonics, full opcode map (flags ZNHC, timings), GB-only ops (ldh, (hl+)/(hl-), ld hl,sp+*, add sp,*, swap, stop, reti), and z80asm synthetics. Use when writing or reviewing gbz80 library asm, +gb / -mgbz80 paths, mapping Nintendo↔Zilog, or /cpu-gbz80.
|
CPU — gbz80
Game Boy CPU (Sharp LR35902). Z80-like with different encodings, a four-flag
register, and no IX/IY/exx/ex de,hl. Classic path
libsrc/l/sccz80/8-gbz80/, product gbz80_crt0.lib, target +gb, assemble
-mgbz80.
z88dk writes Zilog mnemonics. Pastraiser and Nintendo docs use mixed
Nintendo / Intel names. Translate on the way in.
Conventions (always follow)
- Mnemonics are Zilog, as in Z80 assembly — not Nintendo
LDH/LDHL/LDI
as the house spelling (those aliases assemble; do not emit them in new
sources except ldh, which is the Zilog name for the HRAM ops).
- Opcode bytes and timings are Game Boy, not Z80 (many shared mnemonics
use different encodings or do not exist).
- Immediate forms:
* = 8-bit (d8 unsigned, r8 signed, a8 HRAM offset).
** = 16-bit immediate/address (d16/a16). These */** are placeholders.
add sp,* and ld hl,sp+* take a signed 8-bit offset (−128…127).
- Conditional cycle counts use
taken/not-taken (e.g. 12/8, 16/12,
24/12, 20/8).
- One major function per file under
libsrc.
Prefer the full tables in references/opcodes.md. Use
this skill body for rules, flags, mnemonic mapping, and GB-only ops.
Sources
| Topic | Source |
|---|
| Native ISA, flags ZNHC, timings | pastraiser Game Boy opcodes |
| Zilog source forms and encodings | src/z80asm/dev/cpu/cpu_test_gbz80_ok.asm / _strict_ok.asm |
| Usage | libsrc/l/sccz80/8-gbz80/ |
Pastraiser is the native-ISA authority. z80asm encodings match the
Pastraiser opcode bytes 100% (all 245 used cells and the 11 unused cells).
z80asm is the authority for source spelling and for synthetics.
One Pastraiser length cell disagrees with the assembler and with the
hardware fetch: ld (c),a / ld a,(c) (E2/F2). Pastraiser prints 2
bytes. z80asm emits 1 byte. Use 1 byte.
Registers
15 ...... 8 7 ...... 0
A F → AF
B C → BC
D E → DE
H L → HL
15 ............... 0
SP
PC
No IX, IY, AF′, BC′, DE′, HL′, I, or R. Memory via HL is (hl). Stack grows
down. push stores high byte first at sp-1, low at sp-2.
HRAM / I/O window is $FF00–$FFFF. There is no in / out. Use ldh.
Flag register (F)
| Flag | Meaning |
|---|
| Z | Zero |
| N | Subtract (BCD) |
| H | Half-carry |
| C | Carry |
| 0 | Unused. Always zero |
Flag columns in tables are always Z N H C:
- letter → that flag is affected as defined by the instruction
- → unchanged
0 / 1 → forced clear / set
This is not the Z80 layout (Z80 is S Z H P/V N C). Game Boy has no S
and no P/V. Do not test sign or parity flags. Bits 3–0 of F are hardwired
0, so pop af never restores a full 16-bit value.
Condition codes (only these four):
| Zilog cc | Meaning |
|---|
nz / z | Z clear / set |
nc / c | C clear / set |
No po/pe/p/m. z80asm also accepts signed/unsigned sugar (eq, ne,
ltu, geu, …) as synthetics of z/nz/c/nc. Write z/nz/c/nc.
Nintendo / Intel → Zilog
Use Zilog in all generated or edited z88dk code. Nintendo names appear on
Pastraiser and in GBDK-style sources.
| Nintendo / Intel (Pastraiser) | Zilog (write this) |
|---|
NOP | nop |
LD r,r' / LD r,d8 | ld r,r' / ld r,* |
LD BC,d16 | ld bc,** (also de/hl/sp) |
LD (BC),A / LD A,(BC) | ld (bc),a / ld a,(bc) (also de) |
LD (HL+),A / LDI (HL),A / LD (HLI),A | ld (hl+),a |
LD A,(HL+) / LDI A,(HL) / LD A,(HLI) | ld a,(hl+) |
LD (HL-),A / LDD (HL),A / LD (HLD),A | ld (hl-),a |
LD A,(HL-) / LDD A,(HL) / LD A,(HLD) | ld a,(hl-) |
LD (a16),SP | ld (**),sp |
LD (a16),A / LD A,(a16) | ld (**),a / ld a,(**) |
LDH (a8),A / LD ($FF00+a8),A | ldh (*),a |
LDH A,(a8) / LD A,($FF00+a8) | ldh a,(*) |
LD (C),A / LD ($FF00+C),A | ld (c),a or ldh (c),a |
LD A,(C) / LD A,($FF00+C) | ld a,(c) or ldh a,(c) |
z80asm accepts the Nintendo aliases in the table (ld (hli),a, ldi a,(hl),
ldhl sp,*, ld a,(0xff00+c), …). They encode the same bytes. Write the
Zilog column. ldh a,(*) is the house spelling for the $FF00+a8 ops.
z80asm does not encode ld a,($ff00+n) as ldh. That source form is
an absolute ld a,(**) (FA, 3 bytes). Always write ldh a,(n).
GB-only native ops (not Z80)
These encodings are not the Z80 instruction at that byte.
| Op | Zilog | Bytes | Cyc | Flags | Effect |
|---|
08 | ld (**),sp | 3 | 20 | ---- | (**)←SP |
10 00 | stop | 2 | 4 | ---- | stop oscillator. Always emit two bytes |
22 | ld (hl+),a | 1 | 8 | ---- | (HL)←A, HL←HL+1 |
2A | ld a,(hl+) | 1 | 8 | ---- | A←(HL), HL←HL+1 |
32 | ld (hl-),a | 1 | 8 | ---- | (HL)←A, HL←HL−1 |
3A | ld a,(hl-) | 1 | 8 | ---- | A←(HL), HL←HL−1 |
D9 | reti | 1 | 16 | ---- | ret + enable interrupts (not Z80 exx) |
E0 | ldh (*),a | 2 | 12 | ---- | ($FF00+*)←A |
E2 | ld (c),a | 1 | 8 | ---- | ($FF00+C)←A |
E8 |
(hl+) / (hl-) as a single native opcode exist only for A.
ld b,(hl+) and add a,(hl+) are synthetics (ld b,(hl) / inc hl, …).
Absent vs Z80 / 8080 (do not emit as native)
| Missing | What to use instead |
|---|
ex de,hl | pair copy ld de,hl / ld hl,de, or the 4-byte synthetic ex de,hl (56c) |
ex (sp),hl | helper call __z80asm__ex_sp_hl (148c). Avoid in hot paths |
exx, IX, IY, ex af,af' | second long on the stack |
djnz | synthetic dec b / jr nz (accepted in normal mode) |
in / out / block I/O | ldh / ld (c),a |
ld hl,(**) / ld (**),hl | two ld a,(**) or a pointer in HL |
ld sp,(**) | ld hl,(**) synthetic + ld sp,hl, or ld (**),sp is store-only |
sbc hl,de / adc hl,bc | helper call __z80asm__* (slow) or open-code through A |
ldir / ldi as Z80 block | helper, or ld a,(hl+) / ld (de+),a |
po/pe/p/m | only z/nz/c/nc |
im, retn, neg as one opcode | neg is synthetic cpl / inc a |
Unused native opcodes (illegal): D3 DB DD E3 E4 EB EC ED
F4 FC FD. Same 11 holes in Pastraiser and in cpu_test_gbz80_strict_ok.asm.
Flag rules agents must not get wrong
| Group | Flags | Notes |
|---|
8-bit inc | Z0H- | N←0. C unchanged |
8-bit dec | Z1H- | N←1. C unchanged |
inc/dec 16-bit | ---- | no flags (unlike 8085 K) |
add a / adc a | Z0HC | N←0 |
sub / sbc / cp | Z1HC | N←1 |
and | Z010 | H forced 1, C←0 |
xor / or | Z000 | H←0, C←0 |
add hl,rp | -0HC | Z unchanged |
add sp,* / ld hl,sp+* | 00HC | Z forced 0. Save C before these if you still need it |
rlca / rla / rrca / rra | 000C | Z forced 0 (not Z80). Never rla; jr z |
CB rlc/rl/rrc/rr/sla/srl | Z00C | Z is live (unlike the A rotates) |
CB sra | Pastraiser Z000 | see below |
CB swap | Z000 | C←0 |
CB bit | Z01- | H←1, C unchanged |
|
sra and C: Pastraiser prints Z 0 0 0. libsrc/l/sccz80/8-gbz80/l_asr.asm
does sra h / rr l and needs C = old bit 0 of H. Write shifts that way.
rlca/rla/rrca/rra do not set Z. Test with or a / and a first.
Timing notes
Pastraiser machine cycles (not Z80 T-states):
- Conditional
jr: 12 taken / 8 not taken
- Conditional
jp: 16 / 12
- Conditional
call: 24 / 12
- Conditional
ret: 20 / 8
call ** 24, ret 16, reti 16, rst 16
push 16, pop 12
- 8-bit ALU register 4,
(hl) 8, immediate 8
ld hl,sp+* 12, add sp,* 16
- CB op 8, CB
(hl) 16 (includes the prefix)
Coding rules for this project
- Emit Zilog only (
ld a,b not MOV A,B; ld a,(hl+) not LDI A,(HL)).
- Register pairs:
bc, de, hl, af, sp.
- Use
(hl), (bc), (de), (**), (hl+), (hl-), (c) for memory.
- Prefer GB-only natives when they win:
ld hl,sp+*, add sp,*,
ld a,(hl+) / ld (hl+),a, ldh, swap.
- Never assume Z80 prefix encodings (
DD/ED/FD are unused or different).
- When optimizing, consult references/opcodes.md.
- Assembler last resort: fixtures
src/z80asm/dev/cpu/cpu_test_gbz80_{ok,err}.asm (and *_strict_*).
ok = z80asm accepts that source form (native, synthetic, or
call __z80asm__*). err = rejected. _strict_ = synthetics
forbidden. Full decode: tool-z80asm. rg only. Do not bulk-read
*_err.asm.
Usage (from libsrc/l/sccz80/8-gbz80/)
Patterns below are the house style for new gbz80 library asm. Cite the
named file as the example, not as a required callee.
1. Stack is the second long — ld hl,sp+*
There is no exx. The 32-bit primary often sits on the stack.
ld hl,sp+2 ; 12c, 2B; flags 00HC (Z←0)
ld a,(hl+) ; native post-inc
add a,c
See i32/l_long_add.asm, l_long_sub.asm, l_long_and.asm,
i32/l_long_div_0.asm.
* is signed. Offset 0…127 covers a small frame. Larger offsets need
ld hl,nn / add hl,sp (20c, 4B, Z unchanged).
C across ld hl,sp+*: that op clobbers C (and forces Z=0). Save C first:
rra ; C → A
ld hl,sp+n
rla ; C restored
l_long_div_0.asm does this around every stack reload in the shift loop.
2. Drop stack arguments — add sp,*
pop bc ; return address
add sp,4 ; drop a long (signed immediate)
push bc
ret
Same files as §1. add sp,* is 16c and also forces Z=0.
3. Stream bytes — native (hl+) / (hl-) for A
ld a,(hl+) ; 8c, 1B vs ld a,(hl)/inc hl = 16c, 2B
ld (hl+),a
ld a,(hl-)
l_getptr.asm, l_case.asm, l_glong.asm (ld a,(hl-)), l_long_neg_mhl.asm.
ALU with post-inc is a synthetic (sub (hl) / inc hl). Legal in normal
mode and used conceptually in l_long_div_0.asm (ld a,(de+) / sub a,(hl+)).
There is no rl (hl+). l_long_rl_mhl.asm and l_long_div_0.asm do
rl (hl) / inc hl.
4. Park a pair — ld bc,hl synthetics
ld bc,hl ; B←H, C←L
; … use HL …
ld hl,bc
l_long_add.asm parks the low word this way. Prefer this over ex de,hl.
5. 16-bit shifts through CB ops
gbz80 has sla/sra/rl/rr on every 8-bit register and on (hl).
sra h ; signed 16-bit >> (l_asr.asm)
rr l ; C must be old H bit 0
sla c ; scan bitlen (l_long_div_0.asm)
rl b
Word synthetics sra hl → sra h / rr l, rl de → rl e / rl d exist
in normal mode. Prefer them over open-coded pairs when both bytes move.
6. Counted loops — djnz synthetic or dec b / jr nz
djnz is not native (byte 10 is stop). z80asm expands
djnz label to dec b / jr nz,label. l_mult.asm uses it. Fine in
normal mode. Strict mode rejects it.
16-bit counts: dec bc does not set Z. Test with ld a,b / or c.
7. HRAM / MMIO — ldh
ldh a,(n) ; A ← ($FF00+n)
ldh (n),a
ld a,(c) ; A ← ($FF00+C)
ld (c),a
Do not fake in a,(c) / out (c),a.
8. In-place memory ALU
ld a,0 ; not xor a if C must live
sub a,(hl)
ld (hl+),a ; l_long_neg_mhl.asm
9. ex de,hl and ex (sp),hl are last resort
| Form | Encoding | Cost | Use |
|---|
ld de,hl | ld d,h / ld e,l | 2B / 8c | prefer |
ex de,hl | push hl / push de / pop hl / pop de | 4B / 56c | only if you need a true swap |
ex (sp),hl | call __z80asm__ex_sp_hl | 148c | almost never |
l_debug_push_frame.asm parks HL in DE with two 8-bit loads because there
is no cheap ex. l_div.asm remaps sccz80 DE/HL onto the SDCC BC/DE core
with ld bc,de / ld de,hl (HL is dead after the copy). Do not write
ex de,hl for a one-way move.
l_small_atoul (libsrc/l/util/8-gbz80/) does *10 as *8 + *2 with the
*2 LSW in BC. Do not use ex (sp),hl there.
Synthetic opcodes (z80asm, normal mode)
Assembler sugar. Strict / -no-synth forbids free synthetics.
Listings (-l) show the expansion. Full set:
src/z80asm/dev/cpu/cpu_test_gbz80_ok.asm. Prefer the forms below.
Word copies — full set
| Allowed | Forbidden in this set |
|---|
ld among bc / de / hl (any → any) | af, sp |
ld bc,de ; 42 4B
ld bc,hl ; 44 4D
ld de,bc ; 50 59
ld de,hl ; 54 5D
ld hl,bc ; 60 69
ld hl,de ; 62 6B
Useful inline synthetics (not helpers)
| Zilog | Expansion idea | Notes |
|---|
ld a,(de+) / ld (de+),a | ld a,(de) / inc de | same for bc, and - / ldi/ldd spellings |
ld r,(hl+) (r≠A) | ld r,(hl) / inc hl | A form is native 2A |
add a,(hl+) and other ALU+(hl±) | ALU (hl) + inc/dec hl | |
ld (hl),bc / ld bc,(hl) | two byte moves + inc/dec | |
djnz * | dec b / jr nz | |
sra hl / rl de / rr bc | two CB ops | |
neg | cpl / inc a | |
clr r / clr rp | ld r,0 / ld rp,0 | do not use xor a if C must live |
jp (bc) / jp (de) | push rp / ret | |
Do not prefer these long expansions when a native exists:
| Avoid | Why |
|---|
ld de,hl+* / ld de,sp+* | many bytes via ex synthetic. Use ld hl,sp+* |
ld (**),hl / ld hl,(**) | two absolute ld a,(**) plus park A |
ld (de),hl | ex + (hl) traffic. Open-code through A |
Helper calls (CD @__z80asm__…)
These assemble in normal mode but link a routine from src/z80asm/dev/z80asm_lib/.
They are not one-chip ops. Do not use them as the core of a hot hand-written
routine (same rule as other CPUs).
| Source form | Helper | GB cost (from the helper source) |
|---|
ex (sp),hl | __z80asm__ex_sp_hl | 148c |
sub hl,bc (and de/hl/sp) | __z80asm__sub_hl_* | 80c |
sbc hl,rp / adc hl,rp | __z80asm__sbc_hl_* / adc_hl_* | helper |
add hl,a / add de,a / add bc,a | __z80asm__add_*_a | helper |
ldi / ldir / ldd / lddr | __z80asm__ldi* | uses (hl+)/(de+) inside |
cpi / cpir / cpd / cpdr | __z80asm__cpi* | helper |
rld / rrd | __z80asm__rld / rrd | helper |
call (hl) | __z80asm__call_hl | helper |
__z80asm__ldi itself is a good template: ld a,(hl+) / ld (de+),a /
dec bc. Open-code that sequence when you do not need Z80 P/V (Game Boy
has no P/V).
Pitfalls
ld hl,sp+* and add sp,* clear Z and rewrite H,C. Save C with
rra/rla if a rotate chain is live (l_long_div_0.asm).
rlca/rla/rrca/rra force Z=0. They are not test-and-rotate.
- No
ex de,hl as a 1-byte op. The mnemonic is a 56c stack swap.
- No
djnz opcode. 10 is stop (10 00). The mnemonic is sugar.
stop is two bytes (10 00). Do not emit a lone 10.
reti is D9, not ED 4D. D9 is not exx.
pop af is not a 16-bit temp. F bits 3–0 stay 0.
ld a,($ff00+n) is not ldh. It is ld a,(**) (FA). Write ldh a,(n).
dec bc does not set Z. Do not write dec bc; jr nz as a 16-bit
loop test.
- Only A has native
(hl+)/(hl-). Other registers are two ops.
- No copt pass on library asm. Hand-written
libsrc/** is assembled
as-is. Remove dead moves yourself. Match the target file’s whitespace.
Checklist: tool-copt, methodology-measure.
Preference order (gbz80-only code)
ld hl,sp+* for stack pointers (signed −128…127). add hl,sp only when
the offset does not fit or you must keep Z.
add sp,* to drop arguments.
ld a,(hl+) / ld (hl+),a / ld a,(hl-) for streams.
ld bc,hl / ld hl,bc (and the other pair copies) to park a word.
ldh / ld (c),a for $FF00+n.
- CB
sla/sra/rl/rr/swap on registers and (hl).
djnz sugar or dec b/jr nz for 8-bit counts.
- Fall back to helper
call __z80asm__* only when the expansion is large
and the path is not hot.
Assembler capability (last resort)
Fixtures under src/z80asm/dev/cpu/ answer: does z80asm accept this
source line for -mgbz80, and what encoding does it emit?
| File | Meaning |
|---|
cpu_test_gbz80_ok.asm | Assembles successfully. Comment after ; is the encoding |
cpu_test_gbz80_err.asm | Must fail (; Error) |
*_strict_* | Strict mode: synthetics forbidden |
ok is not always one native chip opcode (normal mode may allow
synthetics or CD @__z80asm__… helpers).
How to read lines and rg without loading huge files: skill tool-z80asm.
Related
- Full opcode grid: references/opcodes.md
- Assembler:
tool-z80asm
- Measure:
methodology-measure, tool-ticks
- Target:
lib/config/gb.cfg, libsrc/target/gb/
- sccz80 runtime examples:
libsrc/l/sccz80/8-gbz80/