The LDH instruction is the Game Boy's equivalent of zero-page addressing, faster than the usual three-byte instructions that can address any byte of memory. It is encoded in two bytes, with the second forming the low byte of the address. The high byte is fixed at $FF, so the page is at the end of memory, which is where the I/O registers and $80 bytes of RAM are located.
The syntax of the LDH instruction is inconvenient when dealing with symbolic address constants. For example, to access NR52 symbolically, one must subtract $FF00 when using LDH (using wla-dx), or define the constant as just the low byte, which makes it error-prone with all other instructions
In my code I've been using macros named LDA and STA, which internally do the subtraction. This way, I can always work in terms of the actual addresses.
In my opinion, the assembler syntax for LDH should have been such that the address was a normal one that is in the range $FF00-$FFFF, with the assembler dealing with the encoding. This would have allowed code to be unconcerned with the details:
Am I overlooking something that eliminates this issue entirely? wla-dx includes some automatic usage of LDH, similar to how 65xx assemblers use it when the high byte of an address is zero, but wla-dx requires that the address be of the form $FF00+offset, and I'd rather not rely on a non-standard transformation like this, as lots of my code depends on timing.
The syntax of the LDH instruction is inconvenient when dealing with symbolic address constants. For example, to access NR52 symbolically, one must subtract $FF00 when using LDH (using wla-dx), or define the constant as just the low byte, which makes it error-prone with all other instructions
Code:
; First approach: subtract $FF00 when using LDH
.define NR52 $FF26
LDH A,(NR52-$FF00) ; LD A,($FF26)
; Second approach: define with $FF00 already subtracted, then remember to add $FF00 when using other instructions
.define NR52 $26
LDH A,(NR52) ; LD A,($FF26)
LD HL,NR52+$FF00
XXX (HL)
LD BC,NR52 ; oops, forgot to add $FF00
XXX (BC) ; so this accesses $0026 rather than $FF26
.define NR52 $FF26
LDH A,(NR52-$FF00) ; LD A,($FF26)
; Second approach: define with $FF00 already subtracted, then remember to add $FF00 when using other instructions
.define NR52 $26
LDH A,(NR52) ; LD A,($FF26)
LD HL,NR52+$FF00
XXX (HL)
LD BC,NR52 ; oops, forgot to add $FF00
XXX (BC) ; so this accesses $0026 rather than $FF26
In my code I've been using macros named LDA and STA, which internally do the subtraction. This way, I can always work in terms of the actual addresses.
Code:
.macro LDA ; addr
LDH A,(\1 - $FF00)
.endm
.macro STA ; addr
LDH (\1 - $FF00),A
.endm
.define NR52 $FF26
LDA NR52 ; macro expands to LDH A,($26)
STA NR52 ; macro expands to LDH ($26),A
LDH A,(\1 - $FF00)
.endm
.macro STA ; addr
LDH (\1 - $FF00),A
.endm
.define NR52 $FF26
LDA NR52 ; macro expands to LDH A,($26)
STA NR52 ; macro expands to LDH ($26),A
In my opinion, the assembler syntax for LDH should have been such that the address was a normal one that is in the range $FF00-$FFFF, with the assembler dealing with the encoding. This would have allowed code to be unconcerned with the details:
Code:
; Hypothetical; NOT how GB-Z80 assemblers work!
LDH A,($FF26) ; encoded as $E0 $26
LDH ($FF26),A ; encoded as $F0 $26
LDH A,($FF26) ; encoded as $E0 $26
LDH ($FF26),A ; encoded as $F0 $26
Am I overlooking something that eliminates this issue entirely? wla-dx includes some automatic usage of LDH, similar to how 65xx assemblers use it when the high byte of an address is zero, but wla-dx requires that the address be of the form $FF00+offset, and I'd rather not rely on a non-standard transformation like this, as lots of my code depends on timing.
Code:
LD A,($FF00+$26) ; wla-dx automatically optimizes this into LDH A,($26)
LD A,($FF26) ; wla-dx does not optimize this
LD A,($FF26) ; wla-dx does not optimize this