Page 1 of 2

Help with some GB Z80 commands

Posted: Fri Jun 29, 2012 9:02 pm
by Pennywise
I am having a bit of trouble with writing some Z80 code and while I have written some code before, it's been a while and I'm still quite uncomfortable with Z80 as compared to 6502. Here's my WIP code:

ld hl,C000 21 00 C0
ld a,(hl) 7E
jr nz 20 0A

What I'm trying to do is have the code jump when A doesn't equal 0 ala BNE. Any idea of what I'm doing wrong?

Posted: Fri Jun 29, 2012 9:10 pm
by Drag
The z80 doesn't set the flags on ld operations. You have to use something like

Code: Select all

or a
to set the flags after an ld.

A little different from 6502, huh? :P

Posted: Sat Jun 30, 2012 11:34 am
by Pennywise
Heh, figures.

I've pretty much been converting 6502 to Z80 for all my asm mods so far. Sometimes it worked, sometimes not.

Incidentally is there just a doc that just contains all the Z80 instructions for the GB with opcodes and the basic functions? The best I can seem to find is from that unofficial GB programming doc compiled by several people. It's PDF and is a pain in the ass to navigate.

Posted: Sat Jun 30, 2012 1:25 pm
by Dwedit

Code: Select all

XOR L	AD
XOR H	AC
XOR E	AB
XOR D	AA
XOR C	A9
XOR B	A8
XOR A	AF
XOR (HL)	AE
SWAP L	35CB
SWAP H	34CB
SWAP E	33CB
SWAP D	32CB
SWAP C	31CB
SWAP B	30CB
SWAP A	37CB
SWAP (HL)	36CB
SUB L	95
SUB H	94
SUB E	93
SUB D	92
SUB C	91
SUB B	90
SUB A	97
SUB (HL)	96
STOP ""	10
SRL L	3DCB
SRL H	3CCB
SRL E	3BCB
SRL D	3ACB
SRL C	39CB
SRL B	38CB
SRL A	3FCB
SRL (HL)	3ECB
SRA L	2DCB
SRA H	2CCB
SRA E	2BCB
SRA D	2ACB
SRA C	29CB
SRA B	28CB
SRA A	2FCB
SRA (HL)	2ECB
SLA L	25CB
SLA H	24CB
SLA E	23CB
SLA D	22CB
SLA C	21CB
SLA B	20CB
SLA A	27CB
SLA (HL)	26CB
SCF ""	37
SBC A,L	9D
SBC A,H	9C
SBC A,E	9B
SBC A,D	9A
SBC A,C	99
SBC A,B	98
SBC A,A	9F
SBC A,(HL)	9E
RST 38H	FF
RST 30H	F7
RST 28H	EF
RST 20H	E7
RST 18H	DF
RST 10H	D7
RST 08H	CF
RST 00H	C7
RRCA ""	0F
RRC L	0DCB
RRC H	0CCB
RRC E	0BCB
RRC D	0ACB
RRC C	09CB
RRC B	08CB
RRC A	0FCB
RRC (HL)	0ECB
RRA ""	1F
RR L	1DCB
RR H	1CCB
RR E	1BCB
RR D	1ACB
RR C	19CB
RR B	18CB
RR A	1FCB
RR (HL)	1ECB
RLCA ""	07
RLC L	05CB
RLC H	04CB
RLC E	03CB
RLC D	02CB
RLC C	01CB
RLC B	00CB
RLC A	07CB
RLC (HL)	06CB
RLA ""	17
RL L	15CB
RL H	14CB
RL E	13CB
RL D	12CB
RL C	11CB
RL B	10CB
RL A	17CB
RL (HL)	16CB
RETI ""	D9
RET Z	C8
RET NZ	C0
RET NC	D0
RET C	D8
RET ""	C9
PUSH HL	E5
PUSH DE	D5
PUSH BC	C5
PUSH AF	F5
POP HL	E1
POP DE	D1
POP BC	C1
POP AF	F1
OR L	B5
OR H	B4
OR E	B3
OR D	B2
OR C	B1
OR B	B0
OR A	B7
OR (HL)	B6
NOP ""	00
LDI A,(HL)	2A
LDI (HL),A	22
LDD A,(HL)	3A
LDD (HL),A	32
LD SP,HL	F9
LD L,L	6D
LD L,H	6C
LD L,E	6B
LD L,D	6A
LD L,C	69
LD L,B	68
LD L,A	6F
LD L,(HL)	6E
LD H,L	65
LD H,H	64
LD H,E	63
LD H,D	62
LD H,C	61
LD H,B	60
LD H,A	67
LD H,(HL)	66
LD E,L	5D
LD E,H	5C
LD E,E	5B
LD E,D	5A
LD E,C	59
LD E,B	58
LD E,A	5F
LD E,(HL)	5E
LD D,L	55
LD D,H	54
LD D,E	53
LD D,D	52
LD D,C	51
LD D,B	50
LD D,A	57
LD D,(HL)	56
LD C,L	4D
LD C,H	4C
LD C,E	4B
LD C,D	4A
LD C,C	49
LD C,B	48
LD C,A	4F
LD C,(HL)	4E
LD B,L	45
LD B,H	44
LD B,E	43
LD B,D	42
LD B,C	41
LD B,B	40
LD B,A	47
LD B,(HL)	46
LD A,L	7D
LD A,H	7C
LD A,E	7B
LD A,D	7A
LD A,C	79
LD A,B	78
LD A,A	7F
LD A,(HL)	7E
LD A,(DE)	1A
LD A,(BC)	0A
LDH A,(C)	F2
LD (HL),L	75
LD (HL),H	74
LD (HL),E	73
LD (HL),D	72
LD (HL),C	71
LD (HL),B	70
LD (HL),A	77
LD (DE),A	12
LD (BC),A	02
LDH (C),A	E2
JP HL	E9
INC SP	33
INC L	2C
INC HL	23
INC H	24
INC E	1C
INC DE	13
INC D	14
INC C	0C
INC BC	03
INC B	04
INC A	3C
INC (HL)	34
HALT ""	76
EI ""	FB
DI ""	F3
DEC SP	3B
DEC L	2D
DEC HL	2B
DEC H	25
DEC E	1D
DEC DE	1B
DEC D	15
DEC C	0D
DEC BC	0B
DEC B	05
DEC A	3D
DEC (HL)	35
DAA ""	27
CPL ""	2F
CP L	BD
CP H	BC
CP E	BB
CP D	BA
CP C	B9
CP B	B8
CP A	BF
CP (HL)	BE
CCF ""	3F
AND L	A5
AND H	A4
AND E	A3
AND D	A2
AND C	A1
AND B	A0
AND A	A7
AND (HL)	A6
ADD HL,SP	39
ADD HL,HL	29
ADD HL,DE	19
ADD HL,BC	09
ADD A,L	85
ADD A,H	84
ADD A,E	83
ADD A,D	82
ADD A,C	81
ADD A,B	80
ADD A,A	87
ADD A,(HL)	86
ADC A,L	8D
ADC A,H	8C
ADC A,E	8B
ADC A,D	8A
ADC A,C	89
ADC A,B	88
ADC A,A	8F
ADC A,(HL)	8E
XOR *	EE
SUB *	D6
SET *,L	C5CB
SET *,H	C4CB
SET *,E	C3CB
SET *,D	C2CB
SET *,C	C1CB
SET *,B	C0CB
SET *,A	C7CB
SET *,(HL)	C6CB
SBC A,*	DE
RES *,L	85CB
RES *,H	84CB
RES *,E	83CB
RES *,D	82CB
RES *,C	81CB
RES *,B	80CB
RES *,A	87CB
RES *,(HL)	86CB
OR *	F6
LD SP,*	31
LD L,*	2E
LD HL,SP+*	F8
LD HL,*	21
LD H,*	26
LD E,*	1E
LD DE,*	11
LD D,*	16
LD C,*	0E
LD BC,*	01
LD B,*	06
LD A,(*)	FA
LDH A,(*)	F0
LD A,*	3E
LD (HL),*	36
LD (*),SP	08
LD (*),A	EA
LDH (*),A	E0
JR Z,*	28
JR NZ,*	20
JR NC,*	30
JR C,*	38
JR *	18
JP Z,*	CA
JP NZ,*	C2
JP NC,*	D2
JP C,*	DA
JP *	C3
CP *	FE
CALL Z,*	CC
CALL NZ,*	C4
CALL NC,*	D4
CALL C,*	DC
CALL *	CD
BIT *,L	45CB
BIT *,H	44CB
BIT *,E	43CB
BIT *,D	42CB
BIT *,C	41CB
BIT *,B	40CB
BIT *,A	47CB
BIT *,(HL)	46CB
AND *	E6
ADD SP,*	E8
ADD A,*	C6
ADC A,*	CE

