Add system-specific boot symbols and CPU instruction tests

Introduced symbol files for various Game Boy systems (CGB, AGB, MGB, SGB) to define boot sequences and functionalities. Included CPU instruction behavior tests, with detailed coverage of standard operations and interrupt handling. Added documentation for test execution and internal framework operations.
This commit is contained in:
2025-05-02 17:33:07 +01:00
parent ae44d43175
commit 918c9020b5
74 changed files with 5037 additions and 7 deletions

View File

@@ -0,0 +1,215 @@
; Sound chip utilities
; Turns APU off
; Preserved: BC, DE, HL
sound_off:
wreg NR52,0
ret
; Turns APU on
; Preserved: BC, DE, HL
sound_on:
wreg NR52,$80 ; power
wreg NR51,$FF ; mono
wreg NR50,$77 ; volume
ret
; Synchronizes to APU length counter within
; tens of clocks. Uses square 2 channel.
; Preserved: BC, DE, HL
sync_apu:
wreg NR24,$00 ; disable length
wreg NR21,$3E ; length = 2 (in case of extra len clk)
wreg NR22,$08 ; silent without disabling channel
wreg NR24,$C0 ; start length
- lda NR52 ; wait for length to reach zero
and $02
jr nz,-
ret
; Synchronizes to first square sweep within
; tens of clocks. Uses square 1 channel.
; Preserved: BC, DE, HL
sync_sweep:
wreg NR10,$11 ; sweep period = 1, shift = 1
wreg NR12,$08 ; silent without disabling channel
wreg NR13,$FF ; freq = $3FF
wreg NR14,$83 ; start
- lda NR52
and $01
jr nz,-
ret
; Copies 16-byte wave from (HL) to wave RAM
; Preserved: BC, DE
load_wave:
push bc
wreg NR30,$00 ; disable while writing
ld c,$30
- ld a,(hl+)
ld ($FF00+c),a
inc c
bit 6,c
jr z,-
pop bc
ret
; Makes short beep
; Preserved: BC, DE, HL
beep:
xor a ; sound off
sta NR52
dec a
sta NR52 ; sound on
sta NR51 ; mono
sta NR50 ; volume
wreg NR12,$F1 ; volume, envelope rate
wreg NR14,$86 ; note on, pitch
delay_msec 250
ret
; Marks sound with bits of A encoded into volume
; Preserved: BC, DE, HL
mark_sound:
push bc
ld c,a
ld b,8
wreg NR10,0
wreg NR11,$80
wreg NR13,$F8
- ld a,$60
rl c
jr nc,+
ld a,$A0
+ sta NR12
wreg NR14,$87
delay_usec 300
wreg NR12,0
delay_usec 100
dec b
jr nz,-
pop bc
ret
; Fills wave RAM with A
; Preserved: BC, DE, HL
fill_wave:
push bc
ld c,$30
- ld ($FF00+c),a
inc c
bit 6,c
jr z,-
pop bc
ret
; Gets current length counter value for
; channel with mask A into A. Length counter
; must be enabled for that channel.
; Preserved: BC, DE, HL
get_len_a:
push bc
ld c,a
ld b,0
- lda NR52 ; 3
and c ; 1
jr z,+ ; 2
delay 4096-10
inc b ; 1
jr nz,- ; 3
+ ld a,b
pop bc
ret
; Synchronizes exactly to length clock. Next length clock
; occurs by 4079 clocks after this returns. Uses NR2x.
; Preserved: AF, BC, DE, HL
sync_length:
push af
push hl
ld hl,NR52
wreg NR22,$08 ; silent without disabling channel
wreg NR24,$40 ; avoids extra length clock on trigger
wreg NR21,-2 ; length = 2, in case clock occurs immediately
wreg NR24,$C0 ; start length
; Coarse sync
ld a,$02
- and (hl)
jr nz,-
; Fine sync. Slowly moves "forward" until
; length clock occurs just before reading NR52.
- delay 4097-20
wreg NR21,-1 ; 5
wreg NR24,$C0 ; 5
lda NR52 ; 3
delay 2 ; 2
and $02 ; 2
jr nz,- ; 3
pop hl
pop af
ret
; Delays n*4096 cycles
; Preserved: BC, DE, HL
.macro delay_frames ; n
ld a,\1
call delay_frames_
.endm
; Delays A*4096+13 cycles (including CALL)
; Preserved: BC, DE, HL
delay_a_frames:
or a ; 1
jr nz,+ ; 3
; -1
ret
delay_frames_: ; delays 4096*A-2 cycles (including CALL)
push af ; 4
ld a,256-13-20-12 ; 2
jr ++ ; 3
+
- push af ; 4
ld a,256-13-20 ; 2
++ call delay_a_20_cycles
delay 4096-256
pop af ; 3
dec a ; 1
jr nz,- ; 3
; -1
ret
.macro test_chan_timing ; chan, iter
ld a,\1
call print_dec
call print_space
ld a,\2
- push af
test_chan 1<<\1, \1*5+NR10
pop af
dec a
jr nz,-
call print_newline
.endm
.macro test_chans ARGS iter
test_chan_timing 0,iter
test_chan_timing 1,iter
test_chan_timing 2,iter
test_chan_timing 3,iter
.endm

View File

