Help with some GB Z80 commands

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
Help with some GB Z80 commands
by on (#96245)
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?

by on (#96246)
The z80 doesn't set the flags on ld operations. You have to use something like
Code:
or a

to set the flags after an ld.

A little different from 6502, huh? :P

by on (#96259)
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.

by on (#96264)
Code:
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.

by on (#96277)
Code:
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:
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.

by on (#96278)
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.

by on (#96280)
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.

by on (#96284)
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:
loop:
LDA $----,Y
STA $----
INY
goto loop


will be done like:
Code:
ld hl,----
loop:
ldi a,(hl)  ;increments HL automatically
ld (----),a
goto loop


If both your fetch and store need to be indexed:
Code:
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.

by on (#96290)
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.

by on (#96291)
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.

by on (#96296)
CPI sets overflow flag based on value of BC, so you get the comparison for free. Also code size.

by on (#96299)
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:
 inc hl
 dec bc
 ld a,b
 or c
 jp nz,loop


with (5 bytes, 26 t-states)

Code:
 cpi
 jp po,loop

by on (#96310)
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:
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

by on (#96317)
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.

by on (#96366)
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.

by on (#96401)
Why not use one of the already available assemblers, like WLA-DX or RGBASM? Surely the time spent on hand-assembly could be better spent on writing more code or debugging.

by on (#96436)
I looked at WLA actually, but it looked like I had to compile the latest version myself and I am totally unfamiliar with that process.

But I'm not that big of a GB hacker and the only other code at the moment I need to do is just some simple code to relocate some text at the end of bank to another bank. And at the moment, I have bunch of menu hacking to do which doesn't really require me to write any code of substance.

by on (#96438)
I've hand-assembled z80 dozens of times before. If it's just to assemble a small(ish) routine, then there's nothing wrong with it. :P

by on (#96449)
Compiling WLA is no more complicated than running the included makefile. But I'm using the Windows version and WLA-GB refuses to assemble the code from WLAD-GB (The disassembler). Reading someone else's C code seems like less fun to me than reading asm.

by on (#96612)
So here's a story. I tried building wla-gb for windows and it would always shout "out of 8bit range" when assembling a JR opcode with a negative value. It won't assemble JR $FA but JR $-6 would work. This is interesting because wlad outputs JR $FA.

To fix the bug, I tinkered with wlad and added this code to the output_bank_opcodes routine in main.c (starts at line 198) so it would output JR $-6 instead of JR $FA:

Code:
   for (t = strlen(ot->op), x = 0, p = 0; x < t; ) {
     if (ot->op[x] == 'x') {
       bu[p] = 0;
      
      negval = in[(*i)++];
      if (0x38 == ot->hex || 0x30 == ot->hex || 0x28 == ot->hex || 0x20 == ot->hex || 0x18 == ot->hex || 0xF8 == ot->hex || 0xE8 == ot->hex) {
         if (negval > 0x7F && negval <= 0xFF) {
            signed int nb,na;
            nb = negval;
            negval = (negval - 0xFF) - 1;
            na = negval * -1;
            negval = (negval) + (na << 1);
            if (negval > 0x0F) {
               sprintf(tm, "-$%.2x",negval);
               p += 4;
            }
            else {
               sprintf(tm, "-$%.1x",negval);
               p += 3;
            }
         }
         else {
            sprintf(tm, "$%.2x",negval);
            p += 3;
         }
      }
      else {   
         sprintf(tm, "$%.2x",negval);
         p += 3;
      }
      
       strcat(bu, tm);
       /*p += 3;*/
       x++;
       a++;
     }
     else
       bu[p++] = ot->op[x++];
   }


That seemed to solve all the problems cropping up. Only now I'm trying to assemble this file (with wla-gb) that was generated with the wlad I modified. And now it's giving "unknown symbol JR" errors for no apparent reason at line 1325 ($08ef) even though there's no JR instruction at this line or the two lines before and after it. And of course it would have processed hundreds of JR opcodes at this point with no trouble.

Code:
LD A,($FF00+$a0)      ; $08e2
   INC A      ; $08e4
   CP $05      ; $08e5
   JR NZ,$-28      ; $08e7
   LD A,$04      ; $08e9
   JR $-2c      ; $08eb
   LD A,($FF00+$ac)      ; $08ed
   INC A      ; $08ef ; line 1325
   CP $05      ; $08f0
   JR NZ,$-27      ; $08f2
   LD A,$04      ; $08f4
   JR $-2b      ; $08f6


There could be a bug in wla-gb for assembling pure hex numbers (It seems to work fine with labels). But even after the effort I put into fixing the original problem, it's a very mysterious obstacle.

Edit: After playing around with the generated disassembly, a workaround would be to have the disassembler turn the negative jumps into labels. That should fix everything for good.

Edit: Code changed to reflect working wlad.

by on (#96615)
strat wrote:
So here's a story. I tried building wla-gb for windows and it would always shout "out of 8bit range" when assembling a JR opcode with a negative value. It won't assemble JR $FA but JR $-6 would work. This is interesting because wlad outputs JR $FA.

I think you're mistaken here. $ probably denotes the current program counter value so "$-6" is not "negative 6" but "PC minus 6". At least it would make much more sense for the negative hexadecimal value to be denoted with "-$6" instead.

by on (#96635)
Sweet, that was it. I got wlad to output something that can be reassembled.