Copy-pasted (and edited) from a TASM table. Note that asterisks are usually numbers (possibly 8 or 16-bit depending on the instruction), and CB is a byte prefix, not a suffix.

Posted: Sat Jun 30, 2012 4:18 pm
by Pennywise

Code: Select all

ld de,C000 ; dte second pass check
ld a,(de)	
cp a,00
jr nz		
ld hl,C20C	 ; original text read routine
ld c,(hl)
inc hl		
ld b,(hl)	
ld a,(bc)	
cp a,50	; dte check	
jr nc		
ret		
cp a,80		
jr nc		
Alright, so I'm working on converting a dte routine coded in 6502 to z80 and I am at a loss, but at least the first part is working as intended. How exactly does one write something to WRAM? Is the only option to load the RAM offset into hl etc and put A into it?

I also have this bit of code in 6502 that I'm thinking will need to be changed around a bit to get it to work in z80.

Code: Select all

SEC 
SBC #$80    
ASL    
TAX 
LDA $BD40,X
Two questions, when I want to subtract from A is there an equivalent instruction for SEC or is the carry flag set when I use the SUB command? I'm not sure, but I think that's what the later says in my docs. Lastly what instructions are used to load data from table as in the last LDA? Much appreciate the help.

Posted: Sat Jun 30, 2012 4:39 pm
by Shiru
I think it is better to clear this up: GB does not have Z80. The GB CPU is more like a modified 8080 with some things derived from the Z80 and 6502 even. So answer to a question of the 'how to do this for Z80' kind will be different for GB, as its CPU lacks major number of actual Z80 features.