@@ -0,0 +1,121 @@
; Build as GBS music file
.memoryMap
defaultSlot 0
slot 0 $3000 size $1000
slot 1 $C000 size $1000
.endMe
.romBankSize $1000
.romBanks 2
;;;; GBS music file header
.byte "GBS"
.byte 1 ; vers
.byte 1 ; songs
.byte 1 ; first song
.word load_addr
.word reset
.word gbs_play
.word std_stack
.byte 0,0 ; timer
.ds $60,0
load_addr:
; WLA assumes we're building ROM and messes
; with bytes at the beginning, so skip them.
.ds $100,0
;;;; Shell
.include "runtime.s"
init_runtime:
ld a,$01 ; Identify as DMG hardware
ld (gb_id),a
.ifdef TEST_NAME
print_str TEST_NAME,newline,newline
.endif
ret
std_print:
sta SB
wreg SC,$81
delay 2304
ret
post_exit:
call play_byte
forever:
wreg NR52,0 ; sound off
- jp -
.ifndef CUSTOM_RESET
gbs_play:
.endif
console_flush:
console_normal:
console_inverse:
console_set_mode:
ret
; Reports A in binary as high and low tones, with
; leading low tone for reference. Omits leading
; zeroes.
; Preserved: AF, BC, DE, HL
play_byte:
push af
push hl
; HL = (A << 1) | 1
scf
rla
ld l,a
ld h,0
rl h
; Shift left until next-to-top bit is 1
- add hl,hl
bit 6,h
jr z,-
; Reset sound
delay_msec 400
wreg NR52,0 ; sound off
wreg NR52,$80 ; sound on
wreg NR51,$FF ; mono
wreg NR50,$77 ; volume
- add hl,hl
; Low or high pitch based on bit shifted out
; of HL
ld a,0
jr nc,+
ld a,$FF
+ sta NR23
; Play short tone
wreg NR21,$A0
wreg NR22,$F0
wreg NR24,$86
delay_msec 75
wreg NR22,0
wreg NR23,$F8
wreg NR24,$87
delay_msec 200
; Loop until HL = $8000
ld a,h
xor $80
or l
jr nz,-
pop hl
pop af
ret
.ends

View File

@@ -0,0 +1,80 @@
; Build as GB ROM
.memoryMap
defaultSlot 0
slot 0 $0000 size $4000
slot 1 $C000 size $4000
.endMe
.romBankSize $4000 ; generates $8000 byte ROM
.romBanks 2
.cartridgeType 1 ; MBC1
.computeChecksum
.computeComplementCheck
;;;; GB ROM header
; GB header read by bootrom
.org $100
nop
jp reset
; Nintendo logo required for proper boot
.byte $CE,$ED,$66,$66,$CC,$0D,$00,$0B
.byte $03,$73,$00,$83,$00,$0C,$00,$0D
.byte $00,$08,$11,$1F,$88,$89,$00,$0E
.byte $DC,$CC,$6E,$E6,$DD,$DD,$D9,$99
.byte $BB,$BB,$67,$63,$6E,$0E,$EC,$CC
.byte $DD,$DC,$99,$9F,$BB,$B9,$33,$3E
; Internal name
.ifdef ROM_NAME
.byte ROM_NAME
.endif
; CGB/DMG requirements
.org $143
.ifdef REQUIRE_CGB
.byte $C0
.else
.ifndef REQUIRE_DMG
.byte $80
.endif
.endif
.org $200
;;;; Shell
.include "runtime.s"
.include "console.s"
init_runtime:
call console_init
.ifdef TEST_NAME
print_str TEST_NAME,newline,newline
.endif
ret
std_print:
push af
sta SB
wreg SC,$81
delay 2304
pop af
jp console_print
post_exit:
call console_show
call play_byte
forever:
wreg NR52,0 ; sound off
- jr -
play_byte:
ret
.ends

View File

@@ -0,0 +1,98 @@
; Multiple checksum table handling
.define next_checksum bss+0
.redefine bss bss+2
; If PRINT_CHECKSUMS is defined, checksums are printed
; rather than compared.
; Initializes multiple checksum handler to use checksums
; table (defined by user).
; Preserved: BC, DE, HL
checksums_init:
ld a,<checksums
ld (next_checksum),a
ld a,>checksums
ld (next_checksum+1),a
ret
; Compares current checksum with next checksum in
; list. Z if they match, NZ if not.
; Preserved: BC, DE, HL
checksums_compare:
.ifdef PRINT_CHECKSUMS
lda checksum+3
push af
lda checksum+2
push af
lda checksum+1
push af
lda checksum+0
push af
ld a,(next_checksum)
inc a
ld (next_checksum),a
sub <checksums+1
and $03
ld a,','
jr nz,+
print_str newline,'.',"byte"
ld a,' '
+ call print_char
pop af
call @print_byte
pop af
call @print_byte
pop af
call @print_byte
ld a,'$'
call print_char
pop af
call print_hex
xor a
ret
@print_byte:
push af
ld a,'$'
call print_char
pop af
call print_hex
ld a,','
call print_char
ret
.else
push bc
push de
push hl
ld a,(next_checksum)
ld l,a
ld a,(next_checksum+1)
ld h,a
ld de,checksum
ld b,0
- ld a,(de)
xor (hl)
or b
ld b,a
inc hl
inc e
ld a,e
cp <(checksum+4)
jr nz,-
ld a,l
ld (next_checksum),a
ld a,h
ld (next_checksum+1),a
ld a,b
cp 0
pop hl
pop de
pop bc
ret
.endif

