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)
| Bit |
7 |
6 |
5 |
4 |
3 |
2 |
1 |
0 |
|
Z |
N |
H |
C |
0 |
0 |
0 |
0 |
| 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) |
LD HL,SP+r8 / LDHL SP,r8 |
ld hl,sp+* |
LD SP,HL |
ld sp,hl |
ADD HL,rp |
add hl,bc / de / hl / sp |
ADD SP,r8 |
add sp,* |
INC/DEC |
inc/dec |
ADD A,r / ADC A,r |
add a,r / adc a,r |
SUB r / SBC A,r |
sub r or sub a,r / sbc a,r |
AND/XOR/OR/CP |
and/xor/or/cp |
RLCA/RRCA/RLA/RRA |
rlca/rrca/rla/rra |
JR r8 / JR cc,r8 |
jr * / jr cc,* |
JP a16 / JP cc,a16 |
jp ** / jp cc,** |
JP (HL) |
jp (hl) |
CALL/RET/RETI |
call/ret/reti |
RST 00H…38H |
rst 00h … rst 38h |
PUSH/POP BC|DE|HL|AF |
push/pop bc|de|hl|af |
STOP 0 |
stop |
HALT/DI/EI/DAA/CPL/SCF/CCF |
halt/di/ei/daa/cpl/scf/ccf |
SWAP r |
swap r |
RLC/RRC/RL/RR/SLA/SRA/SRL |
same, lowercase |
BIT/RES/SET n,r |
bit/res/set n,r |
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 |
add sp,* |
2 |
16 |
00HC |
SP←SP+signed *. Z←0 |
F0 |
ldh a,(*) |
2 |
12 |
---- |
A←($FF00+*) |
F2 |
ld a,(c) |
1 |
8 |
---- |
A←($FF00+C) |
F8 |
ld hl,sp+* |
2 |
12 |
00HC |
HL←SP+signed *. Z←0 |
CB 3x |
swap r |
2 |
8/16 |
Z000 |
nibble swap |
(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 |
daa |
Z-0C |
|
cpl |
-11- |
N←1, H←1 |
scf |
-001 |
C←1 |
ccf |
-00C |
C toggled |
pop af |
ZNHC |
restores Z N H C. Low nibble stays 0 |
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
- Float:
library-math32 — --math32 on -clib=gbz80 / -mgbz80 links math32_gbz80.lib (stack-only cores under asm/gbz80/; higher C via make -C …/c gbz80)
- Target:
lib/config/gb.cfg, libsrc/target/gb/
- sccz80 runtime examples:
libsrc/l/sccz80/8-gbz80/
1---2name: cpu-gbz803description: 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.4---56# CPU — gbz8078Game Boy CPU (Sharp LR35902). Z80-like with different encodings, a four-flag9register, and no IX/IY/`exx`/`ex de,hl`. Classic path10`libsrc/l/sccz80/8-gbz80/`, product `gbz80_crt0.lib`, target `+gb`, assemble11`-mgbz80`.1213z88dk writes **Zilog** mnemonics. Pastraiser and Nintendo docs use mixed14Nintendo / Intel names. Translate on the way in.1516## Conventions (always follow)17181. **Mnemonics are Zilog**, as in Z80 assembly — not Nintendo `LDH`/`LDHL`/`LDI`19 as the house spelling (those aliases assemble; do not emit them in new20 sources except `ldh`, which is the Zilog name for the HRAM ops).212. **Opcode bytes and timings are Game Boy**, not Z80 (many shared mnemonics22 use different encodings or do not exist).233. Immediate forms: `*` = 8-bit (d8 unsigned, **r8 signed**, a8 HRAM offset).24 `**` = 16-bit immediate/address (d16/a16). These `*`/`**` are placeholders.254. `add sp,*` and `ld hl,sp+*` take a **signed** 8-bit offset (−128…127).265. Conditional cycle counts use `taken/not-taken` (e.g. `12/8`, `16/12`,27 `24/12`, `20/8`).286. One major function per file under `libsrc`.2930Prefer the full tables in [references/opcodes.md](references/opcodes.md). Use31this skill body for rules, flags, mnemonic mapping, and GB-only ops.3233## Sources3435| Topic | Source |36|-------|--------|37| Native ISA, flags ZNHC, timings | [pastraiser Game Boy opcodes](https://www.pastraiser.com/cpu/gameboy/gameboy_opcodes.html) |38| Zilog source forms and encodings | `src/z80asm/dev/cpu/cpu_test_gbz80_ok.asm` / `_strict_ok.asm` |39| Usage | `libsrc/l/sccz80/8-gbz80/` |4041Pastraiser is the native-ISA authority. **z80asm encodings match the42Pastraiser opcode bytes 100%** (all 245 used cells and the 11 unused cells).43z80asm is the authority for **source spelling** and for **synthetics**.4445One Pastraiser **length** cell disagrees with the assembler and with the46hardware fetch: `ld (c),a` / `ld a,(c)` (`E2`/`F2`). Pastraiser prints 247bytes. z80asm emits **1 byte**. Use 1 byte.4849## Registers5051```5215 ...... 8 7 ...... 053 A F → AF54 B C → BC55 D E → DE56 H L → HL5715 ............... 058 SP59 PC60```6162No IX, IY, AF′, BC′, DE′, HL′, I, or R. Memory via HL is `(hl)`. Stack grows63down. `push` stores high byte first at `sp-1`, low at `sp-2`.6465HRAM / I/O window is `$FF00`–`$FFFF`. There is no `in` / `out`. Use `ldh`.6667## Flag register (F)6869| Bit | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 |70|-----|---|---|---|---|---|---|---|---|71| | Z | N | H | C | 0 | 0 | 0 | 0 |7273| Flag | Meaning |74|------|---------|75| **Z** | Zero |76| **N** | Subtract (BCD) |77| **H** | Half-carry |78| **C** | Carry |79| **0** | Unused. Always zero |8081Flag columns in tables are always **Z N H C**:8283- letter → that flag is affected as defined by the instruction84- `-` → unchanged85- `0` / `1` → forced clear / set8687This is **not** the Z80 layout (Z80 is S Z H P/V N C). Game Boy has **no S**88and **no P/V**. Do not test sign or parity flags. Bits 3–0 of F are hardwired890, so `pop af` never restores a full 16-bit value.9091Condition codes (only these four):9293| Zilog cc | Meaning |94|----------|---------|95| `nz` / `z` | Z clear / set |96| `nc` / `c` | C clear / set |9798No `po`/`pe`/`p`/`m`. z80asm also accepts signed/unsigned sugar (`eq`, `ne`,99`ltu`, `geu`, …) as synthetics of `z`/`nz`/`c`/`nc`. Write `z`/`nz`/`c`/`nc`.100101## Nintendo / Intel → Zilog102103Use Zilog in all generated or edited z88dk code. Nintendo names appear on104Pastraiser and in GBDK-style sources.105106| Nintendo / Intel (Pastraiser) | Zilog (write this) |107|-------------------------------|--------------------|108| `NOP` | `nop` |109| `LD r,r'` / `LD r,d8` | `ld r,r'` / `ld r,*` |110| `LD BC,d16` | `ld bc,**` (also `de`/`hl`/`sp`) |111| `LD (BC),A` / `LD A,(BC)` | `ld (bc),a` / `ld a,(bc)` (also `de`) |112| `LD (HL+),A` / `LDI (HL),A` / `LD (HLI),A` | `ld (hl+),a` |113| `LD A,(HL+)` / `LDI A,(HL)` / `LD A,(HLI)` | `ld a,(hl+)` |114| `LD (HL-),A` / `LDD (HL),A` / `LD (HLD),A` | `ld (hl-),a` |115| `LD A,(HL-)` / `LDD A,(HL)` / `LD A,(HLD)` | `ld a,(hl-)` |116| `LD (a16),SP` | `ld (**),sp` |117| `LD (a16),A` / `LD A,(a16)` | `ld (**),a` / `ld a,(**)` |118| `LDH (a8),A` / `LD ($FF00+a8),A` | `ldh (*),a` |119| `LDH A,(a8)` / `LD A,($FF00+a8)` | `ldh a,(*)` |120| `LD (C),A` / `LD ($FF00+C),A` | `ld (c),a` or `ldh (c),a` |121| `LD A,(C)` / `LD A,($FF00+C)` | `ld a,(c)` or `ldh a,(c)` |122| `LD HL,SP+r8` / `LDHL SP,r8` | `ld hl,sp+*` |123| `LD SP,HL` | `ld sp,hl` |124| `ADD HL,rp` | `add hl,bc` / `de` / `hl` / `sp` |125| `ADD SP,r8` | `add sp,*` |126| `INC`/`DEC` | `inc`/`dec` |127| `ADD A,r` / `ADC A,r` | `add a,r` / `adc a,r` |128| `SUB r` / `SBC A,r` | `sub r` or `sub a,r` / `sbc a,r` |129| `AND`/`XOR`/`OR`/`CP` | `and`/`xor`/`or`/`cp` |130| `RLCA`/`RRCA`/`RLA`/`RRA` | `rlca`/`rrca`/`rla`/`rra` |131| `JR r8` / `JR cc,r8` | `jr *` / `jr cc,*` |132| `JP a16` / `JP cc,a16` | `jp **` / `jp cc,**` |133| `JP (HL)` | `jp (hl)` |134| `CALL`/`RET`/`RETI` | `call`/`ret`/`reti` |135| `RST 00H`…`38H` | `rst 00h` … `rst 38h` |136| `PUSH`/`POP BC\|DE\|HL\|AF` | `push`/`pop bc\|de\|hl\|af` |137| `STOP 0` | `stop` |138| `HALT`/`DI`/`EI`/`DAA`/`CPL`/`SCF`/`CCF` | `halt`/`di`/`ei`/`daa`/`cpl`/`scf`/`ccf` |139| `SWAP r` | `swap r` |140| `RLC`/`RRC`/`RL`/`RR`/`SLA`/`SRA`/`SRL` | same, lowercase |141| `BIT`/`RES`/`SET n,r` | `bit`/`res`/`set n,r` |142143z80asm accepts the Nintendo aliases in the table (`ld (hli),a`, `ldi a,(hl)`,144`ldhl sp,*`, `ld a,(0xff00+c)`, …). They encode the same bytes. **Write the145Zilog column.** `ldh a,(*)` is the house spelling for the `$FF00+a8` ops.146z80asm does **not** encode `ld a,($ff00+n)` as `ldh`. That source form is147an absolute `ld a,(**)` (`FA`, 3 bytes). Always write `ldh a,(n)`.148149## GB-only native ops (not Z80)150151These encodings are **not** the Z80 instruction at that byte.152153| Op | Zilog | Bytes | Cyc | Flags | Effect |154|----|-------|------:|----:|-------|--------|155| `08` | `ld (**),sp` | 3 | 20 | `----` | (`**`)←SP |156| `10 00` | `stop` | 2 | 4 | `----` | stop oscillator. Always emit **two** bytes |157| `22` | `ld (hl+),a` | 1 | 8 | `----` | (HL)←A, HL←HL+1 |158| `2A` | `ld a,(hl+)` | 1 | 8 | `----` | A←(HL), HL←HL+1 |159| `32` | `ld (hl-),a` | 1 | 8 | `----` | (HL)←A, HL←HL−1 |160| `3A` | `ld a,(hl-)` | 1 | 8 | `----` | A←(HL), HL←HL−1 |161| `D9` | `reti` | 1 | 16 | `----` | `ret` + enable interrupts (not Z80 `exx`) |162| `E0` | `ldh (*),a` | 2 | 12 | `----` | (`$FF00`+\*)←A |163| `E2` | `ld (c),a` | 1 | 8 | `----` | (`$FF00`+C)←A |164| `E8` | `add sp,*` | 2 | 16 | `00HC` | SP←SP+signed \*. **Z←0** |165| `F0` | `ldh a,(*)` | 2 | 12 | `----` | A←(`$FF00`+\*) |166| `F2` | `ld a,(c)` | 1 | 8 | `----` | A←(`$FF00`+C) |167| `F8` | `ld hl,sp+*` | 2 | 12 | `00HC` | HL←SP+signed \*. **Z←0** |168| `CB 3x` | `swap r` | 2 | 8/16 | `Z000` | nibble swap |169170`(hl+)` / `(hl-)` as a **single native opcode** exist only for **A**.171`ld b,(hl+)` and `add a,(hl+)` are synthetics (`ld b,(hl)` / `inc hl`, …).172173## Absent vs Z80 / 8080 (do not emit as native)174175| Missing | What to use instead |176|---------|---------------------|177| `ex de,hl` | pair copy `ld de,hl` / `ld hl,de`, or the 4-byte synthetic `ex de,hl` (56c) |178| `ex (sp),hl` | helper `call __z80asm__ex_sp_hl` (**148c**). Avoid in hot paths |179| `exx`, IX, IY, `ex af,af'` | second long on the stack |180| `djnz` | synthetic `dec b` / `jr nz` (accepted in normal mode) |181| `in` / `out` / block I/O | `ldh` / `ld (c),a` |182| `ld hl,(**)` / `ld (**),hl` | two `ld a,(**)` or a pointer in HL |183| `ld sp,(**)` | `ld hl,(**)` synthetic + `ld sp,hl`, or `ld (**),sp` is store-only |184| `sbc hl,de` / `adc hl,bc` | helper `call __z80asm__*` (slow) or open-code through A |185| `ldir` / `ldi` as Z80 block | helper, or `ld a,(hl+)` / `ld (de+),a` |186| `po`/`pe`/`p`/`m` | only `z`/`nz`/`c`/`nc` |187| `im`, `retn`, `neg` as one opcode | `neg` is synthetic `cpl` / `inc a` |188189Unused native opcodes (illegal): `D3` `DB` `DD` `E3` `E4` `EB` `EC` `ED`190`F4` `FC` `FD`. Same 11 holes in Pastraiser and in `cpu_test_gbz80_strict_ok.asm`.191192## Flag rules agents must not get wrong193194| Group | Flags | Notes |195|-------|-------|-------|196| 8-bit `inc` | `Z0H-` | N←0. C unchanged |197| 8-bit `dec` | `Z1H-` | N←1. C unchanged |198| `inc`/`dec` **16-bit** | `----` | no flags (unlike 8085 K) |199| `add a` / `adc a` | `Z0HC` | N←0 |200| `sub` / `sbc` / `cp` | `Z1HC` | N←1 |201| `and` | `Z010` | H forced 1, C←0 |202| `xor` / `or` | `Z000` | H←0, C←0 |203| `add hl,rp` | `-0HC` | Z unchanged |204| `add sp,*` / `ld hl,sp+*` | `00HC` | **Z forced 0**. Save C before these if you still need it |205| `rlca` / `rla` / `rrca` / `rra` | `000C` | **Z forced 0** (not Z80). Never `rla; jr z` |206| CB `rlc`/`rl`/`rrc`/`rr`/`sla`/`srl` | `Z00C` | Z is live (unlike the A rotates) |207| CB `sra` | Pastraiser `Z000` | see below |208| CB `swap` | `Z000` | C←0 |209| CB `bit` | `Z01-` | H←1, C unchanged |210| `daa` | `Z-0C` | |211| `cpl` | `-11-` | N←1, H←1 |212| `scf` | `-001` | C←1 |213| `ccf` | `-00C` | C toggled |214| `pop af` | `ZNHC` | restores Z N H C. Low nibble stays 0 |215216**`sra` and C:** Pastraiser prints `Z 0 0 0`. `libsrc/l/sccz80/8-gbz80/l_asr.asm`217does `sra h` / `rr l` and needs C = old bit 0 of H. Write shifts that way.218219`rlca`/`rla`/`rrca`/`rra` do **not** set Z. Test with `or a` / `and a` first.220221## Timing notes222223Pastraiser machine cycles (not Z80 T-states):224225- Conditional `jr`: 12 taken / 8 not taken226- Conditional `jp`: 16 / 12227- Conditional `call`: 24 / 12228- Conditional `ret`: 20 / 8229- `call **` 24, `ret` 16, `reti` 16, `rst` 16230- `push` 16, `pop` 12231- 8-bit ALU register 4, `(hl)` 8, immediate 8232- `ld hl,sp+*` 12, `add sp,*` 16233- CB op 8, CB `(hl)` 16 (includes the prefix)234235## Coding rules for this project2362371. Emit **Zilog** only (`ld a,b` not `MOV A,B`; `ld a,(hl+)` not `LDI A,(HL)`).2382. Register pairs: `bc`, `de`, `hl`, `af`, `sp`.2393. Use `(hl)`, `(bc)`, `(de)`, `(**)`, `(hl+)`, `(hl-)`, `(c)` for memory.2404. Prefer GB-only natives when they win: `ld hl,sp+*`, `add sp,*`,241 `ld a,(hl+)` / `ld (hl+),a`, `ldh`, `swap`.2425. Never assume Z80 prefix encodings (`DD`/`ED`/`FD` are unused or different).2436. When optimizing, consult [references/opcodes.md](references/opcodes.md).2447. **Assembler last resort:** fixtures245 `src/z80asm/dev/cpu/cpu_test_gbz80_{ok,err}.asm` (and `*_strict_*`).246 **ok** = z80asm accepts that source form (native, synthetic, or247 `call __z80asm__*`). **err** = rejected. **`_strict_`** = synthetics248 forbidden. Full decode: **`tool-z80asm`**. `rg` only. Do not bulk-read249 `*_err.asm`.250251## Usage (from `libsrc/l/sccz80/8-gbz80/`)252253Patterns below are the house style for new gbz80 library asm. Cite the254named file as the example, not as a required callee.255256### 1. Stack is the second long — `ld hl,sp+*`257258There is no `exx`. The 32-bit primary often sits on the stack.259260```asm261 ld hl,sp+2 ; 12c, 2B; flags 00HC (Z←0)262 ld a,(hl+) ; native post-inc263 add a,c264```265266See `i32/l_long_add.asm`, `l_long_sub.asm`, `l_long_and.asm`,267`i32/l_long_div_0.asm`.268269`*` is **signed**. Offset 0…127 covers a small frame. Larger offsets need270`ld hl,nn` / `add hl,sp` (20c, 4B, Z unchanged).271272**C across `ld hl,sp+*`:** that op clobbers C (and forces Z=0). Save C first:273274```asm275 rra ; C → A276 ld hl,sp+n277 rla ; C restored278```279280`l_long_div_0.asm` does this around every stack reload in the shift loop.281282### 2. Drop stack arguments — `add sp,*`283284```asm285 pop bc ; return address286 add sp,4 ; drop a long (signed immediate)287 push bc288 ret289```290291Same files as §1. `add sp,*` is 16c and also forces Z=0.292293### 3. Stream bytes — native `(hl+)` / `(hl-)` for A294295```asm296 ld a,(hl+) ; 8c, 1B vs ld a,(hl)/inc hl = 16c, 2B297 ld (hl+),a298 ld a,(hl-)299```300301`l_getptr.asm`, `l_case.asm`, `l_glong.asm` (`ld a,(hl-)`), `l_long_neg_mhl.asm`.302303ALU with post-inc is a **synthetic** (`sub (hl)` / `inc hl`). Legal in normal304mode and used conceptually in `l_long_div_0.asm` (`ld a,(de+)` / `sub a,(hl+)`).305306There is **no** `rl (hl+)`. `l_long_rl_mhl.asm` and `l_long_div_0.asm` do307`rl (hl)` / `inc hl`.308309### 4. Park a pair — `ld bc,hl` synthetics310311```asm312 ld bc,hl ; B←H, C←L313 ; … use HL …314 ld hl,bc315```316317`l_long_add.asm` parks the low word this way. Prefer this over `ex de,hl`.318319### 5. 16-bit shifts through CB ops320321gbz80 has `sla`/`sra`/`rl`/`rr` on every 8-bit register and on `(hl)`.322323```asm324 sra h ; signed 16-bit >> (l_asr.asm)325 rr l ; C must be old H bit 0326```327328```asm329 sla c ; scan bitlen (l_long_div_0.asm)330 rl b331```332333Word synthetics `sra hl` → `sra h` / `rr l`, `rl de` → `rl e` / `rl d` exist334in normal mode. Prefer them over open-coded pairs when both bytes move.335336### 6. Counted loops — `djnz` synthetic or `dec b` / `jr nz`337338`djnz` is **not** native (byte `10` is `stop`). z80asm expands339`djnz label` to `dec b` / `jr nz,label`. `l_mult.asm` uses it. Fine in340normal mode. Strict mode rejects it.34134216-bit counts: `dec bc` does **not** set Z. Test with `ld a,b` / `or c`.343344### 7. HRAM / MMIO — `ldh`345346```asm347 ldh a,(n) ; A ← ($FF00+n)348 ldh (n),a349 ld a,(c) ; A ← ($FF00+C)350 ld (c),a351```352353Do not fake `in a,(c)` / `out (c),a`.354355### 8. In-place memory ALU356357```asm358 ld a,0 ; not xor a if C must live359 sub a,(hl)360 ld (hl+),a ; l_long_neg_mhl.asm361```362363### 9. `ex de,hl` and `ex (sp),hl` are last resort364365| Form | Encoding | Cost | Use |366|------|----------|-----:|-----|367| `ld de,hl` | `ld d,h` / `ld e,l` | 2B / 8c | prefer |368| `ex de,hl` | `push hl` / `push de` / `pop hl` / `pop de` | 4B / **56c** | only if you need a true swap |369| `ex (sp),hl` | `call __z80asm__ex_sp_hl` | **148c** | almost never |370371`l_debug_push_frame.asm` parks HL in DE with two 8-bit loads because there372is no cheap `ex`. `l_div.asm` remaps sccz80 DE/HL onto the SDCC BC/DE core373with `ld bc,de` / `ld de,hl` (HL is dead after the copy). Do not write374`ex de,hl` for a one-way move.375376`l_small_atoul` (`libsrc/l/util/8-gbz80/`) does `*10` as `*8 + *2` with the377`*2` LSW in BC. Do not use `ex (sp),hl` there.378379## Synthetic opcodes (z80asm, normal mode)380381Assembler sugar. **Strict** / `-no-synth` forbids free synthetics.382Listings (`-l`) show the expansion. Full set:383`src/z80asm/dev/cpu/cpu_test_gbz80_ok.asm`. Prefer the forms below.384385### Word copies — full set386387| Allowed | Forbidden in this set |388|---------|------------------------|389| `ld` among `bc` / `de` / `hl` (any → any) | `af`, `sp` |390391```asm392 ld bc,de ; 42 4B393 ld bc,hl ; 44 4D394 ld de,bc ; 50 59395 ld de,hl ; 54 5D396 ld hl,bc ; 60 69397 ld hl,de ; 62 6B398```399400### Useful inline synthetics (not helpers)401402| Zilog | Expansion idea | Notes |403|-------|----------------|-------|404| `ld a,(de+)` / `ld (de+),a` | `ld a,(de)` / `inc de` | same for `bc`, and `-` / `ldi`/`ldd` spellings |405| `ld r,(hl+)` (r≠A) | `ld r,(hl)` / `inc hl` | A form is **native** `2A` |406| `add a,(hl+)` and other ALU+(hl±) | ALU `(hl)` + `inc`/`dec hl` | |407| `ld (hl),bc` / `ld bc,(hl)` | two byte moves + `inc`/`dec` | |408| `djnz *` | `dec b` / `jr nz` | |409| `sra hl` / `rl de` / `rr bc` | two CB ops | |410| `neg` | `cpl` / `inc a` | |411| `clr r` / `clr rp` | `ld r,0` / `ld rp,0` | do not use `xor a` if C must live |412| `jp (bc)` / `jp (de)` | `push rp` / `ret` | |413414Do **not** prefer these long expansions when a native exists:415416| Avoid | Why |417|-------|-----|418| `ld de,hl+*` / `ld de,sp+*` | many bytes via `ex` synthetic. Use `ld hl,sp+*` |419| `ld (**),hl` / `ld hl,(**)` | two absolute `ld a,(**)` plus park A |420| `ld (de),hl` | `ex` + `(hl)` traffic. Open-code through A |421422### Helper calls (`CD @__z80asm__…`)423424These assemble in normal mode but link a routine from `src/z80asm/dev/z80asm_lib/`.425They are **not** one-chip ops. Do not use them as the core of a hot hand-written426routine (same rule as other CPUs).427428| Source form | Helper | GB cost (from the helper source) |429|-------------|--------|-----------------------------------|430| `ex (sp),hl` | `__z80asm__ex_sp_hl` | 148c |431| `sub hl,bc` (and `de`/`hl`/`sp`) | `__z80asm__sub_hl_*` | 80c |432| `sbc hl,rp` / `adc hl,rp` | `__z80asm__sbc_hl_*` / `adc_hl_*` | helper |433| `add hl,a` / `add de,a` / `add bc,a` | `__z80asm__add_*_a` | helper |434| `ldi` / `ldir` / `ldd` / `lddr` | `__z80asm__ldi*` | uses `(hl+)`/`(de+)` inside |435| `cpi` / `cpir` / `cpd` / `cpdr` | `__z80asm__cpi*` | helper |436| `rld` / `rrd` | `__z80asm__rld` / `rrd` | helper |437| `call (hl)` | `__z80asm__call_hl` | helper |438439`__z80asm__ldi` itself is a good template: `ld a,(hl+)` / `ld (de+),a` /440`dec bc`. Open-code that sequence when you do not need Z80 P/V (Game Boy441has no P/V).442443## Pitfalls4444451. **`ld hl,sp+*` and `add sp,*` clear Z and rewrite H,C.** Save C with446 `rra`/`rla` if a rotate chain is live (`l_long_div_0.asm`).4472. **`rlca`/`rla`/`rrca`/`rra` force Z=0.** They are not test-and-rotate.4483. **No `ex de,hl` as a 1-byte op.** The mnemonic is a 56c stack swap.4494. **No `djnz` opcode.** `10` is `stop` (`10 00`). The mnemonic is sugar.4505. **`stop` is two bytes** (`10 00`). Do not emit a lone `10`.4516. **`reti` is `D9`**, not `ED 4D`. `D9` is not `exx`.4527. **`pop af` is not a 16-bit temp.** F bits 3–0 stay 0.4538. **`ld a,($ff00+n)` is not `ldh`.** It is `ld a,(**)` (`FA`). Write `ldh a,(n)`.4549. **`dec bc` does not set Z.** Do not write `dec bc; jr nz` as a 16-bit455 loop test.45610. **Only A has native `(hl+)`/`(hl-)`.** Other registers are two ops.45711. **No copt pass on library asm.** Hand-written `libsrc/**` is assembled458 as-is. Remove dead moves yourself. Match the target file’s whitespace.459 Checklist: **`tool-copt`**, **`methodology-measure`**.460461## Preference order (gbz80-only code)4624631. `ld hl,sp+*` for stack pointers (signed −128…127). `add hl,sp` only when464 the offset does not fit or you must keep Z.4652. `add sp,*` to drop arguments.4663. `ld a,(hl+)` / `ld (hl+),a` / `ld a,(hl-)` for streams.4674. `ld bc,hl` / `ld hl,bc` (and the other pair copies) to park a word.4685. `ldh` / `ld (c),a` for `$FF00+n`.4696. CB `sla`/`sra`/`rl`/`rr`/`swap` on registers and `(hl)`.4707. `djnz` sugar or `dec b`/`jr nz` for 8-bit counts.4718. Fall back to helper `call __z80asm__*` only when the expansion is large472 and the path is not hot.473474## Assembler capability (last resort)475476Fixtures under `src/z80asm/dev/cpu/` answer: does **z80asm** accept this477**source line** for `-mgbz80`, and what encoding does it emit?478479| File | Meaning |480|------|---------|481| `cpu_test_gbz80_ok.asm` | Assembles successfully. Comment after `;` is the encoding |482| `cpu_test_gbz80_err.asm` | Must fail (`; Error`) |483| `*_strict_*` | Strict mode: synthetics forbidden |484485**ok is not always one native chip opcode** (normal mode may allow486synthetics or `CD @__z80asm__…` helpers).487488How to read lines and `rg` without loading huge files: skill **`tool-z80asm`**.489490## Related491492- Full opcode grid: [references/opcodes.md](references/opcodes.md)493- Assembler: `tool-z80asm`494- Measure: `methodology-measure`, `tool-ticks`495- Float: **`library-math32`** — `--math32` on `-clib=gbz80` / `-mgbz80` links `math32_gbz80.lib` (stack-only cores under `asm/gbz80/`; higher C via `make -C …/c gbz80`)496- Target: `lib/config/gb.cfg`, `libsrc/target/gb/`497- sccz80 runtime examples: `libsrc/l/sccz80/8-gbz80/`