Writing to the WRAM is not a CPU-related question. Since it is a normal memory, any ways of working with memory will work with it, be it ld (hl),r or ld (nnnn),a etc.

There is a nasty bug in the GB CPU - if you use inc rr or dec rr (bc, de, hl) when its content is in $fe00..$feff range, it corrupts OAM. So use these carefully. It also happens with few other, GB CPU specific opcodes.

Z80 does not have an equivalent for lda nnnn,x. GB CPU does, but only for the scratchpad RAM ($ff00..$ffff). Typical trick for 8080/Z80 is to put data at 256-byte boundary, so LSB part of a register pair will work as an index in a 256-byte block.

Posted: Sat Jun 30, 2012 5:21 pm
by Dwedit
Z80 has ld (NNNN),a, or ld (hl),NN. Take your pick.
For where to put things in memory, read the GB memory map. Might need to find some unused memory by running the game in an emulator for a while. Note that old versions of VisualBoyAdvance have a bug: the mirror of RAM is treated as a distinct memory region.

I call it the GBZ80 because the ASM opcodes are written out in a syntax just like the Z80, and most of the opcode byte values match exactly with the Z80. It's close enough for me. But there are still no IX or IY registers, or LDI/CPI/LDIR/CPIR... instructions.

Posted: Sat Jun 30, 2012 8:22 pm
by Drag
The opcode reference I've been using is this one.
The gameboy hardware reference I've been using is Nocash's, which also has an opcode list.

When you're working with the gameboy, most of your indexed addressing is going to be with HL, DE, and BC. For instance, something like:

Code: Select all

loop:
LDA $----,Y
STA $----
INY
goto loop
will be done like:

Code: Select all

ld hl,----
loop:
ldi a,(hl)  ;increments HL automatically
ld (----),a
goto loop
If both your fetch and store need to be indexed:

Code: Select all

ld hl,----
ld de,----
loop:
ldi a,(hl)
ld (de),a
inc de
goto loop
If I recall correctly, "ldi" doesn't exist on the vanilla z80, so you'd have to put an "inc hl" yourself.