Binary file not shown.

View File

@@ -0,0 +1,291 @@
; Scrolling text console
; Console is 20x18 characters. Buffers lines, so
; output doesn't appear until a newline or flush.
; If scrolling isn't supported (i.e. SCY is treated
; as if always zero), the first 18 lines will
; still print properly). Also works properly if
; LY isn't supported (always reads back as the same
; value).
.define console_width 20
.define console_buf bss+0
.define console_pos bss+console_width
.define console_mode bss+console_width+1
.define console_scroll bss+console_width+2
.redefine bss bss+console_width+3
; Waits for start of LCD blanking period
; Preserved: BC, DE, HL
console_wait_vbl:
push bc
; Wait for start of vblank, with
; timeout in case LY doesn't work
; or LCD is disabled.
ld bc,-1250
- inc bc
ld a,b
or c
jr z,@timeout
lda LY
cp 144
jr nz,-
@timeout:
pop bc
ret
; Initializes text console
console_init:
call console_hide
; CGB-specific inits
ld a,(gb_id)
and gb_id_cgb
call nz,@init_cgb
; Clear nametable
ld a,' '
call @fill_nametable
; Load tiles
ld hl,TILES+$200
ld c,0
call @load_tiles
ld hl,TILES+$A00
ld c,$FF
call @load_tiles
; Init state
ld a,console_width
ld (console_pos),a
ld a,0
ld (console_mode),a
ld a,-8
ld (console_scroll),a
call console_scroll_up_
jr console_show
@fill_nametable:
ld hl,BGMAP0
ld b,4
- ld (hl),a
inc l
jr nz,-
inc h
dec b
jr nz,-
ret
@init_cgb:
; Clear palette
wreg $FF68,$80
ld b,16
- wreg $FF69,$FF
wreg $FF69,$7F
wreg $FF69,$00
wreg $FF69,$00
wreg $FF69,$00
wreg $FF69,$00
wreg $FF69,$00
wreg $FF69,$00
dec b
jr nz,-
; Clear attributes
ld a,1
ld (VBK),a
ld a,0
call @fill_nametable
ld a,0
ld (VBK),a
ret
@load_tiles:
ld de,ASCII
ld b,96
-- push bc
ld b,8
- ld a,(de)
inc de
xor c
ldi (hl),a
ldi (hl),a
dec b
jr nz,-
pop bc
dec b
jr nz,--
ret
; Shows console display
; Preserved: AF, BC, DE, HL
console_show:
push af
; Enable LCD
call console_wait_vbl
wreg LCDC,$91
wreg SCX,0
wreg BGP,$E4
jp console_apply_scroll_
; Hides console display by turning LCD off
; Preserved: AF, BC, DE, HL
console_hide:
push af
; LCD off
call console_wait_vbl
wreg LCDC,$11
pop af
ret
; Changes to normal text mode
; Preserved: BC, DE, HL
console_normal:
xor a
jr console_set_mode
; Changes to inverse text mode
; Preserved: BC, DE, HL
console_inverse:
ld a,$80
; Changes console mode to A.
; 0: Normal, $80: Inverse
; Preserved: BC, DE, HL
console_set_mode:
and $80
ld (console_mode),a
ret
; Prints char A to console. Will not appear until
; a newline or flush occurs.
; Preserved: AF, BC, DE, HL
console_print:
push af
cp 10
jr z,console_newline_
push hl
push af
ld hl,console_pos
ldi a,(hl)
cp <console_buf
jr nz,@not_at_end
; Newline if at end of current line. If this
; were done after writing to buffer, calling
; console_newline would print extra newline.
; Doing it before eliminates this.
; Ignore any spaces at end of line
pop af
cp ' '
jr z,@ignore_space
call console_newline
push af
@not_at_end:
pop af
or (hl) ; apply current attributes
dec l ; hl = console_pos
dec (hl) ; console_pos = console_pos - 1
ld l,(hl) ; hl = position in buffer
ld (hl),a
@ignore_space
pop hl
pop af
ret
; Displays current line and starts new one
; Preserved: AF, BC, DE, HL
console_newline:
push af
console_newline_:
call console_wait_vbl
call console_flush_
call console_scroll_up_
call console_flush_
jp console_apply_scroll_
console_scroll_up_:
push bc
push hl
; Scroll up 8 pixels
ld a,(console_scroll)
add 8
ld (console_scroll),a
; Start new clear line
ld a,' '
ld hl,console_buf + console_width - 1
ld b,console_width
- ldd (hl),a
dec b
jr nz,-
ld a,<(console_buf + console_width)
ld (console_pos),a
pop hl
pop bc
ret
; Displays current line's contents without scrolling.
; Preserved: A, BC, DE, HL
console_flush:
push af
call console_wait_vbl
call console_flush_
console_apply_scroll_:
ld a,(console_scroll)
sub 136
sta SCY
pop af
ret
console_flush_:
push de
push hl
; Address of row in nametable
ld a,(console_scroll)
ld l,a
ld h,(>BGMAP0) >> 2
add hl,hl
add hl,hl
; Copy line
ld de,console_buf + console_width
- dec e
ld a,(de)
ldi (hl),a
ld a,e
cp <console_buf
jr nz,-
pop hl
pop de
ret
ASCII:
.incbin "console.bin"

View File

@@ -0,0 +1,64 @@
; CPU speed manipulation.
; Switches to normal speed. No effect on DMG.
; Preserved: BC, DE, HL
cpu_norm:
; Do nothing if not CGB
ld a,(gb_id)
and gb_id_cgb
ret z
lda KEY1
rlca
ret nc
jr cpu_speed_toggle
; Switches to double speed. No effect on DMG.
; Preserved: BC, DE, HL
cpu_fast:
; Do nothing if not CGB
ld a,(gb_id)
and gb_id_cgb
ret z
lda KEY1
rlca
ret c
cpu_speed_toggle:
di
lda IE
push af
xor a
sta IE
sta IF
wreg P1,$30
wreg KEY1,1
stop
nop
pop af
sta IE
ret
; Determines current CPU speed without using KEY1.
; A=1 if fast, 0 if normal. Always 0 on DMG.
; Preserved: BC, DE,HL
get_cpu_speed:
push bc
call sync_apu
wreg NR14,$C0
wreg NR11,-1
wreg NR12,8
wreg NR14,$C0
ld bc,-$262
- inc bc
lda NR52
and 1
jr nz,-
ld a,0
bit 7,b
jr nz,+
inc a
+ pop bc
ret

View File

@@ -0,0 +1,78 @@
; CRC-32 checksum calculation
.define checksum dp+0 ; little-endian, complemented
.redefine dp dp+4
; Initializes checksum module. Might initialize tables
; in the future.
init_crc:
jr reset_crc
; Clears CRC
; Preserved: BC, DE, HL
reset_crc:
ld a,$FF
sta checksum+0
sta checksum+1
sta checksum+2
sta checksum+3
ret
; Updates current checksum with byte A
; Preserved: AF, BC, DE, HL
; Time: 237 cycles average
update_crc:
; 65 cycles + 8*cycles per bit
; min cycles per bit: 14
; max cycles per bit: 29
push af
push bc
push de
push hl
ld hl,checksum+3
ld b,(hl)
dec l
ld c,(hl)
dec l
ld d,(hl)
dec l
xor (hl)
ld h,8
- srl b
rr c
rr d
rra
jr nc,+
ld e,a
ld a,b
xor $ED
ld b,a
ld a,c
xor $B8
ld c,a
ld a,d
xor $83
ld d,a
ld a,e
xor $20
+ dec h
jr nz,-
ld h,>checksum
ldi (hl),a
ld (hl),d
inc l
ld (hl),c
inc l
ld (hl),b
pop hl
pop de
pop bc
pop af
ret

View File

@@ -0,0 +1,88 @@
; Fast table-based CRC-32
.define crc_tables (bss+$FF)&$FF00 ; 256-byte aligned
.redefine bss crc_tables+$400
; Initializes fast CRC tables and resets checksum.
; Time: 47 msec
init_crc_fast:
ld l,0
@next:
xor a
ld c,a
ld d,a
ld e,l
ld h,8
- rra
rr c
rr d
rr e
jr nc,+
xor $ED
ld b,a
ld a,c
xor $B8
ld c,a
ld a,d
xor $83
ld d,a
ld a,e
xor $20
ld e,a
ld a,b
+ dec h
jr nz,-
ld h,>crc_tables
ld (hl),e
inc h
ld (hl),d
inc h
ld (hl),c
inc h
ld (hl),a
inc l
jr nz,@next
jp init_crc
; Faster version of update_crc
; Preserved: BC, DE
; Time: 50 cycles (including CALL)
update_crc_fast:
; Fastest inline macro version of update_crc_fast
; Time: 40 cycles
; Size: 28 bytes
.macro update_crc_fast
ld l,a ; 1
lda checksum ; 3
xor l ; 1
ld l,a ; 1
ld h,>crc_tables ; 2
lda checksum+1 ; 3
xor (hl) ; 2
inc h ; 1
sta checksum ; 3
lda checksum+2 ; 3
xor (hl) ; 2
inc h ; 1
sta checksum+1 ; 3
lda checksum+3 ; 3
xor (hl) ; 2
inc h ; 1
sta checksum+2 ; 3
ld a,(hl) ; 2
sta checksum+3 ; 3
.endm
update_crc_fast
ret

View File