Posted: Sat Jun 30, 2012 9:38 pm
by Dwedit
LDI exists on the regular Z80, but it is completely different:
Copy byte from (HL) to (DE), increment hl and de, decrement bc, set overflow flag to whether bc equals zero.
LDIR is the looping version of LDI, and repeats until bc = 0.
These are found on the regular Z80, not the game boy.

Some people use CPI (compare and increment) as an optimization trick on the Z80 when they want to increment HL, decrement BC, and don't care about the compare part.

Posted: Sat Jun 30, 2012 9:47 pm
by Shiru
What is the point of using CPI instead of INC/DEC on Z80? It is slower, 16 t-states vs 2*6, and takes two bytes in any case.

Posted: Sun Jul 01, 2012 12:58 am
by Dwedit
CPI sets overflow flag based on value of BC, so you get the comparison for free. Also code size.

Posted: Sun Jul 01, 2012 5:30 am
by Shiru
So your explaination of situation where the trick could be useful was missing important part about the overflow flag. The optimization is only could be achieved when the flag could be actually used. Like, replacing the common code piece (7 bytes, 30 t-states)

Code: Select all

 inc hl
 dec bc
 ld a,b
 or c
 jp nz,loop
with (5 bytes, 26 t-states)

Code: Select all

 cpi
 jp po,loop

Posted: Sun Jul 01, 2012 10:09 am
by Pennywise
Here's my tested and working dte code. The game executes this code three times before moving onto to the next text byte and that really threw a wrench into my code. I'm this is not the best routine ever, but it does work as intended.

Code: Select all

ld de,C000	11 00 C0	; dte second pass check	
		ld a,(de)	1A			
		cp a,03		FE 03		; go to second pass routine if 03
		jr z		28 2F
		cp a,04		FE 04		; above except slightly modified
		jr z		28 31
		cp a,05		FE 05
		jr z		28 2D
		cp a,06		FE 06
		jr nz		20 03		
		ld a,00		3E 00
		ld de,a		12
		ld hl,C20C	21 0C C2	; original text read routine
		ld c,(hl)	4E
		inc hl		23
		ld b,(hl)	46
		ld a,(bc)	0A
		cp a,50		FE 50		; dte check values 50-80
		jr nc		30 01
		ret			C9			; return if less than 50 or greater than 80
		cp a,80		FE 80
		jr nc		30 FB
		push af		F5
		ld a,(de)	1A			; increments the dte second pass check byte
		inc a		3C
		ld (de),a	12
		pop af		F1
		sub,50		D6 50		; subtract 50 from the dte value to be used for the table
		push hl		E5
		ld h1,3B80	21 80 3B	; loads the base offset for the dte table
		add l		85			; adds what was previous subtracted to the table
		ld l,a		6F
		ld a,(hl)	7E			; puts the first dte value in a
		pop hl		E1
		ret			C9
		
		ld hl,C20C	21 0C C2	; second pass routine only triggered once
		ld a, (hl)	7E			
		dec a		3D			; decrements the the text offset by 1 to adjust for dte
		ld hl,a		77
		
		ld a,(de)	1A			; second pass routine, goes here if a < 3
		inc a		3C			; increments dte flag
		ld (de),a	12
		ld hl,C20C	21 0C C2	; original text read routine
		ld c,(hl)	4E
		inc hl		23
		ld b,(hl)	46
		ld a,(bc)	0A
		sub,50		D6 50
		push hl		E5
		ld h1,3B80	21 80 3B	; same dte code
		add l		85
		ld l,a		6F
		inc 1		2C			; add 1 to the dte table offset for second dte value
		ld a,(hl)	7E
		pop hl		E1
		ret			C9

Posted: Sun Jul 01, 2012 11:54 am
by Dwedit
It looks like you're assembling by hand, are you really doing this? I'd at least set up TASM or something to make a binary, then I can copy-paste assembled code in with a hex editor.

I see some typos in the ASM code, like missing parenthesis on (de) or (hl), or calling hl "h1". Those don't matter if you're assembling by hand, but an assembler would give errors.

Posted: Mon Jul 02, 2012 3:40 pm
by Pennywise
Yeah, I know assembling code by hand isn't the smartest or even the best way to handle writing and developing code. I'm actually waiting for this person called Lin to release his/her assembler for the system. Hopefully, if it's released this summer, I'll be switching over to that.