@@ -0,0 +1,220 @@
; Delays in cycles, milliseconds, etc.
; All routines are re-entrant (no global data). Routines never
; touch BC, DE, or HL registers. These ASSUME CPU is at normal
; speed. If running at double speed, msec/usec delays are half advertised.
; Delays n cycles, from 0 to 16777215
; Preserved: AF, BC, DE, HL
.macro delay ARGS n
.if n < 0
.printt "Delay must be >= 0"
.fail
.endif
.if n > 16777215
.printt "Delay must be < 16777216"
.fail
.endif
delay_ n&$FFFF, n>>16
.endm
; Delays n clocks, from 0 to 16777216*4. Must be multiple of 4.
; Preserved: AF, BC, DE, HL
.macro delay_clocks ARGS n
.if n # 4 != 0
.printt "Delay must be a multiple of 4"
.fail
.endif
delay_ (n/4)&$FFFF,(n/4)>>16
.endm
; Delays n microseconds (1/1000000 second)
; n can range from 0 to 4000 usec.
; Preserved: AF, BC, DE, HL
.macro delay_usec ARGS n
.if n < 0
.printt "Delay must be >= 0"
.fail
.endif
.if n > 4000
.printt "Delay must be <= 4000 usec"
.fail
.endif
delay_ ((n * 1048576 + 500000) / 1000000)&$FFFF,((n * 1048576 + 500000) / 1000000)>>16
.endm
; Delays n milliseconds (1/1000 second)
; n can range from 0 to 10000 msec.
; Preserved: AF, BC, DE, HL
.macro delay_msec ARGS n
.if n < 0
.printt "Delay must be >= 0"
.fail
.endif
.if n > 10000
.printt "Delay must be <= 10000 msec"
.fail
.endif
delay_ ((n * 1048576 + 500) / 1000)&$FFFF, ((n * 1048576 + 500) / 1000)>>16
.endm
; All the low/high quantities are to deal wla-dx's asinine
; restriction full expressions must evaluate to a 16-bit
; value. If the author ever rectifies this, all "high"
; arguments can be treated as zero and removed. Better yet,
; I'll just find an assembler that didn't crawl out of
; the sewer (this is one of too many bugs I've wasted
; hours working around).
.define max_short_delay 28
.macro delay_long_ ARGS n, high
; 0+ to avoid assembler treating as memory read
ld a,0+(((high<<16)+n) - 11) >> 16
call delay_65536a_9_cycles_
delay_nosave_ (((high<<16)+n) - 11)&$FFFF, 0
.endm
; Doesn't save AF, allowing minimization of AF save/restore
.macro delay_nosave_ ARGS n, high
; 65536+11 = maximum delay using delay_256a_9_cycles_
; 255+22 = maximum delay using delay_a_20_cycles
; 22 = minimum delay using delay_a_20_cycles
.if high > 1
delay_long_ n, high
.else
.if high*n > 11
delay_long_ n, high
.else
.if (high*(255+22+1))|n > 255+22
ld a,>(((high<<16)+n) - 11)
call delay_256a_9_cycles_
delay_nosave_ <(((high<<16)+n) - 11), 0
.else
.if n >= 22
ld a,n - 22
call delay_a_20_cycles
.else
delay_short_ n
.endif
.endif
.endif
.endif
.endm
.macro delay_ ARGS low, high
.if (high*(max_short_delay+1))|low > max_short_delay
push af
delay_nosave_ ((high<<16)+low - 7)&$FFFF, ((high<<16)+low - 7)>>16
pop af
.else
delay_short_ low
.endif
.endm
; Delays A cycles + overhead
; Preserved: BC, DE, HL
; Time: A+20 cycles (including CALL)
delay_a_20_cycles:
- sub 5 ; 2
jr nc,- ;3/2 do multiples of 5
rra ; 1
jr nc,+ ;3/2 bit 0
+ adc 1 ; 2
ret nc ;5/2 -1: 0 cycles
ret z ;5/2 0: 2 cycles
nop ; 1 1: 4 cycles
ret ; 4 (thanks to dclxvi for original algorithm)
; Delays A*256 cycles + overhead
; Preserved: BC, DE, HL
; Time: A*256+12 cycles (including CALL)
delay_256a_12_cycles:
or a ; 1
ret z ; 5/2
delay_256a_9_cycles_:
- delay 256-4
dec a ; 1
jr nz,- ;3/2
ret ; 4
; Delays A*65536 cycles + overhead
; Preserved: BC, DE, HL
; Time: A*65536+12 cycles (including CALL)
delay_65536a_12_cycles:
or a ; 1
ret z ;5/2
delay_65536a_9_cycles_:
- delay 65536-4
dec a ; 1
jr nz,- ;3/2
ret ; 4
; Delays H*256+L cycles + overhead
; Preserved: AF, BC, DE, HL
; Time: H*256+L+51 cycles
delay_hl_51_cycles:
push af
ld a,h
call delay_256a_12_cycles
ld a,l
call delay_a_20_cycles
pop af
ret
; delay_short_ macro calls into these
.ds max_short_delay-10,$00 ; NOP repeated several times
delay_unrolled_:
ret
.macro delay_short_ ARGS n
.if n < 0
.fail
.endif
.if n > max_short_delay
.fail
.endif
.if n == 1
nop
.endif
.if n == 2
nop
nop
.endif
.if n == 3
.byte $18,$00 ; JR +0
.endif
.if n == 4
.byte $18,$00 ; JR +0
nop
.endif
.if n == 5
.byte $18,$00 ; JR +0
nop
nop
.endif
.if n == 6
.byte $18,$00 ; JR +0
.byte $18,$00 ; JR +0
.endif
.if n == 7
push af
pop af
.endif
.if n == 8
push af
pop af
nop
.endif
.if n == 9
push af
pop af
nop
nop
.endif
.if n >= 10
call delay_unrolled_ + 10 - n
.endif
.endm

View File

@@ -0,0 +1,64 @@
; Game Boy hardware addresses
; Memory
.define VRAM $8000 ; video memory
.define TILES $8000 ; tile images
.define BGMAP0 $9800 ; first 32x32 tilemap
.define BGMAP1 $9C00 ; second 32x32 tilemap
.define WRAM $C000 ; internal memory
.define OAM $FE00 ; sprite memory
.define HRAM $FF80 ; fast memory for LDH
.define P1 $FF00
; Game link I/O
.define SB $FF01
.define SC $FF02
; Interrupts
.define DIV $FF04
.define TIMA $FF05
.define TMA $FF06
.define TAC $FF07
.define IF $FF0F
.define IE $FFFF
; LCD registers
.define LCDC $FF40 ; control
.define STAT $FF41 ; status
.define SCY $FF42 ; scroll Y
.define SCX $FF43 ; scroll X
.define LY $FF44 ; current Y being rendered
.define BGP $FF47
.define KEY1 $FF4D ; for changing CPU speed
.define VBK $FF4F
; Sound registers
.define NR10 $FF10
.define NR11 $FF11
.define NR12 $FF12
.define NR13 $FF13
.define NR14 $FF14
.define NR21 $FF16
.define NR22 $FF17
.define NR23 $FF18
.define NR24 $FF19
.define NR30 $FF1A
.define NR31 $FF1B
.define NR32 $FF1C
.define NR33 $FF1D
.define NR34 $FF1E
.define NR41 $FF20
.define NR42 $FF21
.define NR43 $FF22
.define NR44 $FF23
.define NR50 $FF24
.define NR51 $FF25
.define NR52 $FF26
.define WAVE $FF30

View File

@@ -0,0 +1,105 @@
; Framework for CPU instruction tests
; Calls test_instr with each instruction copied
; to instr, with a JP instr_done after it.
; Verifies checksum after testing instruction and
; prints opcode if it's wrong.
.include "checksums.s"
.include "cpu_speed.s"
.include "apu.s"
.include "crc_fast.s"
.define instr $DEF8
.define rp_temp (instr-4)
.define temp bss
; Sets SP to word at addr
; Preserved: BC, DE
.macro ldsp ; addr
ld a,(\1)
ld l,a
ld a,((\1)+1)
ld h,a
ld sp,hl
.endm
main:
call cpu_fast
call init_crc_fast
call checksums_init
set_test 0
ld hl,instrs
- ; Copy instruction
ld a,(hl+)
ld (instr),a
ld a,(hl+)
ld (instr+1),a
ld a,(hl+)
ld (instr+2),a
push hl
; Put JP instr_done after it
ld a,$C3
ld (instr+3),a
ld a,<instr_done
ld (instr+4),a
ld a,>instr_done
ld (instr+5),a
call reset_crc
call test_instr
call checksums_compare
jr z,passed
set_test 1
ld a,(instr)
call print_a
cp $CB
jr nz,+
ld a,(instr+1)
call print_a
+
passed:
; Next instruction
pop hl
ld a,l
cp <instrs_end
jr nz,-
ld a,h
cp >instrs_end
jr nz,-
jp tests_done
; Updates checksum with AF, BC, DE, and HL
checksum_af_bc_de_hl:
push hl
push af
update_crc_fast
pop hl
ld a,l
update_crc_fast
ld a,b
update_crc_fast
ld a,c
update_crc_fast
ld a,d
update_crc_fast
ld a,e
update_crc_fast
pop de
ld a,d
update_crc_fast
ld a,e
update_crc_fast
ret

View File

@@ -0,0 +1,73 @@
; General macros
; Reads A from addr, from $FF00 to $FFFF
; Preserved: F, BC, DE, HL
; Time: 3 cycles
.macro lda ARGS addr
ldh a,(addr - $FF00)
.endm
; Writes A to addr, from $FF00 to $FFFF
; Preserved: AF, BC, DE, HL
; Time: 3 cycles
.macro sta ARGS addr
ldh (addr - $FF00),a
.endm
; Writes immediate data to addr, from $FF00 to $FFFF
; Preserved: F, BC, DE, HL
; Time: 5 cycles
.macro wreg ARGS addr, data
ld a,data
sta addr
.endm
; Calls routine multiple times, with A having the
; value 'start' the first time, 'start+step' the
; second time, up to 'end' for the last time.
; Preserved: BC, DE, HL
.macro for_loop ; routine,start,end,step
ld a,\2
for_loop\@:
push af
call \1
pop af
add \4
cp <(\3 + \4)
jr nz,for_loop\@
.endm
; Calls routine n times. The value of A in the routine
; counts from 0 to n-1.
; Preserved: BC, DE, HL
.macro loop_n_times ; routine,n
for_loop \1,0,\2 - 1,+1
.endm
; Same as for_loop, but counts with 16-bit value in BC.
; Preserved: DE, HL
.macro for_loop16 ; routine,start,end,step
ld bc,\2
for_loop16\@:
push bc
call \1
pop bc
ld a,c
add <\4
ld c,a
ld a,b
adc >\4
ld b,a
cp >(\3+\4)
jr nz,for_loop16\@
ld a,c
cp <(\3+\4)
jr nz,for_loop16\@
.endm

View File

@@ -0,0 +1,38 @@
; RST handlers
.bank 0 slot 0
.org 0
inc a
ret
.ds 6,0
inc a
ret
.ds 6,0
inc a
ret
.ds 6,0
inc a
ret
.ds 6,0
inc a
ret
.ds 6,0
inc a
ret
.ds 6,0
inc a
ret
.ds 6,0
inc a
ret
.ds 6,0
inc a
ret
.ds 6,0
inc a
ret
.ds 6,0
inc a
ret
.ds 6,0
inc a
ret

View File

@@ -0,0 +1,177 @@
; Printing of numeric values
; Prints value of indicated register/pair
; as 2/4 hex digits, followed by a space.
; Updates checksum with printed values.
; Preserved: AF, BC, DE, HL
print_regs:
call print_af
call print_bc
call print_de
call print_hl
call print_newline
ret
print_a:
push af
print_a_:
call print_hex
ld a,' '
call print_char_nocrc
pop af
ret
print_af:
push af
call print_hex
pop af
print_f:
push bc
push af
pop bc
call print_c
pop bc
ret
print_b:
push af
ld a,b
jr print_a_
print_c:
push af
ld a,c
jr print_a_
print_d:
push af
ld a,d
jr print_a_
print_e:
push af
ld a,e
jr print_a_
print_h:
push af
ld a,h
jr print_a_
print_l:
push af
ld a,l
jr print_a_
print_bc:
push af
push bc
print_bc_:
ld a,b
call print_hex
ld a,c
pop bc
jr print_a_
print_de:
push af
push bc
ld b,d
ld c,e
jr print_bc_
print_hl:
push af
push bc
ld b,h
ld c,l
jr print_bc_
; Prints A as two hex chars and updates checksum
; Preserved: BC, DE, HL
print_hex:
call update_crc
print_hex_nocrc:
push af
swap a
call +
pop af
+ and $0F
cp 10
jr c,+
add 7
+ add '0'
jp print_char_nocrc
; Prints char_nz if Z flag is clear,
; char_z if Z flag is set.
; Preserved: AF, BC, DE, HL
.macro print_nz ARGS char_nz, char_z
push af
ld a,char_nz
jr nz,print_nz\@
ld a,char_z
print_nz\@:
call print_char
pop af
.endm
; Prints char_nc if C flag is clear,
; char_c if C flag is set.
; Preserved: AF, BC, DE, HL
.macro print_nc ARGS char_nc, char_c
push af
ld a,char_nc
jr nz,print_nc\@
ld a,char_c
print_nc\@:
call print_char
pop af
.endm
; Prints A as 2 decimal digits
; Preserved: AF, BC, DE, HL
print_dec2:
push af
push bc
jr +
; Prints A as 1-3 digit decimal value
; Preserved: AF, BC, DE, HL
print_dec:
push af
push bc
cp 10
jr c,++
ld c,100
cp c
call nc,@digit
+ ld c,10
call @digit
++ add '0'
call print_char
pop bc
pop af
ret
@digit:
ld b,'0'-1
- inc b
sub c
jr nc,-
add c
ld c,a
ld a,b
call print_char
ld a,c
ret

View File

@@ -0,0 +1,98 @@
; Main printing routine that checksums and
; prints to output device
; Character that does equivalent of print_newline
.define newline 10
; Prints char without updating checksum
; Preserved: BC, DE, HL
.define print_char_nocrc bss
.redefine bss bss+3
; Initializes printing. HL = print routine
init_printing:
ld a,l
ld (print_char_nocrc+1),a
ld a,h
ld (print_char_nocrc+2),a
jr show_printing
; Hides/shows further printing
; Preserved: BC, DE, HL
hide_printing:
ld a,$C9 ; RET
jr +
show_printing:
ld a,$C3 ; JP (nn)
+ ld (print_char_nocrc),a
ret
; Prints character and updates checksum UNLESS
; it's a newline.
; Preserved: AF, BC, DE, HL
print_char:
push af
cp newline
call nz,update_crc
call print_char_nocrc
pop af
ret
; Prints space. Does NOT update checksum.
; Preserved: AF, BC, DE, HL
print_space:
push af
ld a,' '
call print_char_nocrc
pop af
ret
; Advances to next line. Does NOT update checksum.
; Preserved: AF, BC, DE, HL
print_newline:
push af
ld a,newline
call print_char_nocrc
pop af
ret
; Prints immediate string
; Preserved: AF, BC, DE, HL
.macro print_str ; string,string2
push hl
call print_str_
.byte \1
.if NARGS > 1
.byte \2
.endif
.if NARGS > 2
.byte \3
.endif
.byte 0
pop hl
.endm
print_str_:
pop hl
call print_str_hl
jp hl
; Prints zero-terminated string pointed to by HL.
; On return, HL points to byte AFTER zero terminator.
; Preserved: AF, BC, DE
print_str_hl:
push af
jr +
- call print_char
+ ldi a,(hl)
or a
jr nz,-
pop af
ret

View File

@@ -0,0 +1,142 @@
; Common routines and runtime
; Must be defined by target-specific runtime:
;
; init_runtime: ; target-specific inits
; std_print: ; default routine to print char A
; post_exit: ; called at end of std_exit
; report_byte: ; report A to user
.define RUNTIME_INCLUDED 1
.ifndef bss
; address of next normal variable
.define bss $D800
.endif
.ifndef dp
; address of next direct-page ($FFxx) variable
.define dp $FF80
.endif
; DMG/CGB hardware identifier
.define gb_id_cgb $10 ; mask for testing CGB bit
.define gb_id_devcart $04 ; mask for testing "on devcart" bit
.define gb_id bss
.redefine bss bss+1
; Stack is normally here
.define std_stack $DFFF
; Copies $1000 bytes from HL to $C000, then jumps to it.
; A is preserved for jumped-to code.
copy_to_wram_then_run:
ld b,a
ld de,$C000
ld c,$10
- ldi a,(hl)
ld (de),a
inc e
jr nz,-
inc d
dec c
jr nz,-
ld a,b
jp $C000
.ifndef CUSTOM_RESET
reset:
; Run code from $C000, as is done on devcart. This
; ensures minimal difference in how it behaves.
ld hl,$4000
jp copy_to_wram_then_run
.bank 1 slot 1
.org $0 ; otherwise wla pads with lots of zeroes
jp std_reset
.endif
; Common routines
.include "gb.inc"
.include "macros.inc"
.include "delay.s"
.include "crc.s"
.include "printing.s"
.include "numbers.s"
.include "testing.s"
; Sets up hardware and runs main
std_reset:
; Init hardware
di
ld sp,std_stack
; Save DMG/CGB id
ld (gb_id),a
; Init hardware
.ifndef BUILD_GBS
wreg TAC,$00
wreg IF,$00
wreg IE,$00
.endif
wreg NR52,0 ; sound off
wreg NR52,$80 ; sound on
wreg NR51,$FF ; mono
wreg NR50,$77 ; volume
; TODO: clear all memory?
ld hl,std_print
call init_printing
call init_testing
call init_runtime
call reset_crc ; in case init_runtime prints anything
delay_msec 250
; Run user code
call main
; Default is to successful exit
ld a,0
jp exit
; Exits code and reports value of A
exit:
ld sp,std_stack
push af
call +
pop af
jp post_exit
+ push af
call print_newline
call show_printing
pop af
; Report exit status
cp 1
; 0: ""
ret c
; 1: "Failed"
jr nz,+
print_str "Failed",newline
ret
; n: "Failed #n"
+ print_str "Failed #"
call print_dec
call print_newline
ret
; returnOrg puts this code AFTER user code.
.section "runtime" returnOrg

View File

@@ -0,0 +1,176 @@
; Diagnostic and testing utilities
.define result bss+0
.define test_name bss+1
.redefine bss bss+3
; Sets test code and optional error text
; Preserved: AF, BC, DE, HL
.macro set_test ; code[,text[,text2]]
push hl
call set_test_
jr @set_test\@
.byte \1
.if NARGS > 1
.byte \2
.endif
.if NARGS > 2
.byte \3
.endif
.byte 0
@set_test\@:
pop hl
.endm
set_test_:
pop hl
push hl
push af
inc hl
inc hl
ldi a,(hl)
ld (result),a
ld a,l
ld (test_name),a
ld a,h
ld (test_name+1),a
pop af
ret
; Initializes testing module
init_testing:
set_test $FF
call init_crc
ret
; Reports "Passed", then exits with code 0
tests_passed:
call print_newline
print_str "Passed"
ld a,0
jp exit
; Reports "Done" if set_test has never been used,
; "Passed" if set_test 0 was last used, or
; failure if set_test n was last used.
tests_done:
ld a,(result)
inc a
jr z,+
dec a
jr z,tests_passed
jr test_failed
+ print_str "Done"
ld a,0
jp exit
; Reports current error text and exits with result code
test_failed:
ld a,(test_name)
ld l,a
ld a,(test_name+1)
ld h,a
ld a,(hl)
or a
jr z,+
call print_newline
call print_str_hl
call print_newline
+
ld a,(result)
cp 1 ; if a = 0 then a = 1
adc 0
jp exit
; Prints checksum as 8-character hex value
; Preserved: AF, BC, DE, HL
print_crc:
push af
; Must read checksum entirely before printing,
; since printing updates it.
lda checksum
cpl
push af
lda checksum+1
cpl
push af
lda checksum+2
cpl
push af
lda checksum+3
cpl
call print_hex
pop af
call print_hex
pop af
call print_hex
pop af
call print_a
pop af
ret
; If checksum doesn't match expected, reports failed test.
; Passing 0 just prints checksum. Clears checksum afterwards.
.macro check_crc ARGS crc
.if crc == 0
call show_printing
call print_newline
call print_crc
.else
ld bc,(crc >> 16) ~ $FFFF
ld de,(crc & $FFFF) ~ $FFFF
call check_crc_
.endif
.endm
check_crc_:
lda checksum+0
cp e
jr nz,+
lda checksum+1
cp d
jr nz,+
lda checksum+2
cp c
jr nz,+
lda checksum+3
cp b
jr nz,+
jp reset_crc
+ call print_crc
jp test_failed
; Updates checksum with bytes from addr to addr+size-1
.macro checksum_mem ARGS addr,size
ld hl,addr
ld bc,size
call checksum_mem_
.endm
checksum_mem_:
- ldi a,(hl)
call update_crc
dec bc
ld a,b
or c
jr nz,-
ret