From 1734741725feb535634ab8e9ca1fdec0f666892c Mon Sep 17 00:00:00 2001 From: aieque Date: Thu, 31 Dec 2020 19:42:24 +0100 Subject: [PATCH] Added stuff. --- .gitignore | 1 + playing-coffee - Copy/.classpath | 38 ++ playing-coffee - Copy/.gitignore | 1 + playing-coffee - Copy/.project | 23 + .../.settings/org.eclipse.jdt.core.prefs | 11 + .../.settings/org.eclipse.m2e.core.prefs | 4 + playing-coffee - Copy/dmg_boot.bin | Bin 0 -> 256 bytes playing-coffee - Copy/gb-test-roms-master.zip | Bin 0 -> 331384 bytes playing-coffee - Copy/log.txt | 646 ++++++++++++++++++ playing-coffee - Copy/pom.xml | 15 + .../roms/cgb_sound/cgb_sound.gb | Bin 0 -> 65536 bytes .../roms/cgb_sound/readme.txt | 86 +++ .../cgb_sound/rom_singles/01-registers.gb | Bin 0 -> 32768 bytes .../roms/cgb_sound/rom_singles/02-len ctr.gb | Bin 0 -> 32768 bytes .../roms/cgb_sound/rom_singles/03-trigger.gb | Bin 0 -> 32768 bytes .../roms/cgb_sound/rom_singles/04-sweep.gb | Bin 0 -> 32768 bytes .../cgb_sound/rom_singles/05-sweep details.gb | Bin 0 -> 32768 bytes .../rom_singles/06-overflow on trigger.gb | Bin 0 -> 32768 bytes .../rom_singles/07-len sweep period sync.gb | Bin 0 -> 32768 bytes .../rom_singles/08-len ctr during power.gb | Bin 0 -> 32768 bytes .../rom_singles/09-wave read while on.gb | Bin 0 -> 32768 bytes .../rom_singles/10-wave trigger while on.gb | Bin 0 -> 32768 bytes .../rom_singles/11-regs after power.gb | Bin 0 -> 32768 bytes .../roms/cgb_sound/rom_singles/12-wave.gb | Bin 0 -> 32768 bytes .../roms/cgb_sound/source/01-registers.s | 117 ++++ .../roms/cgb_sound/source/02-len ctr.s | 163 +++++ .../roms/cgb_sound/source/03-trigger.s | 200 ++++++ .../roms/cgb_sound/source/04-sweep.s | 120 ++++ .../roms/cgb_sound/source/05-sweep details.s | 121 ++++ .../cgb_sound/source/06-overflow on trigger.s | 64 ++ .../source/07-len sweep period sync.s | 106 +++ .../source/08-len ctr during power.s | 84 +++ .../cgb_sound/source/09-wave read while on.s | 41 ++ .../source/10-wave trigger while on.s | 49 ++ .../cgb_sound/source/11-regs after power.s | 51 ++ .../roms/cgb_sound/source/12-wave.s | 112 +++ .../roms/cgb_sound/source/common/build_gbs.s | 139 ++++ .../roms/cgb_sound/source/common/build_rom.s | 70 ++ .../roms/cgb_sound/source/common/console.bin | Bin 0 -> 768 bytes .../roms/cgb_sound/source/common/console.s | 285 ++++++++ .../roms/cgb_sound/source/common/crc.s | 78 +++ .../roms/cgb_sound/source/common/delay.s | 220 ++++++ .../roms/cgb_sound/source/common/gb.inc | 81 +++ .../roms/cgb_sound/source/common/macros.inc | 91 +++ .../roms/cgb_sound/source/common/numbers.s | 177 +++++ .../roms/cgb_sound/source/common/printing.s | 77 +++ .../roms/cgb_sound/source/common/shell.s | 261 +++++++ .../roms/cgb_sound/source/common/testing.s | 195 ++++++ .../roms/cgb_sound/source/linkfile | 2 + .../roms/cgb_sound/source/readme.txt | 82 +++ .../roms/cgb_sound/source/shell.inc | 27 + playing-coffee - Copy/roms/cpu_instrs.gb | Bin 0 -> 65536 bytes .../roms/cpu_instrs/cpu_instrs.gb | Bin 0 -> 65536 bytes .../roms/cpu_instrs/individual/01-special.gb | Bin 0 -> 32768 bytes .../cpu_instrs/individual/02-interrupts.gb | Bin 0 -> 32768 bytes .../roms/cpu_instrs/individual/03-op sp,hl.gb | Bin 0 -> 32768 bytes .../roms/cpu_instrs/individual/04-op r,imm.gb | Bin 0 -> 32768 bytes .../roms/cpu_instrs/individual/05-op rp.gb | Bin 0 -> 32768 bytes .../roms/cpu_instrs/individual/06-ld r,r.gb | Bin 0 -> 32768 bytes .../individual/07-jr,jp,call,ret,rst.gb | Bin 0 -> 32768 bytes .../cpu_instrs/individual/08-misc instrs.gb | Bin 0 -> 32768 bytes .../roms/cpu_instrs/individual/09-op r,r.gb | Bin 0 -> 32768 bytes .../roms/cpu_instrs/individual/10-bit ops.gb | Bin 0 -> 32768 bytes .../cpu_instrs/individual/11-op a,(hl).gb | Bin 0 -> 32768 bytes .../roms/cpu_instrs/readme.txt | 119 ++++ .../roms/cpu_instrs/source/01-special.s | 78 +++ .../roms/cpu_instrs/source/02-interrupts.s | 73 ++ .../roms/cpu_instrs/source/03-op sp,hl.s | 102 +++ .../roms/cpu_instrs/source/04-op r,imm.s | 88 +++ .../roms/cpu_instrs/source/05-op rp.s | 98 +++ .../roms/cpu_instrs/source/06-ld r,r.s | 115 ++++ .../cpu_instrs/source/07-jr,jp,call,ret,rst.s | 127 ++++ .../roms/cpu_instrs/source/08-misc instrs.s | 110 +++ .../roms/cpu_instrs/source/09-op r,r.s | 269 ++++++++ .../roms/cpu_instrs/source/10-bit ops.s | 315 +++++++++ .../roms/cpu_instrs/source/11-op a,(hl).s | 162 +++++ .../roms/cpu_instrs/source/common/apu.s | 215 ++++++ .../roms/cpu_instrs/source/common/build_gbs.s | 121 ++++ .../roms/cpu_instrs/source/common/build_rom.s | 80 +++ .../roms/cpu_instrs/source/common/checksums.s | 98 +++ .../roms/cpu_instrs/source/common/console.bin | Bin 0 -> 768 bytes .../roms/cpu_instrs/source/common/console.s | 291 ++++++++ .../roms/cpu_instrs/source/common/cpu_speed.s | 64 ++ .../roms/cpu_instrs/source/common/crc.s | 78 +++ .../roms/cpu_instrs/source/common/crc_fast.s | 88 +++ .../roms/cpu_instrs/source/common/delay.s | 220 ++++++ .../roms/cpu_instrs/source/common/gb.inc | 64 ++ .../cpu_instrs/source/common/instr_test.s | 105 +++ .../roms/cpu_instrs/source/common/macros.inc | 73 ++ .../cpu_instrs/source/common/multi_custom.s | 38 ++ .../roms/cpu_instrs/source/common/numbers.s | 177 +++++ .../roms/cpu_instrs/source/common/printing.s | 98 +++ .../roms/cpu_instrs/source/common/runtime.s | 142 ++++ .../roms/cpu_instrs/source/common/testing.s | 176 +++++ .../roms/cpu_instrs/source/linkfile | 2 + .../roms/cpu_instrs/source/shell.inc | 21 + .../roms/dmg_sound/dmg_sound.gb | Bin 0 -> 65536 bytes .../roms/dmg_sound/readme.txt | 86 +++ .../dmg_sound/rom_singles/01-registers.gb | Bin 0 -> 32768 bytes .../roms/dmg_sound/rom_singles/02-len ctr.gb | Bin 0 -> 32768 bytes .../roms/dmg_sound/rom_singles/03-trigger.gb | Bin 0 -> 32768 bytes .../roms/dmg_sound/rom_singles/04-sweep.gb | Bin 0 -> 32768 bytes .../dmg_sound/rom_singles/05-sweep details.gb | Bin 0 -> 32768 bytes .../rom_singles/06-overflow on trigger.gb | Bin 0 -> 32768 bytes .../rom_singles/07-len sweep period sync.gb | Bin 0 -> 32768 bytes .../rom_singles/08-len ctr during power.gb | Bin 0 -> 32768 bytes .../rom_singles/09-wave read while on.gb | Bin 0 -> 32768 bytes .../rom_singles/10-wave trigger while on.gb | Bin 0 -> 32768 bytes .../rom_singles/11-regs after power.gb | Bin 0 -> 32768 bytes .../rom_singles/12-wave write while on.gb | Bin 0 -> 32768 bytes .../roms/dmg_sound/source/01-registers.s | 117 ++++ .../roms/dmg_sound/source/02-len ctr.s | 163 +++++ .../roms/dmg_sound/source/03-trigger.s | 200 ++++++ .../roms/dmg_sound/source/04-sweep.s | 120 ++++ .../roms/dmg_sound/source/05-sweep details.s | 121 ++++ .../dmg_sound/source/06-overflow on trigger.s | 64 ++ .../source/07-len sweep period sync.s | 106 +++ .../source/08-len ctr during power.s | 84 +++ .../dmg_sound/source/09-wave read while on.s | 41 ++ .../source/10-wave trigger while on.s | 49 ++ .../dmg_sound/source/11-regs after power.s | 52 ++ .../dmg_sound/source/12-wave write while on.s | 49 ++ .../roms/dmg_sound/source/common/build_gbs.s | 139 ++++ .../roms/dmg_sound/source/common/build_rom.s | 70 ++ .../roms/dmg_sound/source/common/console.bin | Bin 0 -> 768 bytes .../roms/dmg_sound/source/common/console.s | 285 ++++++++ .../roms/dmg_sound/source/common/crc.s | 78 +++ .../roms/dmg_sound/source/common/delay.s | 220 ++++++ .../roms/dmg_sound/source/common/gb.inc | 81 +++ .../roms/dmg_sound/source/common/macros.inc | 91 +++ .../roms/dmg_sound/source/common/numbers.s | 177 +++++ .../roms/dmg_sound/source/common/printing.s | 77 +++ .../roms/dmg_sound/source/common/shell.s | 261 +++++++ .../roms/dmg_sound/source/common/testing.s | 195 ++++++ .../roms/dmg_sound/source/linkfile | 2 + .../roms/dmg_sound/source/readme.txt | 82 +++ .../roms/dmg_sound/source/shell.inc | 27 + playing-coffee - Copy/roms/drmario.gb | Bin 0 -> 32768 bytes playing-coffee - Copy/roms/halt_bug.gb | Bin 0 -> 32768 bytes .../roms/instr_timing/instr_timing.gb | Bin 0 -> 32768 bytes .../roms/instr_timing/readme.txt | 139 ++++ .../instr_timing/source/common/build_gbs.s | 121 ++++ .../instr_timing/source/common/build_rom.s | 80 +++ .../instr_timing/source/common/console.bin | Bin 0 -> 768 bytes .../roms/instr_timing/source/common/console.s | 291 ++++++++ .../roms/instr_timing/source/common/crc.s | 78 +++ .../roms/instr_timing/source/common/delay.s | 220 ++++++ .../roms/instr_timing/source/common/gb.inc | 64 ++ .../instr_timing/source/common/macros.inc | 73 ++ .../roms/instr_timing/source/common/numbers.s | 177 +++++ .../instr_timing/source/common/printing.s | 98 +++ .../roms/instr_timing/source/common/runtime.s | 142 ++++ .../roms/instr_timing/source/common/testing.s | 176 +++++ .../roms/instr_timing/source/common/timer.s | 88 +++ .../roms/instr_timing/source/instr_timing.s | 336 +++++++++ .../roms/instr_timing/source/linkfile | 2 + .../roms/instr_timing/source/shell.inc | 21 + .../roms/interrupt_time/interrupt_time.gb | Bin 0 -> 32768 bytes .../roms/interrupt_time/interrupt_time.s | 57 ++ playing-coffee - Copy/roms/kwirk.gb | Bin 0 -> 32768 bytes .../roms/mem_timing-2/mem_timing.gb | Bin 0 -> 65536 bytes .../roms/mem_timing-2/readme.txt | 114 ++++ .../rom_singles/01-read_timing.gb | Bin 0 -> 32768 bytes .../rom_singles/02-write_timing.gb | Bin 0 -> 32768 bytes .../rom_singles/03-modify_timing.gb | Bin 0 -> 32768 bytes .../roms/mem_timing-2/source/01-read_timing.s | 150 ++++ .../mem_timing-2/source/02-write_timing.s | 115 ++++ .../mem_timing-2/source/03-modify_timing.s | 156 +++++ .../mem_timing-2/source/common/build_gbs.s | 139 ++++ .../mem_timing-2/source/common/build_rom.s | 84 +++ .../mem_timing-2/source/common/console.bin | Bin 0 -> 768 bytes .../roms/mem_timing-2/source/common/console.s | 285 ++++++++ .../roms/mem_timing-2/source/common/crc.s | 78 +++ .../roms/mem_timing-2/source/common/delay.s | 220 ++++++ .../roms/mem_timing-2/source/common/gb.inc | 81 +++ .../mem_timing-2/source/common/macros.inc | 91 +++ .../roms/mem_timing-2/source/common/numbers.s | 177 +++++ .../mem_timing-2/source/common/printing.s | 77 +++ .../roms/mem_timing-2/source/common/shell.s | 266 ++++++++ .../roms/mem_timing-2/source/common/testing.s | 195 ++++++ .../roms/mem_timing-2/source/common/tima_64.s | 36 + .../roms/mem_timing-2/source/linkfile | 2 + .../roms/mem_timing-2/source/readme.txt | 82 +++ .../roms/mem_timing-2/source/shell.inc | 27 + .../mem_timing/individual/01-read_timing.gb | Bin 0 -> 32768 bytes .../mem_timing/individual/02-write_timing.gb | Bin 0 -> 32768 bytes .../mem_timing/individual/03-modify_timing.gb | Bin 0 -> 32768 bytes .../roms/mem_timing/mem_timing.gb | Bin 0 -> 65536 bytes .../roms/mem_timing/readme.txt | 122 ++++ .../roms/mem_timing/source/01-read_timing.s | 148 ++++ .../roms/mem_timing/source/02-write_timing.s | 115 ++++ .../roms/mem_timing/source/03-modify_timing.s | 156 +++++ .../roms/mem_timing/source/common/build_gbs.s | 121 ++++ .../roms/mem_timing/source/common/build_rom.s | 80 +++ .../roms/mem_timing/source/common/console.bin | Bin 0 -> 768 bytes .../roms/mem_timing/source/common/console.s | 291 ++++++++ .../roms/mem_timing/source/common/crc.s | 78 +++ .../roms/mem_timing/source/common/delay.s | 220 ++++++ .../roms/mem_timing/source/common/gb.inc | 64 ++ .../roms/mem_timing/source/common/macros.inc | 73 ++ .../roms/mem_timing/source/common/numbers.s | 177 +++++ .../roms/mem_timing/source/common/printing.s | 98 +++ .../roms/mem_timing/source/common/runtime.s | 142 ++++ .../roms/mem_timing/source/common/testing.s | 176 +++++ .../roms/mem_timing/source/common/tima_64.s | 33 + .../roms/mem_timing/source/linkfile | 2 + .../roms/mem_timing/source/shell.inc | 21 + playing-coffee - Copy/roms/oam_bug/oam_bug.gb | Bin 0 -> 65536 bytes playing-coffee - Copy/roms/oam_bug/readme.txt | 109 +++ .../roms/oam_bug/rom_singles/1-lcd_sync.gb | Bin 0 -> 32768 bytes .../roms/oam_bug/rom_singles/2-causes.gb | Bin 0 -> 32768 bytes .../roms/oam_bug/rom_singles/3-non_causes.gb | Bin 0 -> 32768 bytes .../oam_bug/rom_singles/4-scanline_timing.gb | Bin 0 -> 32768 bytes .../roms/oam_bug/rom_singles/5-timing_bug.gb | Bin 0 -> 32768 bytes .../oam_bug/rom_singles/6-timing_no_bug.gb | Bin 0 -> 32768 bytes .../oam_bug/rom_singles/7-timing_effect.gb | Bin 0 -> 32768 bytes .../oam_bug/rom_singles/8-instr_effect.gb | Bin 0 -> 32768 bytes .../roms/oam_bug/source/1-lcd_sync.s | 28 + .../roms/oam_bug/source/2-causes.s | 86 +++ .../roms/oam_bug/source/3-non_causes.s | 86 +++ .../roms/oam_bug/source/4-scanline_timing.s | 62 ++ .../roms/oam_bug/source/5-timing_bug.s | 43 ++ .../roms/oam_bug/source/6-timing_no_bug.s | 57 ++ .../roms/oam_bug/source/7-timing_effect.s | 38 ++ .../roms/oam_bug/source/8-instr_effect.s | 63 ++ .../roms/oam_bug/source/common/build_gbs.s | 139 ++++ .../roms/oam_bug/source/common/build_rom.s | 70 ++ .../roms/oam_bug/source/common/console.bin | Bin 0 -> 768 bytes .../roms/oam_bug/source/common/console.s | 285 ++++++++ .../roms/oam_bug/source/common/crc.s | 78 +++ .../roms/oam_bug/source/common/delay.s | 220 ++++++ .../roms/oam_bug/source/common/gb.inc | 81 +++ .../roms/oam_bug/source/common/macros.inc | 91 +++ .../roms/oam_bug/source/common/numbers.s | 177 +++++ .../roms/oam_bug/source/common/ppu.s | 14 + .../roms/oam_bug/source/common/printing.s | 77 +++ .../roms/oam_bug/source/common/shell.s | 261 +++++++ .../roms/oam_bug/source/common/testing.s | 195 ++++++ .../roms/oam_bug/source/linkfile | 2 + .../roms/oam_bug/source/readme.txt | 82 +++ .../roms/oam_bug/source/shell.inc | 27 + playing-coffee - Copy/roms/readme.txt | 4 + playing-coffee - Copy/roms/solitaire.gb | Bin 0 -> 65536 bytes playing-coffee - Copy/roms/supermarioland.gb | Bin 0 -> 65536 bytes playing-coffee - Copy/roms/tetris.gb | Bin 0 -> 32768 bytes .../application/Application.java | 111 +++ .../playingcoffee/application/GameBoy.java | 85 +++ .../java/playingcoffee/core/Cartridge.java | 44 ++ .../playingcoffee/core/InterruptListener.java | 7 + .../playingcoffee/core/InterruptManager.java | 76 +++ .../src/main/java/playingcoffee/core/MMU.java | 93 +++ .../java/playingcoffee/core/MemorySpace.java | 9 + .../main/java/playingcoffee/core/cpu/CPU.java | 343 ++++++++++ .../java/playingcoffee/core/cpu/Flags.java | 29 + .../playingcoffee/core/cpu/Registers.java | 57 ++ .../core/opcode/ALU16Opcode.java | 57 ++ .../playingcoffee/core/opcode/ALUOpcode.java | 134 ++++ .../playingcoffee/core/opcode/Argument.java | 348 ++++++++++ .../playingcoffee/core/opcode/CallOpcode.java | 60 ++ .../core/opcode/ComplementOpcode.java | 18 + .../core/opcode/FlipCarryOpcode.java | 17 + .../core/opcode/InterruptOpcode.java | 29 + .../playingcoffee/core/opcode/JumpOpcode.java | 50 ++ .../core/opcode/JumpRelativeOpcode.java | 51 ++ .../playingcoffee/core/opcode/LoadOpcode.java | 26 + .../playingcoffee/core/opcode/Opcode.java | 10 + .../playingcoffee/core/opcode/PopOpcode.java | 32 + .../playingcoffee/core/opcode/PushOpcode.java | 35 + .../core/opcode/RestartOpcode.java | 26 + .../core/opcode/ReturnOpcode.java | 67 ++ .../core/opcode/RotateAOpcode.java | 68 ++ .../core/opcode/SetCarryOpcode.java | 17 + .../core/opcode/prefixed/BitOpcode.java | 33 + .../core/opcode/prefixed/ResetBitOpcode.java | 25 + .../core/opcode/prefixed/RotateOpcode.java | 54 ++ .../core/opcode/prefixed/SetBitOpcode.java | 25 + .../core/opcode/prefixed/ShiftOpcode.java | 57 ++ .../core/opcode/prefixed/SwapOpcode.java | 25 + .../src/main/java/playingcoffee/log/Log.java | 73 ++ .../src/main/java/playingcoffee/ppu/OAM.java | 52 ++ .../src/main/java/playingcoffee/ppu/PPU.java | 73 ++ .../java/playingcoffee/ppu/PPURegisters.java | 61 ++ .../src/main/java/playingcoffee/ppu/VRAM.java | 35 + .../java/playingcoffee/test/GameBoyTest.java | 26 + playing-coffee old/.classpath | 6 + playing-coffee old/.project | 17 + .../.settings/org.eclipse.jdt.core.prefs | 11 + playing-coffee old/DMG_ROM.bin | Bin 0 -> 256 bytes .../bin/playingcoffee/core/CPU.class | Bin 0 -> 4191 bytes .../bin/playingcoffee/core/MMU.class | Bin 0 -> 1133 bytes .../bin/playingcoffee/core/Registers.class | Bin 0 -> 2919 bytes .../playingcoffee/core/cpu/Argument$1.class | Bin 0 -> 953 bytes .../playingcoffee/core/cpu/Argument$2.class | Bin 0 -> 953 bytes .../playingcoffee/core/cpu/Argument$3.class | Bin 0 -> 953 bytes .../playingcoffee/core/cpu/Argument$4.class | Bin 0 -> 953 bytes .../playingcoffee/core/cpu/Argument$5.class | Bin 0 -> 953 bytes .../bin/playingcoffee/core/cpu/Argument.class | Bin 0 -> 2270 bytes .../playingcoffee/core/cpu/Registers.class | Bin 0 -> 2113 bytes .../bin/playingcoffee/ui/CPUDebugger$1.class | Bin 0 -> 870 bytes .../bin/playingcoffee/ui/CPUDebugger$2.class | Bin 0 -> 937 bytes .../bin/playingcoffee/ui/CPUDebugger.class | Bin 0 -> 6620 bytes .../src/playingcoffee/core/CPU.java | 112 +++ .../src/playingcoffee/core/MMU.java | 34 + .../src/playingcoffee/core/Registers.java | 129 ++++ .../src/playingcoffee/core/cpu/Argument.java | 72 ++ .../src/playingcoffee/core/cpu/Registers.java | 33 + .../src/playingcoffee/ui/CPUDebugger.java | 158 +++++ playing-coffee/.gitignore | 1 + .../.settings/org.eclipse.m2e.core.prefs | 4 + playing-coffee/dmg_boot.bin | Bin 0 -> 256 bytes playing-coffee/gb-test-roms-master.zip | Bin 0 -> 331384 bytes playing-coffee/log.txt | 646 ++++++++++++++++++ playing-coffee/pom.xml | 15 + playing-coffee/roms/cgb_sound/cgb_sound.gb | Bin 0 -> 65536 bytes playing-coffee/roms/cgb_sound/readme.txt | 86 +++ .../cgb_sound/rom_singles/01-registers.gb | Bin 0 -> 32768 bytes .../roms/cgb_sound/rom_singles/02-len ctr.gb | Bin 0 -> 32768 bytes .../roms/cgb_sound/rom_singles/03-trigger.gb | Bin 0 -> 32768 bytes .../roms/cgb_sound/rom_singles/04-sweep.gb | Bin 0 -> 32768 bytes .../cgb_sound/rom_singles/05-sweep details.gb | Bin 0 -> 32768 bytes .../rom_singles/06-overflow on trigger.gb | Bin 0 -> 32768 bytes .../rom_singles/07-len sweep period sync.gb | Bin 0 -> 32768 bytes .../rom_singles/08-len ctr during power.gb | Bin 0 -> 32768 bytes .../rom_singles/09-wave read while on.gb | Bin 0 -> 32768 bytes .../rom_singles/10-wave trigger while on.gb | Bin 0 -> 32768 bytes .../rom_singles/11-regs after power.gb | Bin 0 -> 32768 bytes .../roms/cgb_sound/rom_singles/12-wave.gb | Bin 0 -> 32768 bytes .../roms/cgb_sound/source/01-registers.s | 117 ++++ .../roms/cgb_sound/source/02-len ctr.s | 163 +++++ .../roms/cgb_sound/source/03-trigger.s | 200 ++++++ .../roms/cgb_sound/source/04-sweep.s | 120 ++++ .../roms/cgb_sound/source/05-sweep details.s | 121 ++++ .../cgb_sound/source/06-overflow on trigger.s | 64 ++ .../source/07-len sweep period sync.s | 106 +++ .../source/08-len ctr during power.s | 84 +++ .../cgb_sound/source/09-wave read while on.s | 41 ++ .../source/10-wave trigger while on.s | 49 ++ .../cgb_sound/source/11-regs after power.s | 51 ++ .../roms/cgb_sound/source/12-wave.s | 112 +++ .../roms/cgb_sound/source/common/build_gbs.s | 139 ++++ .../roms/cgb_sound/source/common/build_rom.s | 70 ++ .../roms/cgb_sound/source/common/console.bin | Bin 0 -> 768 bytes .../roms/cgb_sound/source/common/console.s | 285 ++++++++ .../roms/cgb_sound/source/common/crc.s | 78 +++ .../roms/cgb_sound/source/common/delay.s | 220 ++++++ .../roms/cgb_sound/source/common/gb.inc | 81 +++ .../roms/cgb_sound/source/common/macros.inc | 91 +++ .../roms/cgb_sound/source/common/numbers.s | 177 +++++ .../roms/cgb_sound/source/common/printing.s | 77 +++ .../roms/cgb_sound/source/common/shell.s | 261 +++++++ .../roms/cgb_sound/source/common/testing.s | 195 ++++++ playing-coffee/roms/cgb_sound/source/linkfile | 2 + .../roms/cgb_sound/source/readme.txt | 82 +++ .../roms/cgb_sound/source/shell.inc | 27 + playing-coffee/roms/cpu_instrs.gb | Bin 0 -> 65536 bytes playing-coffee/roms/cpu_instrs/cpu_instrs.gb | Bin 0 -> 65536 bytes .../roms/cpu_instrs/individual/01-special.gb | Bin 0 -> 32768 bytes .../cpu_instrs/individual/02-interrupts.gb | Bin 0 -> 32768 bytes .../roms/cpu_instrs/individual/03-op sp,hl.gb | Bin 0 -> 32768 bytes .../roms/cpu_instrs/individual/04-op r,imm.gb | Bin 0 -> 32768 bytes .../roms/cpu_instrs/individual/05-op rp.gb | Bin 0 -> 32768 bytes .../roms/cpu_instrs/individual/06-ld r,r.gb | Bin 0 -> 32768 bytes .../individual/07-jr,jp,call,ret,rst.gb | Bin 0 -> 32768 bytes .../cpu_instrs/individual/08-misc instrs.gb | Bin 0 -> 32768 bytes .../roms/cpu_instrs/individual/09-op r,r.gb | Bin 0 -> 32768 bytes .../roms/cpu_instrs/individual/10-bit ops.gb | Bin 0 -> 32768 bytes .../cpu_instrs/individual/11-op a,(hl).gb | Bin 0 -> 32768 bytes playing-coffee/roms/cpu_instrs/readme.txt | 119 ++++ .../roms/cpu_instrs/source/01-special.s | 78 +++ .../roms/cpu_instrs/source/02-interrupts.s | 73 ++ .../roms/cpu_instrs/source/03-op sp,hl.s | 102 +++ .../roms/cpu_instrs/source/04-op r,imm.s | 88 +++ .../roms/cpu_instrs/source/05-op rp.s | 98 +++ .../roms/cpu_instrs/source/06-ld r,r.s | 115 ++++ .../cpu_instrs/source/07-jr,jp,call,ret,rst.s | 127 ++++ .../roms/cpu_instrs/source/08-misc instrs.s | 110 +++ .../roms/cpu_instrs/source/09-op r,r.s | 269 ++++++++ .../roms/cpu_instrs/source/10-bit ops.s | 315 +++++++++ .../roms/cpu_instrs/source/11-op a,(hl).s | 162 +++++ .../roms/cpu_instrs/source/common/apu.s | 215 ++++++ .../roms/cpu_instrs/source/common/build_gbs.s | 121 ++++ .../roms/cpu_instrs/source/common/build_rom.s | 80 +++ .../roms/cpu_instrs/source/common/checksums.s | 98 +++ .../roms/cpu_instrs/source/common/console.bin | Bin 0 -> 768 bytes .../roms/cpu_instrs/source/common/console.s | 291 ++++++++ .../roms/cpu_instrs/source/common/cpu_speed.s | 64 ++ .../roms/cpu_instrs/source/common/crc.s | 78 +++ .../roms/cpu_instrs/source/common/crc_fast.s | 88 +++ .../roms/cpu_instrs/source/common/delay.s | 220 ++++++ .../roms/cpu_instrs/source/common/gb.inc | 64 ++ .../cpu_instrs/source/common/instr_test.s | 105 +++ .../roms/cpu_instrs/source/common/macros.inc | 73 ++ .../cpu_instrs/source/common/multi_custom.s | 38 ++ .../roms/cpu_instrs/source/common/numbers.s | 177 +++++ .../roms/cpu_instrs/source/common/printing.s | 98 +++ .../roms/cpu_instrs/source/common/runtime.s | 142 ++++ .../roms/cpu_instrs/source/common/testing.s | 176 +++++ .../roms/cpu_instrs/source/linkfile | 2 + .../roms/cpu_instrs/source/shell.inc | 21 + playing-coffee/roms/dmg_sound/dmg_sound.gb | Bin 0 -> 65536 bytes playing-coffee/roms/dmg_sound/readme.txt | 86 +++ .../dmg_sound/rom_singles/01-registers.gb | Bin 0 -> 32768 bytes .../roms/dmg_sound/rom_singles/02-len ctr.gb | Bin 0 -> 32768 bytes .../roms/dmg_sound/rom_singles/03-trigger.gb | Bin 0 -> 32768 bytes .../roms/dmg_sound/rom_singles/04-sweep.gb | Bin 0 -> 32768 bytes .../dmg_sound/rom_singles/05-sweep details.gb | Bin 0 -> 32768 bytes .../rom_singles/06-overflow on trigger.gb | Bin 0 -> 32768 bytes .../rom_singles/07-len sweep period sync.gb | Bin 0 -> 32768 bytes .../rom_singles/08-len ctr during power.gb | Bin 0 -> 32768 bytes .../rom_singles/09-wave read while on.gb | Bin 0 -> 32768 bytes .../rom_singles/10-wave trigger while on.gb | Bin 0 -> 32768 bytes .../rom_singles/11-regs after power.gb | Bin 0 -> 32768 bytes .../rom_singles/12-wave write while on.gb | Bin 0 -> 32768 bytes .../roms/dmg_sound/source/01-registers.s | 117 ++++ .../roms/dmg_sound/source/02-len ctr.s | 163 +++++ .../roms/dmg_sound/source/03-trigger.s | 200 ++++++ .../roms/dmg_sound/source/04-sweep.s | 120 ++++ .../roms/dmg_sound/source/05-sweep details.s | 121 ++++ .../dmg_sound/source/06-overflow on trigger.s | 64 ++ .../source/07-len sweep period sync.s | 106 +++ .../source/08-len ctr during power.s | 84 +++ .../dmg_sound/source/09-wave read while on.s | 41 ++ .../source/10-wave trigger while on.s | 49 ++ .../dmg_sound/source/11-regs after power.s | 52 ++ .../dmg_sound/source/12-wave write while on.s | 49 ++ .../roms/dmg_sound/source/common/build_gbs.s | 139 ++++ .../roms/dmg_sound/source/common/build_rom.s | 70 ++ .../roms/dmg_sound/source/common/console.bin | Bin 0 -> 768 bytes .../roms/dmg_sound/source/common/console.s | 285 ++++++++ .../roms/dmg_sound/source/common/crc.s | 78 +++ .../roms/dmg_sound/source/common/delay.s | 220 ++++++ .../roms/dmg_sound/source/common/gb.inc | 81 +++ .../roms/dmg_sound/source/common/macros.inc | 91 +++ .../roms/dmg_sound/source/common/numbers.s | 177 +++++ .../roms/dmg_sound/source/common/printing.s | 77 +++ .../roms/dmg_sound/source/common/shell.s | 261 +++++++ .../roms/dmg_sound/source/common/testing.s | 195 ++++++ playing-coffee/roms/dmg_sound/source/linkfile | 2 + .../roms/dmg_sound/source/readme.txt | 82 +++ .../roms/dmg_sound/source/shell.inc | 27 + playing-coffee/roms/drmario.gb | Bin 0 -> 32768 bytes playing-coffee/roms/halt_bug.gb | Bin 0 -> 32768 bytes .../roms/instr_timing/instr_timing.gb | Bin 0 -> 32768 bytes playing-coffee/roms/instr_timing/readme.txt | 139 ++++ .../instr_timing/source/common/build_gbs.s | 121 ++++ .../instr_timing/source/common/build_rom.s | 80 +++ .../instr_timing/source/common/console.bin | Bin 0 -> 768 bytes .../roms/instr_timing/source/common/console.s | 291 ++++++++ .../roms/instr_timing/source/common/crc.s | 78 +++ .../roms/instr_timing/source/common/delay.s | 220 ++++++ .../roms/instr_timing/source/common/gb.inc | 64 ++ .../instr_timing/source/common/macros.inc | 73 ++ .../roms/instr_timing/source/common/numbers.s | 177 +++++ .../instr_timing/source/common/printing.s | 98 +++ .../roms/instr_timing/source/common/runtime.s | 142 ++++ .../roms/instr_timing/source/common/testing.s | 176 +++++ .../roms/instr_timing/source/common/timer.s | 88 +++ .../roms/instr_timing/source/instr_timing.s | 336 +++++++++ .../roms/instr_timing/source/linkfile | 2 + .../roms/instr_timing/source/shell.inc | 21 + .../roms/interrupt_time/interrupt_time.gb | Bin 0 -> 32768 bytes .../roms/interrupt_time/interrupt_time.s | 57 ++ playing-coffee/roms/kwirk.gb | Bin 0 -> 32768 bytes .../roms/mem_timing-2/mem_timing.gb | Bin 0 -> 65536 bytes playing-coffee/roms/mem_timing-2/readme.txt | 114 ++++ .../rom_singles/01-read_timing.gb | Bin 0 -> 32768 bytes .../rom_singles/02-write_timing.gb | Bin 0 -> 32768 bytes .../rom_singles/03-modify_timing.gb | Bin 0 -> 32768 bytes .../roms/mem_timing-2/source/01-read_timing.s | 150 ++++ .../mem_timing-2/source/02-write_timing.s | 115 ++++ .../mem_timing-2/source/03-modify_timing.s | 156 +++++ .../mem_timing-2/source/common/build_gbs.s | 139 ++++ .../mem_timing-2/source/common/build_rom.s | 84 +++ .../mem_timing-2/source/common/console.bin | Bin 0 -> 768 bytes .../roms/mem_timing-2/source/common/console.s | 285 ++++++++ .../roms/mem_timing-2/source/common/crc.s | 78 +++ .../roms/mem_timing-2/source/common/delay.s | 220 ++++++ .../roms/mem_timing-2/source/common/gb.inc | 81 +++ .../mem_timing-2/source/common/macros.inc | 91 +++ .../roms/mem_timing-2/source/common/numbers.s | 177 +++++ .../mem_timing-2/source/common/printing.s | 77 +++ .../roms/mem_timing-2/source/common/shell.s | 266 ++++++++ .../roms/mem_timing-2/source/common/testing.s | 195 ++++++ .../roms/mem_timing-2/source/common/tima_64.s | 36 + .../roms/mem_timing-2/source/linkfile | 2 + .../roms/mem_timing-2/source/readme.txt | 82 +++ .../roms/mem_timing-2/source/shell.inc | 27 + .../mem_timing/individual/01-read_timing.gb | Bin 0 -> 32768 bytes .../mem_timing/individual/02-write_timing.gb | Bin 0 -> 32768 bytes .../mem_timing/individual/03-modify_timing.gb | Bin 0 -> 32768 bytes playing-coffee/roms/mem_timing/mem_timing.gb | Bin 0 -> 65536 bytes playing-coffee/roms/mem_timing/readme.txt | 122 ++++ .../roms/mem_timing/source/01-read_timing.s | 148 ++++ .../roms/mem_timing/source/02-write_timing.s | 115 ++++ .../roms/mem_timing/source/03-modify_timing.s | 156 +++++ .../roms/mem_timing/source/common/build_gbs.s | 121 ++++ .../roms/mem_timing/source/common/build_rom.s | 80 +++ .../roms/mem_timing/source/common/console.bin | Bin 0 -> 768 bytes .../roms/mem_timing/source/common/console.s | 291 ++++++++ .../roms/mem_timing/source/common/crc.s | 78 +++ .../roms/mem_timing/source/common/delay.s | 220 ++++++ .../roms/mem_timing/source/common/gb.inc | 64 ++ .../roms/mem_timing/source/common/macros.inc | 73 ++ .../roms/mem_timing/source/common/numbers.s | 177 +++++ .../roms/mem_timing/source/common/printing.s | 98 +++ .../roms/mem_timing/source/common/runtime.s | 142 ++++ .../roms/mem_timing/source/common/testing.s | 176 +++++ .../roms/mem_timing/source/common/tima_64.s | 33 + .../roms/mem_timing/source/linkfile | 2 + .../roms/mem_timing/source/shell.inc | 21 + playing-coffee/roms/oam_bug/oam_bug.gb | Bin 0 -> 65536 bytes playing-coffee/roms/oam_bug/readme.txt | 109 +++ .../roms/oam_bug/rom_singles/1-lcd_sync.gb | Bin 0 -> 32768 bytes .../roms/oam_bug/rom_singles/2-causes.gb | Bin 0 -> 32768 bytes .../roms/oam_bug/rom_singles/3-non_causes.gb | Bin 0 -> 32768 bytes .../oam_bug/rom_singles/4-scanline_timing.gb | Bin 0 -> 32768 bytes .../roms/oam_bug/rom_singles/5-timing_bug.gb | Bin 0 -> 32768 bytes .../oam_bug/rom_singles/6-timing_no_bug.gb | Bin 0 -> 32768 bytes .../oam_bug/rom_singles/7-timing_effect.gb | Bin 0 -> 32768 bytes .../oam_bug/rom_singles/8-instr_effect.gb | Bin 0 -> 32768 bytes .../roms/oam_bug/source/1-lcd_sync.s | 28 + playing-coffee/roms/oam_bug/source/2-causes.s | 86 +++ .../roms/oam_bug/source/3-non_causes.s | 86 +++ .../roms/oam_bug/source/4-scanline_timing.s | 62 ++ .../roms/oam_bug/source/5-timing_bug.s | 43 ++ .../roms/oam_bug/source/6-timing_no_bug.s | 57 ++ .../roms/oam_bug/source/7-timing_effect.s | 38 ++ .../roms/oam_bug/source/8-instr_effect.s | 63 ++ .../roms/oam_bug/source/common/build_gbs.s | 139 ++++ .../roms/oam_bug/source/common/build_rom.s | 70 ++ .../roms/oam_bug/source/common/console.bin | Bin 0 -> 768 bytes .../roms/oam_bug/source/common/console.s | 285 ++++++++ .../roms/oam_bug/source/common/crc.s | 78 +++ .../roms/oam_bug/source/common/delay.s | 220 ++++++ .../roms/oam_bug/source/common/gb.inc | 81 +++ .../roms/oam_bug/source/common/macros.inc | 91 +++ .../roms/oam_bug/source/common/numbers.s | 177 +++++ .../roms/oam_bug/source/common/ppu.s | 14 + .../roms/oam_bug/source/common/printing.s | 77 +++ .../roms/oam_bug/source/common/shell.s | 261 +++++++ .../roms/oam_bug/source/common/testing.s | 195 ++++++ playing-coffee/roms/oam_bug/source/linkfile | 2 + playing-coffee/roms/oam_bug/source/readme.txt | 82 +++ playing-coffee/roms/oam_bug/source/shell.inc | 27 + playing-coffee/roms/readme.txt | 4 + playing-coffee/roms/solitaire.gb | Bin 0 -> 65536 bytes playing-coffee/roms/supermarioland.gb | Bin 0 -> 65536 bytes playing-coffee/roms/tetris.gb | Bin 0 -> 32768 bytes .../application/Application.java | 111 +++ .../playingcoffee/application/GameBoy.java | 85 +++ .../java/playingcoffee/core/Cartridge.java | 44 ++ .../playingcoffee/core/InterruptListener.java | 7 + .../playingcoffee/core/InterruptManager.java | 76 +++ .../src/main/java/playingcoffee/core/MMU.java | 93 +++ .../java/playingcoffee/core/MemorySpace.java | 9 + .../main/java/playingcoffee/core/cpu/CPU.java | 343 ++++++++++ .../java/playingcoffee/core/cpu/Flags.java | 29 + .../playingcoffee/core/cpu/Registers.java | 57 ++ .../core/opcode/ALU16Opcode.java | 57 ++ .../playingcoffee/core/opcode/ALUOpcode.java | 134 ++++ .../playingcoffee/core/opcode/Argument.java | 348 ++++++++++ .../playingcoffee/core/opcode/CallOpcode.java | 60 ++ .../core/opcode/ComplementOpcode.java | 18 + .../core/opcode/FlipCarryOpcode.java | 17 + .../core/opcode/InterruptOpcode.java | 29 + .../playingcoffee/core/opcode/JumpOpcode.java | 50 ++ .../core/opcode/JumpRelativeOpcode.java | 51 ++ .../playingcoffee/core/opcode/LoadOpcode.java | 26 + .../playingcoffee/core/opcode/Opcode.java | 10 + .../playingcoffee/core/opcode/PopOpcode.java | 32 + .../playingcoffee/core/opcode/PushOpcode.java | 35 + .../core/opcode/RestartOpcode.java | 26 + .../core/opcode/ReturnOpcode.java | 67 ++ .../core/opcode/RotateAOpcode.java | 68 ++ .../core/opcode/SetCarryOpcode.java | 17 + .../core/opcode/prefixed/BitOpcode.java | 33 + .../core/opcode/prefixed/ResetBitOpcode.java | 25 + .../core/opcode/prefixed/RotateOpcode.java | 54 ++ .../core/opcode/prefixed/SetBitOpcode.java | 25 + .../core/opcode/prefixed/ShiftOpcode.java | 57 ++ .../core/opcode/prefixed/SwapOpcode.java | 25 + .../src/main/java/playingcoffee/log/Log.java | 73 ++ .../src/main/java/playingcoffee/ppu/OAM.java | 52 ++ .../src/main/java/playingcoffee/ppu/PPU.java | 73 ++ .../java/playingcoffee/ppu/PPURegisters.java | 61 ++ .../src/main/java/playingcoffee/ppu/VRAM.java | 35 + .../java/playingcoffee/test/GameBoyTest.java | 26 + 587 files changed, 44799 insertions(+) create mode 100644 .gitignore create mode 100644 playing-coffee - Copy/.classpath create mode 100644 playing-coffee - Copy/.gitignore create mode 100644 playing-coffee - Copy/.project create mode 100644 playing-coffee - Copy/.settings/org.eclipse.jdt.core.prefs create mode 100644 playing-coffee - Copy/.settings/org.eclipse.m2e.core.prefs create mode 100644 playing-coffee - Copy/dmg_boot.bin create mode 100644 playing-coffee - Copy/gb-test-roms-master.zip create mode 100644 playing-coffee - Copy/log.txt create mode 100644 playing-coffee - Copy/pom.xml create mode 100644 playing-coffee - Copy/roms/cgb_sound/cgb_sound.gb create mode 100644 playing-coffee - Copy/roms/cgb_sound/readme.txt create mode 100644 playing-coffee - Copy/roms/cgb_sound/rom_singles/01-registers.gb create mode 100644 playing-coffee - Copy/roms/cgb_sound/rom_singles/02-len ctr.gb create mode 100644 playing-coffee - Copy/roms/cgb_sound/rom_singles/03-trigger.gb create mode 100644 playing-coffee - Copy/roms/cgb_sound/rom_singles/04-sweep.gb create mode 100644 playing-coffee - Copy/roms/cgb_sound/rom_singles/05-sweep details.gb create mode 100644 playing-coffee - Copy/roms/cgb_sound/rom_singles/06-overflow on trigger.gb create mode 100644 playing-coffee - Copy/roms/cgb_sound/rom_singles/07-len sweep period sync.gb create mode 100644 playing-coffee - Copy/roms/cgb_sound/rom_singles/08-len ctr during power.gb create mode 100644 playing-coffee - Copy/roms/cgb_sound/rom_singles/09-wave read while on.gb create mode 100644 playing-coffee - Copy/roms/cgb_sound/rom_singles/10-wave trigger while on.gb create mode 100644 playing-coffee - Copy/roms/cgb_sound/rom_singles/11-regs after power.gb create mode 100644 playing-coffee - Copy/roms/cgb_sound/rom_singles/12-wave.gb create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/01-registers.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/02-len ctr.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/03-trigger.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/04-sweep.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/05-sweep details.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/06-overflow on trigger.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/07-len sweep period sync.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/08-len ctr during power.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/09-wave read while on.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/10-wave trigger while on.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/11-regs after power.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/12-wave.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/common/build_gbs.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/common/build_rom.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/common/console.bin create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/common/console.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/common/crc.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/common/delay.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/common/gb.inc create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/common/macros.inc create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/common/numbers.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/common/printing.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/common/shell.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/common/testing.s create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/linkfile create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/readme.txt create mode 100644 playing-coffee - Copy/roms/cgb_sound/source/shell.inc create mode 100644 playing-coffee - Copy/roms/cpu_instrs.gb create mode 100644 playing-coffee - Copy/roms/cpu_instrs/cpu_instrs.gb create mode 100644 playing-coffee - Copy/roms/cpu_instrs/individual/01-special.gb create mode 100644 playing-coffee - Copy/roms/cpu_instrs/individual/02-interrupts.gb create mode 100644 playing-coffee - Copy/roms/cpu_instrs/individual/03-op sp,hl.gb create mode 100644 playing-coffee - Copy/roms/cpu_instrs/individual/04-op r,imm.gb create mode 100644 playing-coffee - Copy/roms/cpu_instrs/individual/05-op rp.gb create mode 100644 playing-coffee - Copy/roms/cpu_instrs/individual/06-ld r,r.gb create mode 100644 playing-coffee - Copy/roms/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb create mode 100644 playing-coffee - Copy/roms/cpu_instrs/individual/08-misc instrs.gb create mode 100644 playing-coffee - Copy/roms/cpu_instrs/individual/09-op r,r.gb create mode 100644 playing-coffee - Copy/roms/cpu_instrs/individual/10-bit ops.gb create mode 100644 playing-coffee - Copy/roms/cpu_instrs/individual/11-op a,(hl).gb create mode 100644 playing-coffee - Copy/roms/cpu_instrs/readme.txt create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/01-special.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/02-interrupts.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/03-op sp,hl.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/04-op r,imm.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/05-op rp.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/06-ld r,r.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/07-jr,jp,call,ret,rst.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/08-misc instrs.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/09-op r,r.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/10-bit ops.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/11-op a,(hl).s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/common/apu.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/common/build_gbs.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/common/build_rom.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/common/checksums.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/common/console.bin create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/common/console.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/common/cpu_speed.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/common/crc.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/common/crc_fast.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/common/delay.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/common/gb.inc create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/common/instr_test.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/common/macros.inc create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/common/multi_custom.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/common/numbers.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/common/printing.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/common/runtime.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/common/testing.s create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/linkfile create mode 100644 playing-coffee - Copy/roms/cpu_instrs/source/shell.inc create mode 100644 playing-coffee - Copy/roms/dmg_sound/dmg_sound.gb create mode 100644 playing-coffee - Copy/roms/dmg_sound/readme.txt create mode 100644 playing-coffee - Copy/roms/dmg_sound/rom_singles/01-registers.gb create mode 100644 playing-coffee - Copy/roms/dmg_sound/rom_singles/02-len ctr.gb create mode 100644 playing-coffee - Copy/roms/dmg_sound/rom_singles/03-trigger.gb create mode 100644 playing-coffee - Copy/roms/dmg_sound/rom_singles/04-sweep.gb create mode 100644 playing-coffee - Copy/roms/dmg_sound/rom_singles/05-sweep details.gb create mode 100644 playing-coffee - Copy/roms/dmg_sound/rom_singles/06-overflow on trigger.gb create mode 100644 playing-coffee - Copy/roms/dmg_sound/rom_singles/07-len sweep period sync.gb create mode 100644 playing-coffee - Copy/roms/dmg_sound/rom_singles/08-len ctr during power.gb create mode 100644 playing-coffee - Copy/roms/dmg_sound/rom_singles/09-wave read while on.gb create mode 100644 playing-coffee - Copy/roms/dmg_sound/rom_singles/10-wave trigger while on.gb create mode 100644 playing-coffee - Copy/roms/dmg_sound/rom_singles/11-regs after power.gb create mode 100644 playing-coffee - Copy/roms/dmg_sound/rom_singles/12-wave write while on.gb create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/01-registers.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/02-len ctr.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/03-trigger.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/04-sweep.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/05-sweep details.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/06-overflow on trigger.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/07-len sweep period sync.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/08-len ctr during power.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/09-wave read while on.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/10-wave trigger while on.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/11-regs after power.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/12-wave write while on.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/common/build_gbs.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/common/build_rom.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/common/console.bin create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/common/console.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/common/crc.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/common/delay.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/common/gb.inc create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/common/macros.inc create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/common/numbers.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/common/printing.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/common/shell.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/common/testing.s create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/linkfile create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/readme.txt create mode 100644 playing-coffee - Copy/roms/dmg_sound/source/shell.inc create mode 100644 playing-coffee - Copy/roms/drmario.gb create mode 100644 playing-coffee - Copy/roms/halt_bug.gb create mode 100644 playing-coffee - Copy/roms/instr_timing/instr_timing.gb create mode 100644 playing-coffee - Copy/roms/instr_timing/readme.txt create mode 100644 playing-coffee - Copy/roms/instr_timing/source/common/build_gbs.s create mode 100644 playing-coffee - Copy/roms/instr_timing/source/common/build_rom.s create mode 100644 playing-coffee - Copy/roms/instr_timing/source/common/console.bin create mode 100644 playing-coffee - Copy/roms/instr_timing/source/common/console.s create mode 100644 playing-coffee - Copy/roms/instr_timing/source/common/crc.s create mode 100644 playing-coffee - Copy/roms/instr_timing/source/common/delay.s create mode 100644 playing-coffee - Copy/roms/instr_timing/source/common/gb.inc create mode 100644 playing-coffee - Copy/roms/instr_timing/source/common/macros.inc create mode 100644 playing-coffee - Copy/roms/instr_timing/source/common/numbers.s create mode 100644 playing-coffee - Copy/roms/instr_timing/source/common/printing.s create mode 100644 playing-coffee - Copy/roms/instr_timing/source/common/runtime.s create mode 100644 playing-coffee - Copy/roms/instr_timing/source/common/testing.s create mode 100644 playing-coffee - Copy/roms/instr_timing/source/common/timer.s create mode 100644 playing-coffee - Copy/roms/instr_timing/source/instr_timing.s create mode 100644 playing-coffee - Copy/roms/instr_timing/source/linkfile create mode 100644 playing-coffee - Copy/roms/instr_timing/source/shell.inc create mode 100644 playing-coffee - Copy/roms/interrupt_time/interrupt_time.gb create mode 100644 playing-coffee - Copy/roms/interrupt_time/interrupt_time.s create mode 100644 playing-coffee - Copy/roms/kwirk.gb create mode 100644 playing-coffee - Copy/roms/mem_timing-2/mem_timing.gb create mode 100644 playing-coffee - Copy/roms/mem_timing-2/readme.txt create mode 100644 playing-coffee - Copy/roms/mem_timing-2/rom_singles/01-read_timing.gb create mode 100644 playing-coffee - Copy/roms/mem_timing-2/rom_singles/02-write_timing.gb create mode 100644 playing-coffee - Copy/roms/mem_timing-2/rom_singles/03-modify_timing.gb create mode 100644 playing-coffee - Copy/roms/mem_timing-2/source/01-read_timing.s create mode 100644 playing-coffee - Copy/roms/mem_timing-2/source/02-write_timing.s create mode 100644 playing-coffee - Copy/roms/mem_timing-2/source/03-modify_timing.s create mode 100644 playing-coffee - Copy/roms/mem_timing-2/source/common/build_gbs.s create mode 100644 playing-coffee - Copy/roms/mem_timing-2/source/common/build_rom.s create mode 100644 playing-coffee - Copy/roms/mem_timing-2/source/common/console.bin create mode 100644 playing-coffee - Copy/roms/mem_timing-2/source/common/console.s create mode 100644 playing-coffee - Copy/roms/mem_timing-2/source/common/crc.s create mode 100644 playing-coffee - Copy/roms/mem_timing-2/source/common/delay.s create mode 100644 playing-coffee - Copy/roms/mem_timing-2/source/common/gb.inc create mode 100644 playing-coffee - Copy/roms/mem_timing-2/source/common/macros.inc create mode 100644 playing-coffee - Copy/roms/mem_timing-2/source/common/numbers.s create mode 100644 playing-coffee - Copy/roms/mem_timing-2/source/common/printing.s create mode 100644 playing-coffee - Copy/roms/mem_timing-2/source/common/shell.s create mode 100644 playing-coffee - Copy/roms/mem_timing-2/source/common/testing.s create mode 100644 playing-coffee - Copy/roms/mem_timing-2/source/common/tima_64.s create mode 100644 playing-coffee - Copy/roms/mem_timing-2/source/linkfile create mode 100644 playing-coffee - Copy/roms/mem_timing-2/source/readme.txt create mode 100644 playing-coffee - Copy/roms/mem_timing-2/source/shell.inc create mode 100644 playing-coffee - Copy/roms/mem_timing/individual/01-read_timing.gb create mode 100644 playing-coffee - Copy/roms/mem_timing/individual/02-write_timing.gb create mode 100644 playing-coffee - Copy/roms/mem_timing/individual/03-modify_timing.gb create mode 100644 playing-coffee - Copy/roms/mem_timing/mem_timing.gb create mode 100644 playing-coffee - Copy/roms/mem_timing/readme.txt create mode 100644 playing-coffee - Copy/roms/mem_timing/source/01-read_timing.s create mode 100644 playing-coffee - Copy/roms/mem_timing/source/02-write_timing.s create mode 100644 playing-coffee - Copy/roms/mem_timing/source/03-modify_timing.s create mode 100644 playing-coffee - Copy/roms/mem_timing/source/common/build_gbs.s create mode 100644 playing-coffee - Copy/roms/mem_timing/source/common/build_rom.s create mode 100644 playing-coffee - Copy/roms/mem_timing/source/common/console.bin create mode 100644 playing-coffee - Copy/roms/mem_timing/source/common/console.s create mode 100644 playing-coffee - Copy/roms/mem_timing/source/common/crc.s create mode 100644 playing-coffee - Copy/roms/mem_timing/source/common/delay.s create mode 100644 playing-coffee - Copy/roms/mem_timing/source/common/gb.inc create mode 100644 playing-coffee - Copy/roms/mem_timing/source/common/macros.inc create mode 100644 playing-coffee - Copy/roms/mem_timing/source/common/numbers.s create mode 100644 playing-coffee - Copy/roms/mem_timing/source/common/printing.s create mode 100644 playing-coffee - Copy/roms/mem_timing/source/common/runtime.s create mode 100644 playing-coffee - Copy/roms/mem_timing/source/common/testing.s create mode 100644 playing-coffee - Copy/roms/mem_timing/source/common/tima_64.s create mode 100644 playing-coffee - Copy/roms/mem_timing/source/linkfile create mode 100644 playing-coffee - Copy/roms/mem_timing/source/shell.inc create mode 100644 playing-coffee - Copy/roms/oam_bug/oam_bug.gb create mode 100644 playing-coffee - Copy/roms/oam_bug/readme.txt create mode 100644 playing-coffee - Copy/roms/oam_bug/rom_singles/1-lcd_sync.gb create mode 100644 playing-coffee - Copy/roms/oam_bug/rom_singles/2-causes.gb create mode 100644 playing-coffee - Copy/roms/oam_bug/rom_singles/3-non_causes.gb create mode 100644 playing-coffee - Copy/roms/oam_bug/rom_singles/4-scanline_timing.gb create mode 100644 playing-coffee - Copy/roms/oam_bug/rom_singles/5-timing_bug.gb create mode 100644 playing-coffee - Copy/roms/oam_bug/rom_singles/6-timing_no_bug.gb create mode 100644 playing-coffee - Copy/roms/oam_bug/rom_singles/7-timing_effect.gb create mode 100644 playing-coffee - Copy/roms/oam_bug/rom_singles/8-instr_effect.gb create mode 100644 playing-coffee - Copy/roms/oam_bug/source/1-lcd_sync.s create mode 100644 playing-coffee - Copy/roms/oam_bug/source/2-causes.s create mode 100644 playing-coffee - Copy/roms/oam_bug/source/3-non_causes.s create mode 100644 playing-coffee - Copy/roms/oam_bug/source/4-scanline_timing.s create mode 100644 playing-coffee - Copy/roms/oam_bug/source/5-timing_bug.s create mode 100644 playing-coffee - Copy/roms/oam_bug/source/6-timing_no_bug.s create mode 100644 playing-coffee - Copy/roms/oam_bug/source/7-timing_effect.s create mode 100644 playing-coffee - Copy/roms/oam_bug/source/8-instr_effect.s create mode 100644 playing-coffee - Copy/roms/oam_bug/source/common/build_gbs.s create mode 100644 playing-coffee - Copy/roms/oam_bug/source/common/build_rom.s create mode 100644 playing-coffee - Copy/roms/oam_bug/source/common/console.bin create mode 100644 playing-coffee - Copy/roms/oam_bug/source/common/console.s create mode 100644 playing-coffee - Copy/roms/oam_bug/source/common/crc.s create mode 100644 playing-coffee - Copy/roms/oam_bug/source/common/delay.s create mode 100644 playing-coffee - Copy/roms/oam_bug/source/common/gb.inc create mode 100644 playing-coffee - Copy/roms/oam_bug/source/common/macros.inc create mode 100644 playing-coffee - Copy/roms/oam_bug/source/common/numbers.s create mode 100644 playing-coffee - Copy/roms/oam_bug/source/common/ppu.s create mode 100644 playing-coffee - Copy/roms/oam_bug/source/common/printing.s create mode 100644 playing-coffee - Copy/roms/oam_bug/source/common/shell.s create mode 100644 playing-coffee - Copy/roms/oam_bug/source/common/testing.s create mode 100644 playing-coffee - Copy/roms/oam_bug/source/linkfile create mode 100644 playing-coffee - Copy/roms/oam_bug/source/readme.txt create mode 100644 playing-coffee - Copy/roms/oam_bug/source/shell.inc create mode 100644 playing-coffee - Copy/roms/readme.txt create mode 100644 playing-coffee - Copy/roms/solitaire.gb create mode 100644 playing-coffee - Copy/roms/supermarioland.gb create mode 100644 playing-coffee - Copy/roms/tetris.gb create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/application/Application.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/application/GameBoy.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/Cartridge.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/InterruptListener.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/InterruptManager.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/MMU.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/MemorySpace.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/CPU.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/Flags.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/Registers.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ALU16Opcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ALUOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/Argument.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/CallOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ComplementOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/FlipCarryOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/InterruptOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/JumpOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/JumpRelativeOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/LoadOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/Opcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/PopOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/PushOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/RestartOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ReturnOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/RotateAOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/SetCarryOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/BitOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/ResetBitOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/RotateOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/SetBitOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/ShiftOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/SwapOpcode.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/log/Log.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/ppu/OAM.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/ppu/PPU.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/ppu/PPURegisters.java create mode 100644 playing-coffee - Copy/src/main/java/playingcoffee/ppu/VRAM.java create mode 100644 playing-coffee - Copy/src/test/java/playingcoffee/test/GameBoyTest.java create mode 100644 playing-coffee old/.classpath create mode 100644 playing-coffee old/.project create mode 100644 playing-coffee old/.settings/org.eclipse.jdt.core.prefs create mode 100644 playing-coffee old/DMG_ROM.bin create mode 100644 playing-coffee old/bin/playingcoffee/core/CPU.class create mode 100644 playing-coffee old/bin/playingcoffee/core/MMU.class create mode 100644 playing-coffee old/bin/playingcoffee/core/Registers.class create mode 100644 playing-coffee old/bin/playingcoffee/core/cpu/Argument$1.class create mode 100644 playing-coffee old/bin/playingcoffee/core/cpu/Argument$2.class create mode 100644 playing-coffee old/bin/playingcoffee/core/cpu/Argument$3.class create mode 100644 playing-coffee old/bin/playingcoffee/core/cpu/Argument$4.class create mode 100644 playing-coffee old/bin/playingcoffee/core/cpu/Argument$5.class create mode 100644 playing-coffee old/bin/playingcoffee/core/cpu/Argument.class create mode 100644 playing-coffee old/bin/playingcoffee/core/cpu/Registers.class create mode 100644 playing-coffee old/bin/playingcoffee/ui/CPUDebugger$1.class create mode 100644 playing-coffee old/bin/playingcoffee/ui/CPUDebugger$2.class create mode 100644 playing-coffee old/bin/playingcoffee/ui/CPUDebugger.class create mode 100644 playing-coffee old/src/playingcoffee/core/CPU.java create mode 100644 playing-coffee old/src/playingcoffee/core/MMU.java create mode 100644 playing-coffee old/src/playingcoffee/core/Registers.java create mode 100644 playing-coffee old/src/playingcoffee/core/cpu/Argument.java create mode 100644 playing-coffee old/src/playingcoffee/core/cpu/Registers.java create mode 100644 playing-coffee old/src/playingcoffee/ui/CPUDebugger.java create mode 100644 playing-coffee/.gitignore create mode 100644 playing-coffee/.settings/org.eclipse.m2e.core.prefs create mode 100644 playing-coffee/dmg_boot.bin create mode 100644 playing-coffee/gb-test-roms-master.zip create mode 100644 playing-coffee/log.txt create mode 100644 playing-coffee/pom.xml create mode 100644 playing-coffee/roms/cgb_sound/cgb_sound.gb create mode 100644 playing-coffee/roms/cgb_sound/readme.txt create mode 100644 playing-coffee/roms/cgb_sound/rom_singles/01-registers.gb create mode 100644 playing-coffee/roms/cgb_sound/rom_singles/02-len ctr.gb create mode 100644 playing-coffee/roms/cgb_sound/rom_singles/03-trigger.gb create mode 100644 playing-coffee/roms/cgb_sound/rom_singles/04-sweep.gb create mode 100644 playing-coffee/roms/cgb_sound/rom_singles/05-sweep details.gb create mode 100644 playing-coffee/roms/cgb_sound/rom_singles/06-overflow on trigger.gb create mode 100644 playing-coffee/roms/cgb_sound/rom_singles/07-len sweep period sync.gb create mode 100644 playing-coffee/roms/cgb_sound/rom_singles/08-len ctr during power.gb create mode 100644 playing-coffee/roms/cgb_sound/rom_singles/09-wave read while on.gb create mode 100644 playing-coffee/roms/cgb_sound/rom_singles/10-wave trigger while on.gb create mode 100644 playing-coffee/roms/cgb_sound/rom_singles/11-regs after power.gb create mode 100644 playing-coffee/roms/cgb_sound/rom_singles/12-wave.gb create mode 100644 playing-coffee/roms/cgb_sound/source/01-registers.s create mode 100644 playing-coffee/roms/cgb_sound/source/02-len ctr.s create mode 100644 playing-coffee/roms/cgb_sound/source/03-trigger.s create mode 100644 playing-coffee/roms/cgb_sound/source/04-sweep.s create mode 100644 playing-coffee/roms/cgb_sound/source/05-sweep details.s create mode 100644 playing-coffee/roms/cgb_sound/source/06-overflow on trigger.s create mode 100644 playing-coffee/roms/cgb_sound/source/07-len sweep period sync.s create mode 100644 playing-coffee/roms/cgb_sound/source/08-len ctr during power.s create mode 100644 playing-coffee/roms/cgb_sound/source/09-wave read while on.s create mode 100644 playing-coffee/roms/cgb_sound/source/10-wave trigger while on.s create mode 100644 playing-coffee/roms/cgb_sound/source/11-regs after power.s create mode 100644 playing-coffee/roms/cgb_sound/source/12-wave.s create mode 100644 playing-coffee/roms/cgb_sound/source/common/build_gbs.s create mode 100644 playing-coffee/roms/cgb_sound/source/common/build_rom.s create mode 100644 playing-coffee/roms/cgb_sound/source/common/console.bin create mode 100644 playing-coffee/roms/cgb_sound/source/common/console.s create mode 100644 playing-coffee/roms/cgb_sound/source/common/crc.s create mode 100644 playing-coffee/roms/cgb_sound/source/common/delay.s create mode 100644 playing-coffee/roms/cgb_sound/source/common/gb.inc create mode 100644 playing-coffee/roms/cgb_sound/source/common/macros.inc create mode 100644 playing-coffee/roms/cgb_sound/source/common/numbers.s create mode 100644 playing-coffee/roms/cgb_sound/source/common/printing.s create mode 100644 playing-coffee/roms/cgb_sound/source/common/shell.s create mode 100644 playing-coffee/roms/cgb_sound/source/common/testing.s create mode 100644 playing-coffee/roms/cgb_sound/source/linkfile create mode 100644 playing-coffee/roms/cgb_sound/source/readme.txt create mode 100644 playing-coffee/roms/cgb_sound/source/shell.inc create mode 100644 playing-coffee/roms/cpu_instrs.gb create mode 100644 playing-coffee/roms/cpu_instrs/cpu_instrs.gb create mode 100644 playing-coffee/roms/cpu_instrs/individual/01-special.gb create mode 100644 playing-coffee/roms/cpu_instrs/individual/02-interrupts.gb create mode 100644 playing-coffee/roms/cpu_instrs/individual/03-op sp,hl.gb create mode 100644 playing-coffee/roms/cpu_instrs/individual/04-op r,imm.gb create mode 100644 playing-coffee/roms/cpu_instrs/individual/05-op rp.gb create mode 100644 playing-coffee/roms/cpu_instrs/individual/06-ld r,r.gb create mode 100644 playing-coffee/roms/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb create mode 100644 playing-coffee/roms/cpu_instrs/individual/08-misc instrs.gb create mode 100644 playing-coffee/roms/cpu_instrs/individual/09-op r,r.gb create mode 100644 playing-coffee/roms/cpu_instrs/individual/10-bit ops.gb create mode 100644 playing-coffee/roms/cpu_instrs/individual/11-op a,(hl).gb create mode 100644 playing-coffee/roms/cpu_instrs/readme.txt create mode 100644 playing-coffee/roms/cpu_instrs/source/01-special.s create mode 100644 playing-coffee/roms/cpu_instrs/source/02-interrupts.s create mode 100644 playing-coffee/roms/cpu_instrs/source/03-op sp,hl.s create mode 100644 playing-coffee/roms/cpu_instrs/source/04-op r,imm.s create mode 100644 playing-coffee/roms/cpu_instrs/source/05-op rp.s create mode 100644 playing-coffee/roms/cpu_instrs/source/06-ld r,r.s create mode 100644 playing-coffee/roms/cpu_instrs/source/07-jr,jp,call,ret,rst.s create mode 100644 playing-coffee/roms/cpu_instrs/source/08-misc instrs.s create mode 100644 playing-coffee/roms/cpu_instrs/source/09-op r,r.s create mode 100644 playing-coffee/roms/cpu_instrs/source/10-bit ops.s create mode 100644 playing-coffee/roms/cpu_instrs/source/11-op a,(hl).s create mode 100644 playing-coffee/roms/cpu_instrs/source/common/apu.s create mode 100644 playing-coffee/roms/cpu_instrs/source/common/build_gbs.s create mode 100644 playing-coffee/roms/cpu_instrs/source/common/build_rom.s create mode 100644 playing-coffee/roms/cpu_instrs/source/common/checksums.s create mode 100644 playing-coffee/roms/cpu_instrs/source/common/console.bin create mode 100644 playing-coffee/roms/cpu_instrs/source/common/console.s create mode 100644 playing-coffee/roms/cpu_instrs/source/common/cpu_speed.s create mode 100644 playing-coffee/roms/cpu_instrs/source/common/crc.s create mode 100644 playing-coffee/roms/cpu_instrs/source/common/crc_fast.s create mode 100644 playing-coffee/roms/cpu_instrs/source/common/delay.s create mode 100644 playing-coffee/roms/cpu_instrs/source/common/gb.inc create mode 100644 playing-coffee/roms/cpu_instrs/source/common/instr_test.s create mode 100644 playing-coffee/roms/cpu_instrs/source/common/macros.inc create mode 100644 playing-coffee/roms/cpu_instrs/source/common/multi_custom.s create mode 100644 playing-coffee/roms/cpu_instrs/source/common/numbers.s create mode 100644 playing-coffee/roms/cpu_instrs/source/common/printing.s create mode 100644 playing-coffee/roms/cpu_instrs/source/common/runtime.s create mode 100644 playing-coffee/roms/cpu_instrs/source/common/testing.s create mode 100644 playing-coffee/roms/cpu_instrs/source/linkfile create mode 100644 playing-coffee/roms/cpu_instrs/source/shell.inc create mode 100644 playing-coffee/roms/dmg_sound/dmg_sound.gb create mode 100644 playing-coffee/roms/dmg_sound/readme.txt create mode 100644 playing-coffee/roms/dmg_sound/rom_singles/01-registers.gb create mode 100644 playing-coffee/roms/dmg_sound/rom_singles/02-len ctr.gb create mode 100644 playing-coffee/roms/dmg_sound/rom_singles/03-trigger.gb create mode 100644 playing-coffee/roms/dmg_sound/rom_singles/04-sweep.gb create mode 100644 playing-coffee/roms/dmg_sound/rom_singles/05-sweep details.gb create mode 100644 playing-coffee/roms/dmg_sound/rom_singles/06-overflow on trigger.gb create mode 100644 playing-coffee/roms/dmg_sound/rom_singles/07-len sweep period sync.gb create mode 100644 playing-coffee/roms/dmg_sound/rom_singles/08-len ctr during power.gb create mode 100644 playing-coffee/roms/dmg_sound/rom_singles/09-wave read while on.gb create mode 100644 playing-coffee/roms/dmg_sound/rom_singles/10-wave trigger while on.gb create mode 100644 playing-coffee/roms/dmg_sound/rom_singles/11-regs after power.gb create mode 100644 playing-coffee/roms/dmg_sound/rom_singles/12-wave write while on.gb create mode 100644 playing-coffee/roms/dmg_sound/source/01-registers.s create mode 100644 playing-coffee/roms/dmg_sound/source/02-len ctr.s create mode 100644 playing-coffee/roms/dmg_sound/source/03-trigger.s create mode 100644 playing-coffee/roms/dmg_sound/source/04-sweep.s create mode 100644 playing-coffee/roms/dmg_sound/source/05-sweep details.s create mode 100644 playing-coffee/roms/dmg_sound/source/06-overflow on trigger.s create mode 100644 playing-coffee/roms/dmg_sound/source/07-len sweep period sync.s create mode 100644 playing-coffee/roms/dmg_sound/source/08-len ctr during power.s create mode 100644 playing-coffee/roms/dmg_sound/source/09-wave read while on.s create mode 100644 playing-coffee/roms/dmg_sound/source/10-wave trigger while on.s create mode 100644 playing-coffee/roms/dmg_sound/source/11-regs after power.s create mode 100644 playing-coffee/roms/dmg_sound/source/12-wave write while on.s create mode 100644 playing-coffee/roms/dmg_sound/source/common/build_gbs.s create mode 100644 playing-coffee/roms/dmg_sound/source/common/build_rom.s create mode 100644 playing-coffee/roms/dmg_sound/source/common/console.bin create mode 100644 playing-coffee/roms/dmg_sound/source/common/console.s create mode 100644 playing-coffee/roms/dmg_sound/source/common/crc.s create mode 100644 playing-coffee/roms/dmg_sound/source/common/delay.s create mode 100644 playing-coffee/roms/dmg_sound/source/common/gb.inc create mode 100644 playing-coffee/roms/dmg_sound/source/common/macros.inc create mode 100644 playing-coffee/roms/dmg_sound/source/common/numbers.s create mode 100644 playing-coffee/roms/dmg_sound/source/common/printing.s create mode 100644 playing-coffee/roms/dmg_sound/source/common/shell.s create mode 100644 playing-coffee/roms/dmg_sound/source/common/testing.s create mode 100644 playing-coffee/roms/dmg_sound/source/linkfile create mode 100644 playing-coffee/roms/dmg_sound/source/readme.txt create mode 100644 playing-coffee/roms/dmg_sound/source/shell.inc create mode 100644 playing-coffee/roms/drmario.gb create mode 100644 playing-coffee/roms/halt_bug.gb create mode 100644 playing-coffee/roms/instr_timing/instr_timing.gb create mode 100644 playing-coffee/roms/instr_timing/readme.txt create mode 100644 playing-coffee/roms/instr_timing/source/common/build_gbs.s create mode 100644 playing-coffee/roms/instr_timing/source/common/build_rom.s create mode 100644 playing-coffee/roms/instr_timing/source/common/console.bin create mode 100644 playing-coffee/roms/instr_timing/source/common/console.s create mode 100644 playing-coffee/roms/instr_timing/source/common/crc.s create mode 100644 playing-coffee/roms/instr_timing/source/common/delay.s create mode 100644 playing-coffee/roms/instr_timing/source/common/gb.inc create mode 100644 playing-coffee/roms/instr_timing/source/common/macros.inc create mode 100644 playing-coffee/roms/instr_timing/source/common/numbers.s create mode 100644 playing-coffee/roms/instr_timing/source/common/printing.s create mode 100644 playing-coffee/roms/instr_timing/source/common/runtime.s create mode 100644 playing-coffee/roms/instr_timing/source/common/testing.s create mode 100644 playing-coffee/roms/instr_timing/source/common/timer.s create mode 100644 playing-coffee/roms/instr_timing/source/instr_timing.s create mode 100644 playing-coffee/roms/instr_timing/source/linkfile create mode 100644 playing-coffee/roms/instr_timing/source/shell.inc create mode 100644 playing-coffee/roms/interrupt_time/interrupt_time.gb create mode 100644 playing-coffee/roms/interrupt_time/interrupt_time.s create mode 100644 playing-coffee/roms/kwirk.gb create mode 100644 playing-coffee/roms/mem_timing-2/mem_timing.gb create mode 100644 playing-coffee/roms/mem_timing-2/readme.txt create mode 100644 playing-coffee/roms/mem_timing-2/rom_singles/01-read_timing.gb create mode 100644 playing-coffee/roms/mem_timing-2/rom_singles/02-write_timing.gb create mode 100644 playing-coffee/roms/mem_timing-2/rom_singles/03-modify_timing.gb create mode 100644 playing-coffee/roms/mem_timing-2/source/01-read_timing.s create mode 100644 playing-coffee/roms/mem_timing-2/source/02-write_timing.s create mode 100644 playing-coffee/roms/mem_timing-2/source/03-modify_timing.s create mode 100644 playing-coffee/roms/mem_timing-2/source/common/build_gbs.s create mode 100644 playing-coffee/roms/mem_timing-2/source/common/build_rom.s create mode 100644 playing-coffee/roms/mem_timing-2/source/common/console.bin create mode 100644 playing-coffee/roms/mem_timing-2/source/common/console.s create mode 100644 playing-coffee/roms/mem_timing-2/source/common/crc.s create mode 100644 playing-coffee/roms/mem_timing-2/source/common/delay.s create mode 100644 playing-coffee/roms/mem_timing-2/source/common/gb.inc create mode 100644 playing-coffee/roms/mem_timing-2/source/common/macros.inc create mode 100644 playing-coffee/roms/mem_timing-2/source/common/numbers.s create mode 100644 playing-coffee/roms/mem_timing-2/source/common/printing.s create mode 100644 playing-coffee/roms/mem_timing-2/source/common/shell.s create mode 100644 playing-coffee/roms/mem_timing-2/source/common/testing.s create mode 100644 playing-coffee/roms/mem_timing-2/source/common/tima_64.s create mode 100644 playing-coffee/roms/mem_timing-2/source/linkfile create mode 100644 playing-coffee/roms/mem_timing-2/source/readme.txt create mode 100644 playing-coffee/roms/mem_timing-2/source/shell.inc create mode 100644 playing-coffee/roms/mem_timing/individual/01-read_timing.gb create mode 100644 playing-coffee/roms/mem_timing/individual/02-write_timing.gb create mode 100644 playing-coffee/roms/mem_timing/individual/03-modify_timing.gb create mode 100644 playing-coffee/roms/mem_timing/mem_timing.gb create mode 100644 playing-coffee/roms/mem_timing/readme.txt create mode 100644 playing-coffee/roms/mem_timing/source/01-read_timing.s create mode 100644 playing-coffee/roms/mem_timing/source/02-write_timing.s create mode 100644 playing-coffee/roms/mem_timing/source/03-modify_timing.s create mode 100644 playing-coffee/roms/mem_timing/source/common/build_gbs.s create mode 100644 playing-coffee/roms/mem_timing/source/common/build_rom.s create mode 100644 playing-coffee/roms/mem_timing/source/common/console.bin create mode 100644 playing-coffee/roms/mem_timing/source/common/console.s create mode 100644 playing-coffee/roms/mem_timing/source/common/crc.s create mode 100644 playing-coffee/roms/mem_timing/source/common/delay.s create mode 100644 playing-coffee/roms/mem_timing/source/common/gb.inc create mode 100644 playing-coffee/roms/mem_timing/source/common/macros.inc create mode 100644 playing-coffee/roms/mem_timing/source/common/numbers.s create mode 100644 playing-coffee/roms/mem_timing/source/common/printing.s create mode 100644 playing-coffee/roms/mem_timing/source/common/runtime.s create mode 100644 playing-coffee/roms/mem_timing/source/common/testing.s create mode 100644 playing-coffee/roms/mem_timing/source/common/tima_64.s create mode 100644 playing-coffee/roms/mem_timing/source/linkfile create mode 100644 playing-coffee/roms/mem_timing/source/shell.inc create mode 100644 playing-coffee/roms/oam_bug/oam_bug.gb create mode 100644 playing-coffee/roms/oam_bug/readme.txt create mode 100644 playing-coffee/roms/oam_bug/rom_singles/1-lcd_sync.gb create mode 100644 playing-coffee/roms/oam_bug/rom_singles/2-causes.gb create mode 100644 playing-coffee/roms/oam_bug/rom_singles/3-non_causes.gb create mode 100644 playing-coffee/roms/oam_bug/rom_singles/4-scanline_timing.gb create mode 100644 playing-coffee/roms/oam_bug/rom_singles/5-timing_bug.gb create mode 100644 playing-coffee/roms/oam_bug/rom_singles/6-timing_no_bug.gb create mode 100644 playing-coffee/roms/oam_bug/rom_singles/7-timing_effect.gb create mode 100644 playing-coffee/roms/oam_bug/rom_singles/8-instr_effect.gb create mode 100644 playing-coffee/roms/oam_bug/source/1-lcd_sync.s create mode 100644 playing-coffee/roms/oam_bug/source/2-causes.s create mode 100644 playing-coffee/roms/oam_bug/source/3-non_causes.s create mode 100644 playing-coffee/roms/oam_bug/source/4-scanline_timing.s create mode 100644 playing-coffee/roms/oam_bug/source/5-timing_bug.s create mode 100644 playing-coffee/roms/oam_bug/source/6-timing_no_bug.s create mode 100644 playing-coffee/roms/oam_bug/source/7-timing_effect.s create mode 100644 playing-coffee/roms/oam_bug/source/8-instr_effect.s create mode 100644 playing-coffee/roms/oam_bug/source/common/build_gbs.s create mode 100644 playing-coffee/roms/oam_bug/source/common/build_rom.s create mode 100644 playing-coffee/roms/oam_bug/source/common/console.bin create mode 100644 playing-coffee/roms/oam_bug/source/common/console.s create mode 100644 playing-coffee/roms/oam_bug/source/common/crc.s create mode 100644 playing-coffee/roms/oam_bug/source/common/delay.s create mode 100644 playing-coffee/roms/oam_bug/source/common/gb.inc create mode 100644 playing-coffee/roms/oam_bug/source/common/macros.inc create mode 100644 playing-coffee/roms/oam_bug/source/common/numbers.s create mode 100644 playing-coffee/roms/oam_bug/source/common/ppu.s create mode 100644 playing-coffee/roms/oam_bug/source/common/printing.s create mode 100644 playing-coffee/roms/oam_bug/source/common/shell.s create mode 100644 playing-coffee/roms/oam_bug/source/common/testing.s create mode 100644 playing-coffee/roms/oam_bug/source/linkfile create mode 100644 playing-coffee/roms/oam_bug/source/readme.txt create mode 100644 playing-coffee/roms/oam_bug/source/shell.inc create mode 100644 playing-coffee/roms/readme.txt create mode 100644 playing-coffee/roms/solitaire.gb create mode 100644 playing-coffee/roms/supermarioland.gb create mode 100644 playing-coffee/roms/tetris.gb create mode 100644 playing-coffee/src/main/java/playingcoffee/application/Application.java create mode 100644 playing-coffee/src/main/java/playingcoffee/application/GameBoy.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/Cartridge.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/InterruptListener.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/InterruptManager.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/MMU.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/MemorySpace.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/cpu/CPU.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/cpu/Flags.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/cpu/Registers.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/ALU16Opcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/ALUOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/Argument.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/CallOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/ComplementOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/FlipCarryOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/InterruptOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/JumpOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/JumpRelativeOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/LoadOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/Opcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/PopOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/PushOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/RestartOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/ReturnOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/RotateAOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/SetCarryOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/BitOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/ResetBitOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/RotateOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/SetBitOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/ShiftOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/SwapOpcode.java create mode 100644 playing-coffee/src/main/java/playingcoffee/log/Log.java create mode 100644 playing-coffee/src/main/java/playingcoffee/ppu/OAM.java create mode 100644 playing-coffee/src/main/java/playingcoffee/ppu/PPU.java create mode 100644 playing-coffee/src/main/java/playingcoffee/ppu/PPURegisters.java create mode 100644 playing-coffee/src/main/java/playingcoffee/ppu/VRAM.java create mode 100644 playing-coffee/src/test/java/playingcoffee/test/GameBoyTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e10e727 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/.metadata/ diff --git a/playing-coffee - Copy/.classpath b/playing-coffee - Copy/.classpath new file mode 100644 index 0000000..271ce0c --- /dev/null +++ b/playing-coffee - Copy/.classpath @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/playing-coffee - Copy/.gitignore b/playing-coffee - Copy/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/playing-coffee - Copy/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/playing-coffee - Copy/.project b/playing-coffee - Copy/.project new file mode 100644 index 0000000..8e61026 --- /dev/null +++ b/playing-coffee - Copy/.project @@ -0,0 +1,23 @@ + + + playing-coffee + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.jdt.core.javanature + org.eclipse.m2e.core.maven2Nature + + diff --git a/playing-coffee - Copy/.settings/org.eclipse.jdt.core.prefs b/playing-coffee - Copy/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..cac0df4 --- /dev/null +++ b/playing-coffee - Copy/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=ignore +org.eclipse.jdt.core.compiler.release=disabled +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/playing-coffee - Copy/.settings/org.eclipse.m2e.core.prefs b/playing-coffee - Copy/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/playing-coffee - Copy/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/playing-coffee - Copy/dmg_boot.bin b/playing-coffee - Copy/dmg_boot.bin new file mode 100644 index 0000000000000000000000000000000000000000..afa0ee4792c2ba80afb6b0c1962e249e195e6fc0 GIT binary patch literal 256 zcmV+b0ssCn{{OEb|DQ6;d?5QFCjSl*K7caf3_kPXGCp^AKK$TE5d;At5P%xZl>p6_ z026!uG$8X4*Z>9y8WSQT1t9r88R`(3AupK@3_U0aG7TX4E)N*o0-PxJD zyJur=4(!a`+?k)dxidaILb;{6r9wO*1OXAK02&j%ApRqL{xl%<1{rvUBLyJ)h9Lev G0pL*XS7L7f literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/gb-test-roms-master.zip b/playing-coffee - Copy/gb-test-roms-master.zip new file mode 100644 index 0000000000000000000000000000000000000000..b66c88fe50e813a2e3df4f9e2a349e9d607e0ce8 GIT binary patch literal 331384 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 HpAz^7^EI)n literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/cpu_instrs/readme.txt b/playing-coffee - Copy/roms/cpu_instrs/readme.txt new file mode 100644 index 0000000..6f94955 --- /dev/null +++ b/playing-coffee - Copy/roms/cpu_instrs/readme.txt @@ -0,0 +1,119 @@ +Game Boy CPU Instruction Behavior Test +-------------------------------------- +This ROM tests the behavior of all CPU instructions except STOP and the +11 illegal opcodes. The tests are fairly thorough, running instructions +with boundary data and verifying both the result and that other +registers are not modified. Instructions which perform the same +operation on different registers are each tested just as thoroughly, in +case an emulator implements each independently. Some sub-tests take half +minute to complete. + +Failed instructions are listed as + + [CB] opcode + +Some errors cannot of course be diagnosed properly, since the test +framework itself relies on basic instruction behavior being correct. + + +Internal operation +------------------ +The main tests use a framework that runs each instruction in a loop, +varying the register values on input and examining them on output. +Rather than keep a table of correct values, it simply calculates a +CRC-32 checksum of all the output, then compares this with the correct +value. Instructions are divided into several groups, each with a +different set of input values suited for their behavior; for example, +the bit test instructions are fed $01, $02, $04 ... $40, $80, to ensure +each bit is handled properly, while the arithmetic instructions are fed +$01, $0F, $10, $7F, $FF, to exercise carry and half-carry. A few +instructions require a custom test due to their uniqueness. + + +Multi-ROM +--------- +In the main directory is a single ROM which runs all the tests. It +prints a test's number, runs the test, then "ok" if it passes, otherwise +a failure code. Once all tests have completed it either reports that all +tests passed, or prints the number of failed tests. Finally, it makes +several beeps. If a test fails, it can be run on its own by finding the +corresponding ROM in individual/. + +Ths compact format on screen is to avoid having the results scroll off +the top, so the test can be started and allowed to run without having to +constantly monitor the display. + +Currently there is no well-defined way for an emulator test rig to +programatically find the result of the test; contact me if you're trying +to do completely automated testing of your emulator. One simple approach +is to take a screenshot after all tests have run, or even just a +checksum of one, and compare this with a previous run. + + +Failure codes +------------- +Failed tests may print a failure code, and also short description of the +problem. For more information about a failure code, look in the +corresponding source file in source/; the point in the code where +"set_test n" occurs is where that failure code will be generated. +Failure code 1 is a general failure of the test; any further information +will be printed. + +Note that once a sub-test fails, no further tests for that file are run. + + +Console output +-------------- +Information is printed on screen in a way that needs only minimum LCD +support, and won't hang if LCD output isn't supported at all. +Specifically, while polling LY to wait for vblank, it will time out if +it takes too long, so LY always reading back as the same value won't +hang the test. It's also OK if scrolling isn't supported; in this case, +text will appear starting at the top of the screen. + +Everything printed on screen is also sent to the game link port by +writing the character to SB, then writing $81 to SC. This is useful for +tests which print lots of information that scrolls off screen. + + +Source code +----------- +Source code is included for all tests, in source/. It can be used to +build the individual test ROMs. Code for the multi test isn't included +due to the complexity of putting everything together. + +Code is written for the wla-dx assembler. To assemble a particular test, +execute + + wla -o "source_filename.s" test.o + wlalink linkfile test.gb + +Test code uses a common shell framework contained in common/. + + +Internal framework operation +---------------------------- +Tests use a common framework for setting things up, reporting results, +and ending. All files first include "shell.inc", which sets up the ROM +header and shell code, and includes other commonly-used modules. + +One oddity is that test code is first copied to internal RAM at $D000, +then executed there. This allows self-modification, and ensures the code +is executed the same way it is on my devcart, which doesn't have a +rewritable ROM as most do. + +Some macros are used to simplify common tasks: + + Macro Behavior + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + wreg addr,data Writes data to addr using LDH + lda addr Loads byte from addr into A using LDH + sta addr Stores A at addr using LDH + delay n Delays n cycles, where NOP = 1 cycle + delay_msec n Delays n milliseconds + set_test n,"Cause" Sets failure code and optional string + +Routines and macros are documented where they are defined. + +-- +Shay Green diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/01-special.s b/playing-coffee - Copy/roms/cpu_instrs/source/01-special.s new file mode 100644 index 0000000..776d685 --- /dev/null +++ b/playing-coffee - Copy/roms/cpu_instrs/source/01-special.s @@ -0,0 +1,78 @@ +; Tests instructions that don't fit template + +.include "shell.inc" + +main: + set_test 2,"JR negative" + ld a,0 + jp jr_neg + inc a +- inc a + inc a + cp 2 + jp nz,test_failed + jp + +jr_neg: + jr - ++ + + set_test 3,"JR positive" + ld a,0 + jr + + inc a ++ inc a + inc a + cp 2 + jp nz,test_failed + + + set_test 4,"LD PC,HL" + ld hl,+ + ld a,0 + ld pc,hl + inc a ++ inc a + inc a + cp 2 + jp nz,test_failed + + + set_test 5,"POP AF" + ld bc,$1200 +- push bc + pop af + push af + pop de + ld a,c + and $F0 + cp e + jp nz,test_failed + inc b + inc c + jr nz,- + + + set_test 6,"DAA" + ; Test all combinations of A and flags (256*16 total) + ld de,0 +- push de + pop af + daa + + push af + call update_crc + pop hl + ld a,l + call update_crc + + inc d + jr nz,- + + ld a,e + add $10 + ld e,a + jr nz,- + + check_crc $6A9F8D8A + + jp tests_passed diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/02-interrupts.s b/playing-coffee - Copy/roms/cpu_instrs/source/02-interrupts.s new file mode 100644 index 0000000..de18b34 --- /dev/null +++ b/playing-coffee - Copy/roms/cpu_instrs/source/02-interrupts.s @@ -0,0 +1,73 @@ +; Tests DI, EI, and HALT (STOP proved untestable) + +.include "shell.inc" + +main: + wreg IE,$04 + + set_test 2,"EI" + ei + ld bc,0 + push bc + pop bc + inc b + wreg IF,$04 +interrupt_addr: + dec b + jp nz,test_failed + ld hl,sp-2 + ldi a,(hl) + cp interrupt_addr + jp nz,test_failed + lda IF + and $04 + jp nz,test_failed + + set_test 3,"DI" + di + ld bc,0 + push bc + pop bc + wreg IF,$04 + ld hl,sp-2 + ldi a,(hl) + or (hl) + jp nz,test_failed + lda IF + and $04 + jp z,test_failed + + set_test 4,"Timer doesn't work" + wreg TAC,$05 + wreg TIMA,0 + wreg IF,0 + delay 500 + lda IF + delay 500 + and $04 + jp nz,test_failed + delay 500 + lda IF + and $04 + jp z,test_failed + pop af + + set_test 5,"HALT" + wreg TAC,$05 + wreg TIMA,0 + wreg IF,0 + halt ; timer interrupt will exit halt + nop ; avoids DMG bug + lda IF + and $04 + jp z,test_failed + + jp tests_passed + +.bank 0 slot 0 +.org $50 + inc a + ret diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/03-op sp,hl.s b/playing-coffee - Copy/roms/cpu_instrs/source/03-op sp,hl.s new file mode 100644 index 0000000..9531d51 --- /dev/null +++ b/playing-coffee - Copy/roms/cpu_instrs/source/03-op sp,hl.s @@ -0,0 +1,102 @@ +; Tests SP/HL instructions + +;.define PRINT_CHECKSUMS 1 +.include "shell.inc" +.include "instr_test.s" + +instrs: + .byte $33,0,0 ; INC SP + .byte $3B,0,0 ; DEC SP + .byte $39,0,0 ; ADD HL,SP + .byte $F9,0,0 ; LD SP,HL + .byte $E8,$01,0 ; ADD SP,1 + .byte $E8,$FF,0 ; ADD SP,-1 + .byte $F8,$01,0 ; LD HL,SP+1 + .byte $F8,$FF,0 ; LD HL,SP-1 +instrs_end: + +test_instr: + ; C = flags register + ld c,$00 + call test + ld c,$F0 + call test + ret + +test: + ; Go through each value for HL + ld hl,values +hl_loop: + ld e,(hl) + inc hl + ld d,(hl) + inc hl + push hl + + ; Go through each value for SP + ld hl,values +values_loop: + push bc + push de + push hl + + push bc + pop af + + ; Switch stack + ld (temp),sp + ld a,(hl+) + ld h,(hl) + ld l,a +; call print_regs + ld sp,hl + + ; Set registers + ld h,d + ld l,e + ld a,$12 + ld bc,$5691 + ld de,$9ABC + + jp instr +instr_done: + ; Save new SP and switch to yet another stack + ld (temp+2),sp + ld sp,$DF70 + + call checksum_af_bc_de_hl + + ; Checksum SP + ld a,(temp+2) + call update_crc_fast + ld a,(temp+3) + call update_crc_fast + + ldsp temp + + pop hl + pop de + pop bc + inc hl + inc hl + ld a,l + cp taken ; JP NZ,taken + .byte $C3,taken ; JP taken + .byte $CA,taken ; JP Z,taken + .byte $D2,taken ; JP NC,taken + .byte $DA,taken ; JP C,taken + + .byte $C4,taken ; CALL NZ,taken + .byte $CC,taken ; CALL Z,taken + .byte $CD,taken ; CALL taken + .byte $D4,taken ; CALL NC,taken + .byte $DC,taken ; CALL C,taken + + ; RET cond + ; INC A + .byte $C0,$3C,0 ; RET NZ + .byte $C8,$3C,0 ; RET Z + .byte $C9,$3C,0 ; RET + .byte $D0,$3C,0 ; RET NC + .byte $D8,$3C,0 ; RET C + .byte $D9,$3C,0 ; RETI + + ; RST + ; can only easily test this one on devcart + .byte $C7,0,0 ; RST $00 +.ifndef BUILD_DEVCART + .byte $CF,0,0 ; RST $08 + .byte $D7,0,0 ; RST $10 + .byte $DF,0,0 ; RST $18 + .byte $E7,0,0 ; RST $20 + .byte $EF,0,0 ; RST $28 + .byte $F7,0,0 ; RST $30 + .byte $FF,0,0 ; RST $38 +.endif + +instrs_end: + +test_instr: + wreg IE,0 ; disable interrupts, since RETI does EI + + ; Go through all 16 combinations of flags + ld bc,$1200 +- + ; Fill 4 bytes of new stack + ld a,$12 + ld ($DF80-2),a + ld a,$34 + ld ($DF80-3),a + ld a,$56 + ld ($DF80-4),a + ld a,$78 + ld ($DF80-5),a + + ; Set AF + push bc + pop af + + ; Switch to new stack + ld (temp),sp + ld sp,$DF80 + + ; Set return address + ld de,instr+3 + push de + + jp instr +instr_done: + inc a +taken: + di ; RETI enables interrupts + + ; Save new SP and switch to yet another stack + ld (temp+2),sp + ld sp,$DF70 + + ; Checksum A and SP + call update_crc_fast + ld a,(temp+2) + call update_crc_fast + ld a,(temp+3) + call update_crc_fast + + ; Checksum 4 bytes of stack + ld a,($DF80-2) + call update_crc_fast + ld a,($DF80-3) + call update_crc_fast + ld a,($DF80-4) + call update_crc_fast + ld a,($DF80-5) + call update_crc_fast + + ldsp temp + + ld a,c + add $10 + ld c,a + jr nz,- + + ret + +checksums: + .byte $EC,$A4,$94,$79,$C4,$00,$96,$2C,$C4,$64,$90,$33,$77,$C7,$0A,$D4 + .byte $77,$A3,$0C,$CB,$79,$E7,$7E,$AE,$DA,$DC,$03,$F7,$4F,$9F,$E9,$20 + .byte $72,$12,$DA,$01,$44,$6A,$4D,$8F,$D1,$79,$30,$4C,$AA,$37,$F2,$6A + .byte $97,$EA,$56,$5F,$32,$28,$C7,$D1,$49,$66,$05,$F7,$80,$0F,$BA,$8E + .byte $41,$E2,$A4,$9A,$2D,$2D,$8C,$72,$A5,$13,$76,$A8,$64,$FE,$68,$BC + .byte $2D,$2D,$8C,$72,$50,$96,$24,$27,$50,$96,$24,$27,$50,$96,$24,$27 + .byte $50,$96,$24,$27,$50,$96,$24,$27,$50,$96,$24,$27,$50,$96,$24,$27 + .byte $50,$96,$24,$27 + +.include "multi_custom.s" diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/08-misc instrs.s b/playing-coffee - Copy/roms/cpu_instrs/source/08-misc instrs.s new file mode 100644 index 0000000..5c11c8e --- /dev/null +++ b/playing-coffee - Copy/roms/cpu_instrs/source/08-misc instrs.s @@ -0,0 +1,110 @@ +; Tests miscellaneous instructions + +;.define PRINT_CHECKSUMS 1 +.include "shell.inc" +.include "instr_test.s" + +instrs: + .byte $F0,$91,0 ; LDH A,($91) + .byte $E0,$91,0 ; LDH ($91),A + .byte $F2,$00,0 ; LDH A,(C) + .byte $E2,$00,0 ; LDH (C),A + .byte $FA,$91,$FF ; LD A,($FF91) + .byte $EA,$91,$FF ; LD ($FF91),A + .byte $08,$91,$FF ; LD ($FF91),SP + .byte $01,$23,$01 ; LD BC,$0123 + .byte $11,$23,$01 ; LD DE,$0123 + .byte $21,$23,$01 ; LD HL,$0123 + .byte $31,$23,$01 ; LD SP,$0123 + .byte $F5,0,0 ; PUSH AF + .byte $C5,0,0 ; PUSH BC + .byte $D5,0,0 ; PUSH DE + .byte $E5,0,0 ; PUSH HL + .byte $F1,0,0 ; POP AF + .byte $C1,0,0 ; POP BC + .byte $D1,0,0 ; POP DE + .byte $E1,0,0 ; POP HL +instrs_end: + +test_instr: + ; C = flags register + ld c,$00 + call test + ld c,$10 + call test + ld c,$E0 + call test + ld c,$F0 + call test + ret + +test: + ; Fill RAM + ld a,$FE + ld ($FF90),a + ld a,$DC + ld ($FF91),a + ld a,$BA + ld ($FF92),a + + ; Fill stack + ld a,$13 + ld ($DF80),a + ld a,$57 + ld ($DF80-1),a + ld a,$9B + ld ($DF80-2),a + ld a,$DF + ld ($DF80-3),a + + ; Set registers + ld b,$12 + push bc + ld bc,$5691 + ld de,$9ABC + ld hl,$DEF0 + pop af + + ; Switch stack + ld (temp),sp + ld sp,$DF80-2 + + jp instr +instr_done: + ; Save new SP and switch to another stack + ld (temp+2),sp + ld sp,$DF70 + + call checksum_af_bc_de_hl + + ; Checksum SP + ld a,(temp+2) + call update_crc_fast + ld a,(temp+3) + call update_crc_fast + + ; Checksum RAM + ld a,($FF90) + call update_crc_fast + ld a,($FF91) + call update_crc_fast + ld a,($FF92) + call update_crc_fast + + ; Checksum stack + ld a,($DF80) + call update_crc_fast + ld a,($DF80-1) + call update_crc_fast + ld a,($DF80-2) + call update_crc_fast + ld a,($DF80-3) + call update_crc_fast + + ; Restore SP + ldsp temp + + ret + +checksums: + .byte $4D,$FF,$15,$97,$6D,$A7,$35,$65,$4D,$FF,$15,$97,$6D,$A7,$35,$65,$4D,$FF,$15,$97,$6D,$A7,$35,$65,$AD,$FA,$5E,$41,$D0,$78,$79,$C1,$AF,$66,$99,$34,$0D,$E1,$97,$99,$6F,$D0,$6F,$5D,$C3,$1F,$A3,$8A,$C2,$F1,$9C,$F3,$C1,$C3,$DC,$78,$C0,$2D,$E3,$01,$8F,$C4,$0F,$44,$95,$22,$6A,$39,$61,$C5,$AB,$55,$FB,$DF,$2C,$52, diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/09-op r,r.s b/playing-coffee - Copy/roms/cpu_instrs/source/09-op r,r.s new file mode 100644 index 0000000..432c4e7 --- /dev/null +++ b/playing-coffee - Copy/roms/cpu_instrs/source/09-op r,r.s @@ -0,0 +1,269 @@ +; Tests most register instructions. +; Takes 10 seconds. + +;.define PRINT_CHECKSUMS 1 +.include "shell.inc" +.include "instr_test.s" + +instrs: + .byte $00,0,0 ; NOP + .byte $2F,0,0 ; CPL + .byte $37,0,0 ; SCF + .byte $3F,0,0 ; CCF + + .byte $B0,0,0 ; OR B + .byte $B1,0,0 ; OR C + .byte $B2,0,0 ; OR D + .byte $B3,0,0 ; OR E + .byte $B4,0,0 ; OR H + .byte $B5,0,0 ; OR L + .byte $B7,0,0 ; OR A + + .byte $B8,0,0 ; CP B + .byte $B9,0,0 ; CP C + .byte $BA,0,0 ; CP D + .byte $BB,0,0 ; CP E + .byte $BC,0,0 ; CP H + .byte $BD,0,0 ; CP L + .byte $BF,0,0 ; CP A + + .byte $80,0,0 ; ADD B + .byte $81,0,0 ; ADD C + .byte $82,0,0 ; ADD D + .byte $83,0,0 ; ADD E + .byte $84,0,0 ; ADD H + .byte $85,0,0 ; ADD L + .byte $87,0,0 ; ADD A + + .byte $88,0,0 ; ADC B + .byte $89,0,0 ; ADC C + .byte $8A,0,0 ; ADC D + .byte $8B,0,0 ; ADC E + .byte $8C,0,0 ; ADC H + .byte $8D,0,0 ; ADC L + .byte $8F,0,0 ; ADC A + + .byte $90,0,0 ; SUB B + .byte $91,0,0 ; SUB C + .byte $92,0,0 ; SUB D + .byte $93,0,0 ; SUB E + .byte $94,0,0 ; SUB H + .byte $95,0,0 ; SUB L + .byte $97,0,0 ; SUB A + + .byte $98,0,0 ; SBC B + .byte $99,0,0 ; SBC C + .byte $9A,0,0 ; SBC D + .byte $9B,0,0 ; SBC E + .byte $9C,0,0 ; SBC H + .byte $9D,0,0 ; SBC L + .byte $9F,0,0 ; SBC A + + .byte $A0,0,0 ; AND B + .byte $A1,0,0 ; AND C + .byte $A2,0,0 ; AND D + .byte $A3,0,0 ; AND E + .byte $A4,0,0 ; AND H + .byte $A5,0,0 ; AND L + .byte $A7,0,0 ; AND A + + .byte $A8,0,0 ; XOR B + .byte $A9,0,0 ; XOR C + .byte $AA,0,0 ; XOR D + .byte $AB,0,0 ; XOR E + .byte $AC,0,0 ; XOR H + .byte $AD,0,0 ; XOR L + .byte $AF,0,0 ; XOR A + + .byte $05,0,0 ; DEC B + .byte $0D,0,0 ; DEC C + .byte $15,0,0 ; DEC D + .byte $1D,0,0 ; DEC E + .byte $25,0,0 ; DEC H + .byte $2D,0,0 ; DEC L + .byte $3D,0,0 ; DEC A + + .byte $04,0,0 ; INC B + .byte $0C,0,0 ; INC C + .byte $14,0,0 ; INC D + .byte $1C,0,0 ; INC E + .byte $24,0,0 ; INC H + .byte $2C,0,0 ; INC L + .byte $3C,0,0 ; INC A + + .byte $07,0,0 ; RLCA + .byte $17,0,0 ; RLA + .byte $0F,0,0 ; RRCA + .byte $1F,0,0 ; RRA + + .byte $CB,$00,0 ; RLC B + .byte $CB,$01,0 ; RLC C + .byte $CB,$02,0 ; RLC D + .byte $CB,$03,0 ; RLC E + .byte $CB,$04,0 ; RLC H + .byte $CB,$05,0 ; RLC L + .byte $CB,$07,0 ; RLC A + + .byte $CB,$08,0 ; RRC B + .byte $CB,$09,0 ; RRC C + .byte $CB,$0A,0 ; RRC D + .byte $CB,$0B,0 ; RRC E + .byte $CB,$0C,0 ; RRC H + .byte $CB,$0D,0 ; RRC L + .byte $CB,$0F,0 ; RRC A + + .byte $CB,$10,0 ; RL B + .byte $CB,$11,0 ; RL C + .byte $CB,$12,0 ; RL D + .byte $CB,$13,0 ; RL E + .byte $CB,$14,0 ; RL H + .byte $CB,$15,0 ; RL L + .byte $CB,$17,0 ; RL A + + .byte $CB,$18,0 ; RR B + .byte $CB,$19,0 ; RR C + .byte $CB,$1A,0 ; RR D + .byte $CB,$1B,0 ; RR E + .byte $CB,$1C,0 ; RR H + .byte $CB,$1D,0 ; RR L + .byte $CB,$1F,0 ; RR A + + .byte $CB,$20,0 ; SLA B + .byte $CB,$21,0 ; SLA C + .byte $CB,$22,0 ; SLA D + .byte $CB,$23,0 ; SLA E + .byte $CB,$24,0 ; SLA H + .byte $CB,$25,0 ; SLA L + .byte $CB,$27,0 ; SLA A + + .byte $CB,$28,0 ; SRA B + .byte $CB,$29,0 ; SRA C + .byte $CB,$2A,0 ; SRA D + .byte $CB,$2B,0 ; SRA E + .byte $CB,$2C,0 ; SRA H + .byte $CB,$2D,0 ; SRA L + .byte $CB,$2F,0 ; SRA A + + .byte $CB,$30,0 ; SWAP B + .byte $CB,$31,0 ; SWAP C + .byte $CB,$32,0 ; SWAP D + .byte $CB,$33,0 ; SWAP E + .byte $CB,$34,0 ; SWAP H + .byte $CB,$35,0 ; SWAP L + .byte $CB,$37,0 ; SWAP A + + .byte $CB,$38,0 ; SRL B + .byte $CB,$39,0 ; SRL C + .byte $CB,$3A,0 ; SRL D + .byte $CB,$3B,0 ; SRL E + .byte $CB,$3C,0 ; SRL H + .byte $CB,$3D,0 ; SRL L + .byte $CB,$3F,0 ; SRL A +instrs_end: + +test_instr: + ld c,$00 + call test + ld c,$F0 + call test + ret + +test: + ; Go through each value for A + ld hl,values +a_loop: + ld b,(hl) + push hl + + ; Go through each value for other registers + ld hl,values +values_loop: + push bc + push hl + + push bc + + ; BC + ld a,(hl+) + ld b,a + ld a,(hl+) + ld c,a + + ; HL + ld a,(hl+) + ld d,a + ld a,(hl+) + ld e,a + push de + + ; DE + ld a,(hl+) + ld d,a + ld a,(hl+) + ld e,a + + pop hl + pop af + +; call print_regs + jp instr +instr_done: + + ; Checksum registers + call checksum_af_bc_de_hl + + pop hl + pop bc + inc hl + ld a,l + cp 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 ?@1~S_Jk8EuOnO>t1U_)GPUYcV}PSJ688I zDZyK&1&Z3Yf=R|a=4{O6sIrFR>wY_q1954MiRzbDIv)c%zSNhFNh$uKIuubR9$M#% zQVgthyMM7>6t%8xc-*ZoU#;c~Eo^b8>ieF1Te%=ANe|UbtGOeuN<>ma4(yn>d z&PyDPc+NXzR}RnDiJDL@@*5U1@Exf>ZKHTq^DiA1#(zox`Vp^snt$Nkdp#BumLA0V yczj1Ga3U7XIy~nG6}avCt6hL`&VKmF{r{ZIvhq4Tb=k}k+hUx5zmKoWJ@6mAT&*eq literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/console.s b/playing-coffee - Copy/roms/cpu_instrs/source/common/console.s new file mode 100644 index 0000000..5307f6b --- /dev/null +++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/console.s @@ -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 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 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 diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/crc_fast.s b/playing-coffee - Copy/roms/cpu_instrs/source/common/crc_fast.s new file mode 100644 index 0000000..d1088b0 --- /dev/null +++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/crc_fast.s @@ -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 diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/delay.s b/playing-coffee - Copy/roms/cpu_instrs/source/common/delay.s new file mode 100644 index 0000000..65eb9c0 --- /dev/null +++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/delay.s @@ -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 diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/gb.inc b/playing-coffee - Copy/roms/cpu_instrs/source/common/gb.inc new file mode 100644 index 0000000..31bbf14 --- /dev/null +++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/gb.inc @@ -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 diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/instr_test.s b/playing-coffee - Copy/roms/cpu_instrs/source/common/instr_test.s new file mode 100644 index 0000000..5ed6e2c --- /dev/null +++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/instr_test.s @@ -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+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,- + + 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 diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/macros.inc b/playing-coffee - Copy/roms/cpu_instrs/source/common/macros.inc new file mode 100644 index 0000000..c413bd5 --- /dev/null +++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/macros.inc @@ -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 diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/multi_custom.s b/playing-coffee - Copy/roms/cpu_instrs/source/common/multi_custom.s new file mode 100644 index 0000000..4dbae9d --- /dev/null +++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/multi_custom.s @@ -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 diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/numbers.s b/playing-coffee - Copy/roms/cpu_instrs/source/common/numbers.s new file mode 100644 index 0000000..6d6faf8 --- /dev/null +++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/numbers.s @@ -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 diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/printing.s b/playing-coffee - Copy/roms/cpu_instrs/source/common/printing.s new file mode 100644 index 0000000..ad9d811 --- /dev/null +++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/printing.s @@ -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 diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/runtime.s b/playing-coffee - Copy/roms/cpu_instrs/source/common/runtime.s new file mode 100644 index 0000000..90cd24f --- /dev/null +++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/runtime.s @@ -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 diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/common/testing.s b/playing-coffee - Copy/roms/cpu_instrs/source/common/testing.s new file mode 100644 index 0000000..c102f78 --- /dev/null +++ b/playing-coffee - Copy/roms/cpu_instrs/source/common/testing.s @@ -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 diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/linkfile b/playing-coffee - Copy/roms/cpu_instrs/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/playing-coffee - Copy/roms/cpu_instrs/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/playing-coffee - Copy/roms/cpu_instrs/source/shell.inc b/playing-coffee - Copy/roms/cpu_instrs/source/shell.inc new file mode 100644 index 0000000..277a9b7 --- /dev/null +++ b/playing-coffee - Copy/roms/cpu_instrs/source/shell.inc @@ -0,0 +1,21 @@ +.incdir "common" + +; GBS music file +.ifdef BUILD_GBS + .include "build_gbs.s" +.endif + +; Devcart +.ifdef BUILD_DEVCART + .include "build_devcart.s" +.endif + +; Sub-test in a multi-test ROM +.ifdef BUILD_MULTI + .include "build_multi.s" +.endif + +; GB ROM (default) +.ifndef RUNTIME_INCLUDED + .include "build_rom.s" +.endif diff --git a/playing-coffee - Copy/roms/dmg_sound/dmg_sound.gb b/playing-coffee - Copy/roms/dmg_sound/dmg_sound.gb new file mode 100644 index 0000000000000000000000000000000000000000..fe9131044031c45f404642f5dc8a9ec43ecd2065 GIT binary patch literal 65536 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 diff --git a/playing-coffee - Copy/roms/dmg_sound/rom_singles/01-registers.gb b/playing-coffee - Copy/roms/dmg_sound/rom_singles/01-registers.gb new file mode 100644 index 0000000000000000000000000000000000000000..c1fa6c5d2c003d96130d6cc426d75b9b99f4ba6e GIT binary patch literal 32768 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 literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/dmg_sound/rom_singles/03-trigger.gb b/playing-coffee - Copy/roms/dmg_sound/rom_singles/03-trigger.gb new file mode 100644 index 0000000000000000000000000000000000000000..1b0f03213d12cb67c481134896b04f7739472e41 GIT binary patch literal 32768 zcmeI4ZERE58Gw(S#0kNe@X_`{Q*y$Gmj)tCQK_YxqtLP**9%_?oFR888SZAjDj`V-gq*P| z8pF`mzijW7Z|;51`=0mXIq$jm{t6-g=SRZTdG1S5JiCZ2YzUH3VkV17%aV>a-y)XJ z&iXE2y7cMT>C=P#KFgJ}mp&W&_34wF**%Cg5F?CEpZeiFbU1_rkN^@u0!RP}AOR$R z1dsp{Kmter2_OL^fCP{L5tyOn>CP2^W}S}UM=kQskN zZ%vgX`GE=0^8wH)Y_a@unjMp$OP9bdDrWmAiF9<_rJtzj?IimwB^@1h0ces z3f9ZmXnuLodf7ndq$Fgt(qikLHfLY&x)GO>23y;-xryH7w{29v?K6?0^~0{kS`O!U zj7EAT9G8w~w6Rb4*j8GSk~4#Bu_d-P)s!`?qc{B4iiu=*cNw>+F0AFoNn0EctYq`~ zhoP=jkkP}CO(UJ#(z}H;LLB<_6XB`2q=K2R7SD?WBO{%&^e*a}(JW!oHQ_H%!XbHY zM*bqr+VY2)A%BGR=8wv6WqG}F+s81X>mLBUv0ncW*Bfj4m7s3~J*m!zEV;g+rtbiK zOa1&#&?n*<`LT)O;gq3|otcJfV;_r6OA2e@>y_hNc2q|vro%gmN90ozCqBNKk!Iu% zGr~6GcYUr`y&m5n_4sJe-S77e&>@#sq<)cl-M+!l5FK_0JpKV19P;n-4*0$kqWxZf z|7)6-Ov!(ZSA&2#@gCYA4~KEf}#Hy>c7T$`;IsUJ*6uw2o znBD|xJz&q0k<%RpN(s9Xb5T{jK6C-f~6nSfHaLQB-zvyQvU zNH*ocOULS3c=N*T)&3ly+qXSkS41%6 zAEs(PG^90%Tg@q`XG642VeDhgJYro_L!pOLpoa_&r$FCQm(f*Dun@rZWSWi=mtlk4+zS+8arU$S=(`} z$uOAhi3@2Z`PBD>Yf5tabHWw*qqLHJLiS`zmlu~??Z#Ad%9ij=DoMetBw=8oW#Hr| z?`15l@_U(-4U&PZO&c5Ewap*s3xl{vnwnA8tm!Vqa{CJha*@KpT&enwGFD*Wf033T z*BSYG`gvC{=!RZkB$1ZpVmU`w)7Gwk2#dLHyWi*5H3xG3xr4dE=<%)vFcToaUobCM zsF4|9s#1(?REh`SJP7CLMqYaDT#+eB#n@@Ow0PhYT~s`HlC~5_PtbQPun*4Xq&CI6{;OeE385Gq?y`DGVdMv#kg8AL{sFPBrnUBAyXUd84r5{PIB+FiC%*5E_hBs z46x~wmK@8PoJmv5%4NS@y&C+)o8VhfkwcU0-Fz=tJk37O??}9pRfm@3drQ*q1tW`H zjzKDXcXd57qjZkty32>@f>N1wwaPDNmC~Pj9`}vF=PjQlk7XfU_zPZ=Gjkv0QY}yc zqn+>POv@i@qVsGFYJ(3;%@`~$zL|fjBwgUfeyjD-N0*G{9$C41AH=vmb-wnn5coj^ z;`Msf=ZClKg&!dTb2>~Bfrwtg=1d(S?OWRIe(_0SHk(yDOFPVKw~%2wTnUw z1eG8}!D02V*_?0_)g(fkPGU5f!C^0Ph`!yT=pX{0FGNA+iYSSi1+xjp&Fv0v6k=i}P8;1DKh|>vqsaGdqPNxq-641glkvMqzf`gd&0rOzaFwBKg z`4J_l+499yDn-QI(I`yh`NH8)o~+#j#MDo32E zKXAxp&I4rLc(9I~fCXe8hX1cLK8-Ih0EqBjRRL7TAs<*|wSO#vVB_=D@W5qQ5X_Jk z*g3ELs@l46PY66<_+wKjOSP?_j^iIBfCP{L5j^h#<$5uLo3LY%6h}P{+|`dJe{=ra*V#J6YKR?rue~_C&KPzf0VIF~kN^@u0!RP} zAOR$R1dsp{Kmter2_OL^fCP{L5}JP}U7DF=^~R&@3u7~zHnw=YB^cSv?7yd9 z%ojBME7LH}J7COYP5K6foza&s6u>Sn&-PIgYj6M3`1I*|#wBX^6~^vwz@@x7t*T&qe?I=(571xM)7;eo)|P)Y_aq&lqrv`2cz})#y%;R3^DGJEtr)HAg}BEFAy%;sMZ#>?f!^p=Z^pZ2c#^Ao<$jrTSWUk- zbHyR9w2S1T?e7_4ofsdN4~v^t!`iy5$A=ly>y%S`T)Y2t*8~FJzl1SvOc+VcR4(T{ z$x`pWXZL-7-ygm8v%c9avs>H6<{w8FKRt9}@!h_c7Lz|Xxkz`@PrRiaBYThR9iG3G zDrjc0;llHZz9j>Xj8^IftDe{Yp#0kuYV_BX&a?D3oWYkA{idQ5Rd=fTuN2MHU21g> zJQfr)rC(E0ni&;x<_Tfmd`Y-!o)kVbW5Q+glyJ#ZqF{C+CCr%0)87_um`cxgh3oqB ziaC2g->eoEnu-lRyIQA;=`9&k5nQGM9UI*P=RW9G-3@xTs)`T{MD5&P`?|O?-WP@D z32i=S?%dg#Ph?N!$Fs5gM7B^q-RvdUDLsk?vF7yODbEE)M}q@oo{{v(%yJ^@?{FOK z_?OVc$9f`R*bL>7@oZ#yB0CsA+pz&=0tEO3^J0nGnGL3zONssF(m31`a3}Zk)K_m+ zDQHWH^K{eF_+Mz_(!@Dhzm$BJ)}(tC{XI2(TJ3~cKhv%8V{%^nl6(7oXfWdOR2$ZfJm9dli$_eCY^G zrmU>Mm#HUb^uJ~bx;&SDVa|lC3PoF%y17hmU?>zE01Y5lZPA?|xyM@Rjjmcob~Mrz z2fb}sJQIfVG#m)g;K)b>-Vv+>U|2(*5LTN{X4zE!l`Q*b-ZsVF$=jz`AYU`ZNWON8 zCGuoS%bCkbfc#2!aAy}e1@4l79+kU4edZxp<)KmN=HB+$jGRq{p z(0pMe0~>B2;~^7nqQ4FGYwi-%tTjKG1qW|t*{%FrS^ZH>_>qUa;jBHrm#*w`&|CbM z#$hLaV-2yF;U??f4tf<{b$yJ053uReT3*nclH#o2`pDZmc7RUG0k3saZ_lvTjc%~` z7W>3Flzyt*)narPv_A=UmRLwYDEzzCu`OqMCbONzpV1A4BJF6<>ol|Q=Y8J@ABSHe zTuATHAYAwa6(zU)R#vTt3>fvik#)9y%|Tb#B;*Fa0V>8|aphIx>4J8d8%Mm}#~*K* z%x>Gd<0SaFHGgUKs1SG|0ttme*69%{hTtMZV3OY%6NnrVL`nTQY3pk9MdYW5%jL4{ z+-)$g%}s`VQ2T}nk^LpV3`2qt2f=a?MF}3_l@=kAMC?u%2zC>{96lz?ej@PsLLAzB zAWq^g!R3U0SDQZ+2VZe1=HzX^B*nOWoSTUv*vtIlHFLUr+%LZ>wMe)ZQQ)zHd${14 z&n)|ae8K-u!xAF`2*hQX#AKOY&@V|6=te`bEQg}tUy4Z*#AVeIVUiREmjp&(noRsW ze8Eqgyudt|GYoSfRbJvmT`XQvRh7uc;&GVBOxx#I~WOs5IXOiA16OIKLCMy)If zOU7~#ij0#^+J~gP#bKs(Uz+Jl8(s>O7c0uHz1T}j8>li9k~NK(sfM@~%qCzQspqb2 zgMrTUmC4!J)$YCb-19r8#Fd+W{%Yu-(+7IkFs&&u~t_JMjmCh-_x&V z3Yz|naTwm&ZM1>IF(#T08_?u|}f-l-7dv zBW%QInsZ&Pp!1pn5v_B&+pjpL4z=A%Yf6Q?;fkZ0-sX=Dbm_6%PUc*{YpY+3;XK!< zqSxYS?bPIA--o>K8CuYkTraD4rZ%h9Qx#k2t+1=_99VgYdO4@7+jf)GN_L0>2&1!8MN#YT4{Jr3;Cr#iR_sN+w&iJexH`sP92dUcaX%u-=%$ z`>ZwgpZiwA&YJ&2jtcv;1C@= zD20~ZpFouMOTnGaPi4Jr)z7s3OQ`2#d%_`U zX=FGXoCHT*u%V*}+p^zHUIV=2Bq+=KKi2el6R(sgoQ9`vjPOt1vjD=}%5(*29ItLaVt z^W>nt0p6SddlfG7;H#QmhOfhJMnD5>y0ymRnq5@vHR~Sv!^Vx^CtVG{1x$VaBs*?& zfyHk2nXxw$DR(s*T?Oq=f{mrFrXUpljOzsW)#_f&Ed06Ssn8(& zeBqAtAq~QXPw=AT=1*kR8pwdr*0;0vrYEZD0vmoy(%jhizUmbVOnA4DL*V9?q{g2f=5ga}OZ+7kkigMwRBe?gi%n>}IqyTsve zSa!~4nAhwi5f9uw5kh2d$t%N<$mt<>%tvO}L6%;IV={ zobb$NmUSRs(EnLTWJCZ5Nm(WdS>`A7i=qgA<3U-LgK^Lo6QT%lS$B~zQ4E13fl-(y z6E6>6@De*eU>?kgz+6a`A4#Il7B8u)O60@IBuwP-!r`^_z`$xB4t82?kmUMa?gt?9 zgX_Ry>B(NNK)~0f;w<=+y*zY2&O<130lf{@v<;+-XK%NWxYdr622W63t^*;(anZfl z^x7Htx9~##gp(F|KFrdEww#1^vpWIU`5fqHc3=XyKwlspP=GoDeK;ZjFUa%vSWPe3 zU*iDaUbg4{c>d*fLbUvWL*C{*pv@N^)R7Z#LYvF*|H9(ac!2>xg0EEzK)D~{fg)S$ z$0-PIKF^W|E)hX+Kv-ZWUjI#b>B2K1u)*-nQz&Z7uY%Q&A4mWRAOR$R1dsp{Kmter z2_OL^fCP{L5)qNkv-!?V z8W{AEmx}M`UZ3-w^ZosubI+p*`9E*kK=AU1s(PuBG}Ig?)5J#_NnN1x)t?amjZ5*R zg@x-g7cUO|Ansqjv~XkQt&11F!X84bhPYw&=8lsOS;HwLfCP{L5>W zl0JW{_FV80D+JR;0!RP}AOR$R1dsp{Kmter2_OL^fCP{L5=6=GFuFB`Y^7S)XU0T07aC^;j@eg_WJm{cHN^V%gxI zn}c;R0c$pE;`14HmcNrJgI-E4^iz`T?EH`QxvljGDSV5P&dvw{S1e#PL!=#`-NL4= z*45CGi>?_uWYp%DyKnjmFNN=A4BaI+-}HIteKBmN4~G3-vKsnjxM?$o%OXZCT~1_- zbMJ1>T@`ar(Xyczhgg$8y(jM}xc1U}!=b8|6vo2tUdKhKQu}8)0|?sKy0r`H>VS-P zK{mB?t+%h2)IuEk@qxr*#&E!_Zxk`Ee zB&=56oOSB+G!#a{%Z2k?%eEF?2;bnxvab8v1G%^OZ?%6}k@RP*!k(JXTDd;>RUq&p zm}pOG+L_$EM_%ug$z|7H-X%`?|JbPfV@IfpJ=$VnC;8RPDY0(+_fa(@0%fJGNo&fQ zHf;S`F*sd)w&&YD-|6{%sICwxJXUDyl$*bwSo!eK#LD^p6D!ltpIo7P>HDE-*XX{N z_KhrF0mpWwKG%`ucXBXfbhEE_`b9pT{lJ!L`19HBbMzh@s5i5~g7YkYFAFdXn_tg9 zuz_J&w=?|hY{sw?Qqi7}7VQ(#f_+lDZYQN{_9^L#ttUWjA|t(H>ra1Ex^3$R4@=9u zBWo89@LSpPQd6Zq;?8f&%i5istxG;zhlvLNkqcKFHGe&C)bcVU16h0b*S;>VPxdE3 zoG=!P_U_%?rL=jfG-)PFQ)byH@?Dzbu98I_$QqDqk&kAd8#sP^@CaFCL_3^YOPkTK z=c({5sY$FI9F7k@SeZ13*QU&&)Vc6>umlM33+k0Bbu$;3+NX)L>2RFBJbH9! zXmGT?9x89i+N_4($}s0tadD3LcQa1)5ZUJ6%kde#%)f4E2Vg6YWy=+pN&UjAHvBb2PpYe|c~;J`kgWqoc#4z_ALLSbd2QR$H1j z*=*^w$^Kq)&9Xm~+_P+;R5Qy+sdkp7OJvq4+H2E*{Iof=yO*2-{UoS=p*+55@W17> zjIO&n?$;#FhP|r?D?g)l#^Arx*NC~)oI9+;@%x${BvXFEPecEjzY0BTEzOt^;1?#l zTYAIfw{_`-AUWf$9p6XS_ju@Caf!a@ZHDjXD0>B7^6=L~7nsZHX9Qw^PQMX2koPLO zx2~<_H(g!eC*y$|%jRo2_PW&v8qcuztwY+*yfYQB`pU-JlAEQM(vXVy(!!4y?cj{r zUHKv1Uart^J-?~jm)W6Uwbr`f>gUy2g1XkZ66BVY@qik2ezmCAfRpC{^ADg}ljUuaP$ z9w<2SSghBPA*6^O6@CDsXd(_YC!QLON(6k(S60BE8WpLFb&AsYaa-y%qjAw81*vn@8E^{i6E_rdYBUzWHkkHyPO2~0>_N!Va?IPj}*<2@0 zGGf5EP6BJYz5z|DnpIk}HDm`u4ed?BNm;Kmter2_OL^fCP{L5+Ra}Ap|AMyvvu(O6a-=ep3MNR+Q zH0<*Q*t1xp{*1y->5nT#kW0uj-IT;zTkqPRUfvIqnZ1;>wgw59Vm`A8Jgoxh)oj9S zo)64A=z_*0TI&_tuX<))XuFxxG7hons>eld@nI8P8g{$MeBhUDjnx<~a38hwS}diV zey_UrK3{u`7PU;ik2QLe8&%hgV-vkO5GcFJ%)vHir)?rosy!#B0KrFgc)J?f@>NMtx-R^A^+KQw$=oux@<*a}`T27fbr(cZm#(Uyc=UYd9Afwaw{!+m!wL*e1@$S~cu zJxHHAOowe!z5NIKd&5I^V!prJ*8K`(SgnkTe|u5+U7Gu`PXJH9{BQiqv#I5+rCKFW ztH;b{*iu%hmtEpwt4J<5{`elT%kFQLiu=@_lzFDhHE6~4y~!@(i%EUnIP)f*JnJ4xL z*R9O1r-f_!Yl=0qLw_z^oNX-C2c7B)RaBy?l@UBv1{PL&d(XUoJngO5kEc};f`O>r zTWe2>i(}m}m>$w{d27Rl_CnG)S{O6pg>j=;IXKQTyvbJ-4Pwpf1!Z5)@Nl@7(vwPJdL@kE{AyX&$(^qXSN3wSKD~2!3Ge z>mm176^qsA&ne8VE1g%8{yY>YBmurH-N@*xvqk;vIpx$j3$iSh90v7rvrqQ)N5Z{e z14u5-=mszuvETekPc0+s8|b2o-n1pY799x>MSCJNJTx=_2Z)uy2dgg-!fFc>2Ae7z zGuU4Xjw$xrf^&-X6l$gzDb!A}WPwa+d23+;pdT~(HguAsARhu%M=PPMt`ca=_1E2Zgy>;f@no6=bo3h<2TC*xkCm!SWtw+uaNE=(HW;O7RrQFz19 zBU#~le)77zc619}+~}e=_|Mk?cN3hTe)b~V;pdHO>62Lq7oK1xlV5nlQ0pKAM*XiF?&j?-y2vIWH~3+x z3WLPum(3@N+9fVL5D0AE?3*+mS-buS__&d~xcuo5cq0OdL?ZUrBvOjNMTo$pkUK6A zIUvP|N#%r97%Bnj-sBC;$;V&Gqj zOA^Fo_Yz@}6a|+Ac0o-hAs)UEB5vM54|E1W7gFUdLDad@WmQ#)d@zv!MIJA-knIly z?D@e+r#%Ok+<%DM0f@YDKhSJ{awsGauywgK2lnI;51sGx5Gq{2-#S}u9qHoPyPYIv z&ts%17*Uts10khpNvuvoZU**kypTWPq$QpYv;D$cPQbiHj01MA1MAEUOaK@77f291 zz~b!{*`%Lvh9IEKIc4O&I=FTBPZa6Id{YVD~YS| z7YqR6yjE=hm34>*Ub0<3UO^DKp6wpE3<`n=!U8$z`Y$S_3u8jy0|N&$Ae6M_qhPP& z0|_7jB!C2v01`j~NB{{S0VIF~kN^@u0!RP}AOR$R1dsp{Kmter2_OL^fCP{L5hb zk~pjDIY~i-hl#$7W|JV3rb&B%(Ff?dPMYnTYkEmfMa5d(iDg`uInt~yvpeqArkTz6 z+@t}q{sYy%NB8=i?>XO}pL6bcG$CK`rVRuyeW3H^+B=GLV z_|oFyJG1A{4;_jJu3ubyclLMZ&wYj6g;*VN!|b)MJ#d#boI(Of00|%gB!C2v01`j~ zNB{{S0VIF~kN^@u0!RP}AOR$R1dsp{Kmter2_OL^fCTO_0tZ@1rh7{}z1`YKKTT*P z^Kp>$1zNSIg7;VKPyxW z{-q48iwRh>SrgxrV`upGTm|$}>Owyy$&QZyThDB)N65lADe35l5OBp>tY(O`1+?#H zGgfOkwB({Ih7K9E1?28){)HpqTUkSQ$<5dNUV2*$o9UflzmJqdFNK@dbGRg8c zMh`+Z9=g)o*GoJQhko)%qL4KlFsp0Diy}eEO;-Yajp5?DCK%n2{_f}jAg8>H)#^AG zj_a);1n4sO!x={SYZ=BZ%HD*iA6YHzJ!`AYHVLAp$ee#aD>er@o7M4sIaU)(Tr7$G zvYBf`bt9gezmc&wqPiSO6eePO*4La2SUpJm1Bmk9*VV*Z_RHKgubtAgkLIs><<$#=A7WTjQCWVe`G(9UB-{bUVwvTGp@= zQqi823ii{|qJ3O?$4*LD>=V*uTTg)6WLEl%tv_~9x?$`4za?Gg$8z?v&ksr|{ca0SIP|ko{i~K_Fsez+M2M?11BihUJD`_(t_C6Z^m((QI_K(B| z@2pIjBP-M9Q0i>>KClD`@C)kI8g(-lnA+v^Ub{R6?=-wKdqwFRFKoO~PM@b+%2RL9 z#`5$zT3?=doz`i?Ic{m%TUs~NyE$=&erkOD5V10(| z6TCHXSpWAT;lD9>z0QB6^OKrLT#od_2IKV5u`zo1XJfgE_J zdFEKUMCOd5y)pyLPnttrz2pSwCqezom5GAEAJR1`ue)~Ku1lN=`^x*PzovH9;C1;G zVlFk$zpTUgdtMKc=>XyXfc{s38uYBSG;2bD7fg1u^rFe1$V)#6lG8rV#2&i3(@SrP zyZOhyX83@H*)#BxyT4w#$Xr%GTq@6t3$)*8c`ol$bYFej{jYX*f}gAxE<&4sM`x$3 zKG66Ed*3>s{WR}PwOD-><2A|6(o1PbMSN=E?L|8{Yj#(EPVcK!X}E#!%iEPd_k1-z z0e82s;^TQp7kXRXI7#ucGF5(@3! z-7;%#Z|gh`F>V$vZ`>S`Xhb5hSj@RhV$~Qtgh)(@`jQe+W0I`oe?i)N+an|D*NNZn zck}}7VAmcXqY>yMqlBo@T2zH4NlJmkwI<673{vY7A&NrWK0i3@BT+RzuBuTY34198 zvRFuwlwb1uVBX&zjin$~N=f>J995L0&`$|9Sq6PoJfdele?-I;&w4Kt5k;0nt`Hs` z7z@j_I8ZOd|4Ce7L;?pXRV7JP6%WiSiUNKUF;!J#35c&G6$SEgdWA4Wi9<*Nt6-** zs7POm5}#;b2R5T%3#E#dBKhU&)qFlr)bUgb3`M@sqE0+eaOCk=uOmZ95kD&Y07TJ5 z9B58FH5!!&__|tM27hW)q%PJ)3Ts>--VVoX2k8^l``jeq$O+OMiRCx;fskrOk=Mnj zkAZ&&FVs&2X+_k-oVXyXDUjRcBw!adFwcCz1aLuoi9{d*>?6>JHVW`!qW*{@MnV6& z008}(zVIjNUz3xH;}0APS?~Z^96Z=ZK_CFK2qXRoNSKKY1^`KMRviFq^NT&l_Wo;1$xSj->w~97!v{)jJQLks^}1dsp{Kmter2_OL^fCP{L z52NZg3#c0(q~ z(k^jV$8(Yag(oI4Y1yO^UXYl?pT5wQ0E&Hm%`Wb#s8}mcEZa>tN5bkNyW_5Hn%aEl zCXEpEm4}M&=w6@mo%8+uopaBl0shaMF%mrgzOJ8Zf#$knFb{rcfyVakQ$K>hTj$2s zR#x6zSXw&zow2}`b1QEx{A}s$gZvJ}>cES$mz%HOVU4DU01+SpM1Tko0U|&IhyW2F z0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvzQyNtk4J1qC@?qWAPTG+L?0m~l* zVKC5PJQlpm3ZZm~01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y z5g-CY;I1NI{K4R0hb*45L%e49@@ad&~CN<<*<$29uE7U9Qt{H!a zUf-U3L(VR^osN`iEfx(t=aa$9Ot}1`S5_&l};^}lH=c#^< zIi9E2+uyOo3x?5aSM}*r z3$HGnTUoCev)M}x>SnhJ=RJRY9o$0S*{bnFSE$L|*s0n{h(D!gNZ+?Y_zFSH(!?AC6KdK9lJ?!*1ZwIh7H=%7`-=@yCpK zJYzY+$lSKUVa0UPVj+{ZoVZeSPAIF+Gs=o{QhC!!C>Na><$`0zQSC%p`Lkm_@&)Cp zV;=sJaz!*|ocu#VWGZW|)uxCyw=1U_D_O@>{Emqe&4D9l-?)|uG>L1OoQla{*1kja z&#RlW!*LW(SgS>6-@d+5sxVWUEhI{Fg^E=a4Tj>a!K%Phhq)HTw=$2796L681Xej1 z1KEvKAsTLYIQ*{CD%TE=kB#15nJtWO%oUC%&xG$mON4;Gs9voxFZZCSQ%)Un%Cq>+ z;X8jwmcH}OWGYrUwZwLpXMfLH%5!H~V|o5HR%hHwi{pmz6{8R9ZH}L2*Q^gd4t5C& zp5(4zsvwSMJ1exCq$aNhf2s}B)*Y$i^sr@7|Mz{vm*K|gW*m-o~cwl1s0G# zA00tAN6-giye+YP=<;oM+nfFM9C}*VW&^w7YJ7g|_~_)=NQ{k6PL59^#u`%MO(g(c zUz#uQ#nN*H{&vZ;$bVb%F7lC5-6Drl{UT45V9_c%8}mr~xx&$X12BX72~_`BWqQ>T zM%H-MG(CH7*2PbUedWW|pD+iv^96GQ3TtiI0TWMO*bKs40L1sO|8by(J?|(j6fnR` z1%AEsazXsvRK6L67k%~92ifNS26kQEmQVQF@bh_+KZ`Hi`E6h;++z=Oz!<1AY_+qT zPcwauo%jB-rw9F{8}Mp!#JgGkygi5-pXKk_Lq^SXr`qkoiuD`C%TsG9OhtZZ;XOqs zxKQY;exKb_sj_gB$eT{(cm1Cno5tHzT8YLSri;Jm#Vl^TT*x(I1)K$6EciM;(ZDwO z0@jB2*tRliTsmbxQn4;dV=(qLy-*>h{a;=RT8Vl@BvV` z7WE|*&|`|K<$eHN16`4E{nOz0`(3?27ut0NU?PHjWCB2s)}lHtDM}I@ZZ%caaFE=V z05lD}K0i7fgs46?rRz~pq`i_vSuP|Y=~w(docDJ{V@Zsa)Dk`^M>Q=W^^;OfRZ(A; zkLyXfO`QW^!Y3Mf|({()- z$M{-8(=ac$*MMu<7={E~MKc|uGJPcqKH1O?Z6?qbOO-7Nx%KL$TrLOtR5FQ%GGA;_ zHy$du@>FcVl`*7@AC-O(qHHn_HaDIgjVge?E>+jjpB|N|%XOK;78i`S*EQP|B3{F{g%G;C+pvm6PoJ}9ZFg9Kv^C<+($_ufU*oD|C&dd z$qhyT33*ms09*5z4{ox1egcZ3N;@|^a+y#RKc>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U r1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1TnVFA4k$S>6pe literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/dmg_sound/rom_singles/10-wave trigger while on.gb b/playing-coffee - Copy/roms/dmg_sound/rom_singles/10-wave trigger while on.gb new file mode 100644 index 0000000000000000000000000000000000000000..a7c7ddc03b556fa3e70e82218ab34398331fe5ea GIT binary patch literal 32768 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 disabled clocks as well + ld a,1 + call end + + call begin + wchn 4,$40 ; enable length + wchn 1,-2 ; length = 2 + wchn 4,$40 ; enabled -> enabled doesn't clock + wchn 4,$00 ; enabled -> disabled doesn't clock + ld a,2 + call end +.else + set_test 4,"Anything besides enabling shouldnt't clock" + call begin + wchn 4,$40 ; enable length + wchn 1,-2 ; length = 2 + wchn 4,$40 ; enabled -> enabled doesn't clock + wchn 4,$00 ; enabled -> disabled doesn't clock + wchn 4,$00 ; disabled -> disabled doesn't clock + ld a,2 + call end +.endif + + set_test 5,"If clock makes length zero, should disable chan" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + lda chan_mask + ld b,a + lda NR52 ; channel now disabled + and b + jp nz,test_failed + + set_test 6,"If length already reached zero, shouldn't clock" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + wchn 4,0 + wchn 4,$40 ; no clock; length still 0 + wchn 4,0 + wchn 4,$40 ; no clock; length still 0 + lda chan_maxlen; end triggers channel, which loads it with max length + call end + + set_test 7,"Trigger should un-freeze length that reached zero" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + wchn 4,$00 ; disable + wchn 4,$80 ; trigger unfreezes length, so it takes on maximum value + delay_clocks 8192 + wchn 4,$40 ; enable + delay_apu 2 ; clock length by 2 + lda chan_maxlen + sub 2 + call end_nodelay + + set_test 8,"Trigger that un-freezes enabled length should clock it" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + wchn 4,$00 ; disable + wchn 4,$C0 ; trigger unfreezes length, and since enabled, clocks it + lda chan_maxlen + dec a + call end_nodelay + + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + wchn 4,$C0 ; trigger unfreezes length, and since enabled, clocks it + lda chan_maxlen + dec a + call end_nodelay + + set_test 9,"Triggering that clocks length of 1 ","should clock twice and shouldn't freeze" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$C0 ; trigger and enable + ; First length counter is enabled, which clocks it to 0 and freezes it + ; Trigger unfreezes length counter, which clocks it AGAIN + ; The result is the same as the previous test, which enables separately + lda chan_maxlen + dec a + call end_nodelay + + set_test 10,"Trigger shouldn't otherwise affect length" + call begin + wchn 1,0 ; length = max + delay_clocks 8192 + wchn 4,$80 ; trigger + lda chan_maxlen + call end_nodelay + +.ifndef CGB_02 + call begin + wchn 1,0 ; length = max + wchn 4,$80 ; trigger + lda chan_maxlen + call end + + call begin + wchn 1,-2 ; length = 2 + wchn 4,$80 ; trigger + ld a,2 + call end +.endif + + set_test 11,"Disabled DAC shouldn't stop other trigger effects" + call begin + wchn 0,$00 ; disable wave DAC + wchn 2,$07 ; disable square/noise DAC + wchn 1,-1 + wchn 4,$C0 ; clocks length, which becomes max + wchn 0,$80 ; enable wave DAC + wchn 2,$08 ; enable square/noise DAC + wchn 4,$80 ; trigger + lda chan_maxlen + dec a + call end + + set_test 12,"Other trigger effects should still occur when disabled" + call sync_apu + wchn 0,0 + wchn 4,0 + wchn 1,-1 + wchn 4,$40 ; len = 0 + wchn 4,0 + wchn 4,$40 ; len = 0 + wchn 4,$80 ; len = max + wchn 4,$40 ; len = max-1 + wchn 4,0 + wchn 4,$40 ; len = max-2 + wchn 0,$80 ; enable now + wchn 4,$C0 + lda chan_maxlen + sub 3 + call delay_apu_cycles + lda chan_mask + ld b,a + lda NR52 + and b + jp z,test_failed + delay_apu 1 + lda NR52 + and b + jp nz,test_failed + + ret diff --git a/playing-coffee - Copy/roms/dmg_sound/source/04-sweep.s b/playing-coffee - Copy/roms/dmg_sound/source/04-sweep.s new file mode 100644 index 0000000..17ac4e4 --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/04-sweep.s @@ -0,0 +1,120 @@ +; Calc = calculation of new frequency and check for > $7FF +; Update = modification of frequency with new calculated value +.include "shell.inc" +.include "apu.s" + +begin: + call sync_sweep + wreg NR14,$40 + wreg NR11,-$21 + wreg NR12,$08 + ret + +should_be_almost_off: + lda NR52 + and $01 + jp z,test_failed + delay_apu 1 +should_be_off: + lda NR52 + and $01 + jp nz,test_failed + ret + +main: + set_test 2,"If shift>0, calculates on trigger" + call begin + wreg NR10,$01 + wreg NR13,$FF + wreg NR14,$C7 + call should_be_off + call begin + wreg NR10,$11 + wreg NR13,$FF + wreg NR14,$C7 + call should_be_off + + set_test 3,"If shift=0, doesn't calculate on trigger" + call begin + wreg NR10,$10 + wreg NR13,$FF + wreg NR14,$C7 + delay_apu 1 + call should_be_almost_off + + set_test 4,"If period=0, doesn't calculate" + call begin + wreg NR10,$00 + wreg NR13,$FF + wreg NR14,$C7 + delay_apu $20 + call should_be_almost_off + + set_test 5,"After updating frequency, calculates a second time" + call begin + wreg NR10,$11 + wreg NR13,$00 + wreg NR14,$C5 + delay_apu 1 + call should_be_almost_off + + set_test 6,"If calculation>$7FF, disables channel" + call begin + wreg NR10,$02 + wreg NR13,$67 + wreg NR14,$C6 + call should_be_off + + set_test 7,"If calculation<=$7FF, doesn't disable channel" + call begin + wreg NR10,$01 + wreg NR13,$55 + wreg NR14,$C5 + delay_apu $20 + call should_be_almost_off + + set_test 8,"If shift=0 and period>0, trigger enables" + call begin + wreg NR10,$10 + wreg NR13,$FF + wreg NR14,$C3 + delay_apu 2 + wreg NR10,$11 + delay_apu 1 + call should_be_almost_off + + set_test 9,"If shift>0 and period=0, trigger enables" + call begin + wreg NR10,$01 + wreg NR13,$FF + wreg NR14,$C3 + delay_apu 15 + wreg NR10,$11 + call should_be_almost_off + + set_test 10,"If shift=0 and period=0, trigger disables" + call begin + wreg NR10,$08 + wreg NR13,$FF + wreg NR14,$C3 + wreg NR10,$11 + delay_apu $20 + call should_be_almost_off + + set_test 11,"If shift=0, doesn't update" + call begin + wreg NR10,$10 + wreg NR13,$FF + wreg NR14,$C3 + delay_apu $20 + call should_be_almost_off + + set_test 12,"If period=0, doesn't update" + call begin + wreg NR10,$01 + wreg NR13,$00 + wreg NR14,$C5 + delay_apu $20 + call should_be_almost_off + + jp tests_passed diff --git a/playing-coffee - Copy/roms/dmg_sound/source/05-sweep details.s b/playing-coffee - Copy/roms/dmg_sound/source/05-sweep details.s new file mode 100644 index 0000000..1519b68 --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/05-sweep details.s @@ -0,0 +1,121 @@ +; Calc = calculation of new frequency and check for > $7FF +; Update = modification of frequency with new calculated value +.include "shell.inc" +.include "apu.s" + +begin: + call sync_sweep + wreg NR14,$40 + wreg NR11,-$20 + wreg NR12,$08 + ret + +should_be_almost_off: + lda NR52 + and $01 + jp z,test_failed + delay_apu 1 +should_be_off: + lda NR52 + and $01 + jp nz,test_failed + ret + +main: + set_test 2,"Timer treats period 0 as 8" + call begin + wreg NR10,$11 + wreg NR13,$00 + wreg NR14,$C2 + delay_apu 1 + wreg NR10,$01 ; sweep enabled + delay_apu 3 + wreg NR10,$11 ; non-zero period so calc will occur when timer reloads + delay_apu $11 + call should_be_almost_off + + set_test 3,"Makes private copy of frequency on trigger" + call begin + wreg NR10,$12 + wreg NR13,$04 + wreg NR14,$80 + wreg NR13,$00 + delay_apu $39 + call should_be_almost_off + + set_test 4,"Exiting negate mode after calculation disables channel" + call begin + wreg NR10,$09 ; since shift > 0, calculates sweep value at init + wreg NR13,$00 + wreg NR14,$C0 + delay_apu 2 + wreg NR10,$10 ; neg->pos, so disables channel + call should_be_off + + set_test 5,"Ending negate after it maybe changed freq disables chan" + call begin + wreg NR10,$10 ; enable sweep + wreg NR13,$00 + wreg NR14,$C0 + delay_apu 2 + wreg NR10,$18 ; negate mode + delay_apu 2 + wreg NR10,$10 ; neg->pos, so disables channel + call should_be_off + + set_test 6,"Ending negate mode any other way doesn't disable channel" + call begin + wreg NR10,$1F ; use negate mode once + wreg NR14,$C0 + delay_apu 2 + wreg NR10,$18 ; since period > 0, doesn't calculate at init + wreg NR13,$00 + wreg NR14,$C0 + delay_apu 1 ; no sweep clock here + wreg NR10,$10 ; pos mode before neg mode ever used + delay_apu 1 ; sweep clock occurs here + wreg NR10,$0F ; now let neg mode be seen once, but period = 0 so no calculation is made + delay_apu 2 ; sweep clock occurs here + wreg NR10,$10 ; doesn't affect channel + delay_apu 2 ; sweep clock occurs here + wreg NR10,$1F ; let neg mode get used + delay_apu 18 + wreg NR10,$79 ; period and shift can be changed without channel disabling + delay_apu 5 + call should_be_almost_off + + set_test 7,"Subtract mode uses two's complement" + call begin + delay 2048 ; avoids extra length clocking on CGB-02 + wreg NR10,$1C + wreg NR13,$B0 + wreg NR14,$85 + delay_apu 2 + wreg NR10,$01 + wreg NR14,$C5 + delay_apu $1F + call should_be_almost_off + + set_test 8,"Subtract mode uses two's complement (upper bound)" + call begin + wreg NR10,$1C + wreg NR13,$B1 + wreg NR14,$85 + delay_apu 2 + wreg NR10,$01 + wreg NR14,$C5 + call should_be_off + + set_test 9,"Update channel frequency only when period is reloaded" + call begin + wreg NR10,$74 + wreg NR13,$06 + wreg NR14,$85 + delay_apu 14 ; just reloaded + wreg NR13,$06 + delay_apu 13 ; if 14, fails + wreg NR10,$11 + wreg NR14,$85 ; just before next reload, so freq is still $506 + call should_be_almost_off + + jp tests_passed diff --git a/playing-coffee - Copy/roms/dmg_sound/source/06-overflow on trigger.s b/playing-coffee - Copy/roms/dmg_sound/source/06-overflow on trigger.s new file mode 100644 index 0000000..2ebc236 --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/06-overflow on trigger.s @@ -0,0 +1,64 @@ +; Finds highest and lowest frequencies that don't overflow +; immediately on trigger, for NR10 values of $00-$07 + +.include "shell.inc" +.include "apu.s" + +main: + + ; DMG-06: + ; 0555 0666 071C 0787 07C1 07E0 07F0 + + wreg NR12,8 + ld d,$01 +shift_loop: + ld a,d + sta NR10 + ld bc,$87FF +- ld a,c + sta NR13 + ld a,b + sta NR14 + delay_clocks 40 + lda NR52 + and 1 + jr nz,+ + dec bc + bit 6,b + jr z,- ++ res 7,b + call print_bc + inc d + bit 3,d + jr z,shift_loop + call print_newline + check_crc $F604603B + + ; DMG-05, DMG-06, DMG-09, CGB-04, CGB-05: + ; 0556 0667 071D 0788 07C2 07E1 07F1 + + wreg NR12,8 + ld d,$01 +shift_loop2: + ld a,d + sta NR10 + ld bc,$8000 +- ld a,c + sta NR13 + ld a,b + sta NR14 + delay_clocks 40 + lda NR52 + and 1 + jr z,+ + inc bc + bit 6,b + jr z,- ++ res 7,b + call print_bc + inc d + bit 3,d + jr z,shift_loop2 + check_crc $5A1697EE + + jp tests_passed diff --git a/playing-coffee - Copy/roms/dmg_sound/source/07-len sweep period sync.s b/playing-coffee - Copy/roms/dmg_sound/source/07-len sweep period sync.s new file mode 100644 index 0000000..6b3ed7e --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/07-len sweep period sync.s @@ -0,0 +1,106 @@ +; Tests length and sweep periods, and synchronization between the two +.include "shell.inc" +.include "apu.s" + +test_timing: + ; Time how long until next length clock +- inc de + ld a,(NR52) + and $01 + jr nz,- + + ;call print_de + + ; Error if not in 0...4 range + ld a,d + cp 0 + jp nz,test_failed + ld a,e + cp 5 + jp nc,test_failed + ret + +main: + + set_test 2,"Length period is wrong" + call sync_apu + wreg NR14,$40 ; avoids extra length clock + wreg NR11,$3F ; length = $01 + wreg NR12,$08 ; silent without disabling channel + wreg NR14,$C0 ; start length + ld de,-$170 + call test_timing + + set_test 3,"Sweep period is wrong" + call sync_sweep + wreg NR10,$10 ; sweep period = 1 + wreg NR12,$08 ; silent without disabling channel + wreg NR13,$FF ; max freq + wreg NR14,$87 ; start + ld de,-$2E4 + call test_timing + + set_test 4,"Sweep clock is synchronized with length" + call sync_sweep + wreg NR14,$40 ; avoids extra length clock + wreg NR11,$3F ; length = $01 + wreg NR12,$08 ; silent without disabling channel + wreg NR14,$C0 ; start length + ld de,-$170 + call test_timing + + set_test 5,"Powering up APU MODs next frame time with 8192" + call sync_apu + ld de,-$16F + call test_power + + call sync_apu + ld de,-$B5 + call test_power_off + + call sync_apu + delay_clocks 8192 + ld de,-$B5 + call test_power + + call sync_apu + delay_clocks 8192 + ld de,-$B5 + call test_power_off + + call sync_apu + ld de,-$B5 + wreg NR52,$00 ; power off + delay_clocks 8192 + call test_power + + set_test 6,"Powering up APU resets 128 Hz sweep divider" + call sync_sweep + ld de,-$229 + call test_power2 + + call sync_sweep + delay_apu 1 + ld de,-$229 + call test_power2 + + jp tests_passed + +test_power_off: + wreg NR52,$00 ; power off +test_power: + wreg NR52,$80 ; power on + wreg NR14,$40 + wreg NR11,-1 ; length = 1 + wreg NR12,8 + wreg NR14,$C0 + jp test_timing + +test_power2: + wreg NR52,$00 ; power off + wreg NR52,$80 ; power on + wreg NR10,$11 + wreg NR12,8 + wreg NR13,$00 + wreg NR14,$84 + jp test_timing diff --git a/playing-coffee - Copy/roms/dmg_sound/source/08-len ctr during power.s b/playing-coffee - Copy/roms/dmg_sound/source/08-len ctr during power.s new file mode 100644 index 0000000..1ea218a --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/08-len ctr during power.s @@ -0,0 +1,84 @@ +; On CGB, length counters are reset when powered up. +; On DMG, they are unaffected, and not clocked. + +;.define REQUIRE_DMG 1 +;.define REQUIRE_CGB 1 +.include "shell.inc" +.include "apu.s" + +enable_len_ctrs: + wreg NR22,8 + wreg NR24,$C0 + wreg NR12,8 + wreg NR14,$C0 + wreg NR30,$80 + wreg NR34,$C0 + wreg NR42,8 + wreg NR44,$C0 + ret + +main: + call sync_apu + + ld a,0 + call fill_apu_regs + + ; Load length counters + wreg NR41,-$33 + wreg NR31,-$44 + wreg NR11,-$11 + wreg NR21,-$22 + + delay_clocks 8192 + call enable_len_ctrs + + ; Power down. Comment out to see what would + ; happen if length counters did run. + wreg NR52,$00 + + ; Try to enable length counters + call enable_len_ctrs + + ; Give plenty of time for them to be clocked + delay_msec 250 + + ; Power back on and wait a bit longer + wreg NR52,$80 + ;call enable_len_ctrs ; can't do this here + delay_clocks 2048 + + ; Get values from length counters + wreg NR22,8 + wreg NR24,$C0 + ld a,$02 + call get_len_a + push af + + wreg NR12,8 + wreg NR14,$C0 + ld a,$01 + call get_len_a + push af + + wreg NR30,$80 + wreg NR34,$C0 + ld a,$04 + call get_len_a + push af + + wreg NR42,8 + wreg NR44,$C0 + ld a,$08 + call get_len_a + + ; Print them + call print_a + pop af + call print_a + pop af + call print_a + pop af + call print_a + + check_crc_dmg_cgb $32F0CFBB,$3CF589B4 + jp tests_passed diff --git a/playing-coffee - Copy/roms/dmg_sound/source/09-wave read while on.s b/playing-coffee - Copy/roms/dmg_sound/source/09-wave read while on.s new file mode 100644 index 0000000..2d06a77 --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/09-wave read while on.s @@ -0,0 +1,41 @@ +; Reads from wave RAM while playing, each time 2 +; clocks later. + +;.define REQUIRE_DMG 1 +;.define REQUIRE_CGB 1 +.include "shell.inc" +.include "apu.s" + +main: + wreg NR51,0 ; mute sound + loop_n_times test,69 + check_crc_dmg_cgb $118A3620,$270DA9A3 + jp tests_passed + +test: + add $99 + ld b,a + + ; Reload wave and have its first + ; sample read occur 2 clocks earlier + ; each loop iteration + ld hl,wave + call load_wave + wreg NR30,$80 ; enable + wreg NR32,$00 ; silent + ld a,b + sta NR33 ; period + wreg NR34,$87 ; start + + ; Read from wave + wreg NR33,-2 ; period = 4 + delay_clocks 176 + lda WAVE + + call print_a + + ret + +wave: + .byte $00,$11,$22,$33,$44,$55,$66,$77 + .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF diff --git a/playing-coffee - Copy/roms/dmg_sound/source/10-wave trigger while on.s b/playing-coffee - Copy/roms/dmg_sound/source/10-wave trigger while on.s new file mode 100644 index 0000000..d7ec063 --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/10-wave trigger while on.s @@ -0,0 +1,49 @@ +; Retriggers wave without stopping first + +;.define REQUIRE_DMG 1 +;.define REQUIRE_CGB 1 +.include "shell.inc" +.include "apu.s" + +main: + wreg NR51,0 ; mute sound + loop_n_times test,69 + check_crc_dmg_cgb $533D6D4D,$8130733A + jp tests_passed + +test: + add $99 + ld b,a + + ; Reload wave and have its first + ; sample read occur 2 clocks earlier + ; each loop iteration + ld hl,wave + call load_wave + wreg NR30,$80 ; enable + wreg NR32,$00 ; silent + ld a,b + sta NR33 ; period + wreg NR34,$87 ; start + + ; Retrigger wave + wreg NR33,-2 ; period = 4 + delay_clocks 168 + wreg NR34,$87 ; restart + delay_clocks 40 + + ; Print wave RAM + wreg NR30,0 + ld c,$30 +- ld a,($FF00+c) + call print_a + inc c + bit 6,c + jr z,- + call print_newline + + ret + +wave: + .byte $00,$11,$22,$33,$44,$55,$66,$77 + .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF diff --git a/playing-coffee - Copy/roms/dmg_sound/source/11-regs after power.s b/playing-coffee - Copy/roms/dmg_sound/source/11-regs after power.s new file mode 100644 index 0000000..56506bb --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/11-regs after power.s @@ -0,0 +1,52 @@ +; After powering sound off then on, NR12 and NR14 are 0 +; and NR44 is unchanged. While powered off, writes to +; NR41 are NOT ignored. + +.define REQUIRE_DMG 1 +.include "shell.inc" +.include "apu.s" + +main: + call sync_apu + ld a,$FF + call fill_apu_regs + + ; Power down for a moment + wreg NR52,$00 + wreg NR41,-$12 + wreg NR12,$F0 + delay_msec 100 + wreg NR52,$80 + + set_test 2,"Powering off should clear NR12" + call sync_apu + wreg NR14,$80 + lda NR52 + and $01 + jp nz,test_failed + + set_test 3,"Powering off should clear NR13" + call sync_apu + wreg NR10,$11 + wreg NR12,$08 + wreg NR14,$80 + delay_apu 20 + lda NR52 + and $01 + jp z,test_failed + + set_test 4,"Powering off shouldn't affect NR41" + call sync_apu + delay_clocks 8192 ; avoids extra length clocking + wreg NR42,$08 + wreg NR44,$C0 + delay_apu $11 + lda NR52 + and $08 + jp z,test_failed + delay_apu 1 + lda NR52 + and $08 + jp nz,test_failed + + jp tests_passed diff --git a/playing-coffee - Copy/roms/dmg_sound/source/12-wave write while on.s b/playing-coffee - Copy/roms/dmg_sound/source/12-wave write while on.s new file mode 100644 index 0000000..de4df7e --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/12-wave write while on.s @@ -0,0 +1,49 @@ +; Writes to wave RAM while playing + +.define REQUIRE_DMG 1 +;.define REQUIRE_CGB 1 +.include "shell.inc" +.include "apu.s" + +main: + loop_n_times test,69 + + ; CGB behaves erratically, so DMG-only for now + check_crc_dmg_cgb $3B4538A9,$2B27544E + jp tests_passed + +test: + add $99 + ld b,a + + ; Reload wave and have its first + ; sample read occur 2 clocks earlier + ; each loop iteration + ld hl,wave + call load_wave + wreg NR30,$80 ; enable + wreg NR32,$00 ; silent + ld a,b + sta NR33 ; period + wreg NR34,$87 ; start + + ; Write to wave + wreg NR33,-2 ; period = 4 + delay_clocks 168 + wreg WAVE,$F7 + + ; Print wave RAM + wreg NR30,0 + delay 1000 + ld hl,WAVE +- ld a,(hl+) + call print_a + bit 6,l + jr z,- + call print_newline + + ret + +wave: + .byte $00,$11,$22,$33,$44,$55,$66,$77 + .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/build_gbs.s b/playing-coffee - Copy/roms/dmg_sound/source/common/build_gbs.s new file mode 100644 index 0000000..877754f --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/common/build_gbs.s @@ -0,0 +1,139 @@ +; Build as GBS music file + +.memoryMap + defaultSlot 0 + slot 0 $2000 size $2000 + slot 1 $C000 size $2000 +.endMe + +.romBankSize $2000 +.romBanks 2 + +.define RST_OFFSET $70 + +.ifndef GBS_TMA + .define GBS_TMA 0 +.endif + +.ifndef GBS_TAC + .define GBS_TAC 0 +.endif + +;;;; GBS music file header + +.ifndef CUSTOM_HEADER + .byte "GBS" + .byte 1,1,1 ; vers, song count, first song + .word load_addr, reset, gbs_play_, std_stack + .byte GBS_TMA,GBS_TAC ; timer +.endif + .org $10 + .ds $60,0 +load_addr: + .org RST_OFFSET+$70 ; space for RST vectors + .ds $148-RST_OFFSET-$70,0 + .org $150 ; wla insists on generating GB header + +gbs_play_: + jp gbs_play ; GBS spec disallows having gbs_play in RAM + +;;;; Shell + +.include "shell.s" + +.define gbs_idle nv_ram +.redefine nv_ram nv_ram+2 + +init_runtime: + ; Identify as DMG hardware + ld a,$01 + ld (gb_id),a + + ; Save return address + pop hl + ld a,l + ld (gbs_idle),a + ld a,h + ld (gbs_idle+1),a + + ; Delay 1/4 second to give time + ; for GBS player to interrupt with + ; play, if it's going to do so + delay_msec 250 + +.ifndef CUSTOM_PLAY +gbs_play: +.endif + ; Get return address + ld a,(gbs_idle) + ld l,a + ld a,(gbs_idle+1) + ld h,a + + ; If zero, then play interrupted init + ; call, or another play call, and we + ; can't run the program properly. + or l + jp z,internal_error + + setw gbs_idle,0 + jp hl + + +; 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 diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/build_rom.s b/playing-coffee - Copy/roms/dmg_sound/source/common/build_rom.s new file mode 100644 index 0000000..c1595b7 --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/common/build_rom.s @@ -0,0 +1,70 @@ +; Build as GB ROM + +.memoryMap + defaultSlot 0 + slot 0 $0000 size $4000 + slot 1 $C000 size $4000 +.endMe + +.romBankSize $4000 +.romBanks 2 + +.cartridgeType 3 ; MBC1+RAM+battery +.ramsize 02 ; 8K +.computeChecksum +.computeComplementCheck + +;;;; GB ROM header + + ; Reserve space for RST handlers + .org $70 + + ; Keep unused space filled, otherwise + ; wla moves code here + .ds $90,0 + + ; 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 + + ; Keep unused space filled, otherwise + ; wla moves code here + .org $150 + .ds $2150-$150,0 + +;;;; Shell + +.define NEED_CONSOLE 1 +.include "shell.s" + +init_runtime: + ret + +play_byte: + ret + +.ends diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/console.bin b/playing-coffee - Copy/roms/dmg_sound/source/common/console.bin new file mode 100644 index 0000000000000000000000000000000000000000..b02f2d37ee94c6570a3622734f2d704dfca44b0c GIT binary patch literal 768 zcmX|9F=`|+5G))_B$f+182koBX5hr&r*zbT5qlFY8w?8X8GehSdBVQJIPe4Zu1Z?m z2wEj|cXgKp3^N%Fwbp)6@0AO%)yuRSqFVOjFXoqd`4hj9QtEN1nR)XG+d{u=K$L$a zcxBdDa9GaHO?x1~9ZyqY!B>?@1~S_Jk8EuOnO>t1U_)GPUYcV}PSJ688I zDZyK&1&Z3Yf=R|a=4{O6sIrFR>wY_q1954MiRzbDIv)c%zSNhFNh$uKIuubR9$M#% zQVgthyMM7>6t%8xc-*ZoU#;c~Eo^b8>ieF1Te%=ANe|UbtGOeuN<>ma4(yn>d z&PyDPc+NXzR}RnDiJDL@@*5U1@Exf>ZKHTq^DiA1#(zox`Vp^snt$Nkdp#BumLA0V yczj1Ga3U7XIy~nG6}avCt6hL`&VKmF{r{ZIvhq4Tb=k}k+hUx5zmKoWJ@6mAT&*eq literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/console.s b/playing-coffee - Copy/roms/dmg_sound/source/common/console.s new file mode 100644 index 0000000..403fa5c --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/common/console.s @@ -0,0 +1,285 @@ +; 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 + setb console_pos,console_width + setb console_mode,0 + setb console_scroll,-8 + 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 + wreg VBK,1 + ld a,0 + call @fill_nametable + + wreg VBK,0 + 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 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 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 diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/delay.s b/playing-coffee - Copy/roms/dmg_sound/source/common/delay.s new file mode 100644 index 0000000..cbcdcf3 --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/common/delay.s @@ -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 diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/gb.inc b/playing-coffee - Copy/roms/dmg_sound/source/common/gb.inc new file mode 100644 index 0000000..2d0118d --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/common/gb.inc @@ -0,0 +1,81 @@ +; Game Boy hardware addresses + +; $0000-$3FFF Fixed ROM bank +; $4000-$7FFF Switchable bank +; $8000-$9FFF VRAM +; $A000-$BFFF optional cartridge RAM +; $C000-$DFFF RAM +; $E000-$FDFF RAM mirror +; $FE00-$FE9F OAM +; $FEA0-$FEFF unused +; $FF00-$FF7F registers +; $FF80-$FFFE RAM +; $FFFF register + +; Memory +.define VRAM $8000 ; video memory +.define TILES $8000 ; tile images +.define BGMAP0 $9800 ; first 32x32 tilemap +.define BGMAP1 $9C00 ; second 32x32 tilemap +.define BRAM $A000 ; cart memory +.define WRAM $C000 ; internal memory +.define OAM $FE00 ; sprite memory +.define HRAM $FF80 ; fast memory for LDH + +; Registers + +.define RAMEN $0000 ; cartridge WRAM control +.define BANK $2000 ; bank select +.define P1 $FF00 ; controller + +; Game link I/O +.define SB $FF01 ; serial buffer +.define SC $FF02 ; serial control + +; 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 diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/macros.inc b/playing-coffee - Copy/roms/dmg_sound/source/common/macros.inc new file mode 100644 index 0000000..9d7cf44 --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/common/macros.inc @@ -0,0 +1,91 @@ +; General macros + +; Reads A from addr, from $FF00 to $FFFF +; Preserved: F, BC, DE, HL +; Time: 3 cycles +.macro lda ; addr + ldh a,(\1 - $FF00) +.endm + +; Writes A to addr, from $FF00 to $FFFF +; Preserved: AF, BC, DE, HL +; Time: 3 cycles +.macro sta ; addr + ldh (\1 - $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 + +; Writes byte to addr +; Preserved: F, BC, DE, HL +; Time: 6 cycles +.macro setb ; addr, data + ld a,\2 + ld (\1),a +.endm + +; Writes word to addr +; Preserved: F, BC, DE, HL +; Time: 12 cycles +.macro setw ; addr, data + ld a,<\2 + ld (\1),a + ld a,>\2 + ld (\1+1),a +.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 diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/numbers.s b/playing-coffee - Copy/roms/dmg_sound/source/common/numbers.s new file mode 100644 index 0000000..6d6faf8 --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/common/numbers.s @@ -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 diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/printing.s b/playing-coffee - Copy/roms/dmg_sound/source/common/printing.s new file mode 100644 index 0000000..bb9389b --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/common/printing.s @@ -0,0 +1,77 @@ +; 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 +;print_char_nocrc (defined by user) + + +; 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 diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/shell.s b/playing-coffee - Copy/roms/dmg_sound/source/common/shell.s new file mode 100644 index 0000000..3e588c7 --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/common/shell.s @@ -0,0 +1,261 @@ +; Common routines and runtime + +.define RUNTIME_INCLUDED 1 + +; A few bytes of RAM that aren't cleared +.define nv_ram_base $D800 +.define nv_ram nv_ram_base + +; Address of next normal variable +.define bss_base nv_ram_base+$80 +.define bss bss_base + +; Address of next direct-page ($FFxx) variable +.define dp_base $FF80 +.define dp dp_base + +; Top of stack +.define std_stack $DFFF+1 + +; Final exit result byte is written here +.define final_result $A000 + +; Text output is written here as zero-terminated string +.define text_out_base $A004 + +; 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 nv_ram +.redefine nv_ram nv_ram+1 + +; Copies C*$100 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 a,(hl+) + ld (de),a + inc e + jr nz,- + inc d + dec c + jr nz,- + + ld a,b + jp $C000 + + +.ifndef RST_OFFSET + .define RST_OFFSET 0 +.endif + +.ifndef CUSTOM_RESET + reset: + di + + ; Run code from $C000, as is done on devcart. This + ; ensures minimal difference in how it behaves. + ld hl,$4000 + ld c,$14 + jp copy_to_wram_then_run + + .bank 1 slot 1 + .org 0 + jp std_reset +.endif + +; returnOrg puts this code AFTER user code. +.section "runtime" returnOrg + + ; Catch user code running off end + jp internal_error + +; 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 + + ; Clear memory except very top of stack + ld bc,std_stack-bss_base - 2 + ld hl,bss_base + call clear_mem + ld bc,$FFFF-dp_base + ld hl,dp_base + call clear_mem + + ; Init hardware + wreg TAC,$00 + wreg IF,$00 + wreg IE,$00 + + wreg NR52,0 ; sound off + wreg NR52,$80 ; sound on + wreg NR51,$FF ; mono + wreg NR50,$77 ; volume + + call init_runtime + call init_text_out + call console_init + call init_testing + + .ifdef TEST_NAME + print_str TEST_NAME,newline,newline + .endif + + 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 + + call console_show + pop af + call play_byte + jp post_exit + ++ push af + call print_newline + 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 + + +; Clears BC bytes starting at HL +clear_mem: + ; If C>0, increment B + dec bc + inc c + inc b + + ld a,0 +- ld (hl+),a + dec c + jr nz,- + dec b + jr nz,- + ret + + +; Reports internal error and exits with code 255 +internal_error: + print_str "Internal error" + ld a,255 + jp exit + + +; build_devcart and build_multi customize this +.ifndef CUSTOM_PRINT + .define text_out_addr bss+0 + .redefine bss bss+2 + + ; Initializes text output to cartridge RAM + init_text_out: + ; Enable cartridge RAM and set text output pointer + setb RAMEN,$0A + setw text_out_addr,text_out_base + setb text_out_base-3,$DE + setb text_out_base-2,$B0 + setb text_out_base-1,$61 + setb text_out_base,0 + setb final_result,$80 + ret + + + ; Appends character to text output string + ; Preserved: AF, BC, DE, HL + write_text_out: + push hl + push af + ld a,(text_out_addr) + ld l,a + ld a,(text_out_addr+1) + ld h,a + inc hl + ld (hl),0 + ld a,l + ld (text_out_addr),a + ld a,h + ld (text_out_addr+1),a + dec hl + pop af + ld (hl),a + pop hl + ret + + print_char_nocrc: + call write_text_out + jp console_print +.endif + + +; only build_rom uses console +.ifdef NEED_CONSOLE + .include "console.s" +.else + console_init: + console_print: + console_flush: + console_normal: + console_inverse: + console_show: + console_set_mode: + ret +.endif + + +; build_devcart and build_multi need to customize this +.ifndef CUSTOM_EXIT + post_exit: + ld (final_result),a + forever: + wreg NR52,0 ; sound off +- jr - +.endif + + +.macro def_rst ARGS addr + .bank 0 slot 0 + .org addr+RST_OFFSET +.endm diff --git a/playing-coffee - Copy/roms/dmg_sound/source/common/testing.s b/playing-coffee - Copy/roms/dmg_sound/source/common/testing.s new file mode 100644 index 0000000..5dd63ce --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/common/testing.s @@ -0,0 +1,195 @@ +; Diagnostic and testing utilities + +.define test_code bss+0 +.define test_name bss+1 +.redefine bss bss+3 + + +; Sets test code and optional error text. +; Takes multiple strings due to wla's idiotic +; default limit of 63 chars per string. +; Preserved: AF, BC, DE, HL +.macro set_test ; code[,text[,text2[,text3]]] + push hl + call set_test_ + jr @set_test\@ + .byte \1 + .if NARGS > 1 + .byte \2 + .endif + .if NARGS > 2 + .byte \3 + .endif + .if NARGS > 3 + .byte \4 + .endif + .byte 0 +@set_test\@: + pop hl +.endm + +set_test_: + pop hl + push hl + push af + inc hl + inc hl + ldi a,(hl) + ld (test_code),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,(test_code) + 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,(test_code) + cp $FF ; if a = $FF then a = 1 + jr nz,+ + ld a,1 ++ 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 print_newline + call print_crc + .else + ld bc,(crc >> 16) ~ $FFFF + ld de,(crc & $FFFF) ~ $FFFF + call check_crc_ + .endif +.endm + +; Checks CRC, differing based on DMG or CGB build +.macro check_crc_dmg_cgb ARGS dmg, cgb + .ifdef REQUIRE_DMG + check_crc dmg + .else + .ifdef REQUIRE_CGB + check_crc cgb + .else + .printt "CGB or DMG must be specified" + .fail + .endif + .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 diff --git a/playing-coffee - Copy/roms/dmg_sound/source/linkfile b/playing-coffee - Copy/roms/dmg_sound/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/playing-coffee - Copy/roms/dmg_sound/source/readme.txt b/playing-coffee - Copy/roms/dmg_sound/source/readme.txt new file mode 100644 index 0000000..a0269c1 --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/readme.txt @@ -0,0 +1,82 @@ +Game Boy Tests Source Code +-------------------------- + +Building with wla-dx +-------------------- +To assemble a test ROM with wla-dx, use the following commands: + + wla -o source_filename_here.s test.o + wlalink linkfile test.gb + +To assemble as a GBS music file: + + wla -o source_filename_here.s test.o -DBUILD_GBS + wlalink linkfile test.gbs + +Note that some tests might only work when built as a ROM or GBS file, +but not both. + +Some tests might include a ROM/GBS that has all the tests combined. +Building such a multi-test is complex and the necessary files aren't +included. + + +Framework +--------- +Each test is in a single source file, and makes use of several library +source files from common/. This framework provides common services and +reduces code to only that which performs the actual test. Virtually all +tests include "shell.inc" at the beginning, which sets things up and +includes all the appropriate library files. + +The reset handler does minimal GB hardware initialization, clears RAM, +sets up the text console, then runs main. Main can exit by returning or +jumping to "exit" with an error code in A. Exit reports the code then +goes into an infinite loop. If the code is 0, it doesn't do anything, +otherwise it reports the code. Code 1 is reported as "Failed", and the +rest as "Error ". + +The default is to build a ROM. Defining BUILD_GBS will build as an GBS. +The other build types aren't supported due to their complexity. I load +the code into RAM at $C000 since my devcart requires it, and I don't +want the normal ROM to differ in any way from what I've tested. This +also allows easy self-modifying code. + +Several routines are available to print values and text to the console. +Most update a running CRC-32 checksum which can be checked with +check_crc, allowing ALL the output to be checked very easily. If the +checksum doesn't match, it is printed, so you can run the code on a GB +and paste the correct checksum into your code. + + +Macros +------ +Some macros are used to make common operations more convenient. The left +is equivalent to the right: + + Macro Equivalent + ------------------------------------- + lda addr ldh a,(addr-$FF00) + + sta addr ldh (addr-$FF00),a + + wreg addr,data ld a,data + ldh (addr-$FF00),a + + setb ld a,data + ld (addr),a + + setw setb addr+0,data + + for_loop routine,begin,end,step + calls routine with A set to successive values + + loop_n_times routine,count + calls routine with A from 0 to count-1 + + print_str "str" prints string + + +-- +Shay Green diff --git a/playing-coffee - Copy/roms/dmg_sound/source/shell.inc b/playing-coffee - Copy/roms/dmg_sound/source/shell.inc new file mode 100644 index 0000000..0d43faa --- /dev/null +++ b/playing-coffee - Copy/roms/dmg_sound/source/shell.inc @@ -0,0 +1,27 @@ +; Included at beginning of all programs +; that use standard shell + +; Get include files from common/ +.incdir "common" + +; Sub-test in a multi-test ROM +.ifdef BUILD_MULTI + .include "build_multi.s" +.else + +; GBS music file +.ifdef BUILD_GBS + .include "build_gbs.s" +.endif + +; Devcart +.ifdef BUILD_DEVCART + .include "build_devcart.s" +.endif + +; GB ROM (default) +.ifndef RUNTIME_INCLUDED + .include "build_rom.s" +.endif + +.endif ; .ifdef BUILD_MULTI diff --git a/playing-coffee - Copy/roms/drmario.gb b/playing-coffee - Copy/roms/drmario.gb new file mode 100644 index 0000000000000000000000000000000000000000..2f3dc699a35dea148246a5e0722ffdd9b529b51c GIT binary patch literal 32768 zcmd443wRVo)<4`mGwGS!<`R<72_fko0*u)JLkwZWFc~nChzMC0g>_ZJMPLynfSPcd z3>Os@1zlW0K_Q?~yaX?+thzAVX9z9h4N=kQAPMZK(1<_`q5J=F?dPaN02D`?TKOj!xoyRog&sO6ESiRo0vUA=Dm zqUql;81eVKOsmm7OF7NIQ}fU7lW6hhvbM8(>m7Zh6ZIV5_L9F|`k70XWsY^z;<3m} ze%2D%;*W5l1`^iShzOWM#o{5UQFc0VrNbgE{E3!+mZgKI=mt^e_K7;_t$Ig-%GKZ& z#lySKekJ{5y!7rw#?YAA*dvbnb>WfaEwa(L-z2^*z1x!0y?b{A--%UAEo0XQwWosq z_@`;p4kc5G`gzncDv3&_OtzdAk5cAihwf|n_S>)4efZ(@DfgLwJoN3cbuWJS&uiVK zWBcDSZ0t>ARKayVX!_NEj@WaGrp=**dM;TyjG7wUG)p(=f^8#0BF$b9{@hC%)}rUy zMrfXQV{qZaeeDaL>}z*Z(ML6eBYGPLw^#!1_yYI3f`aPc`F&qBlbR>0HII|d)N}Dt zldN~>h4&PpUw`1Zc-v+_KWeS8us5M(R7wiG9%}joQflCpu-VE`1yFJ__p3YkoM;i1 z264D}gSbT;CeorVLWLr92txKqrSclC&8-(b?k2HJEO$4HH}QQGx39ph6cknSjN;yD zcNY!cUBI;YmhCLOAGQ{o}WRb+M2&tmA3P-qacdhw5jz4eZ` zhH63Q6mB(;1~-w2VW65UQF^VOV}z-C;e=lLF0v|I??{qq+&V{+`zV{>-pMlV0IPF{ zLt7-9bOe5FGvFFRBuf{Jx)Hc7x@5pH6 z8&k+Vzn+UM5Wk9~hZ@kow)lep^6W92Da3!Pcv0KFsC9Z~y}!*Is^<*SC#;?{%)^<* zDM3KpvKlPXk%)9uJRBJTMn|iQQ;1O9i4L9H6cNM0wjm+l(MSE!E=syB5j001CK3ut zO=q4qd~H~jxPQRA=78lo+b^lB(@rHWymm$0e5TxVB>sHL_HG5KmpD58`M3uDhve5% z?oXdua4LOsYJd|e?1CrTR&a+9rRD{^*kF&e}Pp7Iw}cxf8_Q5cD3mZqJ|M z4!bMCm{tcCsMQ8FLr+de0ouTDA!rCnOUk6T8mqX!G}dr`YOLS}HcklCLlQkyvP8F2 zO-||z4Za0#jqX&jsX=@h@SlTjCwuxd;ALu|YU7HNW9;@I+E;n7e>9hUtidlx<%^inS953YVy?e+trzwo#*C^gmxqbEmQmU|RNU%!o5ZaQC;Uk(6;$0v3YDQFWz@({XP#_#4v^V^oDtynDpkT9 zqqr|9E;+j=U_?%7&hCI#Yf!gGTvA35ZXbJIwjY&yhmLUKn}Q=g$nQ~xY!4LX@2C4# z%TN{_85;_sQ~_x>3gxX2lr;c_0WVnAA~g=S=jZ3wv{=D`XA*_siTswe?nl*-Ds&UN?-F;0co+f{eT`MSACKqt>)d~fa)@8&ekRJnu+F`P zi6b0J*SXiJAq>(@{J+C){f+>dZBTgCgu1J2lZY-4zQaJ7L*s?y_?R+nsxniEe%@hJ zO%nAX{9FAEjVJnRtmNVvXK?oi1v*}BM5zgXr8-zclr=OS zd^uNwsuC3}r-eE+hej9Fl*f9Yy9@jq${?>y`|&!{l=Q}ajx@3?DDn-Z&@0FJ-8!J& z;4}OJ+9}cFn> z-4!SI{`y9vv}->GQ&oekHL4&JQIK#xAu4FdCnjjfCnjjfCn{*j=f4s3ZcB%xrCt0r zep>It8!?`atAO})3|C__P}TcQ8L*C|U9!&amZ~$VgG{k(aG01oU~B1^ni@v8BS1Rr zep4KGUhAUggawKAIIi_l@SKgK5UI}asm~?kc_}EpjBzVoe3}1)pNaFH_L1aq+=bwI z%>+CfWy3Wb9g=S}z>pY+ohux(&kWWYz|N%`{Xv&cRw5>7FRwEO}aduom%dU6v?Zm(5eV zZOgbYZ{2Nso+H+h-u5^b25(0^U*WeMPZHMjC1BD%9C2=6#JXE}$fo@vZIMst&IzCP zZV=asm+}66S$cz^R6)h7f=ZS4$h^yNg!7fvh_4IVlleL8NWl}NJ!L}gWV|ay@@gkJ z+@$u3G@;3+{V_hC+JhiO`m3}XyCO? zpw()isFVb2Qfew?GNq&>F{XqRz)VUF8K=iUopZ=YtpnV zSRG?(&V-?8ws%7tY5D>!D&rIO$EM(_jD+kg^Qz1I>oDYc1q!9tC-Mi9F(vbg>%`}R zeS7!fb9(m3=$@9^EyZTFn3Izd6XN4dCZmyI;W?wy?{w>z#WlYV+-qqw9-{3#E}MUfx&E{^=Tm)N39B%gR%`bqS`&ijcy z(*UD(x45TkzZQQIPa^^7#cil?Bsl~YYN4dlNG81s!x8=ORd*a@f{;d|N=p-#+INNB zc2+M4c438Gm}M7^5}WW0!C(vs6$*D)c{(I4u|iTehlJi%)nZHmLr9Ww!cKBq*|R+7 zw+*mjNC;Lr6l;wm%Ua{?X`SJ;TC1FyR-co#&T!eRRjwY^8dtW}=gP3wcyg?lVd{(- z##i}mCMpB%zL`H@9YzA5Fs8f|wC}cLP9*jah+21c5QTq-2nHn-C;Y?Oe*qprhJmWU z=~9p}@ncn;M&m2zJWj2g|2XG^?}L9o{QKdrd<5`SfW!B}zaRen@Y7SWvMwj3nGY31^XYb1ypvf{{<`PnRUQX_jz+L`5fkUDl0+oJ2 zH;yCHXN315`fkD&Rr;!&@9ajj&>?FwqEhN)#+l#NZFhA``xr7b#t}7j>$6ANHv6t# zeg-E80-j5Em#ehAn-Oy%2<<&GnVm)^m3BwtBWdN|z`50$!PO48y#5 zo9uHOlh)Ncj6=nBLLL{C*2=m7$qOb3Y*2c)SKPSYR1K6cDF;1oBS0jEQGabP3gvX{ zyNLTq4HGcmJuLpx5HW`c3F*b>YPI>Vj_HmGv0E3B!e@gG(FXp#=n-~Wh2|9L7nrVO zTCJB`{roSA6x!jic!}86F5$M2v4X*aU+QNV((|LO2A$On zUC2L}=!~%vP!2jxA$pCbaINpCI<03?)ET@9#u$5^??u`slh`Puv9<;IApzTi-Gr6N z!FF6SuJ$vhHUIP(%?JE4Kf`Z(r(0MXv2sG*({#1NAbi~?$lvZ4jILl+;Yv!8{PkQK z8SQ$e^20-z=rVKdRByjQRYm%^akGex8ap%XY<_g z$Qw0}u>_&`E9N~>*1@*1p?XJ{F|tRH1LLsDHr(kBD}i%nb{}J6KVrJE+ZmR9m&s(e zNqf{3Z!kUBEz+-8s}Me6VVE{f+&wEz_%>4*n;|U7kbWf#I4D4G*%07=RSJVPR~Aq2 z!$9TM_0P38RQDh3PP4mNyPKw$kOrOWO-vpmStL&k^d)I zMyuJ}QO%APljux>M6YgBYEv44n%=Co={Uzkw9=`v@!;z2vgzP^-R1a$f$nm`!Qrf& zc<^3UPCEE3dv#+&CDqDzc$stFI(;$1CIL1YHS zZ@sm%hi$x`^swtQNDouh)8FN`+1)ACK|Y=&EOv0vQQr8wx-FE~I1=U09f<{V@#5PM zbo<%!ZYq_P)MOa^5-?=4zgo*f6kGlYnK$@tHxeNP*NpO0YsT%O*4(k1x~!>Tt)HGk zwgKXcb+!joqGFxw;&crtb#S$7b&PJkN+)xi(Y8hI4(PjSnor>W(wf8$h!1uBX?wSlrq8!1+L zfnr-<)KcA|qO8(E%G!F6W2K!y?L;cHlNlF z19jPPbUzaBN8$ig4u1mfN`z;?ufg4x;Kw!=V`)pk+egRfrQ;j@nkmWXy|Uk78l*O% zUxS5(g`}Ma`Am%T&pPx)&jyPg3i9cGX1!N_4oRP@3i5HJx#&ncODB!eD?jbPFk2Wb z%or5pnGR%%hTQ02iZ*5xy$~!?GKw|@`M(eXvTDMcHG)Qmp%AGDk<^nqQr}LH^lLr5 zGD^(q>9>0%$d?1v8G80&{rhjevc2K*KR^7}2OsSq%?uNoY>ijQVRVP8Gm0^Pfr*2r zt=h|^+w6Cw$_KFT5WtHv2#z%AGpB-uZ0q@EtjHEAO=pAWo84w&xiNnB44^tcdkUW2 z@7_k*RdKaSLgH_excgNCP?RLLyR}C`&+N2>o+)hitlkQ_a#DI^XDMms9CMl)Cy%TQ zvpKmVhQMVlVoT`?RKr?ADy|EndrFd7Q7v=;|EH8R^}kN}zs^(lU+2jrWLD>Ak%f%! zi^cDQFr>XOr2Fe(AY+lO(Zi)QmODmyTdtI`NI?`cVJFE>vZ~EQR#sX;*v9UoxR#SarkcGPTozytV(J#7G%!pgPnqjW4 zT&dPM5)dBJ#;Aq9Ra;e&N4>t`z2IP!AY*g)QCL4Nl-TUEB% zz$7$hRyQx&@0^}l-IR}c-r49nMlyFI_|Sl*4=)=qz_%2EL;|q-+LqrIXRSkcX zqC)t0Han2)`CW(U!D>H=4sata2(d$IdnFl5ECJ!RoNm3?^WYv(mqZRDLBPFQJS>dK zC-Z4CwOH*)mqka4nni>78c94lN4St3RB8CrYt3mBv z$rxRp_;#N7PV+u&95;Wmt7!pVl{pJrBuvHZvWH7uDBlPZVFY9X*f!4K-EXZv7u{M; zxkQ-mU}!KBrseP{{)H{A4xKjzkW>|t9)oBrdYGZ<#I+D?YZz;};wsD)o^<=gCx8`J z_aY_xT@V|k!ukxT7|#797Ok||`K9x1<6z&%k;$y30sC^;#ulVnZ9^KZj@UA%zM?oM z*+f^U>t||yYDe5k;JorYO&PHBoLKV~%nB?WXaOvd+9)5bbcZ1wMCV$|q%@mTGwt1y zxwdGFi{pL*Vm-q)o8y0ju$>Awh~KphM+sTCAq8`}JoLOcjMALm&7?pO!$Yx|6eja9 z*#Ac6flGMOTpVOa=#;|@n^`vb)xD`xSOtTTA-s%UiM5ouJlV~@E*n!XGV5}uk=YSu zUC~KlLgNmM#BD*0;}$;#S2FCEs0&h#WURvkJBEGwHJ<%h!E!`#{{dvxLG;IFV5x0X zNY;P)3ExI*J`>L-q8bhM>V23~?81Bu`4fu>Tb~dWzx9^rn)1QhVH8Pk6-P?3bnLgC zjKc=bpBQZ5V6mOl0j*yQk=>-&UJ!PZVtYZ@O!!x|kpZ%r!s+>f zfUQsiIReZCw=6t@S+OU;c%s3I0OQnxWR{)}MO8Yw@pA*VERu?!AF%OS@;L#ULj^4e z*am2zzs9IQu2EqQOi58UpvBTSxD1NyfW$Dw2X@;S$@l6eamjqqhh1SRD7}5eN|t#W zOKcQ1k+e<@`fiurIb!MrXDkx`u~>W;rlvT;?HI~k4lWY^zF2$~8!A1q%+g)@`*i8q z*)}VcA*~uJt_rYXgZK>lZ}Az%XnR=E7SW_f`FnDW(bGAZv%6(Z@0Ka9#{Cpd>-3UV zifg>G;y3{LnWdGdIdM%dFKg=7jXfdDj#lwe>5;O`G1B9W)f^+O_C3lb)SQ=Amz`a` z?mLtCH)%y#x6Ez?Jsaq!qUfih=%<^dC#FkJ&X%63cc6dbK*rzf($jY7QMb8da^Yq<@%@8rIp`(f_R+&#GmavO6G=YE#^W$rh* z;oR?YPv-uRdnWf>?#0|oxe7(~zDULOR;UE{7Wk>~GvH^#?*-ohzaRVp_=DjW!yg8J z1pJ%fSHQm&{_XH5!k-HNuki1KKd-lMwNl-C<2L1r&{G8V7GO&Vc9V*In!w%$Y$<`& z5!ez!w@Ibz3=z6bDjk8<5$sX|t5ado5XLkU`IE%@KxWSti^FI)>v2xVkZ;Diu$Wm! z&i-%?pxqRwbIW$@IdHrdjd#=kbW=D*AlrK;bDx7ecpd|@c$WPt>YwD_UTeFiFN|HX zJdK3G^1%zfD;j2l8hU$PiHzAsM&7@u1~ z?`jY?i0j1{#r@&|F(3xTgW|{HC*r5#XX5AL7vd4|OYx}q71sXo?)pYNCVne^CpL>M z;z{w8ct(`ObK)=JdGUgHQEU}|72Cv1Vmq%3H84}a+O4-Y*zM}%*dEjj2rM&i>_es> zWT8H&h6dfQScl$kunty-SZ`czvR;1T9?ts2 zJl^`uJx=R0^9EWk^Ar5GA4xS)`^ze?Cl-3FPb?c|eP&^W^_gXVv|fI|<*`2C8D?GV zs<1Bh06>=B*uJC$Pdr{}ed2GEtj|1NWqsyvbF7!sST5b1Y)g@~1?xp*z$P8KnXl;^ zUDY;Ry{f&UlW6(x5LM2|rz)#(*Wg}(do}L0`4q8E$k{uxVn?>iwZmN4d&WZR!8`|S zk0Ae}`N%uN>z}lD@;5EZ$m=hsBR^NFl!(CB*8u^r>*Vuk<+{><+bQ377k# zMd2BgP0j?_;53^@2|XM^^Nb_dA%#Edh=KU%)Ja3xU*}}ZhgFdg6#6@Y()u&Q9PxQK z{NbxV)aGBxXZqR6(KE-jw#UMJVjvHe43EKv%j(~agwsga+P?=0$CI$3A%!(&Vzbdr zr(oApICf2ctI+7^&yo;FLJksgl8}ppJS0?3LKP%5frKhaXa)&Ykx&gn=mG14`HOL6 zD=tI^yI)!?Wo9Z#g_s%o5t}Drcn{UVGWI6ir?^`D1-97M{vG_w}c9t z+|qk?Vfi&$g75hwYJxFHa1jYUYdnmnpfWqaf2rcifI9=|`;7d=s z4+|dri{xlkUW0f_I@;3iNWvMZdWRhn{g&)(9mSgadj1?Ka{^h`+Ljz_z(fWrGMliI z$}#g{`mtY{6LHGm7jrTzWh|CwsHME1l@f8EQ`4GSN>OOY)3kPkXzhl@Podw<7Ix)z zYR(MJl|Xajp*ghG|3q~}ZPn*9pg8oIVWG2HqVQGfJFKct!|l0BeebC1dqW*9%bSIn ze`pdG{DFz%hhaV5p>L=*)S#lO?WafO^ID`s_y2*+68d@fhwLPdSG*N-j7)i>!y#{T zxZ=1-kXZ7o9p^#gv9qU}`}OlSsf6DH;};TU@sW59w$bq+ zU{*W(E=CD2rcj^MbLm7oW-@4GOP?`%{-CnMVRKpH{;|_Hiv={no;?@JMiu0Xf!y)SCK8%$F@Px{$sC`UY=I9C5fV;{ zPfjo=TDpp90~E8j-vH>BETCW8mK>@-_-TK#6oq-nr~PfMO6Gp6T#4n}lrap&Z@oD_ z{YlyBGVHguluQ(I2e6X~Eq4ch8|fkzh$|oB>W@LfHh>*b$S=6f7Fq#1{;}I^q8fPa zHk(8Og500&T}a4s3i+A0N68l49wmG1_6{=j#IJg)HiiFG@v3L+__x*(E2=u!c5|pc zAS@dotR5h28W7Z0ib;ka+-kFue1arIsaazcSpzXv?ZqDSo*6vAHDL6BSp$M?Nnpem zVd2<2v29Q&C8Z&fMCNv(yt2G8>49E%?Q3F8nRYDxw4t33@(%T5RZd-JZi^4i?z7LE zf|W@OJmjG2ru8@$Bkda_yov0a)@#Q~n%8T`N_c*Q^l6#&xr(hJrXi^bS%{mjM&^@h zfe|0V0Rb}6zM0H=IFq!yOxnBMF;m))x77}AHW>tBzzUp_Casq2sFDK(T+;8HBqPVk zY}8%+4|^Bb=b^)F6WW|AGxY@Ir`TQnqF6kF6B}Q$+i-{wU4Z1IA=%U9j4)0Qk+Yr2 zT2aU$XB_Jwg7fn|21>ihlw|}zWMIZ<$uPcg@WzP?HZ7azuKgR}77Nd-X?)*VewR>McEV^m+R3hmc|$R`0~X*}MgCcH~=*7QiQ@#A3Q z{ve7s-e2ezQ<`B86++F0`Pk-(nm+_P2G}NGk0^yOOulX`L;N`6Um_kN$LH0fM_rC` ztCI)PQV(7(z}X@(cD5+gc^WCiwE*M)eT=HQ!+H2ZWWu%DQKeW)EN>&(uHQ-a3D-h2 zA3bNZ=fZ%)IPl+bb}5z~>pFg}nVen<07<@wz`>@jNXIEVGR4l5`;*^Iy^CvMpKXQ^ zZ4hEA1ggUc1c+_V{A-DPhzGGW1=WGd*yS9Pwrj5)VQS;CWnts(WMiNLRuuYoF?uVG zqD&DY*kYnFs_O7!Om^f8eF|}6D(H^KX(k*>bSL0W zs%B4NQ<@xg%5a-;TdFe}s@cyQbdhc$6=(oZhdYiSa0UzS$EIL2Us2fBEi`67A!Dfc z=3TsPH#X}M?81$x$sl#n+D~?%`cLC-&z{izkC_$eWtk(omt>V>4&n+qXG0_{6v+xj zGD7?}O5+YpJaPP>dnnQ~6vW3RaC#igq7M!z!owf6{AO7>*2P1qvs z$9cEn>Wux+J$8(|`=dDU#_}haV?!!K-8x2<9edgD;0aZOF`8m`zb~?u$u<@&xHcRB zu0}b8hX)gqCkKAldY_t_US$uT=Vz(vf<{4Mg+N%zKi| zGqucb_d8PYg-x-&@pQio?@-l?%q9&hgQp6rw5oh`jzEia4#UkiS%O1NR*L2CgUCn-m znmtI6J#uuHcM-pOuqMb@wJj7<9?}cR(Kfu!{5UrR&%u zOa(uA9ebE5=ZCLjv82n}*Ri`87q45#e!w{S<7?S>7ze+5E&Dpd@pWt2ml&2`x|V%` zvGSAGvd`iq;PAEVYP_>u*NqudJhY_bw(;6Ep~N|G;E>UyuOB$D;Lmba65GQp-(GTU zY^SjB&*UT)uCB1Z+@W1}cA+aNA2n*!O(oMxy28|yCG!6W8+pqux7>UcT}j0s|MJE96YdHv{*CF7+ktU?%RX%lB6_ zg|6)YX(yCaPMX|RR!Dn$N!6UWzk$t@@9zpjySlSv%GA5N!b))Rv?Pg5ze3J2p+~XlmhaY?FiN_va`tT!~OgzSxy%jq05rv8ybhqM@ZilQW$OtrN)4ZCC99ph>}$};7V+S=CE_F}mJ870Um z^2hBf38m4Po$0o_-FiJf2h5*Xv1(ObURhab=`X*OmbS7drY^;0!*%v-Nl9+5-JX*Z zAK$ZQuaaT4wY4RDj!l+Lur&6z>(Cwvzs$^%-X(lVudLMH=5H{NTsp6k{2c45RWoOj z{O-TMw3MRgD=tL|4m&pP*ypkG?|l8m)nmqVrPp}r^%hHZcCTJB&VIks$xbLNEGd~k z<6Pajw-ulAO6?uBW7^x@{a<|a#dQj;^p@w!$8|bOa*i!K+hEY?Xngcu!cCme(C~*p z+<4;+H+=i;uwm_nwjoVTb#=prEm<;uzFzO~v~F)-UQ@$yem}`;*f5^Y$uXI-vU-(x zAP=3+U}$Y!v10r7z3odOkFNPE$7WBwGXIt~z20abPOm4qLu+B_zGHt{wX$#D8*kjb z`_4O!mn;unfAH+Uy?cibU$$(>5WipK;3_w6^mu$el7gaKF0c3G#U-0`!zqszZ*Twj zJ6(crIb|p=_IjUuve)2YlL(f^Ub@S*Xi*GHy{^S$sp*+DHSO)kkFPjtNFu2To?Mxk zPG^38PEI&XVxqWhwTYcNtNoW>T1nh&&P?H)qh{4koLEcZ6t#VAQr6U2l62t$iMieL z=G}d_-#={R#0C;?ZC$^9)F_5AYzajuie+!V-Rqq;jhg2566}?i4wohw4bGJ~`)74K ze2B<6xBU0ZFQSP-?>o^&kC5t=m)Y z$j&A_NDs7_;F1~UTsN_D#fs(2^?IG|I9jLAS6OMbI-ND;tt+I}m6Zlu?d@Ir8^;wC zl$0Dh777&<%%4wD2E(XP?d{iJpO`pm)C(_AROpLvxT=cdZn|mOwAxz23sPuxxvsx{ z;X=YGDam5V$;rw(cI>8w0&wVJSbNm>SZnoL$J$B~fPOr9J!cyMv?r=R}%YpvQo42BYH+1Eg%ufv)RZuB@!tv%mkI#C1!VhYg0L zB$oZ<7m~hj-@${!;WKAu&o&wf{sgY}_SV)*mrkB!7y=>nwb^){NJ5KqTudGmRXNQw z!sFq&DV2YoFnhKrG7M?a%MJUP=Q1x`Fd94RJq=PQM(twtW)UOtEXcyCRKj`TC{+ri z?os;5lOGuGa#*e1yMO!b_nS*gM~rwgLdM-wr#}4fz&^X3l&F(t=ui^xr1|Kh1N%Oc{~#*a-ub4v1Fx^cbHDs)@Cr85+%vySX8=b(G55F zeA~B|l{uxHaQ^}~%0tsVdmcZYWNE!b@B_|RLsn~2Qcia*{Vx|yi6)cN*?LM#KW5B? z2~(%$=DJ)gOIko%8^groVi=Yst^GIpBf9CLpDkNH`e@7;r(`*-@qgx-4I6&`ImsfO z)A*~kCN)Y@-EZ{!8~&Z`gM_Hs+8@67;5#A1orMa9LVN1M&X z#Z^_>`|!&8QMw`W@PyV=%fsl?;$CIRlC?gL;!ru_ zNPaJgufS<>2ng*)y;S@z#qLhcN`vdf@9cv2E6x%VMjM@8!38ucp;fuO|1!VS|;SlvZMrDi8aTXLte%vOQIzyNy|@r`jk6r zJ${m!xK!1pWT#}4cZ$ZxxwfRH$r1x~_U#&l>v*uEy zTrpQ9GEd9OU{&JgfJMvAPR*6{Dd~6JHP?-(^{IG=Snl`0abk`d=SxTnNXh0){Azp{?NK?R;j3{{8|~8K z54GO}y_eT4kMh^)qUb37QvDKD-a37|mR{91X>VG2H=?B_Y#*|oLj85F`~`oaKtBlOy#FL_MJ8A)cCv= zuO0p7l}jt&lvV|YNAXTurWi4dNduEyAs;dg;<}TaVYL@^m#12PEv5SG5>!z!elZ{T;tm^u*o&I|tXT%M3^?J+Z_z`;Oqfuy2xJdk`y~#0h_LchZxvTg@egC)^2C z<3BCRFP8r_)!HNWqlPDx#A$f1S{}^;CU_DjB_dA}C-tvX?!(iq#z!b4KtrA*qWGB2 zr13YZ_L=r1>Gvp8;SMMUI1(p(HK(a;1bBOu+dBA-Ku!-TccFYS{zNvN^3i0~S-!o> ziKzQtmi*f|+ zW4Qe)y8X6BI*so_Yn+h|+VzjV1oU2S6Ew10SZVzQq$64fyix^@S|6!+MD%qQKB55vD z@bJ_7uW|>T)?5_6ZqNE%qbtiQxmH#&UZkX}xJd<%%r&-H_F7CXr{u>WZ`z`>7|d`N zbm!=^l!UpFVyW4iHPzBJuYyOuXADUuMUrlsLfojP1C^Z9st~I$Ns+R9x%x<>CJ~Hp zmFF-w8?LHSU#q~}myqdJ>Q>}S7$UB_ z2wvmC6N`N+9_1l6er!M^rE4|H^p&#$g3nP8$!m2Xc$i*rYXPV}p+z|spx(x5$!AD36HK$t54^%lTr(HVzf{+EsgbI z&2@6KDK?^{sIwy087H(_JeDpdF-3*PQlaW<+^Ud~lQ=C-QmJvR{q9NH<2-3qo#~|J zw3t1FBx>UoOt8$_xqt}1l&l&GUt!`^o! zJ<8^rGX18ixK=7PfN>3b1cmM6T3u*?)JUo~^#S!0Rih6XIm_z!u~a|mYw9H`Lo`?E zTXm_P>pTl#&9wV2SJm#!NyY?RzeD$e@%`_T)u5%CTCK z^m>_THEl0%HA&X?tP@k$uAMh({)k#nCmto#*UrqILgkRsVu-Z17c1%lg9EQya*awl zdQ_6w)|IiTlnP~-oDA<|vY*r5uGD^CBIUHMSTr>&OGT;=t)8(s+FdscTYI&*9se9&TX7kEc2f2-YFa>=Z03==$=(Jo!qeA1< z^0!j$Yb++Rml1=Zs|S^M z@gYh(+|TF8da6^oq}0kc?5Wz;en?*G^?Lp7zE-P5X}d4jui;AuVj7Wz2_i0IoFzUd zr6jk+WyD$?xd^_*QIcQMC#PFZhQ$I)uUXj(+am`Sj#X(oUUH)3_`k%RVu@ILRC!Lh z<&tNo>Edv7Dg#ukQ_6SJ%GPR~!i6biITC#S!&Iffi(e(uZ%CrYdFy7x<~uU8bgwtdXU z_rI&vSHdEr=<~%GV7$Pdq$4NQVvb4q%K2aCe2%OIX|p-Z|I{@g5?AMca_4;Uw=+O( z{zvAFgdQsgqAx zpVt&|=s3#OtKzqs*jOJoxjK8fi6zd8OYP~TpGW(Bu4%ZHvA&8`ifPnZj6=9wrb0^H zXF#}^tDq|3>!>7CDuofDrOD`>F}cL@ zBtBrV&FkoK7_txjA78OEAXn()@)g&QKaqSlZ+YqE`d41v`r7MnyxGul@)Uu+^X@<1 zdw=^sKlt#Yf9=?L;bJR+?b*9;|A9d8VB;a-=O**`7bqQ1!7f0!FQ#vE!@s!0EN2TiGs5l+=z9Izf%6L3~) z9MbA=b3PMwJ*T(RNXH<((btU{4_ZBbK~Zmw11}?Z7%4sIjL1jtv*0_%c!I%g1IJu& z9F81m#N&{P+!jzLA+EO?DJQ3=47l@=axiD4x}h}b$SKd4KzUF?8*QR!;Kl-Gz)f?> z6s^|EL~^wnQ%u%03SXd5MW{<0FpMve$_6FF87R9>pJKp!w=c>z0C5H-8BZrs!&JNp zbu^-GC<)+c95zZpV?=2z;P;d-i5dug6iEkqBYX==NBl%pjz(V^RiV?R=yg>46`I6I z`MAA{@lOLX9oBU0sZPJiQqv0y1#7!AJ>7mKKN%fTvO@4aHLzBO+ zl&5?-B`}4V8lJL!+AFi(nYCk9aMtIu!m}c?&d;LmO}w|QDa$Vma&6p#}Faw5J( z3j_(s5FpREfXoDfpF58KHu|~tLn@O#IisvU%@lV3spWuCKIz#5kBy<~;S`^=_Q2y6 z)I2zX*l@rny>MXJhAW)1{Dmu=LdqudKTQ&LPIVjjQDVJN?T#*FsJ52y?6#BP z>EwgagN>8Nx1C1(42e%Pn`8_b=ARdc-R6raZCfXasS8DRzSzC((@A26+O(1#j4)>^ zq~}f`KZbX%(09_e|Jg?#sh&Q*Y%ph~R;*sT@s(}c{eci^b7&#q$z4=Q^5;)Xj`!B1 zjZNgwAT*wN*qpNPc+!I7Nj1lV{e4@~9$B+|YH4q5{8Q>rt-^9qS?T%yQ?6!p?UWd+ zVuZ#skFf9b@on*RawxBuP&uRO{um8#72C7Uf-J+pge0o>ThtUc^)F^(aXI}9$O9qqDLkZQWk=Vtk~4zh;GuVJ)W zo?!XIq}Ag4ps<+9??hN_##*~2DEu;@9EZc==k8(i_|NWPdpCoW5XudaU&4{|;m8G& z5^Z1Bng9~XO+wq$AadxA95Pg1!Sg-a=7dwS&Ss_bY`{-(reviwFO-|n3(n(Xy2}n* zP-W4?T25gXHMFrW zK17Qj-D|{OD;EX7xMy-<-VRRRuc7*r;6Aexr1SAJDy_Tlvq@raw?0x6ZixIFcT*VE zfjA;Bh9eE($j)%&a5&-(_-^XIgSJ);TP} z|K=hN{Xh)2iDB785PB4Th!a2BLger`elld;-RudW^uC%Y3zjtBGAXfdp^6H_lEHk><-5%{H)C}{4$OxTe($*?8>*lUv}W_?{^(Q zbLoR04bCM>;;ArxDiz1$(E@qjtf3C37cTZ<9(0!%S%7;{SnQn_Sqv@C4@VYa2m(T; z15mubACl%5<_~Ecaq#jqd*$aak_1F zm^?SC&u!|{PoDhhux(9Ppr-R1!?rDIU|ZPst{U)%ZF@<8wi@`S!$tJ`hi2o`i-yPx z;mDie$c}KZxk1dlXEKD+#O4XxrVIO~Pt?5enl~mte_sCF{5kz_HS<+rFZoX|2Z7sj zbjBBX6NilNw$Q$y;FF*h?#73~lhhcaV;%|XjEbcNdvwiP)up#2CpUG} zWPvVup^m*p#d`5AQLQ7H5F|Gx*pm_p*up0ELy{>zR(K-Zwu)5HSjZXk3-PhJy;?Xv z4R58YFf@{%SAX!*^g+81PtVUsxn=4gHACKT&^KLv;oyxkoH zfzMwza+&y)5}m8rwj`W6J1Y|}nWlbrt*qq?g#eMK;o$PWqHJ-q?;MAZF45aY7Ex_J z^qHjDeW=F84qYE5zVsFptlrTJ%RaQDC!RX|TBY9AO|Nfz67WPh2ft&Uqt_4RF7YY% z#G`R}Q=0ZX|JNeAiS6EO+aKn)sL+LSBiCx?_`P9va5KrAcI1JV-JIEm4pqy=H5_;Q zjwj(eko-J+S)u7UBmE4UW=?10%vLvk^&mfg2!2*QZ(@Gl_-2sdcjU57`T1!2-X?HJ z#+wZ9GYoy+3;yFx{e~3uvsHyN@mnGB#VKy>TUm;DO@uXKZ2%fymldeU1y?$>7!hbSY01#_9q#Ok0&iU&i(_dAoA&- zzCCv6(4h}M{P2UVxYl)EFnVxVv64#JusBbrSd}yX!9z>-Up_7Q)Ow1c@1cWqFI}~6 zKdkoK^v~v12){S=qL^ks%Ke_`e_k1{VXZ!>$D zBg{$WA`TU%8hK+s<8?UQFvj?2<7DGK#(Bnt#%0FGjH`{$8aEj0jP=IXjc*y>Gk##) zVccchYYZ40jUO97F@A15V*JYZjqzJ!SX@Xd-!`@ zd*k#+UlY#A$39s5*eeBAx>slX!EpuF>Rz|itXuow^yk8I@S*DJYFpR%aN9sziv8Nc z(pzsOhp(pD>;;cZyZhF0r8gAVZMJEXY}2OEI)ll|F-a_?$HN()l4~%jAP-`gX6fS+ zQXLhR>|zyNnUY&>veE{f-e}HpRi)*Pi%-!wczmq0G9iuD`3>MvzPd-ztR$9Zl}osP zpt+ykrz^kviN;DB&B{Ayk8+V_e>zEX@NrY4W;NY=@!sV7dfi9eJ}WZUFfVgn{yacU z^Hb+@^YiB;ShwKi1+OgFx&T3DVZy?sh316_-n4N1!YK>yCBY7rkD9f9@y5kxzouZ5}dY zNL>M*bp=DhsBIbC0l zP*+4<`8ayq)^TG;Zy8Bb8_wOsZ7HhHFWOA!7i}r1w-;=t?FAi>dQY8atEaSl^GFZ! zc%G?an9YgmV|_g$TN4u#H#5wZM5_PX>+ah+yU(02Xa8%?zvj%GyJoJUe1C7r+<)Ac zQT3-e?y4hmjsW@wpugT*F}L6B1y%RYsjoUS=ggd%xw`hB=h4u4qWnkRh(30{BHk=nYR%zKyh_Pp+M#=_uYs@Iy8%p?uF9HaQ%1 zj`hEjkfv2ekn4u)35&V(G@7oYA+S4&a7M;EcV;Q=o<>)UDlZ-3!CEn4S9e$CU6prC zm~e;sjOJ}MCncDWcUSy|O_XmF%hs`*GBfKkH)UnjWo^pNuFJlHNMl0NF{VWAn~?7e zdM?SAE+t2Gh)y=vO_*?3<;am0V^tr}SX`~$ru_W6{7wD)*Y)4zbk;flOX8Pk=xkFW zlSr@MOs7FKS}-#$Gpz%jM#tciJW3_cWL?Pk!Zq1 z<8>NxLme_nx*A}P0yH)w>Cwo=!m)}?JqQ)NGw@;|SyD{vGP6*xSO^~Jde8UBdMPx@2pw7(X?76vp9P3?0g`|1p zk@BRaF*I>$OeSQ(SdkHch@_=uT2-IU1ducsAgtDO92iVb{XGD50}6+uzmqW6_@uo; zBcQLdnh~f=px%*N@?lH(oLl8dWMtW5YW&apdqi6l6r2$SgPubevPI! zGik)tF==VE8m>!B%iKg~l3|V}9at^dSenKkxrx^i7=2azYCO8_ve)e!?HrfAG5htb zjagZluV-##+4S^{sj080Y)rA%vGr;m%j)Q{Bgc-cr_~w|pTsB7w~c;jTphh>?AW?a z?2sSIJGSt!c)8sGaq48)!yo09kM{eSE1SVV+zZy@*p#P*Y z?7cZR3dI`|Mg6(F>Z<^Tj~eo7)p97k{a)> zEj{MYKcE-&_hu#6PFibs=FOX#H*em&uaO;sT~#(po3v4?afKi33Q;ENJ>$O`kH^{l z#N&QkNqXbs{&-S}#}(rDBsPeP+0b-07vZ-Uz`>)XC`x3yo*aW&D>1wt5rb^qM_7c7 zgv?2$L0y`n=lSSpi7G4mFR)bt+h*q-YzJGN7>Y%7w}r;Hk1mojA#eoE9e4tl z$8nOHMv{jNJO&JB|K=`*2GZHQ6%=fCWg(cIAvt=2c_Z8U6~iVa&Q${)qv8HDx)Eee z_FW-zUYU~76J+!`G5Qs$^@a3V$8s!g?h>bnSgd1O<^XYyt!upNfUAq4yW*}87cShT zRm>v-1O@I79hxzRPl{4$N`pHv6DANyp+pozUhqFKyBzT#qrzr6_>Xp{!&Ep;M{je8 z2#q@OB(RQ|4Ty0f3lUuTV7a-hRcyc`9Y_ADFb!G44; zF1wr?c(*B7XdB{0_8lNsO1D?Ft_$Db z6&H(4@_{S7mAAOK$a?l*Nt|)x)V9dQ#;TuIzo~|s4>$Y$Yt`4PcdBo0eh2u&>Qwcs z>R*69uD)IUy86);{`#eQ|ID{nZ$XC+a4R&yCHRNECKag-n}<>=l5dkmuora0&Y(P| zX(E!6fmw%CDN==#`~$lVA-8jgb!dVm%N77_E)i(im-{wXsG`t|6a)5&k-UAX=fIBSFfi zbVkooaLFfJK4vITeVUra751pB8TC<$f(f4{RR)qiUD6mpf;B$DfSCWEW00?_ywgF* zQ8*~U?I`?!Q$#5dg%H{Rz>1Ec5TAr!l6JNv>4&XfwtnAgJ(Lc%Wt!W8^S<1p$sSEf ztsj9qOIgNgJyLr#E%vvqlCuMPJ$8EA2^xzU$lh<&9@qY?J*)Nb9*3Tx-S3aosG8<8+&N;IMAJ0bfY#QV6kzBulLu znCHUZ*=E6tY6q7F+ho|9hvn!-S`xFCSESY_7;Tvg`i#C{7R>A%ZkMK&H*`4Kyp=Qb zoNnYKHgw-9x+Mf23vsdS`0(16Z=z`9s~c0|uP8#dBt~|ewjEf7js+7I%xxT@-Od^s z@DpvTfIIZUe2~2&IASrs@ku7m3&&b*ET=yPAsZ lUnGAr;uyX`=;9uWUg!}zPW*Mji;csB8~EkYVcQ*a{R0Z~y?+1z literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/halt_bug.gb b/playing-coffee - Copy/roms/halt_bug.gb new file mode 100644 index 0000000000000000000000000000000000000000..38e36625d805e68cb3b8cbf562d6dff500060bdd GIT binary patch literal 32768 zcmeI)O>7&-6#(Ez{j3$4qD0qdCaz*HrC3o^8)pAp0^Ll7)Uja{snZ@>G>3ZCTB{@= zC`C?XDxw)mN{zI=G{~V*4K%t20h$JA>o^Juu9jrAkjIDt+t>hBLZYF85{^K|*u<6v zvTugA4{6T@jK0PGz1f+0GvChgNPz$I%^8WD|HQJ+b-~uw<1i0F=z@;$u9trep)2PS zSJ&1)SXf#b`$Zyj?cCaxh1ZwPeuIBOEQ4{2DjPku`wPz~Bmoj20TLhq5+DH*AOR8} z0TLhq5+DH*AOR8}0TLhq5+DH*AOR8}0TLhq61WEi4uv5*w5?aawY^LKJn2C8dIW|; z+npyP_uyn`$Rt1lBtQZrKmsH{0wh2JBtQZrKmsH{0wh2JBtQZrKmsH{0wh2JBtQc9 zjDYj*a&`Fr_p1l$ztcC|06nDIv;39faG)n364?N#bD()2 zmL)yv99Cy+fF(tBE~)eQ6XBn}E?!-B`j^G4w)nk$od=2y4#n;%NERF6Z*H3|b^}a> z+k)WpfLP7)1@ZSR9}{n7Yt?QsV~fAqI11O9bS}3?H+MzheBk{RaL=sx{3BWEQT;O& zG-R`j&61kyr^I{NlQMGu^L4lkc%zsp&KBp2^RA!2RF2Fy4(|QI-XHJ%(?G{+Z1sWF z9lN4EN0aNH9GYH#=g14|^FNtc*LUk54>bBF_dK^}qIAK=9QDqeW{dL<-g5o+wNqV7 z;;8*EpS6nb+Cyjbn>AOQvc;QfE=y`UzOd~(ZSc01_a%<)`bk#yr&-B=fvx#7>;pf= zF8L?f1wWs}YST9Rho67!`|MLcf8YsrO=z~i`fagl*RFQgJ7X<{ErqCa-tqG+=;yIu zYv|b7i@qJ|6uw=E;$U#p+P=0|qMNfvk{C|ArLy1OKUB$hCo8jFsxs%*+_Gq0W-SeT zmp8L6(#u8hkqsO4c!Fkx($auzJ`=f*ZWZm-Ff$_xXohP&2_{N+!mOe9hpIkae zV5`SL*`7g&hJ8`bJvOaH>%TKK%Ay_^wwY-hrTX-vt7TbY8*~HKRTHh ziR+`2ljD;p*FYHFSpnc}m3faZR$lV>hn2u0|8u2fk&jeb7dceg7I~%ui*DK9m`CK7 zys`fMa1!gMu=-bPCrYmPi{qTm_-;7q57}%`)YN4uv5I3^J{n)@QnMp*Gl#>)Gxm}%Z=CiD z9XJE7M_%)^?O$)#H~9k2t%P3{X2oh)9hV#=}%V6r_+V=Nz}Asu8v4aANv0<7iSwAhSMWa~XlDoWeEf|w|<$n7b1(_wvz50K0$5Y&G&Ypu#m!Lpu!4%G;`>#MDP?{`_JfFWlX-Ac`7Fa=fPG!AuVa6f zA%`xX%ONyfFyBtqY$puM*=sG3RB;k|V)4S=_W)4On$g>#p>gbA@#6eJN}F;%T;;{s zN@Ls`O(DB%gXLU96Xe4D3}To8-y`bdHi39?Iscdn4Xl4n0^qe-U-l>G-;7hH>JK}V zvGl-LEzh^6q)vM?*1cF7kdU3TO|IcVLn_fBmDN_8Otola7u}!~+50|c zhZozVOt5KU`aQYle)oO;{Qi9I;mE64-B~7@b)VMT{4n3Fjq>GBVzEmtY@IU7rdWWr zu%@-@i1tbp86ZH_o3w_U*yojZ4?xf8)*bGfx=@4t?|RH~J1AIC$XDew03m zE_n#f{`Gm!K18?uZ7dyZ-MOJ%|9n%6ek)>Y5g-CYfCvx)B0vO)01+SpM1Tko z0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm`6D2w3{;f+4S=-kB;^qWkrKo z$7k>OoM@N-nXV7D^^fTyF zyG7ym`iFRZ<;nBsmWN5g5Bz}q& zLF|hx{BEksvPZ0L-L)rnFJ08tJIq<~r6-n}^0)i=53EPng)ZL<{4dt{ZXv_-7v|2* zLE2Xr!}g(xknVK0bgeU3OPAjWcN$LTIQM0XSL_hGBs~8Co^RP5j9<1c=xaM_o|Qaa z&+gTcIy@~-ghEcIF<;UhvDbDd@Xn2|;P_QQ(%6NElz7nbP_J16@?OL0EQKATyYx6V zXG>kqSW}0Slfq70*a?Llu&69rx^wf14yX7TuqqmiJYEuDcnV-^hx3 zTeCmS!u>=x#Lfg6&%*vBSc5e-mA2p*mD(NSc4@1_pUi3B3b9|RzOioo?xTMFwv99O z9kmswYQXqDI{Vk%uU|K$QZG&+g1}#I&2AgZsb)@X+Pv|1ot^NM@#{DPyeDhCUf2Ua z&l(>U_FG4C-cVa%PsKiq@5OM_#@ zx+C6~L&CVjOK$PbRwv(t8Vo(O*-DTWXTTopQ-xNf}GOSp5t*{rzPb?k^hj;8~ z+v059+&PXIxA9I_KAxIrOk?qQ+#8Blw@jr*B)fUJ+M8)jK!k0Xbe-DmV(22i$qL3q#BAb(`2$5fWtj38XG(ri$$3x_G>9< zF_B`afEG|O9%zroQ;3x^ld8~B(@cu>DX}aP!TOlEgf-MakBBR-hK(>0C8CL35gr$w z#m;&h)QkAv9W)K5!9gk(W64-dTo^Y^6Mhr%SS%J#AikM2P2}ZSlNqKth>#4k*cM|^ zk-ipXs%Y4UJ;T_GQbkL#{Bm_6pU<<{$y5p(MZRcJFCG?n`egiRPa~v=9~FK8Q8W<; z&5IX{Ml}Xs3)N-#i$z81VqTM^{y(6JPBdn5_$Vh_d*6-KS524h<#po za2eLL0Mf!b^V46|k1n1W;|H?hLq9M$dSayS#K0>9gU6c1*WWEj#_59W8cpKY4o7_4 zE0&bw*RuSNbK*Z9t$Od@j_2q>Z;~RR$#nzE8d?^^(DEwUTlPP_DT;zTB}rk5(y%L< zA%6*fnn=`vc~;j_Lt_H7ij12q`Q^H04GrUs%MA^APkRv?x?Vb_ytcX(eq>n$7H#$Q ztF4n2#i#gW{GfgnR)Jm745JGEk`c5|@`?I|mV{PhpQzurTK!m$BtZ{ZdlnC({uRAi ze*ZY{mH!{C->>+5D(dn3eE4XAQykFRck@BXx)A=>@g`;GhH`yTZBLHmgS z5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y i5g-CYfCvx)B0vO)01+SpM1Tko0U|&Ih`@i7z~2Eshv>Bc literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/instr_timing/readme.txt b/playing-coffee - Copy/roms/instr_timing/readme.txt new file mode 100644 index 0000000..b64024f --- /dev/null +++ b/playing-coffee - Copy/roms/instr_timing/readme.txt @@ -0,0 +1,139 @@ +Game Boy CPU Instruction Timing Test +------------------------------------ +This ROM tests the timings of all CPU instructions except HALT, STOP, +and the 11 illegal opcodes. For conditional instructions, it tests taken +and not taken timings. This test requires proper timer operation (TAC, +TIMA, TMA). + +Failed instructions are listed as + + [CB] opcode:measured time-correct time + +Times are in terms of instruction cycles, where NOP takes one cycle. + + +Verified cycle timing tables +---------------------------- +The test internally uses a table of proper cycle times, which can be +used in an emulator to ensure proper timing. The only changes below are +removal of the .byte prefixes, and addition of commas at the ends, so +that they can be used without changes in most programming languages. For +added correctness assurance, the original tables can be found at the end +of the source code. + +Normal instructions: + + 1,3,2,2,1,1,2,1,5,2,2,2,1,1,2,1, + 0,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1, + 2,3,2,2,1,1,2,1,2,2,2,2,1,1,2,1, + 2,3,2,2,3,3,3,1,2,2,2,2,1,1,2,1, + 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, + 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, + 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, + 2,2,2,2,2,2,0,2,1,1,1,1,1,1,2,1, + 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, + 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, + 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, + 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, + 2,3,3,4,3,4,2,4,2,4,3,0,3,6,2,4, + 2,3,3,0,3,4,2,4,2,4,3,0,3,0,2,4, + 3,3,2,0,0,4,2,4,4,1,4,0,0,0,2,4, + 3,3,2,1,0,4,2,4,3,2,4,1,0,0,2,4 + +CB-prefixed instructions: + + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2, + 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2, + 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2, + 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + + +Internal operation +------------------ +Before each instruction is executed, the test sets up registers and +memory in such a way that the instruction will cleanly execute and then +end up at a common destination, without trashing anything important. The +timing itself is done by first synchronizing to the timer via a loop, +executing the instruction, then using a similar loop to determine how +many clocks elapsed. + + +Failure codes +------------- +Failed tests may print a failure code, and also short description of the +problem. For more information about a failure code, look in the +corresponding source file in source/; the point in the code where +"set_test n" occurs is where that failure code will be generated. +Failure code 1 is a general failure of the test; any further information +will be printed. + +Note that once a sub-test fails, no further tests for that file are run. + + +Console output +-------------- +Information is printed on screen in a way that needs only minimum LCD +support, and won't hang if LCD output isn't supported at all. +Specifically, while polling LY to wait for vblank, it will time out if +it takes too long, so LY always reading back as the same value won't +hang the test. It's also OK if scrolling isn't supported; in this case, +text will appear starting at the top of the screen. + +Everything printed on screen is also sent to the game link port by +writing the character to SB, then writing $81 to SC. This is useful for +tests which print lots of information that scrolls off screen. + + +Source code +----------- +Source code is included for all tests, in source/. It can be used to +build the individual test ROMs. Code for the multi test isn't included +due to the complexity of putting everything together. + +Code is written for the wla-dx assembler. To assemble a particular test, +execute + + wla -o "source_filename.s" test.o + wlalink linkfile test.gb + +Test code uses a common shell framework contained in common/. + + +Internal framework operation +---------------------------- +Tests use a common framework for setting things up, reporting results, +and ending. All files first include "shell.inc", which sets up the ROM +header and shell code, and includes other commonly-used modules. + +One oddity is that test code is first copied to internal RAM at $D000, +then executed there. This allows self-modification, and ensures the code +is executed the same way it is on my devcart, which doesn't have a +rewritable ROM as most do. + +Some macros are used to simplify common tasks: + + Macro Behavior + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + wreg addr,data Writes data to addr using LDH + lda addr Loads byte from addr into A using LDH + sta addr Stores A at addr using LDH + delay n Delays n cycles, where NOP = 1 cycle + delay_msec n Delays n milliseconds + set_test n,"Cause" Sets failure code and optional string + +Routines and macros are documented where they are defined. + +-- +Shay Green diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/build_gbs.s b/playing-coffee - Copy/roms/instr_timing/source/common/build_gbs.s new file mode 100644 index 0000000..7ac8d3c --- /dev/null +++ b/playing-coffee - Copy/roms/instr_timing/source/common/build_gbs.s @@ -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 diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/build_rom.s b/playing-coffee - Copy/roms/instr_timing/source/common/build_rom.s new file mode 100644 index 0000000..e1e220f --- /dev/null +++ b/playing-coffee - Copy/roms/instr_timing/source/common/build_rom.s @@ -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 diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/console.bin b/playing-coffee - Copy/roms/instr_timing/source/common/console.bin new file mode 100644 index 0000000000000000000000000000000000000000..b02f2d37ee94c6570a3622734f2d704dfca44b0c GIT binary patch literal 768 zcmX|9F=`|+5G))_B$f+182koBX5hr&r*zbT5qlFY8w?8X8GehSdBVQJIPe4Zu1Z?m z2wEj|cXgKp3^N%Fwbp)6@0AO%)yuRSqFVOjFXoqd`4hj9QtEN1nR)XG+d{u=K$L$a zcxBdDa9GaHO?x1~9ZyqY!B>?@1~S_Jk8EuOnO>t1U_)GPUYcV}PSJ688I zDZyK&1&Z3Yf=R|a=4{O6sIrFR>wY_q1954MiRzbDIv)c%zSNhFNh$uKIuubR9$M#% zQVgthyMM7>6t%8xc-*ZoU#;c~Eo^b8>ieF1Te%=ANe|UbtGOeuN<>ma4(yn>d z&PyDPc+NXzR}RnDiJDL@@*5U1@Exf>ZKHTq^DiA1#(zox`Vp^snt$Nkdp#BumLA0V yczj1Ga3U7XIy~nG6}avCt6hL`&VKmF{r{ZIvhq4Tb=k}k+hUx5zmKoWJ@6mAT&*eq literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/console.s b/playing-coffee - Copy/roms/instr_timing/source/common/console.s new file mode 100644 index 0000000..5307f6b --- /dev/null +++ b/playing-coffee - Copy/roms/instr_timing/source/common/console.s @@ -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 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 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 diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/delay.s b/playing-coffee - Copy/roms/instr_timing/source/common/delay.s new file mode 100644 index 0000000..65eb9c0 --- /dev/null +++ b/playing-coffee - Copy/roms/instr_timing/source/common/delay.s @@ -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 diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/gb.inc b/playing-coffee - Copy/roms/instr_timing/source/common/gb.inc new file mode 100644 index 0000000..31bbf14 --- /dev/null +++ b/playing-coffee - Copy/roms/instr_timing/source/common/gb.inc @@ -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 diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/macros.inc b/playing-coffee - Copy/roms/instr_timing/source/common/macros.inc new file mode 100644 index 0000000..c413bd5 --- /dev/null +++ b/playing-coffee - Copy/roms/instr_timing/source/common/macros.inc @@ -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 diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/numbers.s b/playing-coffee - Copy/roms/instr_timing/source/common/numbers.s new file mode 100644 index 0000000..6d6faf8 --- /dev/null +++ b/playing-coffee - Copy/roms/instr_timing/source/common/numbers.s @@ -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 diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/printing.s b/playing-coffee - Copy/roms/instr_timing/source/common/printing.s new file mode 100644 index 0000000..ad9d811 --- /dev/null +++ b/playing-coffee - Copy/roms/instr_timing/source/common/printing.s @@ -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 diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/runtime.s b/playing-coffee - Copy/roms/instr_timing/source/common/runtime.s new file mode 100644 index 0000000..90cd24f --- /dev/null +++ b/playing-coffee - Copy/roms/instr_timing/source/common/runtime.s @@ -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 diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/testing.s b/playing-coffee - Copy/roms/instr_timing/source/common/testing.s new file mode 100644 index 0000000..c102f78 --- /dev/null +++ b/playing-coffee - Copy/roms/instr_timing/source/common/testing.s @@ -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 diff --git a/playing-coffee - Copy/roms/instr_timing/source/common/timer.s b/playing-coffee - Copy/roms/instr_timing/source/common/timer.s new file mode 100644 index 0000000..32c713e --- /dev/null +++ b/playing-coffee - Copy/roms/instr_timing/source/common/timer.s @@ -0,0 +1,88 @@ +; Cycle-accurate timer + +; TIMA is incremented every 4 cycles. Loops +; check for increment within 3-cycle window, +; so when it occurs outside this, loop is +; exactly synchronized. Loop iterations are +; one more or less than 12 cycles, so they +; will never run more than 4 times. + +; Initializes timer +; Preserved: AF, BC, DE, HL +init_timer: + push af + di + lda IE ; disable timer interrupt + and ~$04 + sta IE + wreg TMA,0 ; max period + wreg TAC,$05 ; 262144 Hz + + ; Be sure timer doesn't expire + ; immediately or take too long + wreg IF,0 + wreg TIMA,-20 + delay 70 + lda IF + and $04 + jp nz,test_failed + lda IF + and $04 + jp z,test_failed + + pop af + ret + + +; Starts timer +; Preserved: AF, BC, DE, HL +start_timer: + push af + +- xor a ; 1 + sta TIMA ; 3 + lda TIMA ; 3 + or a ; 1 + jr nz,- ; 3 + + pop af + ret + + +; Stops timer and determines cycles since +; it was started. A = cycles (0 to 255). +; Preserved: BC, DE, HL +stop_timer: + push de + call stop_timer_word + ld a,e + sub 10 + pop de + ret + + +; Same as stop_timer, but with greater range. +; DE = cycles (0 to 1019). +; Preserved: BC, HL +stop_timer_word: + + ld d,0 + + ; Get main count (TIMA*4) + lda TIMA + sub 5 + add a + rl d + add a + rl d + ld e,a + + ; One iteration per remaining cycle +- xor a ; 1 + sta TIMA ; 3 + lda TIMA ; 3 + dec de ; 2 + or a ; 1 + jr nz,- ; 3 + + ret diff --git a/playing-coffee - Copy/roms/instr_timing/source/instr_timing.s b/playing-coffee - Copy/roms/instr_timing/source/instr_timing.s new file mode 100644 index 0000000..b6b7338 --- /dev/null +++ b/playing-coffee - Copy/roms/instr_timing/source/instr_timing.s @@ -0,0 +1,336 @@ +; Tests number of cycles taken by instructions +; except STOP, HALT, and illegals. + +.include "shell.inc" +.include "timer.s" + +.define saved_sp bss+0 +.define instr bss+2 ; 3-byte instr + JP instr_end +.define instr_addr bss+8 ; JP instr_end +.redefine bss bss+11 + +main: + call init_timer + call test_timer + set_test 0 + call test_main_ops + call test_cb_ops + jp tests_done + + +; Ensures timer works +test_timer: + call start_timer + call stop_timer + or a + ret z + set_test 2,"Timer doesn't work properly" + jp test_failed + + +; Tests main opcodes +test_main_ops: + ld l,0 +- ld h,>op_times + ld a,(hl) + cp 0 + call nz,@test_op + inc l + jr nz,- + ret + +@test_op: + ; Can't test the 8 RST instructions on devcart + ld a,l + cpl + and $C7 + jr nz,+ + ld a,(gb_id) + and gb_id_devcart + ret nz ++ + ; Test with flags set so that branches are + ; not taken + ld a,l ; e = (l & 0x08 ? 0 : 0xFF) + and $08 + add $F8 + ld e,a + call @copy_and_exec + ld d,0 + cp (hl) + jr z,+ + ld d,a + call print_failed_opcode ++ + + ; Time with branches not taken + ld a,e + cpl + ld e,a + call @copy_and_exec + ld h,>op_times_taken + cp (hl) + ret z + + ; If opcode already failed and timed the + ; same again, avoid re-reporting. + cp d + ret z + + call print_failed_opcode + ret + +@copy_and_exec: + push de + push hl + + ld h,>op_lens + ld c,(hl) + ld a,l + ld hl,instr + ld (hl+),a + dec c + jr z,@one_byte + ld a,0 + dec c + jr z,@two_bytes + ld a,instr_addr +@two_bytes: + ld (hl+),a +@one_byte: + ld a,e + call time_instruction + + pop hl + pop de + ret + + +; Tests CB opcodes +test_cb_ops: + ld hl,cb_op_times +- ld a,(hl) + cp 0 + call nz,@test_op_cb + inc l + jr nz,- + ret + +@test_op_cb: + ; Test with flags clear + ld e,$00 + call @copy_and_exec_cb + cp (hl) + jr nz,+ + + ; Test with flags set + ld e,$FF + call @copy_and_exec_cb + cp (hl) + jr nz,+ + + ret ++ print_str "CB " + call print_failed_opcode + ret + +@copy_and_exec_cb: + push hl + + ; Copy instr to exec space + ld a,l + ld hl,instr+1 + ld (hl+),a + ld a,$CB + ld (instr),a + call time_instruction + + pop hl + ret + + +; Reports failed opcode +; L -> opcode +; A -> cycles it took +; (HL) -> cycles it should have taken +; Preserved: HL +print_failed_opcode: + ; Print opcode + push af + ld a,l + call print_hex + ld a,':' + call print_char + pop af + + ; Print actual and correct times + call print_dec + ld a,'-' + call print_char + ld a,(hl) + call print_dec + ld a,' ' + call print_char + + ; Remember that failure occurred + set_test 1 + + ret + + +; Times instruction. +; HL -> address of byte just after instruction +; A -> flags when executing instruction +; A <- number of cycles instruction took +time_instruction: + ld c,a + + ; Write JP instr_end to HL and instr_addr + ld a,$C3 ; JP + ld (hl+),a + ld (instr_addr),a + + ld a,instr_end + ld (instr_addr+2),a + ld (hl),a + + ; Save sp + ld (saved_sp),sp + + ; Set regs and stack contents + push bc + ld bc,instr_addr + ld de,instr_addr + ld hl,instr_addr + call start_timer + pop af + push hl + + ; Environment instruction executes in: + ; 1 byte: OP + ; 2 byte: OP 00 + ; 3 byte: OP instr_addr + ; BC,DE,HL = instr_addr + ; Stack has instr_addr pushed on it. + ; Stack pointer can be trashed by instr. + ; instr_addr contains JP instr_end, that + ; can be trashed. Instructions which trash + ; this don't execute it. + + jp instr +instr_end: ; instruction jumps here when done + di + + ; Restore sp + ld sp,saved_sp + pop hl + ld sp,hl + + call stop_timer + sub 24 + ret + +.section "page_aligned" align 256 + +; Instruction lengths of opcodes. +; 0 for instructions not timed. +op_lens: + .byte 1,3,1,1,1,1,2,1,3,1,1,1,1,1,2,1 ; 0 + .byte 0,3,1,1,1,1,2,1,2,1,1,1,1,1,2,1 ; 1 + .byte 2,3,1,1,1,1,2,1,2,1,1,1,1,1,2,1 ; 2 + .byte 2,3,1,1,1,1,2,1,2,1,1,1,1,1,2,1 ; 3 + .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 4 + .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 5 + .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 6 + .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 7 + .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 8 + .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 9 + .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; A + .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; B + .byte 1,1,3,3,3,1,2,1,1,1,3,0,3,3,2,1 ; C + .byte 1,1,3,0,3,1,2,1,1,1,3,0,3,0,2,1 ; D + .byte 2,1,1,0,0,1,2,1,2,1,3,0,0,0,2,1 ; E + .byte 2,1,1,1,0,1,2,1,2,1,3,1,0,0,2,1 ; F + +; Timings for main opcodes +op_times: + .byte 1,3,2,2,1,1,2,1,5,2,2,2,1,1,2,1 + .byte 0,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1 + .byte 2,3,2,2,1,1,2,1,2,2,2,2,1,1,2,1 + .byte 2,3,2,2,3,3,3,1,2,2,2,2,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 2,2,2,2,2,2,0,2,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 2,3,3,4,3,4,2,4,2,4,3,0,3,6,2,4 + .byte 2,3,3,0,3,4,2,4,2,4,3,0,3,0,2,4 + .byte 3,3,2,0,0,4,2,4,4,1,4,0,0,0,2,4 + .byte 3,3,2,1,0,4,2,4,3,2,4,1,0,0,2,4 + +; Timings when conditionals are taken +op_times_taken: + .byte 1,3,2,2,1,1,2,1,5,2,2,2,1,1,2,1 + .byte 0,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1 + .byte 3,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1 + .byte 3,3,2,2,3,3,3,1,3,2,2,2,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 2,2,2,2,2,2,0,2,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 5,3,4,4,6,4,2,4,5,4,4,0,6,6,2,4 + .byte 5,3,4,0,6,4,2,4,5,4,4,0,6,0,2,4 + .byte 3,3,2,0,0,4,2,4,4,1,4,0,0,0,2,4 + .byte 3,3,2,1,0,4,2,4,3,2,4,1,0,0,2,4 + +; Timings for CB-prefixed opcodes +cb_op_times: + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2 + .byte 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2 + .byte 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2 + .byte 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 +.ends + +; RST handlers +.bank 0 slot 0 +.org $00 + jp instr_end +.org $08 + jp instr_end +.org $10 + jp instr_end +.org $18 + jp instr_end +.org $20 + jp instr_end +.org $28 + jp instr_end +.org $30 + jp instr_end +.org $38 + jp instr_end diff --git a/playing-coffee - Copy/roms/instr_timing/source/linkfile b/playing-coffee - Copy/roms/instr_timing/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/playing-coffee - Copy/roms/instr_timing/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/playing-coffee - Copy/roms/instr_timing/source/shell.inc b/playing-coffee - Copy/roms/instr_timing/source/shell.inc new file mode 100644 index 0000000..277a9b7 --- /dev/null +++ b/playing-coffee - Copy/roms/instr_timing/source/shell.inc @@ -0,0 +1,21 @@ +.incdir "common" + +; GBS music file +.ifdef BUILD_GBS + .include "build_gbs.s" +.endif + +; Devcart +.ifdef BUILD_DEVCART + .include "build_devcart.s" +.endif + +; Sub-test in a multi-test ROM +.ifdef BUILD_MULTI + .include "build_multi.s" +.endif + +; GB ROM (default) +.ifndef RUNTIME_INCLUDED + .include "build_rom.s" +.endif diff --git a/playing-coffee - Copy/roms/interrupt_time/interrupt_time.gb b/playing-coffee - Copy/roms/interrupt_time/interrupt_time.gb new file mode 100644 index 0000000000000000000000000000000000000000..1b17845e33205df4c97eaa140f8e6db2a1194ee0 GIT binary patch literal 32768 zcmeI4Uu;v?8NiPn`zAGxozN(|4S`%IV3`bryJ^AF+@pvg(6+Mlsg?TD8;x9+$xTvV z!GL3&r2n?3@iH~7)TPpdv^})VOH;;+?VIayF$Wc`M7L7IuDd!?tu(Shw^Hu^8GYbz5V?-{(pN_2Lm_0R+Q__WLv{2GEKasnKZR_ zz4?3M`~3RI^3u|0Ggq#h`1Ocy<@(a+Gk?Bv`N#B;6w)9&?YTWerAO9KL;^?v2_OL^ zfCP{L5ZcVWh8(EkN^@u0!RP}AOR$R1dsp{Kmter2_OL^fCP{L z5%>@YxmrHf_=)J*8XI@ZHR}VjX8LK^B-CLZYatc0 zrfbYh)3n(}Yi28LGPm=w0bITB9T%mmJZIZh`r-%dAmi3e;^jLhq+5rrV-> z*^Ajq*}v?{Zm7JwwxL#Zh~zH2=cRiU{T6#WeSXn%Q*S3__oDla{C0R=ve$RaOW*3? zDX7zw8H5b>&@<+4-6}#NKcElHc1Y%_K(GcY?bLZmdQhn7OijZ&pMW)s`q^-n&hZpL zFRA1QB$DXrdSpGlwH_h)XC>0r6(Qh?wwf(=$u;vyI&HSuC9mrmWYpmc_kQ8czYEUodAm!kDA^&C$H+T$}v=Yzg^H(?LKIL=INEKZxo}hkTYIn|^ zckGeYMuRnhbiD20|+|khPeak+6DRVfNWgSdS8DZaX}pEn}hK}Mz_Ii z)Qjguf|65O_w_f2ikq5XbVvLrdm-IU*uHFg6Z|m$cLc}kFIux+Mib>>B@7FYg;$sPT>1 zD%eV}6WIyAuK)L!ql*M`PnnbElsTx^`s$ul7H`|NUT7K9YYb-_aFrc`3g7 z_0hAd9}k>coqpl`shMYA&OkP62dmi<*v#F=Z%h1Cn1rHNikgNbdviFDP>HQCXGaC%Bbi?_P#1QYox&blGOoy zk-63922Y(DI!+3dsK&y2%7}*C&xHOh`uW=7(UGBtE0f0P`jl}ZIUjlgECB-if_k+k zIjI9ot#aysRi1=*3f}1hy!5R%2;Xfvbw%1 z-fi(Y_-5}uP0SK89LdH&%3$r9;{pk!YA}HFQ@L>)ezBQUoEiRxeB|kt6ANG+A39t zA^EtoBZI@Sq2u5K_&t=Q9&q`Fz2DosE=szar46^VW^4SM2Ok-XNsz+mN#IulJk$gK z?t{8Y(*~U_y=u_gCC4oNu;iSjgQbR9N=mL-nktc5y=bjZ1L{|e6Fq%o0`wE0{=>?d zg3g9C_1T=}*mb`lJ{J#?R=N=27*%oITZQ_E+=|L?=xxryv#ng@tw& zt-y@YTYXJ>qEeMY9`-3Sa2=tLLLX>DNs=$&cCc$C7g~Kuo zlA97CvP_(U7aaDJs4_CHC{ZGEdoc+zUr3UqSM&-n@9m7nk`OB?Cj>4>WjVq1lUywv z27QG;yr+UU!sGI1vkwyAv`=V=9cw1P%p&)?TAc?2o92pLK2F?ADEYA8T`g$ zilW5g5MNHnGUR3VGNH0O0wD>kf|)|1Jbf`r1m3_7Y{tMAO64s{a;w$FTrNkH@njMV zdA`u1c05q9uKPAdj=j%L$Iv0qy%Qo9Z z`g!$&lf-Q~PFfiU>`XFAILlm z|K~y6jBhXiNbs|21E|kKKCsF5`SFQjnA_Rmfy)lkdD+Ke{j`1TGl(FoR-M z-})5ndHg{FNB{{S0VIF~kN^@u0!RP}AOR$R1dsp{Kmter2_OL^fCP{L5Cevt3|Euy1T5XqzND)qyZJYp`=Iwqe7|{Fkte1pXW*1!uq+set-PtNuHTG zbLPyMGxswyiRchyi+#G5lr#5g^3G-oDMzs>z(+U_s%PsV$SDb649 zKW`ERa($+iezPe)u*?;(x~?_{ruYITU*I;MxA_r}X%0kM{UX0OVDtrU^#%4g1J$m; zB3D4<3y6JzNj{!}wH0(*i zt(!_Ixb^H|4XY*4%VMI$qpT3IL>_&ChOHMd5yJGv3pDoiqFK!QmRQ%9M+9A)h^e~~ zs~I6u8e2q647e*#JVcw)tW6;qcDmt!H|h>d^aXx!1}6I;!GAde?>gnO%OGp=LG*f7 zFVd!(BAGHSBu~{Ova}?w!xZs9<6r0HhUq8PYZ~*spWNnMm|$No$rZ5 z`MyRj%iAv}RO1>6G;iV1;5tva;WJ$-k|!pqe!%HAiBYgrN{>lgt)ga73mJWlhD5|Q zNr|?0tebb3E_fAV-Fk7v^MUCvevxODC+sHFLne_wLh&dDYL-CJ^`6xGx!fC+GzT7c z1@ft`7=5~%u8%I4Tn?e?{mU7$x*$H&1$nCeS0_Yv4H3Z;pE=ahX2>}3eysZ4>Z9B_ z`2;li3ca+!+bY)=G-%9BBxcA@iqEv$yKNXBJCx=N2M>QD2}caOkP@?>^#@a!!pV-xt{H47}&m z>{95(JV*J&k({Z--7FC|5zr4foFOp74$EY&tuhee2}59-GYY+gk3F$L!ztt#15hMEpvt5b)pp$k;iUiK?M3yB0RqzI0a7D$r_y( zWyZh#ZO8LBz)5BY3p20?x zTeHw_xF*W@#0z~qB`z-b%-O7P){)mt&5+*6K!H&6B&bJ}#7((`EhIh>e9r6D3>5wM z4pFVh#L#gNzlLFuu!`z{TE+ZH! z@67;B6H*H~y``hhijb7sd-+!Vs(O@gSZwI6Nsa?8c#L4^Qc&-$_x=ax)<0FR8A1WX zrdumbkcv|xa+WACVmx0g1OsFFzcMxrq!N^Noi(;-Bn zWO5Y-phz#Jv^V@BrAE?Un<3RndC1;KEQ&T>nUDa41Zgj6!cDmmDbrGX!E1`JUQ!K{ z6B_~~FQQ-?Nmqih1eHpIKv}{~k=zJx3IqNz!oAY~ek}=VD3DwhNB}I+CwjFY21o)xLAe88v7eczBIA2k3vRkotG2!gv(T}W1=)vef93w7 z)7js^ZDOPRqeh@Jsd%sx+;rgU9#UkTH{adae&Bev%zI~oF3J0Hf;AlEB+WW8`T}vn zv6i+DcV{RW@>2Webzq26hy5Jk7^DZ)K=M}&pz@%?Xb$x9fmTQZWUX(WcfbDnk^&`U zYyt8)Z%F4dNQw%l!+U>%TO?)Wo;;5(iTmYjvM0NdlX;sHbT^0N;nfPxWj5rX)=3_ zNv>ZL!x`CW-j;+D8iPze)jJ|dKG_>iH029{@?pR~}0B%?$Oy z6iFtxPzrY;g+;lBB+Ft*U=&~Oz|B|T|fd%^|dk( zm{&=4f`^N`;PVhbaGpTO4U)NB2t!P$m#?Tp#)%KATTmiv_!9BtoqR3{uWj^NNv{P- z)%v<w-_?ix2C2!03RYW%#0a)uZ(0#^x9UwbF}Fv+T?yRh0c-Eg+7Z7Et;3^j zN35936Loo8^?cZ3{VRNmZB_1YFh??8C(3i{tu30gs-6fLPm6}<%!k_?hm-NhwnFYb2zA61K~3YNFIO^I+UInl3N zu5{;5oK!S<%AI#jyJyDCS!KWbz5T_PcfHd1+HTv5+KmLKaDr3#i}qbFH@>p_wH3CF zwLwRV)8$tyMN+XOF-|6rPg1B7lI=TReCf?Mh{CI{HSK;q*zt4ck3U_yeC3yGzlN@c zBUm1JS!Hveh-Na4hAdMs@V6FB!n|6&Ub5BudyXa`VlELgG6D^#?*jFq88ZU*I{hM= zM`99fQqtV{axZUTDpBKPa?xd7hFpBP84E4%=Qqj!==~@etBB?R<~SFewBoqv^s-c1 zo$<-1A}OXjt47OyQ|)b6hdu)T zoOib-bPH;9pU_xeSYnWSYYpDif$%i$pRe^U)Oy#Y@PzvD>B+p$_149Cf7d%S3UQR) zZM{RIeT|MMz^3c=0`?w>jj9B;>J)N8}s=TR&aHejM=L`KlQ2UCVZ z7oA3f1R28m<)COVG8eAklA2+MF7SX0DtjFT~aI9yhla2F~JI{>u%yTXq>sjF3HP%z*>@&`@+&O+6`np?v zZO2+Vu6`bT##6NaFR7uU7=jMLGt-dBXIU;+U52ntlE7G>n-X&n<{^2?Ws@y1!RO@{ zwtV^Pt6M}^K0e#G%YhO_R5u^Z`CujFgCaQKJ)>^H%s0qMsRDVH8{*_N{r}dfyB(yf z;1yxY3hyv@7Z@~hDZJ;Mp^u$sPX#`9>Use@&yXw^BN7k3!~u+g+;gZp@R3t*$O|2F zuBI_D^Z}}q$jz6rIX4P03~84$ucx1W4NFTusLV`B&jbP{{YsR))U+HeeOV?FyJV|s zlZ%09)uzC!b$eg!2gF=Me6Asdf2-Y^?;~ltcugux(@6*DL_@7-g8`ot!`A}zFb{?8 zhIwI!;r?)~p*&n{m=iW18Kw{0js!EQW*silB0&{jBbDA~X{|Tjhpbr)NueT?saQ7} zAha-=t@oyiDdME^cwIbdyHuX26U&vCfiT7wxJ4)!P9l#(o8bWv3?#@G8gN>jZp6<$ zGVJEuBU5g6^Hp8O$JL*7LRgrdKv=CL1a;^9e$^O;}|Ej8@0r0M>vK-(A(vm(C z0x3sCZwD`9gZkej+D&X^l-Nijb`Fj5oRcf|PEJ9yKj)-G#i+HDQ}_%XS!cldWu3v> zbhCHc&CNnCkRu@@6oJo8D8M(3fHU-+vy}}Fe23-BCqhQBtVsKPY5Ibc^m%=cgZD;* z3UZSZSSQs)B5BA-6ebiK72&aE+LWbQH=jB58)2yKDlbT-FiG!1&FH%xZg34r= zK()piUdCw$SUn3kx#wclP+f0NGu~=zcsZh2b@H&46PuP-X~L1Tc1?98&3YCkH@0o8 zdraFH_k9l$oIqj#s8XTQgw+N)#Cge4g|{&*<)DXvbl>Y|Z8Utv%U1rYDn8w&tsZGC zqGrT@lDHoby}8PbQk1g;)q!*$22QH;@%o&+>eU*?ba7}7)vH%)#0*88W}VqKq-cnx zKkJBM6(4GA=(ExF~%m(lpdj)2?|h<5~3j=&m6V4WjS z>j-RegkEq2vV5Vfj=(@4uwcL7Fzxc+VN(02n!fRyY28NMf8loO{;^P>-An%zxk=e` zQvl$Pl7n}g5c}^4mvcYq^*cy`A!4ENz8XtxwJt3A6B-ZS(<|3-YwnTr8GNLJQbfYc z)Z~JO4dX_Z4{3)6`oM&nV6Jnb1%sF=-a9mdSa73v4Gi+3Ck+htQ8*MYSiEcgp@il{ zb6UxF>k`dHrG8=q)|Jp-5GxXfde>9+z=?Ie8V%ac=z6{**Dy5KFc6BqPyrgOgj5KF zNFx>+?Bo0D0v{IK7|mRxdO;n;w{(e%*Ld(1x;c>NLl+wOqBZbkYhbx6U~>hoIs@07 zG|+)zaC^}i2slx{10Bx5k4|(D=w1Rs3m@eRjP^xa$j3sv5EL+cZADXYhtL(N9da#o z6Qg{5HNvVuOI5fEb*niu6TN+I;HVSxei}}FSbd|)g_VwG0Nn^|bUxjOMAw%WT|ZL) zj24Fcbd2Ceqz9`A1OGwM9!^2(3fHc2-i>`w*v4>ws>S=9ZICq<^_r)Y`uppnt9dNt zn`kKyyg_UMUrdc?Gw~=YOuqHa{CuG=FUo!-N`0UngS&ocgLge=G9#JhfY}GFo__Rg zA{~Q@&%5UKsICuMgc?`KjPf|`{Y8WcB4*?mPt873@o5y^((&X$!i|PF0+onea`{it zLN`N3$eo+73w=phf}!oJeo))C3FBBZO2g$zcFU!ggGEKExMqKm_v1d*{F~Qpq6$*| z>m83O+6(hjP(t7HAZ31Bx_%dBUjIhDcU>+&W@Ge&l7&WZnMno}p#EIFHz)+jOi!S| z^@AF`Z*ck*4Vo$Gi0XYw7wTxGzDvrt^WZ)>B$vR_fac zpqZYoe~C{NvR;>~f4zP@C6I^VM5Q@Y8OlJ?8qI}DU83fg;FD<1R1y*J`9rsDK2C6bH=?ezIndD>`q5bw z_^vgCTKs+MTZY?6U-tu|>(94pzH2>TElRJ=NioR0d1<=DlmWLDrT4APk-*o&=Qu%T zQ1QnOMl-Udf!R?j2QjQu)<}S(zmI^US;}y4`B_6Ayt7z z_kLx1Xpsxdi5m?GMd4l9T?E<- zWfI_mekpPjX=GraKiQkxWNON5GVdjKmjXu!YL=@pJd#!!g9=)9bcKIF^NCY*(iwJO zkr7DO4NOUqNWv|GN5^}df+vCZxHuiqmTCR9OH;J|=e(EGgX_h?XC-1?A2hL)Wq?Pa zfiuqFvw?PJ3uzCWi_@#=8x(^P$1?OTO9RxmSOPcsOmRhl z{=QIOtmOMag>n;ADE)m!?PVCht=?M(cY0q!)6D45wrOr=X^Tc0%X?!m?HN#Z6HsLa zNfLJ$%@7~ZFot_^{Xr)eqz}btYYZ%{EA3Pyty_EGI!XgtNPYPomSjs$w9GT5qKxG- zD2`HP+%Ghr(6IWn(%)lArf1tVia1h)`nPN1$f3!dpwHiqn&YM}CX&u~{bTj@j!?TZ zGjzt8Ibc91oS1!fWa_s%FlK8+d7i;5%T4U*ndzA(HD;v)24rG+iq+}xei&#}xrY93 zy`w=hh&DcWSNsHOsG@q!Ljs7^YyL?teSE#Ha8|Bs5G9z$0@7F$cyUI#UjnGc$AaZqcDeOuxGGM z$cP(5kI)B7ynp4QBSkGgCEyo{-si{Zt6G)?E;*M5E<5#0o$^G}XS83WU*i-dLS5X+ z&$(nBrmH$p2Xx2SNBG5QN(@|a>VK~H8Vx92m906D!OxNRYbt{iJAVomgbkQGUqSbF zLX05)L@X1po>g$%0gPT(oERN{ae7Y=3SDw82wis08yZW$MJ~c*|GtN;2gTv726>f+ zhDjnfpU8FiCteknm`F6qIq4=^eReH$q9Lpj%T>YcmP;UTfq%JFBVqb+6Vfzlhp8Cd zrCpnX9ksqgv?{8g=&jIs@=GY{1&#(%8e+CPp!bDru3XFv8V|uy6k6`ugGr`ZcbncU zhX-}i^Z;H(2=v~S(LR3=6I?vB{op9-oe1Igy;)lt|5_Hv^y!DzM+-*Z(x6eYw2l#BUT=!;hU3Kw@R5)6AZrU$Mh=CZn^{Gaj?74*I&!CWn&^aR-e;0l7^PT_@(lUV|PJ z@&TCS!EztKf(j7mXrZ7jqFH;uDaG=PM{03n7}x1SLCp9%oVLLj1p%P-*N;x@=aS+y z2O8HPhfutm@f|u8?jm5l#mP)TN}(u}gK0_aUkYNNIMA;9iEqEudijMordH`t#d(kEy(2Tdy)wNMGQC&z-upAX4`=FT zVO5zVlh>p@Str5tup~Y&1xrXOEX*#s#VuO(G#^|Mfv&g%A1#u{_rYf&Z?c*53?bBB@Ln zmzabdX^Ao}K7om)a%Fs?3bTMKPH@)@5u!X>sW2gBUIWAR19#yShv#R7YdB?pDN>!O zo7Z0sCTHr(QKJ3e3a_Ma8^RgHI8#@I@ONS7p-lS$61K(SLw5uCVBk^wjIh)>KMSC6 zO^g5zV<13|gg^ja1jb|~QZg*PNlH>rj!rV(qg#|~3CnYMfwhOFa6kqp!(uofr;jm- z2LYW&|7g|{stgunXahJB=jK`#cj^NIbS9u1QGZ0(VCp!V+^PREi;xNIxh)sN%rDt8 zjB&h9WIQUZAQdIC2IBo80jyW`_&{ebfg3d#T*$!z<;%}WI&^(H^ow|+AnbiG%W6mS z5>s_uwDPG1OIz7IOr0eR=RuNHNr@?a`VSmB>Q?jJg$0Qf*)?*9L^psS*(oRm6SH-fm{CYM>h7z+|dnl zkBn|8EiEZ2EbP^b4(KGtN#sch3RN;gs#2sYCWYv642|e_%c+Z|Z`!d%nbnz#GnZtN zF47$vnl)RUq!kh$&L4<6ZQ-l$zJVzNWX_QTy$@t0Ifo8(eccrVH622x3L%pQ#)yb| zu{djIa~0&RS)Lc2VHLG|xRb98Y$NLh!FpS;whU}@y*F^EW(STH40I3B{DrqkNu=2( zIHBy*?BtzD`~WF$iVaCn@x?*qr?kqMPG&u!mix8URW0rw^9(*GhPzOtyzVfOpVILf zL|u|b#ITx6WmwHYLlvuou^f;XV%hf&qD9OV$~||%t?9*}VS2%dO|eFUJYD~XJe@>e znU{Vx`S{snez3D@&nD}a=aJi5)CSb_D$4V7gZQk!Em(HJ+Bc|;S7=(P*3;C}WUF$M zl1u<>1YkuKx;Q>~1AH}A=<|5r^YE>#a+61u-e^!j6q18#x5@8Lck^=~MYL1u%G1)W zJkgw+ijKR)=jwY6f}(RX??VR6^12A*Qm=;U(@6?5RQ6`<^9j9RN7#RO|6ufu2yu{J zS>Z#!s6S<*9aLnVuVh2eZ(8Tk?k~aw`S98@!>Cx-DQM!9(Ai4EjO$CKP9TPDNwpyn zU?nmZkJ+fQ%}VP?)73V9IT^)680vx?bwR9?g(~uNr&S_8dOYd(5M8Vi$C8q>0YtiI z6tTxKlr+dU01r|AIkAfAOKA>2D-B!L`m!{u?n#nVv|^;Ebj`r{xs{CjL74P#!lWmO z``nFb#s|;>oKXuk(`@+ zv9uRqOuVbPoXTFz?%La23c600uBs;SI7n;h)|ymya~$MFe?<%xQ&1Hw#C$;4*> zSnLExZX_q~4}}>FnjA?cEONxEW-RkVP+r9jtlRmV{2))bd?Q9Pf1HaNYzG9a|HU~5 zsT#|%>OJ0DiFBYTB4Qe?Lyv}zA^N}&?;oMKsHT*ma|kAJ-yXYZ3Gf>^Z%ntuGcE~*0uc1zT*Pyg84i41G`Xh(Ht;e zz!8D(xeMGy_C1TRpI8U~?cjc7^rbNRQW$;Lv#0s*)7&$RzHa8>zem_UM&F+t9sUl- zkB;YT>+I`n&)Q6!A2hn3k;umQBB-Hzz^)5@%MyI%wVnQTi~P@+vi;1hvdQE1n}!ZK zoru+^+vM<1G8uBYU)?vZp6lgY8q_GixoCC=Ys-Vrop8()F}5U}nxl}MNFAT2>+Ho{ z5~NJ^e^Jz__CM=h-A0nWcfgF}ZQlkX{*9j1{s@+v8*)NH^V#Kob8sWh5(dql2#$Et zs9C-QXH4GWe&NSRePkOBe3ma6MLLRK+^8?)xZ+NkM|&SB-il%A=)$nMeKxPCL!me; zF(`8l7xS7cJ4n_KenN(J6?oPL$LWefeR>Fv5pL{Ahg} z49Pqza!SA;!A_lubwzMjw|4?(LC3H`bbxWuQ^;N$RyW7xNtchcwRne&z(N>DN~)*_ z5`3lz?>T@|6oc4^!cu&ZTYrz^%ziQ(l z3yPLHd+UaXC8U?$IKnyN#}Sz$r;S`O@~Bl3BIXfLq!Zi_9ku@ z-rE?ZvE#Zl>VUVw+?_@P6X?DI%$@d^Y%wYuc=)D@IxYr@*#iH6;kF`1PksdcH*>Ed z#=rCP8t83f+u1g5hwTMhjQ>p>!8e#Unq&M6{A}g^0_r{a7x;M<)8iZY-^dy`JNF{; zO8m#6SswJy@4O%pWv@@zs1iM?t1=X{1E(leBx&-_cHh) z{&(60`3wBK%C=N&GG8x$f&Z5w&z<&edB*tPV!mFU4agVKfBpvkWAvZDfq$wCl;6f1 z_#rsux1Qa|k5C^Pk+19d5$ahz;62N?H^Ui=aB+58k=}jF5KC<~j?!FNtpDs5ew~J% zLyA+3!2^pH3=+$6{16?qx)&6@)#2nL|GZ_OL9#@5{;oxVptEI>{w~^=;*}JgOR*S} zFFTvOKqp;xHn^a}EmOoLCa3i2ryo3gjIq$d>3XMX)xTW1e5v#2pMLDnXk4KxSHR#4 z-0TYs@dbwZRQKb^D>NNAgIf*#+`;}{U!B6X2Gm{hR7>CVe;MR`F+tHuO}llXMbLMJhWb1iNFrqLwGKp@MWLI0t)r3h znxfDUU+X|CE~AswgSQLj7yPEj9s0rOk_ zphY5WO~sCq^LfDCkBO8@zOUIok#cg?HKme2#iT$obf1Bz%Wr;y7DhJbSHumDW5*IC zb|MB7j=Cs4`yf0Vo(a8&5`MzfczDPd9Ecw>W{)95x`*qF^Yf%Mr@(ief~tG~RXXq8 zV|dC)U;@f>uk)?0<5SJpLPkQ27s7i^sEX+v{Bjpmni>7w#E68#rF^&u<5XS_kvjat zIG5n!5{SPiKFSz<1Iu$UoxXrEH=|2(e73bf$54$!i`YojNj>Epss6eOp~;XH-fI{{ z*BJ;(QFs~LA!G?E=|`ZFel!{kIJQ+0Dfmn$rXIm{XX7||hyNMG>p&UOt(Dt+S6$DF zqO^IOu*eCr-(x$2?rT(0I^f^vYj%O0zypuxDFcp0l{F(A>&TFQM;%TfgCE4h$;~_v4q~pWO{^}vxsg$ zP4eNsl*EUMMdIO9d=a8Rgi;JisRTn3U-*&3e;yNl6jk88aaXDN(5XJ*`V`m+EOy}# z1U7=A+Y#6&(u=lgJhwq-j(v9A7h2~n9~Y{|m4Rxy+2I;;E773x#eNTVyS*p-;3Og+ z`>-?zyZf&9Z-r{f^kk-KO(rphy0H;;V`HX?Wt!Fz)bs+mLn22JZfr0xfqdgr9S}JJ zGDi}pRY^l~NB9NAd;u~4g0n9&6Ss%^jP~+yrRQ@we+0ohEQjtN^*n;Wr#DQ0KMo~` zghL7Xy|;;!lIspAM2{yNZsA$I|2Ex6LdO#=Yv=p7>MkJ9sGbMpbdmri2lcmHru&J= zJA}^bLiGMoRHA%WD+m=aqJl#);e5$rS2J$sU-&jYr=09A1yY$lwPZ)QDJh;LC%*PBSbA)Noc4%Lx$k=X5i0e~dXQwdhfj8#wur z8V*UZSvX)r2U;L5oc8H)cMFi{u^_q(#ovSi9wFW4!+{|*L!(fb9dNt7i^X)M=Av*1 zO`nM?HEP;G{zHQX&9&FpC+{~Zx$A> zW(#MniFsM{q6WA3K*3$<;U+Flk7eau1u428F&+#FPvT@y%jp#1BCZ!4?FU4sB5^IW z+EMtb+ER2!;Kb5Yx4*@GhiM@&j8N_waRw#7-CKTpn~B5hCn+i4RD~cEW1P=ajldpZ z+&B5AMLpsn7VVCBt8SOcpmrl0surASsk+_Wtu5=M{-ncw934e(R+iST*Rg7>s|wqC zJ7!^?h0WPx?q>8tjdH$B3~@LjrukJ#`|PV5G!iA1+U^ETS}s8(4LCzaT^CdJM+X|) z$O5B)r315`4Mxm=?8dX%&hL#pI?YMC zhEC!_jI}eC)BSJ)m%pEgW*tikduIB{4VW>6mbhkymbyIS|EhnL{ENcz*-3C#LyV9# z7L`d*D)y%l#QC$4h7hMJ*Y*AF`X#RFk!NuQPQTPOR+rS%I|ekvkt5$4k)LpU4%Dnm zTu4Fy!RA58Chq0|OhQBi8;&-$xH62UI5*KPf)fjTA|_43U9?EgD{@^2g>WTp?+y3my27G|h;V1mWC#@c zuwXgZALsh2>CBqOW$K@4lyO(x?U0`SbCaNanAGNG{rit9HbhLKJmN%_C*$p2dA;(| z;4|bH7 zUAY|Y4E=KHS6pPU;~K-uxW@46>uX6Dq6itJi{z-Zz9F@kjdrGIzmtDx>^S<oGQhA)0Dvj^m{W?CM8gXp?s13+vbzP6NR4@ zeo=U`@bNic7y1jU<}8|Xy6{Zl*~0UMfx_<#FBAp~e=L+vl27`%@KWKGLgl1T;kCkW zA)6$g)VnORtlpAbrY`GKrYlP?)0geA*e!`={mZh-v}HRjFI!T}(#ralab*pb7c76V zyl8pN@;6IL*`TslEd$C1mZ{2mmGvvzYH6~(X*q1Usq9~tmn?5s-nRVJqAANL+ipoJ z+huvjve)v8rP1=1^vR^oNhw9Q6-}L7HFpemQQ}I=)$8;k~1a4rp+#~meiKKS;9?wwM0Da#gc0!%4u6lwwF|wJYDk2@LyWOYWXCJ8MqPw2Ni&v#V!6IlF$gX3majUrckCeLj2YoME$nnP$AlG<(hL zH)hM`Oq=%hv?J3}?|Jo}Ii9JtOCo&1o;YWA@)?&!6VFM^UDi_I26pdlr-iN-OWFxrZx#xzt@cY&x6quhLNIgR`D49WcFoX8!aE zGs>qwI{nM(=clL57(1h6#+g}drNPqF>2=fZoc`ua=gh>?+otcCv25nz8M3nTGnCVJ z&DuBppR*3l8a`|5j9X`|oK`pUqnXNCYp1u*TsNcdtd3dpXM8?GF|%am@mYm4SIrtV zvuakutX?y@nTKbDW~e19NupdWQ^}GP$%?q7WOV{=eybC-35m(tq$IUAAvq4mFOs!t zwMwnh#%Yt4N^O!_S@@z_spazV4cda-k2YP<98?Fx$r+Q2#2LzPa?q&e?oq0_pfOl2 z!?b&fnlTv*R=27As!LxpHbKgmU-gZx#dgfL#`dHwlPTE*re>D``Ofx@Z8E1^`dvSz z)N+JL?|YfqMo5##MBY(tN-jv=Z`m(#jKrY~gN?K0vKQ<|#@UA1^lT_o8%sG&c91K( zc9X3i>sPJLR%c%^Di2v43fq-$(v{|>{>tzNS5}4WS7hmJ#moCG4`(>8WjKB>>LGLD9fz@d{TSt=W3XJ(eMn56_*$_18k zn*Rh?-!RiW$~0P*GK7`Pw&6a@$ScWLyw&wNKhOHBa_8jzwr|yEs?Y6-?(enoJynsTZAB&&CfbJSj5QOggmUooTN= z{kKQ=Jbi3cQ~a*St5Gog1llSKIeA&3eB6lUYx49Y@!i?K!JwjnThb+&Qt~4EB9V|I@{uF5-w`=&i`Z=u zzb*2KBjR)bZnK$ho=UW*zHGBGbFI{zZ?`Fxtj0`kKqAW>6l}v|X2op#WR`8*FLJa^ zci_Fo?AR%DJf(F!Gu6CH>sTvu*vteMLK)k+m9g!R8_QrjGemfI83Hnnu`Oipq*s@* z+wHs?wp~xkdb@~`I|k(KuGdccW;IW&3&J}#Kfm*Z=S7>J?*UEe;b!~_s?@&X3p zaCDh%pjXdcAb*b>b_GWrwAn<4$0Ns1JH7vRCdGaoK77QAmKTM3xJJYaNk9Soe{SmQ zcQ7p;%VZnCmS-Gwc)~h)4rJ zZ$MK`Ra&jqP$jydc1MvIL*WG|LXfZ$)3%K2B2>$=>Eg=MA zB5HcHz!-?t)n`!VU`FnYxkNEX7go1pOG7sms_eX+sD(2-)j|L?^d1gg?dpw4d_#u1 zf!t_Jj8A7HN`)N2Kswm-V;guX8Z}9R)zvkS%?k*9H=&|@)Ifus0olU8#jb{@hY_hk z3jSKYG@hpx4EZrNaj3SVpkVAgDMG`z!FIqVvyJdz?DXnskdD2l|9ZDt=yq!PxZC;n z^T;Np)`e(~Qmz$XJp;OMdZdd=6E*2|>GpJT;~=b*hpE|?Ej_D}UCkjWNLO9U336m; zvFL8q*lUNO9B)GnBm*Z5Dh&2yfF%JzT3s*Fa9{!RXECr5?_wPEJIR#vr$y=a+}`UOpvME{-=1nhY*OLn8;r zu{yZxVdKMpiO1^=G9cn>>~N|JeCro#M_pZQEs2bm4q|VGi)+|IqEM?FVC{sxeFKEd zsnzua=IZ%oNr=QD%$6-1H}a*? zF3x7f6Gvwb0N&`#(F1@ujj`#)0}*rD=;;FoWDdju8=%XIGc(1b_?Y|)V3vKZCadOL zWaaLaFFljv`i{9g5rZ@6$W$_Qfg0P_yJ~jTL|7@qBF|RiuGuB}?$e*xHxc&_>>GDv zcGhfsGS_T>(H7ag+pv3gWbNDB&b5&Zn7wZ(S;I}{)cdtB;T#`0Os+N`xTk(wX4A8H~kEn`|*BDR-T*j~2fXbsw& zh*5XhsEhamXZ-$1mfSQ*8o729E!W0g6TOCe6SY!K$+1$gxtEzZFF^0M(L6=Mc5c{J zQzK%3XXlt`O3RbeYs4Fs>U?!(fl``j1&Y+bDesfkN;Az1#S6_5mbF4{)~fUGL%tz1qHMO6s-@ytgZ79s<0h}LO^(a^#-2IZ=J@C{=SL2;<7eqc z#y&HDo*;T-=+>ccL`IfBXTIQn`t3chZ#nbLi=Q~_v)?XI){J@ZLFX{ziYY&B|ExU{ zVb;K%OWBVFLkb?KTv5AUTObxz?OfB-=IPSnr21SvWB+mOA8BgaibF%Dvgs>IUoB|L z{;=Xx1IyH%7qcMuh2yk%@(iUpz$u+akM94}!Q$;Kp5v=5UR$@vTs>{-pUF_0;OAw$ z4{l^ZFhWh|-5PYaS~TGicKdL3#+FT+qmdD~13hZ&&Eq0LbYB03spI+CCd@bZhDBWq z+8D>b=@l~sS8J&~VRLJCw1h8$fUtrpExO`|+dj?jegD3Sp08A$z>dL+d{0s=lo z$q+s<8>8Fs;TT$|Lx~!pr{z66t{dEA2-IoBkm`2StJPb!H6Ru0XOMezFWoF+0)XzG zguO|K%?I(3A!V=?rK8?3vW^O?fqH$RClm1Twr60>d0w!(fKi8iYj;{vP+h%UgHg4C zQKzfkyqSgpdn}#?6GcF27{#Q9_r!t-9TfpJfsX+z7Q#_^fDbh}V!)Uk1=#EAkRkpR z3)!-H(+iu}_WIho=eO5CU%TUlO?CCPnz}kohKA1}>Ieit(O}R$XLRKy162&*2EZt& z?gfoj{la#1+}ez-86;*h>P?$A4c}6ife}f)NwaB_TAhKC*a8;o>blB%M?=Qety|Q1 zHmf%S4KFHcsz{6`XWiMs0IPvWGVoIK6Tl3X!C@rhIRNap4G5?qNs=54<9JJW5l|0V zLdsE-3Y)|mlW!Ev$BvQc5ys~-S_+5*jIQt2x(z^7Z{4!Nz7bu!nr+&IsXK*JBz_9R zyU5qWsJCKj#q2!9UdNX{c<5#j@zr%|`=%|MHCu=wMqj?3zyU*;@YCC_(ePdk3>h1%myjJ*vaDf zH)!&S*c${6`M5-B1ES)f9OZ!$8jfjC3OY>93t)g65lSUR&1GaDGa7X*wN)4m0zeN2 zbuvbp0nV2Zq6mRK-h3R2s=lsf!Z>f_W<6%>k%UZ=y(BHla8-ZbU7$Gka}qEhI!}$UnLOG%Zv@2w_Vd?W{Cpj!MyF z7R!oRF=k&Sm}F@%U&54&Ws1$JKda0UvHD4F4dded(qgGFIb!d!ds)P`xn=FnIMJtt zqUnrfl8QrJvjLB7=drcBMALVd?w%fD`C{>xx?-q4%uJ$w`m4@MlHFwx2nKdh;g zX+&+`aw3t4M%L#y5EiXmVrDb7%+35feoH;|?%RKJye4$&2kUPbTcg7@gc@C#lX&@o z0{&YwGB|kYE4*llOzTWo)DF98Ob|^5UMuO z5UPo26TFE9lII03<`Zl?t49?i;USQ|9C6Q*WEzD^-pi~m)|!ipdzr;{hF=h4(9C4f z8*V_LTUCltmkO0|@YcF#JV~!Xy%Q=cK2I3_w}|Vv?5wX7{r1Mpe*!gP2EGH! zlbv|yGb4z`v2+%Xw-R46>#(f2lYNBuI^JJ_>Q&1sA&d&%Pp|ywvp5Qwhv~*@eCI{| zFSD`ib(Y0;Mg12+-Ny|03Z{}x=AX)_{|wIXwqYsX7WEgfRZPaDcm(qrdxPy}ui+8Q zGW55?Bly3?k9dCv{=bceKfz%>l5k|ba)bFHs-MT9Tj-)cZ~mL{5KTdFOkA@32*gz zt4|Gc)tqz9bqdeH6}S@Ozco887<-m8s6`#NeYSn8_pN?!)q7zU2}>kc*+d)>|H$k< z5~h%>mmhg5_Bfc|@kqt!wsEJ8?K$76FITf`)$7;EmlAKfV75P+!T+(Et{Jc`&e|02Lti<1{6RVU)vzUN9X$mG`~)|!1S<$oACx=)5m#pGg%Xl3*vd=`-` zS1j$uC*D?m;JVj!@42~FqmiwO%1JI(pjbsLmhZISt(8H)pRMFeeHBW+_2jkcYDTF! z$}3RvX>rL41xu8zlL<0tUA0;wVL9I!+sBPdKkoYH;KLvO*|os2z)|5}j{Oz~NwFuz`^SCrgzJ*zl0>cu zM}Cb+5PN*Z_=@a`yyba$NRP2Rwmh#Q50LTqkLT0-UjDs2QGP9aO_1p|7QTiWlPLfG z;P(d~rWj%-6UWPX$LY}{lCiN0Td{q9?UM+(KGb|esF*E1lgq@TFc!(! z6!x?8o9McCi1guDth{}kY_VS$|g<05RMroS;(^( zW8rJ+Nv}$o(a|I7f8nfVPonRs&OVea!5?6ZW)^h7{gV4AQxi zzY7P-e`1uFfx21wRAvl!+?lZ-2^5%D+uS6f%1Qd9U!tME)R%2|T^ zcGY91bF&*~yR25;KUy`>^wPbJ_xh~ir4&y7mDbf(AHo@%&Hg#N*^2M%IzYPo_cq^K zXKe-jXt=f6T4%l6Sc-P^X6dS>ZzCe{b7#&V z-6Hvxii)m0F3qvB&G+5c!*66+I$f9F$YvN_d~RLHtr7iacYg9Qe)Z(`Zum=Cxb#F9 zHyCds%Wd6Hth1snkRE!4Mcohim)p$yglD;p`~p_DD=ZTKDm-BkpuCB&SRye$;nPEK za&#GZuJcFZ*I2us>%+&fDdQ&epU^)uF%`SjQWh`mr5ce@p0{s#dEUqjfo=`Uui54L z^4vLe>gOU~p4;Wx>BIBKcQ?l|f{AN={s>1&^#-R0bM`17-;ul;;wWJXcmws`}) z@CE;_i7(&LFqh;T3xC-o;P*`bRNlpmi@{U=eGaQ_scjEWete|E!M@)Lf{zYFmwy( zU-S{u>rS8e>zO|B*OT8^{8)a3_*qonzI^}7FVAH~2%lZ&f8W|2PBp(LzXE^V_&wVi zGQvNUpYDf1v6dEVSv{I>Sjc}j{*A61<5LcL#utTqQvdhR>$xH7l;7^>dhUqU_0&m% z-Pr%`B2voB%F9Q4SqHqfB=@%f~_(F_sd?8$O zL382q1tBLyu{&I#*BvgT*Ub-wlku}X<~NI*N}EapsWEpTHH6;;2{Yjx=JEI!`qQF4 z!V~LHlY_z|$RaH2-zGfW{b_e}rpwCDx8~=wrTO{vVa9y3*_>}=)dl(a`32R?YBn!5 zo2^mXOyPIuBOIZKmQP9@GR&%knVik?3no`iE||nX1;)zq=gtONKD%$)w5d}kO+tJ! zMm{|tkbm0LF=F07xd1ANe6vxEa1+r(hs>i3>E&l->3IL_xllOdXA$eHpuixcFP7>! zq_>PT!cZp_{%Lw)VIf1Oh6MmV_z7`5Oe*bCr!XstLEv7^ataD`v;;{?Oij(oDques zkST)tkIYs~`fZg;Wsnt3AhU>l&8O2JsxB!E2o*eBT3W!m(pSG-Tta$$2j?(kA40Zv z?A*B{-zblZi%X7+W4BD5nx8-Q7FJtatX3Dl!2Y;-^XAUYo0u5RnQ?dq49y*ze542xW;*TJwW zH>MYXx|U1?Dp{DQ1&6?J#3q=im&MTeL7D;yt!H2qLD1gS2o`!J9tDaJ!L~)=G1Xtx zFC+F(bl`rkRa*GK;-hMOjW=L63{^5C*6ZXkc)#D8}+Bd93p79I9<{ZTdvnBqLP3$>ns(Gbq*f>$M=XljHdQ#H)Z;I|CxpL4cnWx(M^&|5= zW39M-9UL~+kT+~B7Z`T$uo)@omX!2iTe3yX!yg^~*ziXzn&(c1R-Rf}zDPTKxW~#J zXxICfSFe6*!J@lQ54gL{l=Sxn*3-J?>cGlV!#!W|1aLQf9{zhF6~C0CZ`WUb>}>LC zi=4$x8ZdYMx9y&NaO>ZFBF@@2tZKOK*jfDE=*m-PCAM~ba5=K3Uww)I_)E$*-8*m; zd3JbOJyz$?N2?J8gprouz@463cKQdpZhmyw-JTu8AMtzzR>P%+qZXc2SaSHa#}*A= zbyvY^{3?$A{Np62q zRs^m+#~&Fs&PLKUCB3l#znU}bv0;yr(8VTYQB2;|+Totf5V*&$|4pik{F$gh19ZFl zF3edtapAOuzhC&+!nF(S3*TC}cj2Lh7Zz$CFMYhv!qUgvT&o^UNnfoGJwc^#S3#TS z_^|jY3BI0ZJ)8N!Hjft`a<-Wgi6eUR-r>VhZ-$W*yjxb+=o^tcfWR=(?ExHXkW!AK z(AD}nAo7*zv9e*0&TMn7CS(B|MdL%u_@I<(+H;yrw(lcrMj}bo#=> zjgnACp0BK@hb3Am1<$9H268cE?gCfdg9f}tfjf&S5Z!7+xcvta(qD65)aY*4&v?`7% zK2e-de6ILX@s;9~qD^sHaYk`gaZd5ABB1zA@x9^)#RbJh#nh@hs_t5xzx2@3tYuTH zrdIva>al)jO<10}yma~4<@YULy!^T4FJopt75AcWpi!<&RO`5GbA`=8qnS(*pPbs? zU@We-)0B_N;}W&Htp8Wl)rGiGMd2jP7Ll@UnoV{#KRbyT(@Z8anVlq5X~h*(6vd|l zrG3%|@kK-g&2~4t$^LjdQ!Eu9WW|?0#A&QPl!Ax_Wga5rr9#0cDdLmhlW8`8TeRbM zCt13k+@EvK{m!}Po_p>c2y9k=^{vkw&f+_z*+?S)EPc@W)AwZcM0K`0SDmj$s{%f5 z*G;*7f7gA&9l1TU)5lXpi%{rJ%`$GE<8RsZ?b zz#A8jscY^f>IUyOdN&XJ!xvYbPj!=asT=5B+wb=S!`W3c&s5W3c~fNur-%BgDYF7Z zvRQx#^iW*oz#P*|o#jl8=^iaZQ3px|4|)I??~lr$277!!fpVECP%e8kSE_j6if5n# zdC4-^GSrLX538`Gc|K^K58BAnDtQ)4oSp~_Phfb0wpntR)-h#=mD;9pNE;(>{SChQ zD|$C@e`UWvlrCC&0a~X_1(WJ}L&Gew3MzUFzE~x= zx@;PNX_ldDssc0^9t*=Nl_to`6{}?Eh5@V&tfIhou6zVhw#tZ-ZkBpu*E~GEDQtixp*p6|>_o1arwa*)bSwLE4Jh0tpm_j%T$MV&PU~z$Xop6=&fn z#gp8&m1OZ42~h$L=0awaMKI4vvcfD9rPyN{2~Q)zq5>sC6pBy;lVJ8FCE^4h7X?}v zzkl%xk<-_@zMf;wG3p-cHOv%C4XhNNv9abmzvlmIEpT=H*lRdWGsypj(~Q|MUa6V1 zle}UnvE-6Sy_L>Z|HRod1AuT!-d>jvd{eNPY{kfv6%TyrW+GuH6C^_A+JV10yt~#9 z<|I)Xfa*04RWey38Z(T^Qd$~JA^l)BBW4DpLuW)=Y@$*#ZKru9TNKkzXW&d5&YU^R zo#C!<5Namf5(#`!YKo$r7AYg)Tn~5FZDUz4wiLTq`L67-1|Ger9_nQkc+;I3hu3%d zK_xe^%Y0Okb6F_^GZ4zzf$hmgwyNrSt75CLHBF(NFg3&EwbyAULe0pey-rPRDJ@`o z!`G=r!>4Ks*!HG?CIT^d(9jE$9f5@=K`6i#dnng9h*yx0>@Og##Hsf9=XK;32Q5Mx zp4JHZ2#q30BR<8U6+SPoBj1}f|J7RH`x@W$FLB9DuDv}+>f{`yEjzhR*2%TA;6Zym zmw%xu&YlcNwkjyZ9rvHuWfK98#6hE-Y7(oIzSj@14`@4Sk%D-xTu zOSHR0EJ?dbVuDEw(QtkGhX3!4!1J4Yg}uh`)`oMVIJ~go*m_Y@3rb!F(+@a$Tft(L zK{tR4-)(khAXBhHsht}*UEg(OODU-MtLkpBrB6mxghXn~eDnu(>#?C?M?q+mfl~yA zhmm88ZN*FFCL{9T|Z~R2{jkbc3U>$$J~Y`EQCl-n(cR-#oH} z_~wx%cnjzUv0suScKC6E70F3YNCieSoh*)(ci|z?_`Nlc{D|OUiXtzFT45yLBrBUk zYm)=`AWtBkT@Xn~yfkG<0$OnU0Rj9DzuTH34LF;evPsVJG~MBsARiqCZ*RI?A2QA7 zJcw@R9xPGL4c^^6{>N0@aJKRDXS3L2zQfz8r9{jP-rvN(#+s<{@u(0x9QWwsv-K&z zm6CD=eLIy9=I7ktGZ6EsR-Elzwvg8~cjinz_9)Pv_9 zX*}m1f9El?+%ihp4Q@O_=~V7HXx!lEM}%q~LN)IOZ`5aQ)?J>}x9W~}m96ZaE}y;x z{4e#HJ9YOH?5#7tTZfO_)oq;$B-1I%x+mus786V9%wZpT`XzHNwbJkhCSQd84=SH< AjsO4v literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/mem_timing-2/mem_timing.gb b/playing-coffee - Copy/roms/mem_timing-2/mem_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..2665aa2d4f2b613b79eceef1a3365d238f919bb8 GIT binary patch literal 65536 zcmeI3dvH|OeaFx0v0x!BlA-FgOyKIVtF>gA#Sfu6>uc&bFpkS(oYXHe@iInB6$=Rq z8LdF91p=JbjRy~&)YKV@|FNgd3^Fr~h--%G-YY4-T_4x9w!E1Jd)JMaW<#h}%C(Jv z-G0y6)e8{)r(~SY@6P_tJ?Gr>{G4;I`2Bu|bo%#uv9H=?7F53f+hFj$BDNrRJL_SE ztccAoS@wg!WUgPn*K+pEnO_`!=bbH2x46!|cjlLe|L&c)@6y(ObL~^#d1&oJ>(&2lX@cpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y z5g-CYfCvx)B0vOg1c5artiR^wCF;e+Md}wJll5yX=CQQym`6*;+*(Jh%Cbka`LRFN zF2_o>{@5+$o;046YB_JHm*Yu`Z!gDlk%4EnwvcyLX~+5ZtCA=e4o23itZUh_E1vgH zJ#S!yFVcEqi$^_Y9qNQNh!K^!d^MjHMxOOv(5*p-ulTdV zeD$J;7ORud^-ebGdEL7(JBIf~A9?DzkZzrLH=FmN$h%KXT7x57w1uwjs-gUd!>wLu z^`xCFvcsEGFPreB^8V$e3Lq%cF2!!eyq01_w_-GT>O}pjdX|TN)X!Ij;<_clT*^#d zOcI51)Ct$BBJW7HBr1(5|31N@XN%t*T|4gsrC=%^9Uo2Gy|Hv`q2`M*%@JE$%#yLR zHOLQ^qu0e~utf4e+;QY`uHR@avZOoM(ktuEahpNMe6_biY5gKXQNMet&|g zqx&N(&-y-J_1IEYmNJb8D|uNJAFSk6Rc2WwFLICWJK_B%?j!t<+>bohq6RlLH8*Zn z+Z(sHZ!chRk=@j?xoJn!=I1swH+QPR*0yZ+*4DO0b#r4oDr~<4d3;kj50-;giI&9L zvDhM4zVx-ZckHOJgvvfT-GvMb>@`B8b!Nym>_35)WQ zDuocl~VItqgtkrzMX`d(@tKA9Vk9(7%-k*C#;mM-r%Ie{K-=$?f z_P*nTcbVKnpJukt=Q1JZHMODaF5XeKTRgYq5t~_or7$dIcWh5=Z>-0%2R|Aq?@6z& zTUYmJ-7(MnNJHe7NZB%9@l&Dmzg^RL{%7lVp6_{V*Ll^g{>qcCXuJK{+qcC}>q*N_ z6%6QA{7^Nbnwq^A$a|cZxqoddxqPCk=7f3yZ{?r5dC<+bx%mz^R@t(7mwU1fqRBy9 z=a0B`%MK|ccBc}zcPeM>UCJ-)F6AS8w{qGZ453)3uKe5{eBfba%pQDjt#Xd*Rd!@0 zKj2QDU6?9p$Qhb9J&242uFY?M_?FvMz~6EY`7jua+POUMW#6Se>qAI( zTJaIPva$x7V0U6qv@5YUn#5wAaw|D$tk6GJS=k-AVbk{Qjhk6qW9B)}M0eEh&A-q4 zpUOh9LJzjKG)_L*6K$Q?8{HB<;k^kp0RbV3r_ySU=0H_@w0pTdx(Bis(z9GleM;)x zNo%zG9rfnXJ^!Q@jqZJ0oj=<1Gd0)jtKxMYv(Qt6`HHReg*Co{#nzlncypXAlweUu zcwTvTl-E={o@Zhw)>LEL&x0(4q#>S}-%tK&E#FYh|Dl?nmap5^#;s4aH*IZd*-{{0 zpT4TA-V|O`DQAveZGPFq8?ZxTwdQcGjrk>0j%a3Mua@sD+K?MQlTs_uymd=>rSwv)`&gP1Kj(fTPo}E`u1S?_C;%x9dV0yR+|CupkwL9+|Y5abM(Q~ zLDkkR{;*em>ITkLW9xlVUC#Ep7_an}v%hksan}|n4oA_!-$u1B6R$=2%UaenOg4&G<2oOJTug z#TOVlz?Rf6X=n|8ofQ@q%5ttHsJFz$wl(11u#K^xKkX0Vk)njrVCM4q0*HjODaHZ; zmg6i$gR7W7*s>!S^fN`&SHeh(2VoX2R0^HQFI?hp4x_Jdpvx)J{y?Bhln;wyJ|D^l z#ZTNdXJLcrSNyVf9}~U!6fstGk00WqW~Lv^7yW;~C7>|{4TOV1))fqjAMyi%0NM>T z2ZO=p5c&^v1p*kCya$*T2(+L}hNq|&WPUMx#m}7LLOs;khPs%lxWa7cd}?@TXov-O zgu|#P#*51@`$GYl-qBny)96z4?-%U=qPRpqxMY7pzh7Z!YdCct?FIc}=;FB;LWT?a zTPmw9Wvj&OojEKd(;-&e&^$D?4veMx1HNq1@6^z~#EbbeK{_Djqse}e4u+9l;_Cu- zQ3v^&6HI^${VS{iJzyO{AJ;a(YZmiwkV!wvpA!IZ&y*MKiTP*JT>;r18Wd^418K4G zu#SR&3u)1fmbqj83V`xwhP1vvnD3GySz5y%^m zW02L5b&y9P--84pEs!u|3{n6ogVaFog^cF2+=={jUSu`PjjV$_3i%!+2x)=rfP`5t z&+Q-KuNeyde|^0Cwtha*e`=t=zjAl#b#>hQS7T(&y4u>>np8y1-N* zA>J;dbu8YQjlyjSPpoLm0VhvQ;!pIN#J!NO>myFaD|EQL2XWKkxylQr3e+*Ur{In$ z?ALBO2pYYhQ~$gpKV1S!&%?@U-*n6ATe|jJrZR91=O*yor%pOIAJw%F1yJEFhja5g z`fib$@>58otqlGTotsB=e)mAextX~a$aI{4p-boH$9mSexzym_F!)-7f7{>*;beKj zfQL!eK~y?7mmAl2Za!gL<=i}?3+Lw3#!Tnt-2=0on?E$*-29;d=jO-yEa&FO`W((p zv3kO}`7#`030@H^-{y91-Z{YU8sIAi_%XSAeJkqP8aHj8>EJx13kT=Z zI*tNhS8=hA`0M>}aB@>RI6pB_J<0DGFzW|w%yH7eIi}Baa89PAgR@<(`xOq(G5s12 zPHtM_GTHq%=5s`?5&eLfRnP zA?=XoARQ3s=Q%e+Dj;`4K7+)d<0qiwOCA34X&t@~I{ZGN!#6^Q|Ld#{ALgY{d3rOt z`wAJAx_c&?(cMKG8Qp!nzb5fJh}{p}eUN|Jucz|8&Q#i)qhyr#-%Kg*MF)AF4&6Pg zx#tVb9U2s-2m6C%4x86d-tll+d0!-ycbOxV_Yx@YNZnZFeWT9D%#8A$xfjSZPW5J# z_g5yB_y5p2GkAf)iw*v!P~J<0@-FKjDwX$h`t_Cf(yNvCjZ%46XDRPv=4|EtTS9sN zmQdbbnXSCPGMDl$mQN_}>!7^93FW$H(#LF_b@;++vq{;u=$|hAqphjE zai;dZL2B=+(B84FxY$U%QE2ZkX0-Poi~31EZkoR{ZA@}fd+(j4y-%j3_P#>#bM8Hn| zs*EmY@m<*{3=E;^+)hNf%ZQI~(M!h8@FdO$Y=nGQo)LIh=lALxAdknb_55M|?S64k zpuz5RvJK*(fIK0fl^o>H=)GAp11L%}gLbVm0(dxWX4omr3^GTW8A32KAa!HS42I61 z%$^a*+zVtn&Tlp38G(zFW`+k0zQy3r8oWawf|+5rFf+(Hh)OfVxBr(j0=us^GZ@m$ z@WL!J!;@E>5jZZ)49A6;;o@vF!^OGG3}W?!nc=4}Gq~iE&p0D6KHJPtYD}FGz>}F~ z2A2T{s6TCH7{A)gfT@e=OIbPB8G#czzr!?(%o=W7*UYeD@{GWW>z@&Le1JbOz#kjn z|0XBryQ^+%>*l6l=S(}p32A3|LD(6vuejJr{3pWBFrKk9>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1x zKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1x zKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1x zKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1x XKm>>Y5g-CYfCvx)B0vQGpAz^V^e*2# literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/mem_timing-2/readme.txt b/playing-coffee - Copy/roms/mem_timing-2/readme.txt new file mode 100644 index 0000000..f6577c1 --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing-2/readme.txt @@ -0,0 +1,114 @@ +Game Boy CPU Memory Access Timing Test +-------------------------------------- +These tests verify the timing of memory reads and writes made by +instructions, except stack and program counter accesses. These tests +require correct instruction timing and proper timer operation (TAC, +TIMA, TMA). + +The read and write tests list failing instructions as + + [CB] opcode:tested-correct + +The read-modify-write test lists failing instructions as + + [CB] opcode:tested read/tested write-correct read/correct write + +The values after the opcode refer to which instruction cycle the access +occurs on, with 1 being the first. If a time couldn't be determined due +to some other problem, it prints 0. + +For instructions which either read or write, but not both, the CPU makes +the access on the last cycle. For instructions which read, modify, then +write back, the CPU reads on the next-to-last cycle, and writes on the +last cycle. + + +Internal operation +------------------ +The tests have the timer increment TIMA every 64 cycles, synchronize +with this, delay a variable amount, then have the instruction under test +access the timer. By varying the delay in one-cycle increments, the +memory access made by the instruction can be made to fall before and +after a TIMA increment. By then examining the registers and value in +TIMA, it can be determined which occurred. + + +Multi-ROM +--------- +In the main directory is a single ROM/GBS which runs all the tests. It +prints a test's number, runs the test, then "ok" if it passes, otherwise +a failure code. Once all tests have completed it either reports that all +tests passed, or reports the number of the first failed test as the +result code (1 = first). Finally, it makes several beeps. If a test +fails, it can be run on its own by finding the corresponding ROM/GBS in +the singles directories. + +Ths compact format on screen is to avoid having the results scroll off +the top, so the test can be started and allowed to run without having to +constantly monitor the display. + + +Failure information +------------------- +For more information about a failure code or information printed, see +the test's source code in source/. To find failure code N, search for +"set_test N", which will usually be before the subtest which failed. + + +Flashes, clicks, other glitches +------------------------------- +Some tests might need to turn the screen off and on, or cause slight +audio clicks. This does not indicate failure, and should be ignored. +Only the test result reported at the end is important, unless stated +otherwise. + + +LCD support +----------- +Tests generally print information on screen. The tests will work fine if +run on an emulator with NO LCD support, or as an GBS which has no +inherent screen; in particular, the VBL wait routine has a timeout in +case LY doesn't reflect the current LCD line. The text printing will +also work if the LCD doesn't support scrolling. + + +Output to memory +---------------- +Text output and the final result are also written to memory at $A000, +allowing testing a very minimal emulator that supports little more than +CPU and RAM. To reliably indicate that the data is from a test and not +random data, $A001-$A003 are written with a signature: $DE,$B0,$61. If +this is present, then the text string and final result status are valid. + +$A000 holds the overall status. If the test is still running, it holds +$80, otherwise it holds the final result code. + +All text output is appended to a zero-terminated string at $A004. An +emulator could regularly check this string for any additional +characters, and output them, allowing real-time text output, rather than +just printing the final output at the end. + + +GBS versions +------------ +Many GBS-based tests require that the GBS player either not interrupt +the init routine with the play routine, or if they do, not interrupt the +play routine again if it hasn't returned yet. This is because many tests +need to run for a while without returning. + +In addition to the other text output methods described above, GBS builds +report essential information bytes audibly, including the final result. +A byte is reported as a series of tones. The code is in binary, with a +low tone for 0 and a high tone for 1. The first tone is always a zero. A +final code of 0 means passed, 1 means failure, and 2 or higher indicates +a specific reason as listed in the source code by the corresponding +set_code line. Examples: + +Tones Binary Decimal Meaning +- - - - - - - - - - - - - - - - - - - - +low 0 0 passed +low high 01 1 failed +low high low 010 2 error 2 + +-- +Shay Green diff --git a/playing-coffee - Copy/roms/mem_timing-2/rom_singles/01-read_timing.gb b/playing-coffee - Copy/roms/mem_timing-2/rom_singles/01-read_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..660298b1ee8ce2fd1128db5921d0312fabc30519 GIT binary patch literal 32768 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 J2>dsJe*@ghw{-vj literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/mem_timing-2/rom_singles/03-modify_timing.gb b/playing-coffee - Copy/roms/mem_timing-2/rom_singles/03-modify_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..7579c42719bb9dcb01556eb82204d93ccc482614 GIT binary patch literal 32768 zcmeI)UrZdw836DP4iJq#M3BqR+Uz!G^^fE=RWy^|ethQ>}w2~LqETz31 z1V`q8<9tAxxP7RSm&!O&%2TB(QXC#);<$3TKYL|`$EvFsZKROisX3~i9K;1}>|lD= zZ}zY&Reep=O5f=A+nwE+`DT9mM}9G-(wAG{f2|~Yyw^X`^=tL8&2<{4zzy|K+pzoG zFTwNSwb07)@(0sbuJrvR!#_Ovy`#sD9*^OYTu_P8 zH~!w2t4Y~NfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zz6gOM4KVL}uu;9cvtGR&PQv`U7rHz&62Cjly5-;iNZ0aw+cM}~)FXp*5-hR0;+kb%qavJAhL;Wsk8A;a%vcuR&$ zGF+7*EyHygmSp%yh7}oBW%x{nA_n1_&xt=K82(TDtas16NYA&77vEHGC)X3ADL#6& zMRko`UC15ZdwB2Oy}sh2S|tu7qGCLe6AvYxg6ya`?;P%j?AX>}J7mNEK0AVk!js6B zvs6IV^yQz6`|>}+?Z@$)0^*stcq)DZw;yE9ZmeCNuL>9II_(h!PME?ykA59zdBdw{ zcM<;{fX!V}aih9qCV6V9+P}Hm57#UHya49hg3bRbFFvh)=2S{zG|t~IpDCDjM10)TwF-uAy>Edb6%7ZPGv5}7u#-s^*h1;dVu?mdB5p4p5U(T(E8CH=GF_AQCoiOIgSl-3Pi*s?y8QlZ!c!+^ z6KOwA24}78uX@eDInf=)>WG=m*)1);e9RipPgs%sq*XA>cdrCl@lM>txyrkLqUZGK z-cyj}kX%o0#H>JD_0w&CW83AmLj$4Sdsil`fsIM4FFM!u0QN)#jM%(bQY(1{cD2`H z`|Y&}+$M3G+Ao)We>;#Um}{{s>Vs<&@2T}`lb6-nwW)ViS8^^c9!n5AdC=!EHNk_(o=0c?`Ew=21SJc|@!% zh-Xt`HYNVzEDrx1#q$HF`t^~MFZMs*AL^^C!?n-F<-_+%oIB5zkWZ3$bKY4$=C(yu zN_@*Gh`%o+mlteYWuaJMsUGQZxTinZdkV)u$$K@m1&4HA4s)BkibHd~x>>D$?zA`> zda-vX)Du*DhlU1*P_l$jye<#GtMXG8pUJ;u@sIKqGyLs*M-Y z-k3u4m#n^)4j9Mw5p2GGPIt5tk)#7v66mg&M4-{v1@jwJtJ zIHwviT?O+ZtK_kj7*0jLYi$qbZ11$?EB-=#piorX>clz2E?jDVGBk{zSJ_J#22K|v zj$-6CUboVYr5#kZ{77+Wlz%N#72H)Z32*490H8m6s>cQub; zjyu^K_dgv>7GMwz2Axkyuo%Q20OMLfi7?QE%&(NXj(+JkIH6#KepH9Pwu(m?v#1uZ)@)dnZ?iKTroZV@L2XN=fU-2 z{-1?34h#o~>N-SpUHD-FpT-Nh^FDZoV^BI(?XaM@G5rGAt0yE0-(r( z{jkp<_Qj>j5{2|?aVecngMKC&#g1~mSOQKyY~a+-1UsBMrj+>uavVgIMdrcc_ z2Pv(|^>8OI*7YdX8~qVvmwoU&SI`8xFh7G%%z*b1^|1^hUQn*T)2Rlq{VfTA`*M3Z zo?L&q9?_ifa6nm?9$1$T9^OYO;K8~~!^?YIKcK);L_VtyK>0k*2XC_T{CF7i%YIIJ zbQxsKjnl$*+NW=nA6+~Kzz>Z4c(S5t(!cjefCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ K1W4fjN#I|aZiWc} literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/01-read_timing.s b/playing-coffee - Copy/roms/mem_timing-2/source/01-read_timing.s new file mode 100644 index 0000000..5084d4b --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing-2/source/01-read_timing.s @@ -0,0 +1,150 @@ +; Tests timing of accesses made by +; memory read instructions + +.define ROM_NAME "foo" + +.include "shell.inc" +.include "tima_64.s" + +instructions: + ; last value is time of read + .byte $B6,$00,$00,2 ; OR (HL) + .byte $BE,$00,$00,2 ; CP (HL) + .byte $86,$00,$00,2 ; ADD (HL) + .byte $8E,$00,$00,2 ; ADC (HL) + .byte $96,$00,$00,2 ; SUB (HL) + .byte $9E,$00,$00,2 ; SBC (HL) + .byte $A6,$00,$00,2 ; AND (HL) + .byte $AE,$00,$00,2 ; XOR (HL) + .byte $46,$00,$00,2 ; LD B,(HL) + .byte $4E,$00,$00,2 ; LD C,(HL) + .byte $56,$00,$00,2 ; LD D,(HL) + .byte $5E,$00,$00,2 ; LD E,(HL) + .byte $66,$00,$00,2 ; LD H,(HL) + .byte $6E,$00,$00,2 ; LD L,(HL) + .byte $7E,$00,$00,2 ; LD A,(HL) + .byte $F2,$00,$00,2 ; LDH A,(C) + .byte $0A,$00,$00,2 ; LD A,(BC) + .byte $1A,$00,$00,2 ; LD A,(DE) + .byte $2A,$00,$00,2 ; LD A,(HL+) + .byte $3A,$00,$00,2 ; LD A,(HL-) + .byte $F0,tima_64,4 ; LD A,($0000) + + .byte $CB,$46,$00,3 ; BIT 0,(HL) + .byte $CB,$4E,$00,3 ; BIT 1,(HL) + .byte $CB,$56,$00,3 ; BIT 2,(HL) + .byte $CB,$5E,$00,3 ; BIT 3,(HL) + .byte $CB,$66,$00,3 ; BIT 4,(HL) + .byte $CB,$6E,$00,3 ; BIT 5,(HL) + .byte $CB,$76,$00,3 ; BIT 6,(HL) + .byte $CB,$7E,$00,3 ; BIT 7,(HL) +instructions_end: + +main: + call init_tima_64 + set_test 0 + + ; Test instructions + ld hl,instructions +- call @time_instr + cp (hl) + call nz,@print_failed + inc hl + ld a,l + cp 3-byte instruction +; HL <- HL + 3 +@time_instr: + ; Copy instr + ld a,(hl+) + ld (instr+0),a + ld a,(hl+) + ld (instr+1),a + ld a,(hl+) + ld (instr+2),a + push hl + + ; Find result when access doesn't occur + ld b,0 + call @time_access + ld c,a + + ; Test for accesses on each cycle + ld b,0 +- push bc + call @time_access + pop bc + cp c + jr nz,@found + inc b + ld a,b + cp 10 + jr nz,- + ld b,0 + +@found: + ld a,b + pop hl + ret + +; Tests for read +; B -> which cycle to test +; A <- timer value after test +@time_access: + call sync_tima_64 + ld a,9 + sub b + call delay_a_20_cycles + xor a ; clear flags + ld hl,tima_64 + ld (hl),$7F + ld bc,tima_64 + ld de,tima_64 + ld a,$7F +instr: + nop + nop + nop + + ; Add all registers together to yield + ; unique value that differs based on + ; read occurring before or after tima_64 + ; increments. + push af + add hl,bc + add hl,de + pop de + add hl,de + ld a,h + add l + ret diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/02-write_timing.s b/playing-coffee - Copy/roms/mem_timing-2/source/02-write_timing.s new file mode 100644 index 0000000..acd6d9d --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing-2/source/02-write_timing.s @@ -0,0 +1,115 @@ +; Tests timing of accesses made by +; memory write instructions + +.include "shell.inc" +.include "tima_64.s" + +instructions: + ; last value is time of write + .byte $36,$FF,$00,3 ; LD (HL),n + .byte $70,$00,$00,2 ; LD (HL),B + .byte $71,$00,$00,2 ; LD (HL),C + .byte $72,$00,$00,2 ; LD (HL),D + .byte $73,$00,$00,2 ; LD (HL),E + .byte $74,$00,$00,2 ; LD (HL),H + .byte $75,$00,$00,2 ; LD (HL),L + .byte $77,$00,$00,2 ; LD (HL),A + .byte $02,$00,$00,2 ; LD (BC),A + .byte $12,$00,$00,2 ; LD (DE),A + .byte $22,$00,$00,2 ; LD (HL+),A + .byte $32,$00,$00,2 ; LD (HL-),A + .byte $E2,$00,$00,2 ; LDH (C),A + .byte $E0,tima_64,4 ; LD (nn),A +instructions_end: + +main: + call init_tima_64 + set_test 0 + + ; Test instructions + ld hl,instructions +- call @test_instr + cp (hl) + call nz,@print_failed + inc hl + ld a,l + cp 3-byte instruction +; HL <- HL + 3 +@test_instr: + ; Copy instr + ld a,(hl+) + ld (instr+0),a + ld a,(hl+) + ld (instr+1),a + ld a,(hl+) + ld (instr+2),a + push hl + + ; Test for writes on each cycle + ld b,0 +- push bc + call @time_write + pop bc + cp tima_64 + jr z,@no_write + jr @found +@no_write: + inc b + ld a,b + cp 10 + jr nz,- + ld b,0 + +@found: + ld a,b + pop hl + ret + +; Tests for write +; B -> which cycle to test +; A <- timer value after test +@time_write: + call sync_tima_64 + ld a,13 + sub b + call delay_a_20_cycles + ld hl,tima_64 + ld bc,tima_64 + ld de,tima_64 + ld a, 3-byte instruction +; HL <- HL + 3 +@time_instr: + ; Copy instr + ld a,(hl+) + ld (instr+0),a + ld a,(hl+) + ld (instr+1),a + ld a,(hl+) + ld (instr+2),a + push hl + + ; Find result when access doesn't occur + ld b,0 + call @time_access + + ; Find first access + call @find_next_access + ld d,b + + ; Find second access + call @find_next_access + ld e,b + + pop hl + ret + +; A -> current timer result +; B -> starting clock +; B <- clock next access occurs on +; A <- new timer result +@find_next_access: + ld c,a +- call @time_access + cp c + ret nz + inc b + ld a,b + cp 10 + jr c,- + + ; Couldn't find time, so return 0/0 + ld a,c + ld b,0 + ld d,b + ret + +; Tests for access +; B -> which cycle to test +; A <- timer value after test +@time_access: + call sync_tima_64 + ld hl,tima_64 + ld (hl),$7F + ld a,17 + sub b + call delay_a_20_cycles + xor a ; clear flags +instr: + nop + nop + nop + delay 32 + ld a,(tima_64) + ret diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/build_gbs.s b/playing-coffee - Copy/roms/mem_timing-2/source/common/build_gbs.s new file mode 100644 index 0000000..877754f --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/build_gbs.s @@ -0,0 +1,139 @@ +; Build as GBS music file + +.memoryMap + defaultSlot 0 + slot 0 $2000 size $2000 + slot 1 $C000 size $2000 +.endMe + +.romBankSize $2000 +.romBanks 2 + +.define RST_OFFSET $70 + +.ifndef GBS_TMA + .define GBS_TMA 0 +.endif + +.ifndef GBS_TAC + .define GBS_TAC 0 +.endif + +;;;; GBS music file header + +.ifndef CUSTOM_HEADER + .byte "GBS" + .byte 1,1,1 ; vers, song count, first song + .word load_addr, reset, gbs_play_, std_stack + .byte GBS_TMA,GBS_TAC ; timer +.endif + .org $10 + .ds $60,0 +load_addr: + .org RST_OFFSET+$70 ; space for RST vectors + .ds $148-RST_OFFSET-$70,0 + .org $150 ; wla insists on generating GB header + +gbs_play_: + jp gbs_play ; GBS spec disallows having gbs_play in RAM + +;;;; Shell + +.include "shell.s" + +.define gbs_idle nv_ram +.redefine nv_ram nv_ram+2 + +init_runtime: + ; Identify as DMG hardware + ld a,$01 + ld (gb_id),a + + ; Save return address + pop hl + ld a,l + ld (gbs_idle),a + ld a,h + ld (gbs_idle+1),a + + ; Delay 1/4 second to give time + ; for GBS player to interrupt with + ; play, if it's going to do so + delay_msec 250 + +.ifndef CUSTOM_PLAY +gbs_play: +.endif + ; Get return address + ld a,(gbs_idle) + ld l,a + ld a,(gbs_idle+1) + ld h,a + + ; If zero, then play interrupted init + ; call, or another play call, and we + ; can't run the program properly. + or l + jp z,internal_error + + setw gbs_idle,0 + jp hl + + +; 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 diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/build_rom.s b/playing-coffee - Copy/roms/mem_timing-2/source/common/build_rom.s new file mode 100644 index 0000000..0f090aa --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/build_rom.s @@ -0,0 +1,84 @@ +; Build as GB ROM + +.memoryMap + defaultSlot 0 + slot 0 $0000 size $4000 + slot 1 $C000 size $4000 +.endMe + +.romBankSize $4000 +.romBanks 2 + +.cartridgeType 3 ; MBC1+RAM+battery +.ramsize 02 ; 8K +.computeChecksum +.computeComplementCheck +.emptyfill $FF + +;;;; GB ROM header + +.org $134 + .ds 15,0 + + ; Reserve space for RST handlers + .org $70 + + ; Keep unused space filled, otherwise + ; wla moves code here + .ds $90,0 + + ; 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 + +.section "HEADER" OVERWRITE + ; Internal name + .ifdef ROM_NAME + .byte ROM_NAME + .else + .ifdef ROM_NAME_DEFAULT + .byte ROM_NAME_DEFAULT + .endif + .endif +.ends + + ; CGB/DMG requirements + .org $143 + .ifdef REQUIRE_CGB + .byte $C0 + .else + .ifndef REQUIRE_DMG + .byte $80 + .endif + .endif + .byte 0,0,0 + + .org $14A + .byte 0,0,0 + + ; Keep unused space filled, otherwise + ; wla moves code here + .org $150 + .ds $2150-$150,$FF + +;;;; Shell + +.define NEED_CONSOLE 1 +.include "shell.s" + +init_runtime: + ret + +play_byte: + ret + +.ends diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/console.bin b/playing-coffee - Copy/roms/mem_timing-2/source/common/console.bin new file mode 100644 index 0000000000000000000000000000000000000000..b02f2d37ee94c6570a3622734f2d704dfca44b0c GIT binary patch literal 768 zcmX|9F=`|+5G))_B$f+182koBX5hr&r*zbT5qlFY8w?8X8GehSdBVQJIPe4Zu1Z?m z2wEj|cXgKp3^N%Fwbp)6@0AO%)yuRSqFVOjFXoqd`4hj9QtEN1nR)XG+d{u=K$L$a zcxBdDa9GaHO?x1~9ZyqY!B>?@1~S_Jk8EuOnO>t1U_)GPUYcV}PSJ688I zDZyK&1&Z3Yf=R|a=4{O6sIrFR>wY_q1954MiRzbDIv)c%zSNhFNh$uKIuubR9$M#% zQVgthyMM7>6t%8xc-*ZoU#;c~Eo^b8>ieF1Te%=ANe|UbtGOeuN<>ma4(yn>d z&PyDPc+NXzR}RnDiJDL@@*5U1@Exf>ZKHTq^DiA1#(zox`Vp^snt$Nkdp#BumLA0V yczj1Ga3U7XIy~nG6}avCt6hL`&VKmF{r{ZIvhq4Tb=k}k+hUx5zmKoWJ@6mAT&*eq literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/console.s b/playing-coffee - Copy/roms/mem_timing-2/source/common/console.s new file mode 100644 index 0000000..403fa5c --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/console.s @@ -0,0 +1,285 @@ +; 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 + setb console_pos,console_width + setb console_mode,0 + setb console_scroll,-8 + 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 + wreg VBK,1 + ld a,0 + call @fill_nametable + + wreg VBK,0 + 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 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 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 diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/delay.s b/playing-coffee - Copy/roms/mem_timing-2/source/common/delay.s new file mode 100644 index 0000000..cbcdcf3 --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/delay.s @@ -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 diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/gb.inc b/playing-coffee - Copy/roms/mem_timing-2/source/common/gb.inc new file mode 100644 index 0000000..2d0118d --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/gb.inc @@ -0,0 +1,81 @@ +; Game Boy hardware addresses + +; $0000-$3FFF Fixed ROM bank +; $4000-$7FFF Switchable bank +; $8000-$9FFF VRAM +; $A000-$BFFF optional cartridge RAM +; $C000-$DFFF RAM +; $E000-$FDFF RAM mirror +; $FE00-$FE9F OAM +; $FEA0-$FEFF unused +; $FF00-$FF7F registers +; $FF80-$FFFE RAM +; $FFFF register + +; Memory +.define VRAM $8000 ; video memory +.define TILES $8000 ; tile images +.define BGMAP0 $9800 ; first 32x32 tilemap +.define BGMAP1 $9C00 ; second 32x32 tilemap +.define BRAM $A000 ; cart memory +.define WRAM $C000 ; internal memory +.define OAM $FE00 ; sprite memory +.define HRAM $FF80 ; fast memory for LDH + +; Registers + +.define RAMEN $0000 ; cartridge WRAM control +.define BANK $2000 ; bank select +.define P1 $FF00 ; controller + +; Game link I/O +.define SB $FF01 ; serial buffer +.define SC $FF02 ; serial control + +; 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 diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/macros.inc b/playing-coffee - Copy/roms/mem_timing-2/source/common/macros.inc new file mode 100644 index 0000000..9d7cf44 --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/macros.inc @@ -0,0 +1,91 @@ +; General macros + +; Reads A from addr, from $FF00 to $FFFF +; Preserved: F, BC, DE, HL +; Time: 3 cycles +.macro lda ; addr + ldh a,(\1 - $FF00) +.endm + +; Writes A to addr, from $FF00 to $FFFF +; Preserved: AF, BC, DE, HL +; Time: 3 cycles +.macro sta ; addr + ldh (\1 - $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 + +; Writes byte to addr +; Preserved: F, BC, DE, HL +; Time: 6 cycles +.macro setb ; addr, data + ld a,\2 + ld (\1),a +.endm + +; Writes word to addr +; Preserved: F, BC, DE, HL +; Time: 12 cycles +.macro setw ; addr, data + ld a,<\2 + ld (\1),a + ld a,>\2 + ld (\1+1),a +.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 diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/numbers.s b/playing-coffee - Copy/roms/mem_timing-2/source/common/numbers.s new file mode 100644 index 0000000..6d6faf8 --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/numbers.s @@ -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 diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/printing.s b/playing-coffee - Copy/roms/mem_timing-2/source/common/printing.s new file mode 100644 index 0000000..bb9389b --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/printing.s @@ -0,0 +1,77 @@ +; 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 +;print_char_nocrc (defined by user) + + +; 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 diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/shell.s b/playing-coffee - Copy/roms/mem_timing-2/source/common/shell.s new file mode 100644 index 0000000..a045121 --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/shell.s @@ -0,0 +1,266 @@ +; Common routines and runtime + +.define RUNTIME_INCLUDED 1 + +; A few bytes of RAM that aren't cleared +.define nv_ram_base $D800 +.define nv_ram nv_ram_base + +; Address of next normal variable +.define bss_base nv_ram_base+$80 +.define bss bss_base + +; Address of next direct-page ($FFxx) variable +.define dp_base $FF80 +.define dp dp_base + +; Top of stack +.define std_stack $DFFF+1 + +; Final exit result byte is written here +.define final_result $A000 + +; Text output is written here as zero-terminated string +.define text_out_base $A004 + +; 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 nv_ram +.redefine nv_ram nv_ram+1 + +; Copies C*$100 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 a,(hl+) + ld (de),a + inc e + jr nz,- + inc d + dec c + jr nz,- + + ld a,b + jp $C000 + + +.ifndef RST_OFFSET + .define RST_OFFSET 0 +.endif + +.ifndef CUSTOM_RESET + reset: + di + + ; Run code from $C000, as is done on devcart. This + ; ensures minimal difference in how it behaves. + ld hl,$4000 + ld c,$14 + jp copy_to_wram_then_run + + .bank 1 slot 1 + .org 0 + jp std_reset +.endif + +; returnOrg puts this code AFTER user code. +.section "runtime" returnOrg + + ; Catch user code running off end + jp internal_error + +; 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 b,a + and $EA + jr z,+ + ld b,0 ++ ld a,b + ld (gb_id),a + + ; Clear memory except very top of stack + ld bc,std_stack-bss_base - 2 + ld hl,bss_base + call clear_mem + ld bc,$FFFF-dp_base + ld hl,dp_base + call clear_mem + + ; Init hardware + wreg TAC,$00 + wreg IF,$00 + wreg IE,$00 + + wreg NR52,0 ; sound off + wreg NR52,$80 ; sound on + wreg NR51,$FF ; mono + wreg NR50,$77 ; volume + + call init_runtime + call init_text_out + call console_init + call init_testing + + .ifdef TEST_NAME + print_str TEST_NAME,newline,newline + .endif + + 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 + + call console_show + pop af + call play_byte + jp post_exit + ++ push af + call print_newline + 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 + + +; Clears BC bytes starting at HL +clear_mem: + ; If C>0, increment B + dec bc + inc c + inc b + + ld a,0 +- ld (hl+),a + dec c + jr nz,- + dec b + jr nz,- + ret + + +; Reports internal error and exits with code 255 +internal_error: + print_str "Internal error" + ld a,255 + jp exit + + +; build_devcart and build_multi customize this +.ifndef CUSTOM_PRINT + .define text_out_addr bss+0 + .redefine bss bss+2 + + ; Initializes text output to cartridge RAM + init_text_out: + ; Enable cartridge RAM and set text output pointer + setb RAMEN,$0A + setw text_out_addr,text_out_base + setb text_out_base-3,$DE + setb text_out_base-2,$B0 + setb text_out_base-1,$61 + setb text_out_base,0 + setb final_result,$80 + ret + + + ; Appends character to text output string + ; Preserved: AF, BC, DE, HL + write_text_out: + push hl + push af + ld a,(text_out_addr) + ld l,a + ld a,(text_out_addr+1) + ld h,a + inc hl + ld (hl),0 + ld a,l + ld (text_out_addr),a + ld a,h + ld (text_out_addr+1),a + dec hl + pop af + ld (hl),a + pop hl + ret + + print_char_nocrc: + call write_text_out + jp console_print +.endif + + +; only build_rom uses console +.ifdef NEED_CONSOLE + .include "console.s" +.else + console_init: + console_print: + console_flush: + console_normal: + console_inverse: + console_show: + console_set_mode: + ret +.endif + + +; build_devcart and build_multi need to customize this +.ifndef CUSTOM_EXIT + post_exit: + ld (final_result),a + forever: + wreg NR52,0 ; sound off +- jr - +.endif + + +.macro def_rst ARGS addr + .bank 0 slot 0 + .org addr+RST_OFFSET +.endm diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/testing.s b/playing-coffee - Copy/roms/mem_timing-2/source/common/testing.s new file mode 100644 index 0000000..5dd63ce --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/testing.s @@ -0,0 +1,195 @@ +; Diagnostic and testing utilities + +.define test_code bss+0 +.define test_name bss+1 +.redefine bss bss+3 + + +; Sets test code and optional error text. +; Takes multiple strings due to wla's idiotic +; default limit of 63 chars per string. +; Preserved: AF, BC, DE, HL +.macro set_test ; code[,text[,text2[,text3]]] + push hl + call set_test_ + jr @set_test\@ + .byte \1 + .if NARGS > 1 + .byte \2 + .endif + .if NARGS > 2 + .byte \3 + .endif + .if NARGS > 3 + .byte \4 + .endif + .byte 0 +@set_test\@: + pop hl +.endm + +set_test_: + pop hl + push hl + push af + inc hl + inc hl + ldi a,(hl) + ld (test_code),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,(test_code) + 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,(test_code) + cp $FF ; if a = $FF then a = 1 + jr nz,+ + ld a,1 ++ 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 print_newline + call print_crc + .else + ld bc,(crc >> 16) ~ $FFFF + ld de,(crc & $FFFF) ~ $FFFF + call check_crc_ + .endif +.endm + +; Checks CRC, differing based on DMG or CGB build +.macro check_crc_dmg_cgb ARGS dmg, cgb + .ifdef REQUIRE_DMG + check_crc dmg + .else + .ifdef REQUIRE_CGB + check_crc cgb + .else + .printt "CGB or DMG must be specified" + .fail + .endif + .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 diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/common/tima_64.s b/playing-coffee - Copy/roms/mem_timing-2/source/common/tima_64.s new file mode 100644 index 0000000..5b6c3a1 --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing-2/source/common/tima_64.s @@ -0,0 +1,36 @@ +; Timer that's incremented every 64 cycles + +; Initializes timer for use by sync_tima_64 +init_tima_64: + wreg TMA,0 + wreg TAC,$07 + ret + +; Synchronizes to timer +; Preserved: AF, BC, DE, HL +sync_tima_64: + push af + push hl + + ; Coarse + ld hl,TIMA + ld a,0 + ld (hl),a +- or (hl) + jr z,- + + ; Fine +- delay 65-12 + xor a + ld (hl),a + or (hl) + delay 4 + jr z,- + + pop hl + pop af + ret + +; Read from this to get count that's incremented +; every 64 cycles. +.define tima_64 TIMA diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/linkfile b/playing-coffee - Copy/roms/mem_timing-2/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing-2/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/readme.txt b/playing-coffee - Copy/roms/mem_timing-2/source/readme.txt new file mode 100644 index 0000000..a0269c1 --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing-2/source/readme.txt @@ -0,0 +1,82 @@ +Game Boy Tests Source Code +-------------------------- + +Building with wla-dx +-------------------- +To assemble a test ROM with wla-dx, use the following commands: + + wla -o source_filename_here.s test.o + wlalink linkfile test.gb + +To assemble as a GBS music file: + + wla -o source_filename_here.s test.o -DBUILD_GBS + wlalink linkfile test.gbs + +Note that some tests might only work when built as a ROM or GBS file, +but not both. + +Some tests might include a ROM/GBS that has all the tests combined. +Building such a multi-test is complex and the necessary files aren't +included. + + +Framework +--------- +Each test is in a single source file, and makes use of several library +source files from common/. This framework provides common services and +reduces code to only that which performs the actual test. Virtually all +tests include "shell.inc" at the beginning, which sets things up and +includes all the appropriate library files. + +The reset handler does minimal GB hardware initialization, clears RAM, +sets up the text console, then runs main. Main can exit by returning or +jumping to "exit" with an error code in A. Exit reports the code then +goes into an infinite loop. If the code is 0, it doesn't do anything, +otherwise it reports the code. Code 1 is reported as "Failed", and the +rest as "Error ". + +The default is to build a ROM. Defining BUILD_GBS will build as an GBS. +The other build types aren't supported due to their complexity. I load +the code into RAM at $C000 since my devcart requires it, and I don't +want the normal ROM to differ in any way from what I've tested. This +also allows easy self-modifying code. + +Several routines are available to print values and text to the console. +Most update a running CRC-32 checksum which can be checked with +check_crc, allowing ALL the output to be checked very easily. If the +checksum doesn't match, it is printed, so you can run the code on a GB +and paste the correct checksum into your code. + + +Macros +------ +Some macros are used to make common operations more convenient. The left +is equivalent to the right: + + Macro Equivalent + ------------------------------------- + lda addr ldh a,(addr-$FF00) + + sta addr ldh (addr-$FF00),a + + wreg addr,data ld a,data + ldh (addr-$FF00),a + + setb ld a,data + ld (addr),a + + setw setb addr+0,data + + for_loop routine,begin,end,step + calls routine with A set to successive values + + loop_n_times routine,count + calls routine with A from 0 to count-1 + + print_str "str" prints string + + +-- +Shay Green diff --git a/playing-coffee - Copy/roms/mem_timing-2/source/shell.inc b/playing-coffee - Copy/roms/mem_timing-2/source/shell.inc new file mode 100644 index 0000000..0d43faa --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing-2/source/shell.inc @@ -0,0 +1,27 @@ +; Included at beginning of all programs +; that use standard shell + +; Get include files from common/ +.incdir "common" + +; Sub-test in a multi-test ROM +.ifdef BUILD_MULTI + .include "build_multi.s" +.else + +; GBS music file +.ifdef BUILD_GBS + .include "build_gbs.s" +.endif + +; Devcart +.ifdef BUILD_DEVCART + .include "build_devcart.s" +.endif + +; GB ROM (default) +.ifndef RUNTIME_INCLUDED + .include "build_rom.s" +.endif + +.endif ; .ifdef BUILD_MULTI diff --git a/playing-coffee - Copy/roms/mem_timing/individual/01-read_timing.gb b/playing-coffee - Copy/roms/mem_timing/individual/01-read_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..d0836aabd5807ad6781955db20358fd20a384b66 GIT binary patch literal 32768 zcmeH}Z)_9i9mgNr@g*2z65z!{NZ>-+C>SE#Rbv)&Pf-`ai>YLbIRXRC`u6eFLRP)m_eZa|#byq!L{vvf83%nnk0S>Q!n*a3gc$lm>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la z5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la z5CI}U1c<=@76DtGTM8#0>`Jg3fj`eP=gPdNeJVm`{VDbDzR?t0Ozq>=NZs*rqSKs` zmh4UFLdupbiSBf`TX$Y%KM|Q ztjs=pZbf}u{cNDQ`{jK<-8WWQwCc`OE$HTIA(yvl4i9Q}E#Nv{BT{(I6ag10XzF?& z@%xJS$o!kfL*kES{EGUIx+7LhvDFg2mgu*{2d3kRJ=ViDxUCmGOEgW(@v^+?P4bHO z3Sail@lU)Qzv)f!MX#8}v`LG9j5lIG1?WKer zaJ?drdPNLujGegp@$*(JBA&NO2_%C`l>Ke5CmzghykZbq2Zs&F+_lKhkY%-|F-=b<66^2kNHP3s=>S)!Fye zkUeS&*Rro#aa6l8`=0ux^TiX*PixH8Sh*eA3^kzvZ5nEpAJ-Y9DJ)xY#HBfH!)u^%32Y9W4}_at z;ih&TZ>YCn`{MT8&z)jm=U1WZdxOgANaHotvmEie!Wwh$bQTU4@NVoY^sx&uCeEV& zWvq!_+g96wU9a`H+WlIuD=G#4!#?&~rEOxL`rxs!df&kj_>t0yLvUI<53&EOu)3@T zYNI$b2m*hjO{Z(1sOUwdqkHQ+yLZEr6;|<9i@)01>*Zni`JVP!`LG=>`a@mi;kt7P zpMmK+(?~{s2nKtrUf&Kk-e^}h)EnwxL@XA(`sJZ-XC`n6B4PZ(OR>7v>y|oD zgQoU%yUMn2h1Cbz4pdjcC!jSMGnOuvpRPMM<>WI11AF##?Qna$cb`Ly`<2B<-#RWE z<1C#{`=7sbBaMqOuIVWy$C;7l3BB|x>p9SqJY#&9MWa!FUaSY}dSYxWi9R{Tn2~Cx zaQqnOc{psfL_)_64&!>$brw{ja5&6TMrPbFQjE*>JP%tw$g_NuM-_}mds68x=X4!;`Mu6GUC$sS!&59XSW2ePQ%sQ!>#$}FYZ<(tC(e(K;JO+#JZVMH2C-NqJAbx>#`orj|Hbrd|v*g;EhLb zkMD|phyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko v0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvzQ|4QIrKpA>a literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/mem_timing/individual/02-write_timing.gb b/playing-coffee - Copy/roms/mem_timing/individual/02-write_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..cfe7c0fa5716f723c3482f915ab9543679e269cc GIT binary patch literal 32768 zcmeI4Uu+b|8Nk20^Q|xDeAqyxDcG=QC+=ae*bUyAY&2tlfLy>|yIGpI;x=861pMLyX(bYLh6Id z6E{{?-hb`Fg`+>2NZq`=^1*9wTsZ$A{{pdD)Fa`T`OHgSnETSJhkC(E^$c(6Q$OF< zt=`HzV5MQyf#{b$b4rj15CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+Sp zM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+Sp zM1Tko0V43fMZi%Pm*d)nYedsUGa_< z&t>jION4;GxV#m)A`Shu*?s=n9LhY(!aiB}J-LqCw>Eo0-Lf|KuG+mee_l}umK*2YA4EOlmKOj&!Pm?2l!O={&ef2__Za9wzz&# zj9EU9iCq>C&yA2LMI@ZC7WCwcQ;SXKE0=tk@tan&74g)REbNh|$8$#xcR>x2+||@U z|FLJXdSD-BB9lI6JD6Jq9Wr>b_ zt4sZx0{NON&Mflom$4MAD-sWS;{lK3-I5rwy!CkCmLoeqkLBOWC~F5>FRQ-oiXWEN z!Mo8@y5-=({@6*wd970g9o}%OQ@yX*QZrrRrIOz~JMxW* zDcnJ6C6-EG)z$F+win}yUW4PGgMQwX63&R7^Htkjv%5Z&$9B!v}zILstq6%pB8<$}gbrKwtKl`E5uh zli|8lAKLY$U^0t+77v)I2fB$QgiKL}Aa83$V! z&(w7W=xe#PivCPprY`4Y3T-YJZ)a$>6GmnAN)+;;o`;@nu5xc509uxzZK%4!(SL{+ z>jz2NkoE8|F6w3x^**hD*rg4Qa|M|oE{xA0ixF@ikv_IbgqM@`XG2xT^*1E|?Az<5 zKUsfUFBqXebSQPn19f@ua33Xs6zVbzZ}(U~Ac5tAJgXsq_BiH)n;f2>6l0pS3&SIq zNyd_x7OpcseZ75j@f|?C?Z_{cwtr;m`1r{qPn{fpZhYcsm;BiJ)d-)fMtz=;f8{;- z!D%|rmBe>TB7I4I=UMCC^$C5^4-p^&M1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y z2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y M2oM1x@LwSCFNP9pv;Y7A literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/mem_timing/individual/03-modify_timing.gb b/playing-coffee - Copy/roms/mem_timing/individual/03-modify_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..28f1ae66df4cf2df43b3b164e9e10fae46848bcc GIT binary patch literal 32768 zcmeH}|8G;*6~K?}_$3X-B!H$naNwD=Q3zysWo6dpK1Ce@sZ$9*ZBwNfC(@GCcp%k z025#WOn?b60Vco%m;e)C0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k z025#WOyK{EfUPbphZA>qC*W$}Z;RkuT=cXLWyq{Qs($J#O}W+7uD53Dj+d2P<}_)} ze{*mIXwA}0X|{B(H0OAQ8ARbj@^l_r!%#Wk51O!89rN^ z`{7hgeMtSNues-!`+u>2ynM~7JJZ#mo2`ai(WW^fsM*zk>v)aM{F9~(xR`>bZj7D& zSeEaZ|L{ae{?Qz`sQ#<&$eJm)S+d8Hy_S5}bUeA&x@Uv0^@3-~rfE4|MpV2>QTEP? z6>mzs?`6deZ(3aQ3K?8BX^Ho|!eieTpLm7CPl#LcNz+^UwmfLnSGP1elfhzpF<}Q> zuOK2`0Vg&^Pt0F`%8GW%r>tTElfg=q18uJ)?#>QpP@Qzj6|c81e7(fpIumQb5s4{NBeJzE%fd1*yzc7U&8L#y5gS7 zUF?4VEfE4rT;2#=B|~p*=76_0i~St-xdT-A{eBa-Z*At1x^->#U3K%?xp}o?ZSEa4 zWDlFtwd{Fo0IS`Ud0V~h{PUa553As6P-@4%1$z^FpEiTNS$asDC_NZA7UgeEnX}~M zi*nfXw2<6wYW}&AbW-Fo+ph&ZdGgFc-JV|bDC0kv^+v!|qg2?DXGYQ|Pjo^Vk=)bN zUgUDv-$D$jXzitT$E#=%4>u<+^ndPYg1n!fxF7)f>*c!h2tY9OEfPx#gxZ#(B)&#s z7l}R+-ym^_ght{862BqwDv8%gyh-9M5*J8ZCQ&4DmBccMzmr%cQ6upwi3SS!=0a;r z0m|_!3v$Hnoo*acZ`*HJ(z4H9?o~rmmlrF?_8r-`Z{I*;No|ui%^dCvRs9_(pT&CV z^#GKoHeLszocYh}WD7{S6(xYY2CARi1Jwrhrd?4$)@-?AAIJWqvNMd@yxEq)L? zT)>1;qL)Hty~izfU=5lY-{C6T9}cT`wVha98Lx@fVqCHOV(GEEbAuL-_4V!D+r886 z*|BE|W85iUyMG@FY9Jt;PWxA6x{<~QKxleO$qF#iBB2*wfY_l}a?JQHL?RJ?T{MPv zu_%luaZHW_7^!B;z)wQt&|#}35<0%*HZ%ZThoBNcheMDuM$Z^V3Iy4U9P0ET2f2ud zC^#R9rP4W!mD95d=_y^$()t`NOC)f;K@W|Z5=l~AdN#%cP?UtATroU8_)eCsI9M;n z|M{q{fj|d2!+@+|(1Y{3uA|>f+AxfC2IK2lUB|rqQ3p-eM=>PeS2QyqMd^zaDAdpn zZN||SOQn{BVy&@UEEd5ylgptY<%=!l$HN7_ekOg$*D)l;Pmv#lNR8rP^Wzz*lmPU# z+^C^HBSopxcS@nf1>^1V&33^MRj&jgWK|CrD*8i$BXp?k=Cgm z&5w(^kwZO}$Rc*K!Ff$VCWs5;3rJ!F+()F3Z5-jHss5y|rf~f&0)S&{J^7>hTY6Ua z{h>qBi3jR*@NgfAKooTfqqRn?ACSOumd>gVpf!*A;3oU$Cn`jO?ELV^Wn72|riJVD zPhM{wU3>?Su{>Q}iGzdBjhz@VCZGP*$a5p3r#k6U_eue6wiNWV4*GAxr5mSiU(d@w z=jBU_^!G$G?*D##XDpZi6JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C0!)AjFaajO z1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C0!)Aje5DEe E8=ZrgSO5S3 literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/mem_timing/mem_timing.gb b/playing-coffee - Copy/roms/mem_timing/mem_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..78766b579f74433332c546c9cf06f8b972a4c2d8 GIT binary patch literal 65536 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 O5g-CYfCzlO2>cuVGucxB literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/mem_timing/readme.txt b/playing-coffee - Copy/roms/mem_timing/readme.txt new file mode 100644 index 0000000..98d76e2 --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing/readme.txt @@ -0,0 +1,122 @@ +Game Boy CPU Memory Access Timing Test +-------------------------------------- +This ROM tests the timing of memory reads and writes made by +instructions, except stack and program counter accesses. These tests +require correct instruction timing and proper timer operation (TAC, +TIMA, TMA). + +The read and write tests list failing instructions as + + [CB] opcode:tested-correct + +The read-modify-write test lists failing instructions as + + [CB] opcode:tested read/tested write-correct read/correct write + +The values after the opcode refer to which instruction cycle the access +occurs on, with 1 being the first. If a time couldn't be determined due +to some other problem, it prints 0. + +For instructions which either read or write, but not both, the CPU makes +the access on the last cycle. For instructions which read, modify, then +write back, the CPU reads on the next-to-last cycle, and writes on the +last cycle. + + +Internal operation +------------------ +The tests have the timer increment TIMA every 64 cycles, synchronize +with this, delay a variable amount, then have the instruction under test +access the timer. By varying the delay in one-cycle increments, the +memory access made by the instruction can be made to fall before and +after a TIMA increment. By then examining the registers and value in +TIMA, it can be determined which occurred. + + +Multi-ROM +--------- +In the main directory is a single ROM which runs all the tests. It +prints a test's number, runs the test, then "ok" if it passes, otherwise +a failure code. Once all tests have completed it either reports that all +tests passed, or prints the number of failed tests. Finally, it makes +several beeps. If a test fails, it can be run on its own by finding the +corresponding ROM in individual/. + +Ths compact format on screen is to avoid having the results scroll off +the top, so the test can be started and allowed to run without having to +constantly monitor the display. + +Currently there is no well-defined way for an emulator test rig to +programatically find the result of the test; contact me if you're trying +to do completely automated testing of your emulator. One simple approach +is to take a screenshot after all tests have run, or even just a +checksum of one, and compare this with a previous run. + + +Failure codes +------------- +Failed tests may print a failure code, and also short description of the +problem. For more information about a failure code, look in the +corresponding source file in source/; the point in the code where +"set_test n" occurs is where that failure code will be generated. +Failure code 1 is a general failure of the test; any further information +will be printed. + +Note that once a sub-test fails, no further tests for that file are run. + + +Console output +-------------- +Information is printed on screen in a way that needs only minimum LCD +support, and won't hang if LCD output isn't supported at all. +Specifically, while polling LY to wait for vblank, it will time out if +it takes too long, so LY always reading back as the same value won't +hang the test. It's also OK if scrolling isn't supported; in this case, +text will appear starting at the top of the screen. + +Everything printed on screen is also sent to the game link port by +writing the character to SB, then writing $81 to SC. This is useful for +tests which print lots of information that scrolls off screen. + + +Source code +----------- +Source code is included for all tests, in source/. It can be used to +build the individual test ROMs. Code for the multi test isn't included +due to the complexity of putting everything together. + +Code is written for the wla-dx assembler. To assemble a particular test, +execute + + wla -o "source_filename.s" test.o + wlalink linkfile test.gb + +Test code uses a common shell framework contained in common/. + + +Internal framework operation +---------------------------- +Tests use a common framework for setting things up, reporting results, +and ending. All files first include "shell.inc", which sets up the ROM +header and shell code, and includes other commonly-used modules. + +One oddity is that test code is first copied to internal RAM at $D000, +then executed there. This allows self-modification, and ensures the code +is executed the same way it is on my devcart, which doesn't have a +rewritable ROM as most do. + +Some macros are used to simplify common tasks: + + Macro Behavior + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + wreg addr,data Writes data to addr using LDH + lda addr Loads byte from addr into A using LDH + sta addr Stores A at addr using LDH + delay n Delays n cycles, where NOP = 1 cycle + delay_msec n Delays n milliseconds + set_test n,"Cause" Sets failure code and optional string + +Routines and macros are documented where they are defined. + +-- +Shay Green diff --git a/playing-coffee - Copy/roms/mem_timing/source/01-read_timing.s b/playing-coffee - Copy/roms/mem_timing/source/01-read_timing.s new file mode 100644 index 0000000..e119615 --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing/source/01-read_timing.s @@ -0,0 +1,148 @@ +; Tests timing of accesses made by +; memory read instructions + +.include "shell.inc" +.include "tima_64.s" + +instructions: + ; last value is time of read + .byte $B6,$00,$00,2 ; OR (HL) + .byte $BE,$00,$00,2 ; CP (HL) + .byte $86,$00,$00,2 ; ADD (HL) + .byte $8E,$00,$00,2 ; ADC (HL) + .byte $96,$00,$00,2 ; SUB (HL) + .byte $9E,$00,$00,2 ; SBC (HL) + .byte $A6,$00,$00,2 ; AND (HL) + .byte $AE,$00,$00,2 ; XOR (HL) + .byte $46,$00,$00,2 ; LD B,(HL) + .byte $4E,$00,$00,2 ; LD C,(HL) + .byte $56,$00,$00,2 ; LD D,(HL) + .byte $5E,$00,$00,2 ; LD E,(HL) + .byte $66,$00,$00,2 ; LD H,(HL) + .byte $6E,$00,$00,2 ; LD L,(HL) + .byte $7E,$00,$00,2 ; LD A,(HL) + .byte $F2,$00,$00,2 ; LDH A,(C) + .byte $0A,$00,$00,2 ; LD A,(BC) + .byte $1A,$00,$00,2 ; LD A,(DE) + .byte $2A,$00,$00,2 ; LD A,(HL+) + .byte $3A,$00,$00,2 ; LD A,(HL-) + .byte $F0,tima_64,4 ; LD A,($0000) + + .byte $CB,$46,$00,3 ; BIT 0,(HL) + .byte $CB,$4E,$00,3 ; BIT 1,(HL) + .byte $CB,$56,$00,3 ; BIT 2,(HL) + .byte $CB,$5E,$00,3 ; BIT 3,(HL) + .byte $CB,$66,$00,3 ; BIT 4,(HL) + .byte $CB,$6E,$00,3 ; BIT 5,(HL) + .byte $CB,$76,$00,3 ; BIT 6,(HL) + .byte $CB,$7E,$00,3 ; BIT 7,(HL) +instructions_end: + +main: + call init_tima_64 + set_test 0 + + ; Test instructions + ld hl,instructions +- call @time_instr + cp (hl) + call nz,@print_failed + inc hl + ld a,l + cp 3-byte instruction +; HL <- HL + 3 +@time_instr: + ; Copy instr + ld a,(hl+) + ld (instr+0),a + ld a,(hl+) + ld (instr+1),a + ld a,(hl+) + ld (instr+2),a + push hl + + ; Find result when access doesn't occur + ld b,0 + call @time_access + ld c,a + + ; Test for accesses on each cycle + ld b,0 +- push bc + call @time_access + pop bc + cp c + jr nz,@found + inc b + ld a,b + cp 10 + jr nz,- + ld b,0 + +@found: + ld a,b + pop hl + ret + +; Tests for read +; B -> which cycle to test +; A <- timer value after test +@time_access: + call sync_tima_64 + ld a,9 + sub b + call delay_a_20_cycles + xor a ; clear flags + ld hl,tima_64 + ld (hl),$7F + ld bc,tima_64 + ld de,tima_64 + ld a,$7F +instr: + nop + nop + nop + + ; Add all registers together to yield + ; unique value that differs based on + ; read occurring before or after tima_64 + ; increments. + push af + add hl,bc + add hl,de + pop de + add hl,de + ld a,h + add l + ret diff --git a/playing-coffee - Copy/roms/mem_timing/source/02-write_timing.s b/playing-coffee - Copy/roms/mem_timing/source/02-write_timing.s new file mode 100644 index 0000000..acd6d9d --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing/source/02-write_timing.s @@ -0,0 +1,115 @@ +; Tests timing of accesses made by +; memory write instructions + +.include "shell.inc" +.include "tima_64.s" + +instructions: + ; last value is time of write + .byte $36,$FF,$00,3 ; LD (HL),n + .byte $70,$00,$00,2 ; LD (HL),B + .byte $71,$00,$00,2 ; LD (HL),C + .byte $72,$00,$00,2 ; LD (HL),D + .byte $73,$00,$00,2 ; LD (HL),E + .byte $74,$00,$00,2 ; LD (HL),H + .byte $75,$00,$00,2 ; LD (HL),L + .byte $77,$00,$00,2 ; LD (HL),A + .byte $02,$00,$00,2 ; LD (BC),A + .byte $12,$00,$00,2 ; LD (DE),A + .byte $22,$00,$00,2 ; LD (HL+),A + .byte $32,$00,$00,2 ; LD (HL-),A + .byte $E2,$00,$00,2 ; LDH (C),A + .byte $E0,tima_64,4 ; LD (nn),A +instructions_end: + +main: + call init_tima_64 + set_test 0 + + ; Test instructions + ld hl,instructions +- call @test_instr + cp (hl) + call nz,@print_failed + inc hl + ld a,l + cp 3-byte instruction +; HL <- HL + 3 +@test_instr: + ; Copy instr + ld a,(hl+) + ld (instr+0),a + ld a,(hl+) + ld (instr+1),a + ld a,(hl+) + ld (instr+2),a + push hl + + ; Test for writes on each cycle + ld b,0 +- push bc + call @time_write + pop bc + cp tima_64 + jr z,@no_write + jr @found +@no_write: + inc b + ld a,b + cp 10 + jr nz,- + ld b,0 + +@found: + ld a,b + pop hl + ret + +; Tests for write +; B -> which cycle to test +; A <- timer value after test +@time_write: + call sync_tima_64 + ld a,13 + sub b + call delay_a_20_cycles + ld hl,tima_64 + ld bc,tima_64 + ld de,tima_64 + ld a, 3-byte instruction +; HL <- HL + 3 +@time_instr: + ; Copy instr + ld a,(hl+) + ld (instr+0),a + ld a,(hl+) + ld (instr+1),a + ld a,(hl+) + ld (instr+2),a + push hl + + ; Find result when access doesn't occur + ld b,0 + call @time_access + + ; Find first access + call @find_next_access + ld d,b + + ; Find second access + call @find_next_access + ld e,b + + pop hl + ret + +; A -> current timer result +; B -> starting clock +; B <- clock next access occurs on +; A <- new timer result +@find_next_access: + ld c,a +- call @time_access + cp c + ret nz + inc b + ld a,b + cp 10 + jr c,- + + ; Couldn't find time, so return 0/0 + ld a,c + ld b,0 + ld d,b + ret + +; Tests for access +; B -> which cycle to test +; A <- timer value after test +@time_access: + call sync_tima_64 + ld hl,tima_64 + ld (hl),$7F + ld a,17 + sub b + call delay_a_20_cycles + xor a ; clear flags +instr: + nop + nop + nop + delay 32 + ld a,(tima_64) + ret diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/build_gbs.s b/playing-coffee - Copy/roms/mem_timing/source/common/build_gbs.s new file mode 100644 index 0000000..7ac8d3c --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing/source/common/build_gbs.s @@ -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 diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/build_rom.s b/playing-coffee - Copy/roms/mem_timing/source/common/build_rom.s new file mode 100644 index 0000000..e1e220f --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing/source/common/build_rom.s @@ -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 diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/console.bin b/playing-coffee - Copy/roms/mem_timing/source/common/console.bin new file mode 100644 index 0000000000000000000000000000000000000000..b02f2d37ee94c6570a3622734f2d704dfca44b0c GIT binary patch literal 768 zcmX|9F=`|+5G))_B$f+182koBX5hr&r*zbT5qlFY8w?8X8GehSdBVQJIPe4Zu1Z?m z2wEj|cXgKp3^N%Fwbp)6@0AO%)yuRSqFVOjFXoqd`4hj9QtEN1nR)XG+d{u=K$L$a zcxBdDa9GaHO?x1~9ZyqY!B>?@1~S_Jk8EuOnO>t1U_)GPUYcV}PSJ688I zDZyK&1&Z3Yf=R|a=4{O6sIrFR>wY_q1954MiRzbDIv)c%zSNhFNh$uKIuubR9$M#% zQVgthyMM7>6t%8xc-*ZoU#;c~Eo^b8>ieF1Te%=ANe|UbtGOeuN<>ma4(yn>d z&PyDPc+NXzR}RnDiJDL@@*5U1@Exf>ZKHTq^DiA1#(zox`Vp^snt$Nkdp#BumLA0V yczj1Ga3U7XIy~nG6}avCt6hL`&VKmF{r{ZIvhq4Tb=k}k+hUx5zmKoWJ@6mAT&*eq literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/console.s b/playing-coffee - Copy/roms/mem_timing/source/common/console.s new file mode 100644 index 0000000..5307f6b --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing/source/common/console.s @@ -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 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 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 diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/delay.s b/playing-coffee - Copy/roms/mem_timing/source/common/delay.s new file mode 100644 index 0000000..65eb9c0 --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing/source/common/delay.s @@ -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 diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/gb.inc b/playing-coffee - Copy/roms/mem_timing/source/common/gb.inc new file mode 100644 index 0000000..31bbf14 --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing/source/common/gb.inc @@ -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 diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/macros.inc b/playing-coffee - Copy/roms/mem_timing/source/common/macros.inc new file mode 100644 index 0000000..c413bd5 --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing/source/common/macros.inc @@ -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 diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/numbers.s b/playing-coffee - Copy/roms/mem_timing/source/common/numbers.s new file mode 100644 index 0000000..6d6faf8 --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing/source/common/numbers.s @@ -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 diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/printing.s b/playing-coffee - Copy/roms/mem_timing/source/common/printing.s new file mode 100644 index 0000000..ad9d811 --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing/source/common/printing.s @@ -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 diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/runtime.s b/playing-coffee - Copy/roms/mem_timing/source/common/runtime.s new file mode 100644 index 0000000..90cd24f --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing/source/common/runtime.s @@ -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 diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/testing.s b/playing-coffee - Copy/roms/mem_timing/source/common/testing.s new file mode 100644 index 0000000..c102f78 --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing/source/common/testing.s @@ -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 diff --git a/playing-coffee - Copy/roms/mem_timing/source/common/tima_64.s b/playing-coffee - Copy/roms/mem_timing/source/common/tima_64.s new file mode 100644 index 0000000..1db7211 --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing/source/common/tima_64.s @@ -0,0 +1,33 @@ +; Timer that's incremented every 64 cycles + +; Initializes timer for use by sync_tima_64 +init_tima_64: + wreg TMA,0 + wreg TAC,$07 + ret + +; Synchronizes to timer +sync_tima_64: + push af + push hl + + ld a,0 + ld hl,TIMA + ld (hl),a +- or (hl) + jr z,- + +- delay 65-12 + xor a + ld (hl),a + or (hl) + delay 4 + jr z,- + + pop hl + pop af + ret + +; Read from this to get count that's incremented +; every 64 cycles. +.define tima_64 TIMA diff --git a/playing-coffee - Copy/roms/mem_timing/source/linkfile b/playing-coffee - Copy/roms/mem_timing/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/playing-coffee - Copy/roms/mem_timing/source/shell.inc b/playing-coffee - Copy/roms/mem_timing/source/shell.inc new file mode 100644 index 0000000..277a9b7 --- /dev/null +++ b/playing-coffee - Copy/roms/mem_timing/source/shell.inc @@ -0,0 +1,21 @@ +.incdir "common" + +; GBS music file +.ifdef BUILD_GBS + .include "build_gbs.s" +.endif + +; Devcart +.ifdef BUILD_DEVCART + .include "build_devcart.s" +.endif + +; Sub-test in a multi-test ROM +.ifdef BUILD_MULTI + .include "build_multi.s" +.endif + +; GB ROM (default) +.ifndef RUNTIME_INCLUDED + .include "build_rom.s" +.endif diff --git a/playing-coffee - Copy/roms/oam_bug/oam_bug.gb b/playing-coffee - Copy/roms/oam_bug/oam_bug.gb new file mode 100644 index 0000000000000000000000000000000000000000..42ff9ca21f9d5fc87488b621867192d77716eb26 GIT binary patch literal 65536 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 diff --git a/playing-coffee - Copy/roms/oam_bug/rom_singles/1-lcd_sync.gb b/playing-coffee - Copy/roms/oam_bug/rom_singles/1-lcd_sync.gb new file mode 100644 index 0000000000000000000000000000000000000000..8f3d856c9217a13eb9b6e22117a4bef976924f0e GIT binary patch literal 32768 zcmeI4-)~#h9l*bi6E`@H+d!qARiV93A(#*=hH0IpUCshjMAODvq-jih;hHqJ3%YZ% zBx@Y%7$@mAn6_z1dmu&H@;Xf#@W4vf3B|s)$yIt#Bhlz0kt(a zvGLyGrAw!W6TxejH{M_Tr}`1{P?-}u}!5{Up2AOb{y2oM1xKm>>Y z5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5P{o}z)%+~_ubj8e0s1`xt=s( z`I8U~1`nDiL$_fwR5B4D0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO) z01+SpM1Tm~HU!KE%j6hl3wJZyE_(@qKwg9o_&=q|!^(eLp&<#q+tk!7CnX}-vgGx@`)@JejZ&sGSDG&^SWe+;CA83Z zJo3HB4Gbr_OFE_bMOuHhL!df7(A@UcxzBv?wpMVm zqCJQDB&#?xtn56;Hk`BUJtxJkI8!K*4WZ_0 zoBQkjSW9tVF={@NcM2@v6j0F-JazG1Js0!~Jy(okF<7;4p!KEb?%ataiZfQZ;v7BN zSIyY7)j2y=owsYv-EV2&o<5tkuvU3YPR1uEM@~VRgL&TE$=GVR?cwkTY`=VVd`upI z=94*lY-ip+oxTvh13eJ}{-Sxkp|o%hx;opL0cU#-=RD4Z0onSkiRaVW&RkON+@AZJ z(z!i-J|D!XmUP1oYviK&r^MBGqDCML!vkX-|FaDNqmw^kQyzEnMD^M6!5OZ>HJ%My=Q zy-OUbtxG&pg(a)v>?|PWbN1<@$6yxiQ)vD`ZMtlU`|@V6Q1Bf7)SG-g>~lXP4({R2 z{0`Wg2lD6hc;tSa55as8#2T)@3N~=%2dj%VCOB{N8`W2A@vQ>;Q3zi4wNCddyZ5vy zH!P(1W8VSfILe>L3IBh#DI45VI>7;Rpv?)Z>s-O76@2Z7?)p8_D@efNw=5WDPb(IEd)~l?AXErlfihR4mcUPRyqTN^jxpGIXu7v$!py1U08u@x+ zy4(yTi~^R6zZj)Z*?Gk-wqple3B7Fl4nEYT?D9qItt{WSp*)(Q#rg}SM{CwqX*|~3 zd;k4ii}u}zdd^~w8|AfI9}6Z2Ffa_m{eBpA11|u^HPx45pc^cz6@LNU$GT%<`ZpmE z2)K5^ZuIL8!gvhV*f@Z$HdGxSF_y-F%@K`ic$40f05lC+d;ttN2&$eqqw6X#>Ce(A z%Li#l2Ux&|`arj8q%l`oOZlX%YFbL#r=?jmiuSs^msO$Sw$ z&s6Zqfqv*Sj=tEc9BC+S)z^!~BIsw*X>^qJ;!xduXyD3cjAO2hDP?|D#zBa3$UHdQ ze7dSKz*y_`EsUqDvUK@emeAya`3}2ohhb25?`wgiD<|PV%qZU42Y~vr7Tpt7AIJDE zUhE$vX-)RS-MlF4X_UL8Da0;)P|tnH1aV=01~JTl`-t>$j3Ycl_8)Ua745G{0Jt{o zWjxt`Q%-4aJPash$pdA1@Ngd`fgs8SPyQpdwzn9MWvsc z9=VJ&7QnL5PW$)|&7+I=0QiBCpG{V`ZoLYwo_>e`5g-CYfCvx)B0vO)01+SpM1Tko z0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko U0U|&IhyW2F0z`lad|3(n7h?{-Q~&?~ literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/oam_bug/rom_singles/2-causes.gb b/playing-coffee - Copy/roms/oam_bug/rom_singles/2-causes.gb new file mode 100644 index 0000000000000000000000000000000000000000..b997cda45a53b2fbdb472e590f2e4163c8bb7e44 GIT binary patch literal 32768 zcmeI4|4$p|9mhY$;1FY+kYvl1(c}y%b0}WiR!dRlJ|YPrsg@PCXq_}^+OdYqT6w@= z4W==!fi!FS%hsP(R%zqcs?wTDUD7mC1U}B|4W6oz5_FYHXLmKytQ6VJZcQk&_j%62 zE3E%Ow9lh^{M_ex-k&ee>z=zG4e)>7v>xAkpQ`G7Ez~%NU;;c)3)OYIUpx=q59j-q z78gI5ymIB#Q+?i>^NSx&{_)D?N7x2pakQOKfxka{^qmcBWD)@)Km>>Y5g-CYfCvx) zB0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOiOwfunUW-LkDg{$fY1{CPx! z>03T%_wLZTefQvIsAM8Q1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko z0U|&IhyW3|X9#G|&gR;8zMtzT{8nBu*jTFF;Ld3VsdkHfpN}NiEPp1EH<%~mgTipA zb#ByuXYLn%D~(e)b9LIDx{Xu+e4{jFjV^7G-GBBwd960e_iK6Us=qq6;K5s|CX?Y` z(*gk*(&@bIc?~@|Dvj#1#SFiyNnfux-XV7!Y2DL#Bp8$r$d5m97)?m?gNZsQZ0A2o zlw{8JvW_EPmVKivt1QbpJ2yltekocN$_^b~m-NtKJE;Q-&T=#t%8nghCp&iBmLZF# zvTPH|PIRx6o#?h@4?*FP+}Zxz3#aas)zxe24!gweu5K))q%Qt@W`la?<~4N*PxlW` z94pJApl^`ZyVqBY*1=Z)vEvW!U8}lfqesnGdj!fJ2mb4!eWda2)BKy$;^0`!!Otbw zHgRfp+NVaW9hm;BXV%aH`a)H3b$1ZnbG-j97?<9)*x!WaG5KG%Q50U+OEY=Hit^#a zs2$^f-y}lyX1jx4|3 z_1yBrkIpR156GW17aIp#f7v>azBYq73hvpN1V69iEtBt#ZhK7gdhH*UUbklW540J>ibxr2L`qxFNsHDQ=>sb&-LOWbYnC2C z*~pCa7fV0%UFkDR@A#f{lg}iqxkvdqEx)v-;BKo(Zb}BV^SY%=9!tlG8t=)=*Dq*Z zH@~1IgIEk!?b=uQa&UF5D}v^Tk>ar_H-1^J&P}{6JGF}m9+=fUvn{yan#h~- z=fX8n_Si%&x0>s#2ftXY{I?;dl7pxi(@aQ9$>?%J^Y5jJ9R##^K15| z9u7R!^XyRXklT%Wy_m3=Q=C5g9C2}S_<8({VbPe&;uXD_GPQE`}kk~*rrp0uaxQtf%;4XJ{~m*QB8c(?rb zXDr{O*;05x-kdMUem5UZTKS8CZ}$zSOM&=XNh}wCF^Zm9dDTo-;|`eYd&P9^c(_Vl zWs|tKG`>}s6=kSccrkS-Z`=^#{^sVrd+R36`|BIeV2<1AYsT8ALJ|!m2#3S=RJahv z3qWE@$Q6}94NE~Kc@7%(H?;Mu-vN)uW9z&P=-1$dfi@i51_0DhF{I*>B*idbsRe@y z-o)-&0Ez+?E)NE5hmhKLT2(_J34bYuwpfTk%p-YRIPYl)g=3g2rbJ!B4k=1h=wm__ z45D5Yml&DL(+To?ktHaJS}{FdcrQFld2qj&|Cv68frJ5KstQq66&KDcih^+? zVO3Sb5zMbd6$R_EM+KOo^kGWCRdiD!B+8dU;1Uh}&}RUBajT-mAh}#vNG6k@o{q)P zQPhhqWamSHZJ!SBw{1)*@`pqmgeaQGgU!yThC&ixtcAid##2M0bg?cXdoofmC2hIT_Rir9q@&NCM> zL0p($f;P;6&k^Zk8$ftrvHv#P459v}0DxmjFXD;)m+Yuw$HRca7Cg`v2M?d4AmBw? zq!E83B;3RUMgUQ9R&4;Kd8`K?vVDHMk`xqvc6#J8AW0r93w6q8e<&SYya&J!jQD6u z1*7~b*z@#51c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F w0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+Sp_a1@&0J=Xo=l}o! literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/oam_bug/rom_singles/3-non_causes.gb b/playing-coffee - Copy/roms/oam_bug/rom_singles/3-non_causes.gb new file mode 100644 index 0000000000000000000000000000000000000000..d545e832119ea12092a7729978fdc3e8452232f3 GIT binary patch literal 32768 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 b0z`la5CI}U1c(3;AOb{y2oQn)HGzKs4~-Dp literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/oam_bug/rom_singles/5-timing_bug.gb b/playing-coffee - Copy/roms/oam_bug/rom_singles/5-timing_bug.gb new file mode 100644 index 0000000000000000000000000000000000000000..47a5c021a6db95a4010407642191f4378e9ecbc3 GIT binary patch literal 32768 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 afCvx)B0vO)01+SpM1Tko0V43f5coF%-mLxr literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/oam_bug/rom_singles/7-timing_effect.gb b/playing-coffee - Copy/roms/oam_bug/rom_singles/7-timing_effect.gb new file mode 100644 index 0000000000000000000000000000000000000000..2212779ed9328f79b759653802280acdd3f4cc57 GIT binary patch literal 32768 zcmeI4Uu;`f8Nfg1kCQo$+pLv1R9SnStf`kJswtu~aCa1yXcZb=#tTS&+0EA6Qsy}! ztr~ZAJSQI2#$M=K!=w>S0)&J%h6G)y)cN|_CRgdH7HQ4yv;}N2XJEA<-ng{QBAf4A zw|y8q@mBF2-Q#n-yzR=x9C;Y4AfQw0G@#>17CfbUC)V zvhu;)#fwLui3P4*UioP5=NB)0m4AU)3gu>K!r!NV)BA-z5{Up2AOb{y2oM1xKm>>Y z5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5P^G;z`-ua^xxmDe7?O?xe+%Z z^H~sv0^7|egZE%Dlrj+@0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO) z01+SpM1Tm~GX%`l?E27`-d!KA{Z!eo_)Kxg63rP7#UY1(S5z`QCqB$nE$%M|p*E!s zEKY}REj|}=O1Ok8moo0sOoAG>zQi7Q#+wJo-?yu=_T<_)*Q*I2lHZEM(oLmTt-g5kugGk z@s5E;;!wsl#jN9)ro+E3lkI}qHp~2lx!fAw+!cl^o_80(I=|rXzsTK3m4CTHT^9FV zW2I_23Guhgw3Mv>JUWX3uNPCrnc{3QZ8`a?}Ua-#@*k>{dSRtM^O}{AggJv}9n6nlHD{5S|=PxpI5-xnoJZV*K4< z&Ei?3|GaXmYKh+&;(bGu4DpF9)N;giYQ=zR8Ha$5f&-_jv7diq* zF1#1a27DrzErc-{%-XxRp4#iI&tRX$p57}*=?ZW^<1rUdx)( z*?z3IBmS0h!}{ziP^^IMNwx)3wm4^cUVvb#juX7-ZP>S9uVe4yDeU#)Za!6fP#jti zmrTKO;y_j={>b2oW6vFn9erv{*TZUgMqt+~TFym#Np z1z|P-;w+9o3Dj}q+beT62KbrHZ&qHlMLow32jQHzWokg#e6UryX(7rVc()?VWBdi| z@ZWo@vcf&Z5e^swbw;eN;k;MNd)s$x`*lwb`Y~D+d|yOcj-M?Kp~koP$HjwYZ{A(% zDh^exH(3);t)?&)`HF=eC_BM9yTA5h<-Tf73Hii7a!&ORgI|kHl^Tx3KpxY@U-XhM zZ@g+3+OYzz1kc&t?fY7lO+JUUmE=p;Ce=`5`K984s&!Q=kN5RG^ibEF{lJc%(-`As zY3c44gUJRAqS2`P5sB8K_y91json$wJ<7sb;S6-|?~aV?-vqzk@9G7*(XKlH6A>IE z69BqeS9RQCEQt;qEgaTxlDsVeXc{zm{pfHAR6TZ5*HvKBo+VM18%ap|ncs`+{%$pz z#8^o!;gzzgX$h&HlxpEH>g)26Blr3vGOm1Xk71Be!c69h;qk$_v~0w|dNKYFV;TpB z4wAYK30;>Du4|fxe&bPH*Q0TauO&1M^KwTGxTeK0B;YQZ>7dH=nF?On&<<@T&=yOT zEeVCS+H#>#0R3b#iH0&?Y^oa%6sCJ>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la K5P|=Vz`p@31Fawc literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/oam_bug/rom_singles/8-instr_effect.gb b/playing-coffee - Copy/roms/oam_bug/rom_singles/8-instr_effect.gb new file mode 100644 index 0000000000000000000000000000000000000000..464a5f4e682a8a2becab8665ea09890f0b70df3c GIT binary patch literal 32768 zcmeI4Uu;`f8NiR@#LXPXjoV6`rfGYfw3(YFiVLDx!#m2_vTo2VL1;w+X}h7#&5Dc@ zlB#i6$90kpZF|}t1`8D$osSjeYBOnaj*}VUAlmVkFmRv2SxV%`*Hq>9WB*1wW}w zgu53e1FH)^3RroVf|>c0J#`zV{&ul8Wlb(`mfXJ$IQcPklAlw{)_kBbvFL$U+Egg; zr_`PviRANT+wvM%a?msy&sPh4lSX&eJ@&+*-oxKGB#n(qql2eT4UI*mljG9F*hqAk zhvb7vsKZ--cyt z{lJsodGx=T>i=T;)1yA0wqd%v;pk!M;qJhrM<3|z+4sds`%9HBhzY}kBJ{(nzVFY2 zy>`FGx2E|0DM8+14*qVMZ4ubF+t|h0gQt%@~$`z2KjzKGO3<&#|6g zbv7=97Pc?6?hdv*9b5VM$eES5`k!5y`R>^jX|MEAXSIE-`^Vj*`D<`CtcpA1R=Jsh zS0>#V-S(L0$JM`B)X5L2U6-WQvcX?e`9+mqR{0-PvFjGs)eReXE$ddAUr^JA6{7{~ z49#25(k1IGy@Zr?$P5YHI-g4NN{RZNc%Dd^MCXL?2HcRO4eqm;ZNZ7EItUTQ8)UzQq6 zGjB;wt(xYiHSI~Q3+io(y(xWa+}claMPfP`n^c0(BZDU+Lnpuo@Y|524se;X_q)kc&q#Z- zwB8`C+8U2V#RVUXNJC>|BV)j?3Ne_wNC>Mh&X{bjc-~|m79Df!*TuRyHdu7dF;c9b zW62_!GYZz)44^)54tE?Nlb|05^#{rmd4tbqwAXdrvFkHu?9G77{=QkTk1y$K#9VI4 z?APJgb?AOF?Irvf7+>^OVPxBivnB-ixyf!9FPMB$r{D9FS6%fJ-O~D=2I;l|B%g7$ z0Lzo?9CY%%DA@))3 zh_-lPQB42aFomog1 ze!+`gSi4|m8=(Rw`Cm0%+rHc&t+QFEEf04KqJkP|EIyw*ST?Q;<&nEU{+hAz!JwiMN+OYnJr$`$;30%EIqZs4qC{v=&i;h79cT-UC|@TYkH^;Ywt-!n zmyCvB42=?^gsWi%mMBet!&(alWq3*4kqD7xQs?r3!#)yLqNf!lOsKG@36RA?f+RfD zw;Wi1ZW z3-Ny#l^LPnAfYHEt|;Pxd0CdhZ!DrHN+bsH<+vNnjPs6cQHc(=c&~ z26kXG3bs(HXbF;CsVru*S)!ayB*0MQ3oUHN0|i??9XVjj5K_bs3qJr+G!X}y9Zv~| zDFI)Ll@;)(ghlFNU8GRs0`Yd)X1hqAsNPjaVzwM3Eul#E?miGwNy))GV%WvNzl|5_ zCxWyr>S1+uAd7?@1~S_Jk8EuOnO>t1U_)GPUYcV}PSJ688I zDZyK&1&Z3Yf=R|a=4{O6sIrFR>wY_q1954MiRzbDIv)c%zSNhFNh$uKIuubR9$M#% zQVgthyMM7>6t%8xc-*ZoU#;c~Eo^b8>ieF1Te%=ANe|UbtGOeuN<>ma4(yn>d z&PyDPc+NXzR}RnDiJDL@@*5U1@Exf>ZKHTq^DiA1#(zox`Vp^snt$Nkdp#BumLA0V yczj1Ga3U7XIy~nG6}avCt6hL`&VKmF{r{ZIvhq4Tb=k}k+hUx5zmKoWJ@6mAT&*eq literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/console.s b/playing-coffee - Copy/roms/oam_bug/source/common/console.s new file mode 100644 index 0000000..403fa5c --- /dev/null +++ b/playing-coffee - Copy/roms/oam_bug/source/common/console.s @@ -0,0 +1,285 @@ +; 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 + setb console_pos,console_width + setb console_mode,0 + setb console_scroll,-8 + 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 + wreg VBK,1 + ld a,0 + call @fill_nametable + + wreg VBK,0 + 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 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 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 diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/delay.s b/playing-coffee - Copy/roms/oam_bug/source/common/delay.s new file mode 100644 index 0000000..cbcdcf3 --- /dev/null +++ b/playing-coffee - Copy/roms/oam_bug/source/common/delay.s @@ -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 diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/gb.inc b/playing-coffee - Copy/roms/oam_bug/source/common/gb.inc new file mode 100644 index 0000000..2d0118d --- /dev/null +++ b/playing-coffee - Copy/roms/oam_bug/source/common/gb.inc @@ -0,0 +1,81 @@ +; Game Boy hardware addresses + +; $0000-$3FFF Fixed ROM bank +; $4000-$7FFF Switchable bank +; $8000-$9FFF VRAM +; $A000-$BFFF optional cartridge RAM +; $C000-$DFFF RAM +; $E000-$FDFF RAM mirror +; $FE00-$FE9F OAM +; $FEA0-$FEFF unused +; $FF00-$FF7F registers +; $FF80-$FFFE RAM +; $FFFF register + +; Memory +.define VRAM $8000 ; video memory +.define TILES $8000 ; tile images +.define BGMAP0 $9800 ; first 32x32 tilemap +.define BGMAP1 $9C00 ; second 32x32 tilemap +.define BRAM $A000 ; cart memory +.define WRAM $C000 ; internal memory +.define OAM $FE00 ; sprite memory +.define HRAM $FF80 ; fast memory for LDH + +; Registers + +.define RAMEN $0000 ; cartridge WRAM control +.define BANK $2000 ; bank select +.define P1 $FF00 ; controller + +; Game link I/O +.define SB $FF01 ; serial buffer +.define SC $FF02 ; serial control + +; 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 diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/macros.inc b/playing-coffee - Copy/roms/oam_bug/source/common/macros.inc new file mode 100644 index 0000000..9d7cf44 --- /dev/null +++ b/playing-coffee - Copy/roms/oam_bug/source/common/macros.inc @@ -0,0 +1,91 @@ +; General macros + +; Reads A from addr, from $FF00 to $FFFF +; Preserved: F, BC, DE, HL +; Time: 3 cycles +.macro lda ; addr + ldh a,(\1 - $FF00) +.endm + +; Writes A to addr, from $FF00 to $FFFF +; Preserved: AF, BC, DE, HL +; Time: 3 cycles +.macro sta ; addr + ldh (\1 - $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 + +; Writes byte to addr +; Preserved: F, BC, DE, HL +; Time: 6 cycles +.macro setb ; addr, data + ld a,\2 + ld (\1),a +.endm + +; Writes word to addr +; Preserved: F, BC, DE, HL +; Time: 12 cycles +.macro setw ; addr, data + ld a,<\2 + ld (\1),a + ld a,>\2 + ld (\1+1),a +.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 diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/numbers.s b/playing-coffee - Copy/roms/oam_bug/source/common/numbers.s new file mode 100644 index 0000000..6d6faf8 --- /dev/null +++ b/playing-coffee - Copy/roms/oam_bug/source/common/numbers.s @@ -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 diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/ppu.s b/playing-coffee - Copy/roms/oam_bug/source/common/ppu.s new file mode 100644 index 0000000..e1d2a6a --- /dev/null +++ b/playing-coffee - Copy/roms/oam_bug/source/common/ppu.s @@ -0,0 +1,14 @@ +; Waits for LCD blanking period +; Preserved: BC, DE, HL +wait_vbl: + ; Return if off + lda LCDC + rla + ret nc + + ; Wait for start of vblank +- lda LY + cp 144 + jr nz,- + + ret diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/printing.s b/playing-coffee - Copy/roms/oam_bug/source/common/printing.s new file mode 100644 index 0000000..bb9389b --- /dev/null +++ b/playing-coffee - Copy/roms/oam_bug/source/common/printing.s @@ -0,0 +1,77 @@ +; 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 +;print_char_nocrc (defined by user) + + +; 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 diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/shell.s b/playing-coffee - Copy/roms/oam_bug/source/common/shell.s new file mode 100644 index 0000000..3e588c7 --- /dev/null +++ b/playing-coffee - Copy/roms/oam_bug/source/common/shell.s @@ -0,0 +1,261 @@ +; Common routines and runtime + +.define RUNTIME_INCLUDED 1 + +; A few bytes of RAM that aren't cleared +.define nv_ram_base $D800 +.define nv_ram nv_ram_base + +; Address of next normal variable +.define bss_base nv_ram_base+$80 +.define bss bss_base + +; Address of next direct-page ($FFxx) variable +.define dp_base $FF80 +.define dp dp_base + +; Top of stack +.define std_stack $DFFF+1 + +; Final exit result byte is written here +.define final_result $A000 + +; Text output is written here as zero-terminated string +.define text_out_base $A004 + +; 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 nv_ram +.redefine nv_ram nv_ram+1 + +; Copies C*$100 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 a,(hl+) + ld (de),a + inc e + jr nz,- + inc d + dec c + jr nz,- + + ld a,b + jp $C000 + + +.ifndef RST_OFFSET + .define RST_OFFSET 0 +.endif + +.ifndef CUSTOM_RESET + reset: + di + + ; Run code from $C000, as is done on devcart. This + ; ensures minimal difference in how it behaves. + ld hl,$4000 + ld c,$14 + jp copy_to_wram_then_run + + .bank 1 slot 1 + .org 0 + jp std_reset +.endif + +; returnOrg puts this code AFTER user code. +.section "runtime" returnOrg + + ; Catch user code running off end + jp internal_error + +; 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 + + ; Clear memory except very top of stack + ld bc,std_stack-bss_base - 2 + ld hl,bss_base + call clear_mem + ld bc,$FFFF-dp_base + ld hl,dp_base + call clear_mem + + ; Init hardware + wreg TAC,$00 + wreg IF,$00 + wreg IE,$00 + + wreg NR52,0 ; sound off + wreg NR52,$80 ; sound on + wreg NR51,$FF ; mono + wreg NR50,$77 ; volume + + call init_runtime + call init_text_out + call console_init + call init_testing + + .ifdef TEST_NAME + print_str TEST_NAME,newline,newline + .endif + + 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 + + call console_show + pop af + call play_byte + jp post_exit + ++ push af + call print_newline + 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 + + +; Clears BC bytes starting at HL +clear_mem: + ; If C>0, increment B + dec bc + inc c + inc b + + ld a,0 +- ld (hl+),a + dec c + jr nz,- + dec b + jr nz,- + ret + + +; Reports internal error and exits with code 255 +internal_error: + print_str "Internal error" + ld a,255 + jp exit + + +; build_devcart and build_multi customize this +.ifndef CUSTOM_PRINT + .define text_out_addr bss+0 + .redefine bss bss+2 + + ; Initializes text output to cartridge RAM + init_text_out: + ; Enable cartridge RAM and set text output pointer + setb RAMEN,$0A + setw text_out_addr,text_out_base + setb text_out_base-3,$DE + setb text_out_base-2,$B0 + setb text_out_base-1,$61 + setb text_out_base,0 + setb final_result,$80 + ret + + + ; Appends character to text output string + ; Preserved: AF, BC, DE, HL + write_text_out: + push hl + push af + ld a,(text_out_addr) + ld l,a + ld a,(text_out_addr+1) + ld h,a + inc hl + ld (hl),0 + ld a,l + ld (text_out_addr),a + ld a,h + ld (text_out_addr+1),a + dec hl + pop af + ld (hl),a + pop hl + ret + + print_char_nocrc: + call write_text_out + jp console_print +.endif + + +; only build_rom uses console +.ifdef NEED_CONSOLE + .include "console.s" +.else + console_init: + console_print: + console_flush: + console_normal: + console_inverse: + console_show: + console_set_mode: + ret +.endif + + +; build_devcart and build_multi need to customize this +.ifndef CUSTOM_EXIT + post_exit: + ld (final_result),a + forever: + wreg NR52,0 ; sound off +- jr - +.endif + + +.macro def_rst ARGS addr + .bank 0 slot 0 + .org addr+RST_OFFSET +.endm diff --git a/playing-coffee - Copy/roms/oam_bug/source/common/testing.s b/playing-coffee - Copy/roms/oam_bug/source/common/testing.s new file mode 100644 index 0000000..5dd63ce --- /dev/null +++ b/playing-coffee - Copy/roms/oam_bug/source/common/testing.s @@ -0,0 +1,195 @@ +; Diagnostic and testing utilities + +.define test_code bss+0 +.define test_name bss+1 +.redefine bss bss+3 + + +; Sets test code and optional error text. +; Takes multiple strings due to wla's idiotic +; default limit of 63 chars per string. +; Preserved: AF, BC, DE, HL +.macro set_test ; code[,text[,text2[,text3]]] + push hl + call set_test_ + jr @set_test\@ + .byte \1 + .if NARGS > 1 + .byte \2 + .endif + .if NARGS > 2 + .byte \3 + .endif + .if NARGS > 3 + .byte \4 + .endif + .byte 0 +@set_test\@: + pop hl +.endm + +set_test_: + pop hl + push hl + push af + inc hl + inc hl + ldi a,(hl) + ld (test_code),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,(test_code) + 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,(test_code) + cp $FF ; if a = $FF then a = 1 + jr nz,+ + ld a,1 ++ 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 print_newline + call print_crc + .else + ld bc,(crc >> 16) ~ $FFFF + ld de,(crc & $FFFF) ~ $FFFF + call check_crc_ + .endif +.endm + +; Checks CRC, differing based on DMG or CGB build +.macro check_crc_dmg_cgb ARGS dmg, cgb + .ifdef REQUIRE_DMG + check_crc dmg + .else + .ifdef REQUIRE_CGB + check_crc cgb + .else + .printt "CGB or DMG must be specified" + .fail + .endif + .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 diff --git a/playing-coffee - Copy/roms/oam_bug/source/linkfile b/playing-coffee - Copy/roms/oam_bug/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/playing-coffee - Copy/roms/oam_bug/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/playing-coffee - Copy/roms/oam_bug/source/readme.txt b/playing-coffee - Copy/roms/oam_bug/source/readme.txt new file mode 100644 index 0000000..a0269c1 --- /dev/null +++ b/playing-coffee - Copy/roms/oam_bug/source/readme.txt @@ -0,0 +1,82 @@ +Game Boy Tests Source Code +-------------------------- + +Building with wla-dx +-------------------- +To assemble a test ROM with wla-dx, use the following commands: + + wla -o source_filename_here.s test.o + wlalink linkfile test.gb + +To assemble as a GBS music file: + + wla -o source_filename_here.s test.o -DBUILD_GBS + wlalink linkfile test.gbs + +Note that some tests might only work when built as a ROM or GBS file, +but not both. + +Some tests might include a ROM/GBS that has all the tests combined. +Building such a multi-test is complex and the necessary files aren't +included. + + +Framework +--------- +Each test is in a single source file, and makes use of several library +source files from common/. This framework provides common services and +reduces code to only that which performs the actual test. Virtually all +tests include "shell.inc" at the beginning, which sets things up and +includes all the appropriate library files. + +The reset handler does minimal GB hardware initialization, clears RAM, +sets up the text console, then runs main. Main can exit by returning or +jumping to "exit" with an error code in A. Exit reports the code then +goes into an infinite loop. If the code is 0, it doesn't do anything, +otherwise it reports the code. Code 1 is reported as "Failed", and the +rest as "Error ". + +The default is to build a ROM. Defining BUILD_GBS will build as an GBS. +The other build types aren't supported due to their complexity. I load +the code into RAM at $C000 since my devcart requires it, and I don't +want the normal ROM to differ in any way from what I've tested. This +also allows easy self-modifying code. + +Several routines are available to print values and text to the console. +Most update a running CRC-32 checksum which can be checked with +check_crc, allowing ALL the output to be checked very easily. If the +checksum doesn't match, it is printed, so you can run the code on a GB +and paste the correct checksum into your code. + + +Macros +------ +Some macros are used to make common operations more convenient. The left +is equivalent to the right: + + Macro Equivalent + ------------------------------------- + lda addr ldh a,(addr-$FF00) + + sta addr ldh (addr-$FF00),a + + wreg addr,data ld a,data + ldh (addr-$FF00),a + + setb ld a,data + ld (addr),a + + setw setb addr+0,data + + for_loop routine,begin,end,step + calls routine with A set to successive values + + loop_n_times routine,count + calls routine with A from 0 to count-1 + + print_str "str" prints string + + +-- +Shay Green diff --git a/playing-coffee - Copy/roms/oam_bug/source/shell.inc b/playing-coffee - Copy/roms/oam_bug/source/shell.inc new file mode 100644 index 0000000..0d43faa --- /dev/null +++ b/playing-coffee - Copy/roms/oam_bug/source/shell.inc @@ -0,0 +1,27 @@ +; Included at beginning of all programs +; that use standard shell + +; Get include files from common/ +.incdir "common" + +; Sub-test in a multi-test ROM +.ifdef BUILD_MULTI + .include "build_multi.s" +.else + +; GBS music file +.ifdef BUILD_GBS + .include "build_gbs.s" +.endif + +; Devcart +.ifdef BUILD_DEVCART + .include "build_devcart.s" +.endif + +; GB ROM (default) +.ifndef RUNTIME_INCLUDED + .include "build_rom.s" +.endif + +.endif ; .ifdef BUILD_MULTI diff --git a/playing-coffee - Copy/roms/readme.txt b/playing-coffee - Copy/roms/readme.txt new file mode 100644 index 0000000..2c40e27 --- /dev/null +++ b/playing-coffee - Copy/roms/readme.txt @@ -0,0 +1,4 @@ +Blargg's Gameboy hardware test ROMs. Originally hosted at http://blargg.parodius.com/gb-tests/ +before parodious.com went down. +New official location: http://blargg.8bitalley.com/parodius/gb-tests/ + diff --git a/playing-coffee - Copy/roms/solitaire.gb b/playing-coffee - Copy/roms/solitaire.gb new file mode 100644 index 0000000000000000000000000000000000000000..8c5b8c6888cac8c3f832c7774495a293be49f21f GIT binary patch literal 65536 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 literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/supermarioland.gb b/playing-coffee - Copy/roms/supermarioland.gb new file mode 100644 index 0000000000000000000000000000000000000000..e5d712509040aa23d23e6d908b44da130c13f174 GIT binary patch literal 65536 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�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 literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/application/Application.java b/playing-coffee - Copy/src/main/java/playingcoffee/application/Application.java new file mode 100644 index 0000000..37b7ab4 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/application/Application.java @@ -0,0 +1,111 @@ +package playingcoffee.application; + +import java.awt.Canvas; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.image.BufferStrategy; + +import javax.swing.JFrame; + +public class Application extends Canvas implements Runnable { + + private static final long serialVersionUID = 1L; + + private boolean running; + private Thread thread; + + private GameBoy gameBoy; + + public Application() { + setPreferredSize(new Dimension(320, 288)); + + gameBoy = new GameBoy(); + gameBoy.start(); + } + + public void renderGameBoy() { + BufferStrategy bs = getBufferStrategy(); + if (bs == null) { + createBufferStrategy(2); + return; + } + + Graphics g = bs.getDrawGraphics(); + + g.setColor(Color.WHITE); + g.fillRect(0, 0, getWidth(), getHeight()); + + int lcdControl = (gameBoy.getMMU().read(0xFF40)); + + int tileDataOffset = ((lcdControl & 0x10) != 0) ? 0x8000 : 0x8800; + + for (int tile = 0; tile < 512; tile++) { + int tileIndex = tile; + for (int y = 0; y < 8; y++) { + for (int n = 0; n < 8; n++) { + int value = (((gameBoy.getMMU().read(0x8000 + y * 2 + tileIndex * 16 + 1) >> (7 - n)) & 1) << 1) | + (((gameBoy.getMMU().read(0x8000 + y * 2 + tileIndex * 16) >> (7 - n)) & 1)); + + g.setColor(new Color(value * 64, value * 64, value * 64)); + if (value != 0x0) + g.fillRect((n + (tile % 16) * 8), (y + (tile / 16) * 8), 1, 1); + } + } + } + + if ((lcdControl & 0x80) != 0) { + for (int tileY = 0; tileY < 32; tileY++) { + for (int tileX = 0; tileX < 32; tileX++) { + int tileIndex = gameBoy.getMMU().read(0x9800 + (tileX + tileY * 32)); + for (int y = 0; y < 8; y++) { + for (int n = 0; n < 8; n++) { + int value = (((gameBoy.getMMU().read(tileDataOffset + y * 2 + tileIndex * 16 + 1) >> (7 - n)) & 1) << 1) | + (((gameBoy.getMMU().read(tileDataOffset + y * 2 + tileIndex * 16) >> (7 - n)) & 1)); + + g.setColor(new Color(value * 64, value * 64, value * 64)); + if (value != 0x0) + g.fillRect((n + (tileX) * 8 - gameBoy.getPPU().getRegisters().scrollX) * 2, (y + (tileY) * 8 - gameBoy.getPPU().getRegisters().scrollY) * 2, 2, 2); + } + } + } + } + } + + g.dispose(); + + bs.show(); + } + + @Override + public void run() { + while (running) { + renderGameBoy(); + } + + gameBoy.stop(); + } + + public void start() { + if (running) return; + + running = true; + + thread = new Thread(this); + thread.start(); + } + + public static void main(String[] args) { + Application application = new Application(); + + JFrame frame = new JFrame("playing-coffee"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setResizable(false); + frame.add(application); + frame.pack(); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + + application.start(); + } +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/application/GameBoy.java b/playing-coffee - Copy/src/main/java/playingcoffee/application/GameBoy.java new file mode 100644 index 0000000..25480f7 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/application/GameBoy.java @@ -0,0 +1,85 @@ +package playingcoffee.application; + +import playingcoffee.core.Cartridge; +import playingcoffee.core.InterruptManager; +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.CPU; +import playingcoffee.log.Log; +import playingcoffee.ppu.PPU; + +public class GameBoy implements Runnable { + + private Thread thread; + private boolean running = false; + + private InterruptManager interruptManager; + private PPU ppu; + private MMU mmu; + private CPU cpu; + + public GameBoy() { + interruptManager = new InterruptManager(); + mmu = new MMU(); + + ppu = new PPU(mmu, interruptManager); + cpu = new CPU(mmu, interruptManager); + + mmu.connectMemorySpace(interruptManager); + mmu.connectMemorySpace(new Cartridge("roms/kwirk.gb")); + } + + public void start() { + if (running) + return; + + running = true; + thread = new Thread(this); + thread.start(); + } + + public void stop() { + if (!running) + return; + + running = false; + try { + thread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public void run() { + Log.init(); + + while (running) { + for (int i = 0; i < 8192; i++) { + ppu.clock(); + cpu.clock(); + interruptManager.clock(); + } + + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + Log.close(); + } + + public PPU getPPU() { + return ppu; + } + + public MMU getMMU() { + return mmu; + } + + public CPU getCPU() { + return cpu; + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/Cartridge.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/Cartridge.java new file mode 100644 index 0000000..21c9dea --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/Cartridge.java @@ -0,0 +1,44 @@ +package playingcoffee.core; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +import playingcoffee.log.Log; + +public class Cartridge implements MemorySpace { + + private int[] rom; + + public Cartridge(String rom) { + byte[] bin = null; + + try { + bin = Files.readAllBytes(Paths.get(rom)); + } catch (IOException e) { + e.printStackTrace(); + } + + this.rom = new int[bin.length]; + + for (int i = 0; i < bin.length; i++) { + this.rom[i] = Byte.toUnsignedInt(bin[i]); + } + } + + @Override + public int read(int address) { + return rom[address]; + } + + @Override + public void write(int value, int address) { + Log.warn("Attempting to write to ROM at address: 0x%4x.", address); + } + + @Override + public boolean inMemorySpace(int address) { + return address >= 0x0000 && address <= 0x7FFF; + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/InterruptListener.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/InterruptListener.java new file mode 100644 index 0000000..088c107 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/InterruptListener.java @@ -0,0 +1,7 @@ +package playingcoffee.core; + +public interface InterruptListener { + + public void interruptOccured(int type); + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/InterruptManager.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/InterruptManager.java new file mode 100644 index 0000000..5bd5d61 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/InterruptManager.java @@ -0,0 +1,76 @@ +package playingcoffee.core; + +import java.util.ArrayList; +import java.util.List; + +public class InterruptManager implements MemorySpace { + + private boolean enabled; + + private int interruptEnable; + private int interruptFlag; + + private List listeners; + + public static final int VBLANK = 1 << 0; + public static final int LCD_STAT = 1 << 1; + public static final int TIMER = 1 << 2; + public static final int SERIAL = 1 << 3; + public static final int JOYPAD = 1 << 4; + + public InterruptManager() { + listeners = new ArrayList(); + } + + public void addListener(InterruptListener listener) { + listeners.add(listener); + } + + public void enable() { enabled = true; } + public void disable() { enabled = false; } + + public boolean isEnabled() { return enabled; } + + public void requestInterrupt(int type) { + interruptFlag |= type; + } + + public void clock() { + if (enabled) { + int toOccur = interruptFlag & interruptEnable; + + //System.out.println(toOccur); + + if (toOccur != 0) { + for (int i = 0; i < 8; i++) { + if ((toOccur & (1 << i)) != 0) { + for (InterruptListener listener : listeners) { + listener.interruptOccured(1 << i); + } + interruptFlag &= ~(1 << i); + } + } + } + } + } + + @Override + public int read(int address) { + if (address == 0xFFFF) return interruptEnable; + if (address == 0xFF0F) return interruptFlag; + + return 0; + } + + @Override + public void write(int value, int address) { + if (address == 0xFFFF) interruptEnable = value; + if (address == 0xFF0F) interruptFlag = value; + } + + @Override + public boolean inMemorySpace(int address) { + return address == 0xFFFF || address == 0xFF0F; + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/MMU.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/MMU.java new file mode 100644 index 0000000..fecec2e --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/MMU.java @@ -0,0 +1,93 @@ +package playingcoffee.core; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +public class MMU { + + private int[] memory; + private int[] bootRom; + + private List memorySpaces; + + public MMU() { + memory = new int[0x10000]; + + memorySpaces = new ArrayList(); + + loadBootROM(); + } + + public void connectMemorySpace(MemorySpace memorySpace) { + memorySpaces.add(memorySpace); + } + + // TODO: Move the ROM into a separate file. + + public void loadROM(String rom) { + byte[] bin = null; + + try { + bin = Files.readAllBytes(Paths.get(rom)); + } catch (IOException e) { + e.printStackTrace(); + } + + for (int i = 0; i < bin.length; i++) { + memory[i] = Byte.toUnsignedInt(bin[i]); + } + } + + // TODO: Move the boot ROM to a separate file. + private void loadBootROM() { + bootRom = new int[0x100]; + + byte[] bin = null; + + try { + bin = Files.readAllBytes(Paths.get("dmg_boot.bin")); + } catch (IOException e) { + e.printStackTrace(); + } + + for (int i = 0; i < 0x100; i++) { + bootRom[i] = Byte.toUnsignedInt(bin[i]); + } + } + + public int read(int address) { + if (address == 0xFF00) return 0xF; // TODO: Remove + + if (address >= 0x00 && address <= 0xFF && read(0xFF50) == 0) + return bootRom[address]; + + for (MemorySpace memorySpace : memorySpaces) { + if (memorySpace.inMemorySpace(address)) { + return memorySpace.read(address); + } + } + + return memory[address]; + } + + public void write(int value, int address) { + if (address == 0xFF00) return; // TODO: Remove + + if (address >= 0x00 && address <= 0xFF && read(0xFF50) == 0) + bootRom[address] = value & 0xFF; + + for (MemorySpace memorySpace : memorySpaces) { + if (memorySpace.inMemorySpace(address)) { + memorySpace.write(value & 0xFF, address); + } + } + memory[address] = value & 0xFF; + } + + public int[] getMemory() { + return memory; + } +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/MemorySpace.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/MemorySpace.java new file mode 100644 index 0000000..c2ce195 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/MemorySpace.java @@ -0,0 +1,9 @@ +package playingcoffee.core; + +public interface MemorySpace { + + public int read(int address); + public void write(int value, int address); + public boolean inMemorySpace(int address); + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/CPU.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/CPU.java new file mode 100644 index 0000000..2c25bf1 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/CPU.java @@ -0,0 +1,343 @@ +package playingcoffee.core.cpu; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import playingcoffee.core.InterruptListener; +import playingcoffee.core.InterruptManager; +import playingcoffee.core.MMU; +import playingcoffee.core.opcode.ALU16Opcode; +import playingcoffee.core.opcode.ALU16Opcode.ALU16Type; +import playingcoffee.core.opcode.ALUOpcode; +import playingcoffee.core.opcode.ALUOpcode.ALUType; +import playingcoffee.core.opcode.Argument; +import playingcoffee.core.opcode.CallOpcode; +import playingcoffee.core.opcode.ComplementOpcode; +import playingcoffee.core.opcode.FlipCarryOpcode; +import playingcoffee.core.opcode.InterruptOpcode; +import playingcoffee.core.opcode.JumpOpcode; +import playingcoffee.core.opcode.JumpRelativeOpcode; +import playingcoffee.core.opcode.LoadOpcode; +import playingcoffee.core.opcode.Opcode; +import playingcoffee.core.opcode.PopOpcode; +import playingcoffee.core.opcode.PushOpcode; +import playingcoffee.core.opcode.RestartOpcode; +import playingcoffee.core.opcode.ReturnOpcode; +import playingcoffee.core.opcode.RotateAOpcode; +import playingcoffee.core.opcode.SetCarryOpcode; +import playingcoffee.core.opcode.prefixed.BitOpcode; +import playingcoffee.core.opcode.prefixed.ResetBitOpcode; +import playingcoffee.core.opcode.prefixed.RotateOpcode; +import playingcoffee.core.opcode.prefixed.SetBitOpcode; +import playingcoffee.core.opcode.prefixed.ShiftOpcode; +import playingcoffee.core.opcode.prefixed.ShiftOpcode.ShiftType; +import playingcoffee.core.opcode.prefixed.SwapOpcode; +import playingcoffee.log.Log; + +public class CPU implements InterruptListener { + + private final MMU mmu; + private final InterruptManager interruptManager; + + private Registers registers; + + private int cycles; + + Opcode[] opcodes; + Opcode[] prefixedOpcodes; + + public CPU(final MMU mmu, final InterruptManager interruptManager) { + this.mmu = mmu; + this.interruptManager = interruptManager; + + registers = new Registers(); + + opcodes = new Opcode[0x100]; + prefixedOpcodes = new Opcode[0x100]; + + loadOpcodes(); + loadPrefixedOpcodes(); + + interruptManager.addListener(this); + } + + private void loadOpcodes() { + // NOP + opcodes[0x00] = new Opcode() { + @Override + public int run(Registers registers, MMU mmu) { + return 0; + } + + @Override + public String toString() { + return "NOP"; + } + }; + + // Load Opcodes + for (Entry val : indexedList(0x01, 0x10, Argument.BC, Argument.DE, Argument.HL, Argument.SP)) { + opcodes[val.getKey()] = new LoadOpcode(Argument.D16, val.getValue()); + } + + for (Entry val : indexedList(0x02, 0x10, Argument._BC, Argument._DE, Argument._HL_INC, Argument._HL_DEC)) { + opcodes[val.getKey()] = new LoadOpcode(Argument.A, val.getValue()); + } + for (Entry val : indexedList(0x06, 0x10, Argument.B, Argument.D, Argument.H, Argument._HL)) { + opcodes[val.getKey()] = new LoadOpcode(Argument.D8, val.getValue()); + } + for (Entry val : indexedList(0x0A, 0x10, Argument._BC, Argument._DE, Argument._HL_INC, Argument._HL_DEC)) { + opcodes[val.getKey()] = new LoadOpcode(val.getValue(), Argument.A); + } + for (Entry val : indexedList(0x0E, 0x10, Argument.C, Argument.E, Argument.L, Argument.A)) { + opcodes[val.getKey()] = new LoadOpcode(Argument.D8, val.getValue()); + } + + for (Entry val : indexedList(0x40, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + for (Entry row : indexedList(val.getKey(), 0x10, Argument.B, Argument.D, Argument.H, Argument._HL)) { + opcodes[row.getKey()] = new LoadOpcode(val.getValue(), row.getValue()); + } + } + for (Entry val : indexedList(0x48, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + for (Entry row : indexedList(val.getKey(), 0x10, Argument.C, Argument.E, Argument.L, Argument.A)) { + opcodes[row.getKey()] = new LoadOpcode(val.getValue(), row.getValue()); + } + } + + opcodes[0x08] = new LoadOpcode(Argument.SP, Argument._D16_SHORT); + + opcodes[0xE0] = new LoadOpcode(Argument.A, Argument._D8); + opcodes[0xF0] = new LoadOpcode(Argument._D8, Argument.A); + + opcodes[0xE2] = new LoadOpcode(Argument.A, Argument._C); + opcodes[0xF2] = new LoadOpcode(Argument._C, Argument.A); + + opcodes[0xF9] = new LoadOpcode(Argument.HL, Argument.SP); + + opcodes[0xEA] = new LoadOpcode(Argument.A, Argument._D16); + opcodes[0xFA] = new LoadOpcode(Argument._D16, Argument.A); + + // POP and PUSH + for (Entry val : indexedList(0xC1, 0x10, Argument.BC, Argument.DE, Argument.HL, Argument.AF)) { + opcodes[val.getKey()] = new PopOpcode(val.getValue()); + } + for (Entry val : indexedList(0xC5, 0x10, Argument.BC, Argument.DE, Argument.HL, Argument.AF)) { + opcodes[val.getKey()] = new PushOpcode(val.getValue()); + } + + // TODO: Override 0x76 with HALT + + // Jumps, Calls and Returns + opcodes[0x20] = new JumpRelativeOpcode(Flags.ZERO, Argument.I8, true); + opcodes[0x30] = new JumpRelativeOpcode(Flags.CARRY, Argument.I8, true); + + opcodes[0x18] = new JumpRelativeOpcode(0, Argument.I8, false); + opcodes[0x28] = new JumpRelativeOpcode(Flags.ZERO, Argument.I8, false); + opcodes[0x38] = new JumpRelativeOpcode(Flags.CARRY, Argument.I8, false); + + opcodes[0xC3] = new JumpOpcode(0, Argument.D16, false); + opcodes[0xC2] = new JumpOpcode(Flags.ZERO, Argument.D16, true); + opcodes[0xD2] = new JumpOpcode(Flags.CARRY, Argument.D16, true); + + opcodes[0xE9] = new JumpOpcode(0, Argument.HL, false); + + opcodes[0xCA] = new JumpOpcode(Flags.ZERO, Argument.D16, false); + opcodes[0xDA] = new JumpOpcode(Flags.CARRY, Argument.D16, false); + + opcodes[0xC4] = new CallOpcode(Flags.ZERO, Argument.D16, true); + opcodes[0xD4] = new CallOpcode(Flags.CARRY, Argument.D16, true); + + opcodes[0xCC] = new CallOpcode(Flags.ZERO, Argument.D16, false); + opcodes[0xDC] = new CallOpcode(Flags.CARRY, Argument.D16, false); + + opcodes[0xCD] = new CallOpcode(0, Argument.D16, false); + + opcodes[0xC0] = new ReturnOpcode(Flags.ZERO, true, false, interruptManager); + opcodes[0xD0] = new ReturnOpcode(Flags.CARRY, true, false, interruptManager); + + opcodes[0xC8] = new ReturnOpcode(Flags.ZERO, false, false, interruptManager); + opcodes[0xD8] = new ReturnOpcode(Flags.CARRY, false, false, interruptManager); + + opcodes[0xC9] = new ReturnOpcode(0, false, false, interruptManager); + opcodes[0xD9] = new ReturnOpcode(0, false, true, interruptManager); + + // Bit Operations + + opcodes[0x07] = new RotateAOpcode(true, RotateAOpcode.LEFT); + opcodes[0x17] = new RotateAOpcode(false, RotateAOpcode.LEFT); + opcodes[0x0F] = new RotateAOpcode(true, RotateAOpcode.RIGHT); + opcodes[0x1F] = new RotateAOpcode(false, RotateAOpcode.RIGHT); + + // 16-bit ALU Opcodes + for (Entry val : indexedList(0x03, 0x10, Argument.BC, Argument.DE,Argument.HL, Argument.SP)) { + opcodes[val.getKey()] = new ALU16Opcode(ALU16Type.INC, val.getValue()); + opcodes[val.getKey() + 8] = new ALU16Opcode(ALU16Type.DEC, val.getValue()); + } + for (Entry val : indexedList(0x09, 0x10, Argument.BC, Argument.DE,Argument.HL, Argument.SP)) { + opcodes[val.getKey()] = new ALU16Opcode(ALU16Type.ADD, val.getValue()); + } + + // ALU Opcodes + for (Entry val : indexedList(0x04, 8, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + opcodes[val.getKey()] = new ALUOpcode(ALUType.INC, val.getValue()); + opcodes[val.getKey() + 1] = new ALUOpcode(ALUType.DEC, val.getValue()); + } + + for (Entry val : indexedList(0x80, 8, ALUType.ADD, ALUType.ADC, ALUType.SUB, ALUType.SBC, ALUType.AND, ALUType.XOR, ALUType.OR, ALUType.CP)) { + for (Entry row : indexedList(val.getKey(), 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + opcodes[row.getKey()] = new ALUOpcode(val.getValue(), row.getValue()); + } + } + for (Entry val : indexedList(0xC6, 8, ALUType.ADD, ALUType.ADC, ALUType.SUB, ALUType.SBC, ALUType.AND, ALUType.XOR, ALUType.OR, ALUType.CP)) { + opcodes[val.getKey()] = new ALUOpcode(val.getValue(), Argument.D8); + } + + // Restarts + for (Entry val : indexedList(0xC7, 8, 0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38)) { + opcodes[val.getKey()] = new RestartOpcode(val.getValue()); + } + + // Interrupts + opcodes[0xF3] = new InterruptOpcode(false, interruptManager); + opcodes[0xFB] = new InterruptOpcode(true, interruptManager); + + // Misc + opcodes[0x2F] = new ComplementOpcode(); + opcodes[0x37] = new SetCarryOpcode(); + opcodes[0x3F] = new FlipCarryOpcode(); + } + + public void loadPrefixedOpcodes() { + for (Entry val : indexedList(0x00, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + prefixedOpcodes[val.getKey() + 0x00] = new RotateOpcode(true, val.getValue(), RotateOpcode.LEFT); + prefixedOpcodes[val.getKey() + 0x08] = new RotateOpcode(true, val.getValue(), RotateOpcode.RIGHT); + prefixedOpcodes[val.getKey() + 0x10] = new RotateOpcode(false, val.getValue(), RotateOpcode.LEFT); + prefixedOpcodes[val.getKey() + 0x18] = new RotateOpcode(false, val.getValue(), RotateOpcode.RIGHT); + } + + for (Entry val : indexedList(0x40, 8, 0, 1, 2, 3, 4, 5, 6, 7)) { + for (Entry row : indexedList(val.getKey(), 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + prefixedOpcodes[row.getKey()] = new BitOpcode(val.getValue(), row.getValue()); + } + } + + for (Entry val : indexedList(0x30, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + prefixedOpcodes[val.getKey()] = new SwapOpcode(val.getValue()); + } + + // Reset Bit + for (Entry val : indexedList(0x80, 8, 0, 1, 2, 3, 4, 5, 6, 7)) { + for (Entry row : indexedList(val.getKey(), 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + prefixedOpcodes[row.getKey()] = new ResetBitOpcode(val.getValue(), row.getValue()); + } + } + + // Set Bit + for (Entry val : indexedList(0xC0, 8, 0, 1, 2, 3, 4, 5, 6, 7)) { + for (Entry row : indexedList(val.getKey(), 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + prefixedOpcodes[row.getKey()] = new SetBitOpcode(val.getValue(), row.getValue()); + } + } + + for (Entry val : indexedList(0x20, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + prefixedOpcodes[val.getKey()] = new ShiftOpcode(ShiftType.SLA, val.getValue()); + } + for (Entry val : indexedList(0x28, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + prefixedOpcodes[val.getKey()] = new ShiftOpcode(ShiftType.SRA, val.getValue()); + } + for (Entry val : indexedList(0x38, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + prefixedOpcodes[val.getKey()] = new ShiftOpcode(ShiftType.SRL, val.getValue()); + } + } + + @SafeVarargs + private static Iterable> indexedList(int start, int step, T... values) { + Map map = new LinkedHashMap<>(); + int i = start; + for (T e : values) { + map.put(i, e); + i += step; + } + return map.entrySet(); + } + + public void clock() { + if (cycles == 0) { + //Log.info("PC: 0x%4x", registers.getPC()); + + if (registers.getPC() == 0x2EFC) { + Log.info("Breakpoint."); + } + + int opcodeValue = mmu.read(registers.getPC()); + registers.incPC(); + + if (opcodeValue == 0xCB) { + int prefixedValue = mmu.read(registers.getPC()); + registers.incPC(); + + runPrefixedOpcode(prefixedValue); + + } else { + runOpcode(opcodeValue); + } + } + + cycles--; + } + + private void runOpcode(int opcodeValue) { + Opcode opcode = opcodes[opcodeValue]; + + if (opcode == null) { + Log.error("Unimplemented opcode 0x%2x at 0x%4x!", opcodeValue, registers.getPC()); + + Log.close(); + + throw new IllegalStateException(); + + //System.exit(0); + } + + //Log.info("Executing opcode: 0x%2x (%s)", opcodeValue, opcode.toString()); + cycles += opcode.run(registers, mmu) + 4; // Adding 4 because we fetch the instruction. + } + + private void runPrefixedOpcode(int opcodeValue) { + Opcode opcode = prefixedOpcodes[opcodeValue]; + + if (opcode == null) { + Log.error("Unimplemented prefixed opcode 0x%2x!", opcodeValue); + + Log.close(); + + throw new IllegalStateException(); + + //System.exit(0); + } + + //Log.info("Executing prefixed opcode: 0x%2x (%s)", opcodeValue, opcode.toString()); + cycles += opcode.run(registers, mmu) + 8; // Adding 8 because we fetch the 0xCB prefix and the instruction. + } + + @Override + public void interruptOccured(int types) { + /*if ((types & InterruptManager.VBLANK) != 0) { + interruptManager.disable(); + + registers.decSP(); + mmu.write(registers.getPC() >> 8, registers.getSP()); + registers.decSP(); + mmu.write(registers.getPC(), registers.getSP()); + + Log.info("Pushing 0x%4x to the stack.", registers.getPC()); + + registers.setPC(0x40); + + cycles += 12; + }*/ + } + + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/Flags.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/Flags.java new file mode 100644 index 0000000..c4e121a --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/Flags.java @@ -0,0 +1,29 @@ +package playingcoffee.core.cpu; + +public class Flags { + + private int f; // Technically the "correct" notation + + public static final int ZERO = 1 << 7; + public static final int NEGATIVE = 1 << 6; + public static final int HALF_CARRY = 1 << 5; + public static final int CARRY = 1 << 4; + + public void set(int value) { + f = value & 0xFF; + } + + public int get() { + return f; + } + + public void set(int flag, boolean value) { + if (value) f |= flag; + else f &= ~flag; + } + + public boolean get(int flag) { + return (f & flag) != 0; + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/Registers.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/Registers.java new file mode 100644 index 0000000..82c97f6 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/cpu/Registers.java @@ -0,0 +1,57 @@ +package playingcoffee.core.cpu; + +public class Registers { + + private int a, b, c, d, e, h, l; + + private int pc, sp; + + private Flags flags; + + public Registers() { + flags = new Flags(); + } + + public int getA() { return a; } + public int getB() { return b; } + public int getC() { return c; } + public int getD() { return d; } + public int getE() { return e; } + public int getH() { return h; } + public int getL() { return l; } + + public void setA(int value) { a = value & 0xFF; } + public void setB(int value) { b = value & 0xFF; } + public void setC(int value) { c = value & 0xFF; } + public void setD(int value) { d = value & 0xFF; } + public void setE(int value) { e = value & 0xFF; } + public void setH(int value) { h = value & 0xFF; } + public void setL(int value) { l = value & 0xFF; } + + public Flags getFlags() { return flags; } + + public int getAF() { return (a << 8) | flags.get(); } + public int getBC() { return (b << 8) | c; } + public int getDE() { return (d << 8) | e; } + public int getHL() { return (h << 8) | l; } + + public int getPC() { return pc; } + public int getSP() { return sp; } + + public void setAF(int value) { flags.set(value >> 8); setA(value); } + public void setBC(int value) { setB(value >> 8); setC(value); } + public void setDE(int value) { setD(value >> 8); setE(value); } + public void setHL(int value) { setH(value >> 8); setL(value); } + + public void setPC(int value) { pc = value & 0xFFFF; } + public void setSP(int value) { sp = value & 0xFFFF; } + + public void addPC(int value) { setPC(getPC() + value); }; + public void addSP(int value) { setSP(getSP() + value); }; + + public void incPC() { addPC(1); } + public void incSP() { addSP(1); } + + public void decPC() { addPC(-1); } + public void decSP() { addSP(-1); } +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ALU16Opcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ALU16Opcode.java new file mode 100644 index 0000000..b4fef41 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ALU16Opcode.java @@ -0,0 +1,57 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; +import playingcoffee.log.Log; + +public class ALU16Opcode implements Opcode { + + private final ALU16Type type; + private final Argument register; + + public ALU16Opcode(ALU16Type type, Argument register) { + this.type = type; + this.register = register; + } + + @Override + public int run(Registers registers, MMU mmu) { + int result = 0; + + switch (type) { + case INC: + register.write(register.read(registers, mmu) + 1, registers, mmu); + break; + case DEC: + register.write(register.read(registers, mmu) - 1, registers, mmu); + break; + case ADD: + int value = register.read(registers, mmu); + result = registers.getHL() + value; + + registers.getFlags().set(Flags.NEGATIVE, false); + registers.getFlags().set(Flags.HALF_CARRY, ((value & 0xF) + (registers.getHL() & 0xF) > 0xF)); + registers.getFlags().set(Flags.CARRY, (result & 0xFFFF0000) != 0); + + register.write(result, registers, mmu); + + break; + default: + Log.error("wtf!? how did we get here?!?!?"); + break; + } + + return register.getCycles() + 4; + } + + public enum ALU16Type { + INC, DEC, ADD + } + + @Override + public String toString() { + return type.name() + " " + register.getName(); + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ALUOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ALUOpcode.java new file mode 100644 index 0000000..8cfa92a --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ALUOpcode.java @@ -0,0 +1,134 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; +import playingcoffee.log.Log; + +public class ALUOpcode implements Opcode { + + private final ALUType type; + private final Argument register; + + public ALUOpcode(ALUType type, Argument register) { + this.type = type; + this.register = register; + } + + @Override + public int run(Registers registers, MMU mmu) { + int result = 0; + int registerValue = register.read(registers, mmu); + + switch (type) { + case ADD: + result = registers.getA() + registerValue; + + registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0); + registers.getFlags().set(Flags.NEGATIVE, false); + registers.getFlags().set(Flags.HALF_CARRY, (registers.getA() & 0xF) + (registerValue & 0xF) > 0xF); + registers.getFlags().set(Flags.CARRY, (result & 0xFF00) > 0); + + break; + case ADC: + int value = registerValue + (registers.getFlags().get(Flags.CARRY) ? 1 : 0); + + result = registers.getA() + value; + + registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0); + registers.getFlags().set(Flags.NEGATIVE, false); + registers.getFlags().set(Flags.HALF_CARRY, (registers.getA() & 0xF) + (value & 0xF) > 0xF); + registers.getFlags().set(Flags.CARRY, (result & 0xFF00) > 0); + + break; + case AND: + result = registers.getA() & registerValue; + + registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0); + registers.getFlags().set(Flags.NEGATIVE, false); + registers.getFlags().set(Flags.HALF_CARRY, true); + registers.getFlags().set(Flags.CARRY, false); + + break; + case CP: + result = registers.getA(); + + registers.getFlags().set(Flags.ZERO, registers.getA() == registerValue); + registers.getFlags().set(Flags.NEGATIVE, true); + registers.getFlags().set(Flags.HALF_CARRY, (registerValue & 0xF) > (registers.getA() & 0xF)); + registers.getFlags().set(Flags.CARRY, registerValue > registers.getA()); + + break; + case OR: + result = registers.getA() | registerValue; + + registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0); + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY | Flags.CARRY, false); + + break; + case SBC: + value = registerValue + (registers.getFlags().get(Flags.CARRY) ? 1 : 0); + + registers.getFlags().set(Flags.ZERO, value == registers.getA()); + registers.getFlags().set(Flags.NEGATIVE, true); + registers.getFlags().set(Flags.HALF_CARRY, (value & 0xF) > (registers.getA() & 0xF)); + registers.getFlags().set(Flags.CARRY, value > registers.getA()); + + result = registers.getA() - value; + + break; + case SUB: + registers.getFlags().set(Flags.ZERO, registerValue == registers.getA()); + registers.getFlags().set(Flags.NEGATIVE, true); + registers.getFlags().set(Flags.HALF_CARRY, registerValue > (registers.getA() & 0xF)); + registers.getFlags().set(Flags.CARRY, registerValue > registers.getA()); + + result = registers.getA() - registerValue; + + break; + case XOR: + result = registers.getA() ^ registerValue; + + registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0); + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY | Flags.CARRY, false); + + break; + case INC: + result = registerValue + 1; + + registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0); + registers.getFlags().set(Flags.NEGATIVE, false); + registers.getFlags().set(Flags.HALF_CARRY, (result & 0x10) != 0); + + register.write(result, registers, mmu); + break; + case DEC: + result = registerValue - 1; + + registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0); + registers.getFlags().set(Flags.NEGATIVE, true); + registers.getFlags().set(Flags.HALF_CARRY, (registerValue & 0x0F) == 0x0F); + + register.write(result, registers, mmu); + break; + default: + Log.error("wtf!? how did we get here?!?!?"); + break; + } + + if (type != ALUType.INC && type != ALUType.DEC) + registers.setA(result); + + return 0; + } + + public enum ALUType { + ADD, ADC, SUB, SBC, AND, XOR, OR, CP, INC, DEC + } + + @Override + public String toString() { + return type.name() + " A, " + register.getName(); + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/Argument.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/Argument.java new file mode 100644 index 0000000..6250bad --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/Argument.java @@ -0,0 +1,348 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; +import playingcoffee.log.Log; + +public enum Argument { + + // Note: The '_' prefix, specifies the value located at the memory address the register is pointing to. + // Example: _HL <=> memory[HL] + + A { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getA(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setA(value); + } + }, B { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getB(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setB(value); + } + }, C { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getC(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setC(value); + } + }, D { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getD(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setD(value); + } + }, E { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getE(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setE(value); + } + }, H { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getH(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setH(value); + } + }, L { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getL(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setL(value); + } + }, AF { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getAF(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setAF(value); + } + }, BC { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getBC(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setBC(value); + } + }, DE { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getDE(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setDE(value); + } + }, HL { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getHL(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setHL(value); + } + }, SP { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getSP(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setSP(value); + } + }, _BC("(BC)", 4) { + @Override + public int read(Registers registers, MMU mmu) { + return mmu.read(registers.getBC()); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + mmu.write(value, registers.getBC()); + } + }, _DE("(DE)", 4) { + @Override + public int read(Registers registers, MMU mmu) { + return mmu.read(registers.getDE()); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + mmu.write(value, registers.getDE()); + } + }, _HL("(HL)", 4) { + @Override + public int read(Registers registers, MMU mmu) { + return mmu.read(registers.getHL()); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + mmu.write(value, registers.getHL()); + } + }, _HL_INC("(HL+)", 4) { + @Override + public int read(Registers registers, MMU mmu) { + int value = mmu.read(registers.getHL()); + registers.setHL(registers.getHL() + 1); + return value; + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + int address = registers.getHL(); + registers.setHL(registers.getHL() + 1); + mmu.write(value, address); + } + }, _HL_DEC("(HL-)", 4) { + @Override + public int read(Registers registers, MMU mmu) { + int value = mmu.read(registers.getHL()); + registers.setHL(registers.getHL() - 1); + return value; + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + int address = registers.getHL(); + registers.setHL(registers.getHL() - 1); + mmu.write(value, address); + } + }, D8(4) { + @Override + public int read(Registers registers, MMU mmu) { + int value = mmu.read(registers.getPC()); + registers.incPC(); + + return value; + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + Log.warn("Why are you writing to this argument?"); + } + }, D16(8) { + @Override + public int read(Registers registers, MMU mmu) { + int value = mmu.read(registers.getPC()); + + registers.incPC(); + + value |= mmu.read(registers.getPC()) << 8; + + registers.incPC(); + + return value; + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + Log.warn("Why are you writing to this argument?"); + } + }, I8(4) { + + @Override + public int read(Registers registers, MMU mmu) { + byte relativeAddress = (byte) mmu.read(registers.getPC()); + + registers.incPC(); + + return relativeAddress; + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + Log.warn("Why are you writing to this argument?"); + } + + }, _C("(C)", 4) { // memory[0xFF00 + C] + @Override + public int read(Registers registers, MMU mmu) { + int value = mmu.read(0xFF00 + registers.getC()); + + return value; + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + mmu.write(value, 0xFF00 + registers.getC()); + } + }, _D8("(D8)", 8) { // memory[0xFF00 + memory[PC++]] + @Override + public int read(Registers registers, MMU mmu) { + int value = mmu.read(0xFF00 + mmu.read(registers.getPC())); + + registers.incPC(); + + return value; + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + mmu.write(value, 0xFF00 + mmu.read(registers.getPC())); + + registers.incPC(); + } + }, _D16("(D16)", 12) { + @Override + public int read(Registers registers, MMU mmu) { + int address = mmu.read(registers.getPC()); + + registers.incPC(); + + address |= mmu.read(registers.getPC()) << 8; + + registers.incPC(); + + return mmu.read(address); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + int address = mmu.read(registers.getPC()); + + registers.incPC(); + + address |= mmu.read(registers.getPC()) << 8; + + registers.incPC(); + + mmu.write(value, address); + } + }, _D16_SHORT("(D16 (16 bits))", 16) { + @Override + public int read(Registers registers, MMU mmu) { + int address = mmu.read(registers.getPC()); + + registers.incPC(); + + address |= mmu.read(registers.getPC()) << 8; + + registers.incPC(); + + return (mmu.read(address) << 8) | mmu.read(address); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + int address = mmu.read(registers.getPC()); + + registers.incPC(); + + address |= mmu.read(registers.getPC()) << 8; + + registers.incPC(); + + mmu.write(value >> 8, address); + mmu.write(value, address + 1); + } + }; + + private String name; + private int cycles; + + Argument(String name, int cycles) { + this.name = name; + this.cycles = cycles; + } + + Argument(int cycles) { + this.name = name(); + this.cycles = cycles; + } + + Argument() { + this.name = name(); + this.cycles = 0; + } + + public abstract int read(Registers registers, MMU mmu); + public abstract void write(int value, Registers registers, MMU mmu); + + public String getName() { + return name; + } + + public int getCycles() { + return cycles; + } +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/CallOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/CallOpcode.java new file mode 100644 index 0000000..2a0e2ad --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/CallOpcode.java @@ -0,0 +1,60 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; +import playingcoffee.log.Log; + +public class CallOpcode implements Opcode { + + private int conditionFlag; + private Argument address; + private boolean not; + + public CallOpcode(int conditionFlag, Argument address, boolean not) { + this.conditionFlag = conditionFlag; + this.address = address; + this.not = not; + } + + public boolean canExecute(Registers registers) { + return (conditionFlag == 0) || (registers.getFlags().get(conditionFlag) ^ not); + } + + @Override + public int run(Registers registers, MMU mmu) { + int addressToJump = address.read(registers, mmu); + + if (canExecute(registers)) { + registers.decSP(); + mmu.write(registers.getPC() >> 8, registers.getSP()); + registers.decSP(); + mmu.write(registers.getPC(), registers.getSP()); + + Log.info("Pushing 0x%4x to the stack.", registers.getPC()); + + registers.setPC(addressToJump); + + return 12 + address.getCycles(); // 2 memory writes and additional cycle + } + + return address.getCycles(); + } + + @Override + public String toString() { + if (conditionFlag == 0) + return "CALL " + address.getName(); + + char flag = ' '; + switch (conditionFlag) { + case Flags.ZERO: flag = 'Z'; break; + case Flags.NEGATIVE: flag = 'N'; break; + case Flags.HALF_CARRY: flag = 'H'; break; + case Flags.CARRY: flag = 'C'; break; + } + + return "CALL " + (not ? "" : "N") + flag + ", " + address.getName(); + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ComplementOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ComplementOpcode.java new file mode 100644 index 0000000..ece82c6 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ComplementOpcode.java @@ -0,0 +1,18 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; + +public class ComplementOpcode implements Opcode { + + @Override + public int run(Registers registers, MMU mmu) { + registers.setA(~registers.getA()); + + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false); + + return 0; + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/FlipCarryOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/FlipCarryOpcode.java new file mode 100644 index 0000000..0b32d4a --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/FlipCarryOpcode.java @@ -0,0 +1,17 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; + +public class FlipCarryOpcode implements Opcode { + + @Override + public int run(Registers registers, MMU mmu) { + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, !registers.getFlags().get(Flags.CARRY)); + + return 0; + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/InterruptOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/InterruptOpcode.java new file mode 100644 index 0000000..9c398f6 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/InterruptOpcode.java @@ -0,0 +1,29 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.InterruptManager; +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; + +public class InterruptOpcode implements Opcode { + + private boolean enable; + private InterruptManager interruptManager; + + public InterruptOpcode(boolean enable, InterruptManager interruptManager) { + this.enable = enable; + this.interruptManager = interruptManager; + } + + @Override + public int run(Registers registers, MMU mmu) { + if (enable) + interruptManager.enable(); + else + interruptManager.disable(); + + return 0; + } + + + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/JumpOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/JumpOpcode.java new file mode 100644 index 0000000..5444f41 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/JumpOpcode.java @@ -0,0 +1,50 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; + +public class JumpOpcode implements Opcode { + + private int conditionFlag; + private Argument address; + private boolean not; + + public JumpOpcode(int conditionFlag, Argument address, boolean not) { + this.conditionFlag = conditionFlag; + this.address = address; + this.not = not; + } + + public boolean canExecute(Registers registers) { + return (conditionFlag == 0) || (registers.getFlags().get(conditionFlag) ^ not); + } + + @Override + public int run(Registers registers, MMU mmu) { + int addressToJump = address.read(registers, mmu); + + if (canExecute(registers)) { + registers.setPC(addressToJump); + return address.getCycles() + 4; + } + + return address.getCycles(); + } + + @Override + public String toString() { + if (conditionFlag == 0) + return "JP " + address.getName(); + + char flag = ' '; + switch (conditionFlag) { + case Flags.ZERO: flag = 'Z'; break; + case Flags.NEGATIVE: flag = 'N'; break; + case Flags.HALF_CARRY: flag = 'H'; break; + case Flags.CARRY: flag = 'C'; break; + } + + return "JP " + (not ? "" : "N") + flag + ", " + address.getName(); + } +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/JumpRelativeOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/JumpRelativeOpcode.java new file mode 100644 index 0000000..3a804a8 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/JumpRelativeOpcode.java @@ -0,0 +1,51 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; + +public class JumpRelativeOpcode implements Opcode { + + private int conditionFlag; + private Argument address; + private boolean not; + + public JumpRelativeOpcode(int conditionFlag, Argument address, boolean not) { + this.conditionFlag = conditionFlag; + this.address = address; + this.not = not; + } + + public boolean canExecute(Registers registers) { + return (conditionFlag == 0) || (registers.getFlags().get(conditionFlag) ^ not); + } + + @Override + public int run(Registers registers, MMU mmu) { + int relativeValue = address.read(registers, mmu); + + int addressToJump = registers.getPC() + relativeValue; + if (canExecute(registers)) { + registers.setPC(addressToJump); + return address.getCycles() + 4; + } + + return address.getCycles(); + } + + @Override + public String toString() { + if (conditionFlag == 0) + return "JR " + address.getName(); + + char flag = ' '; + switch (conditionFlag) { + case Flags.ZERO: flag = 'Z'; break; + case Flags.NEGATIVE: flag = 'N'; break; + case Flags.HALF_CARRY: flag = 'H'; break; + case Flags.CARRY: flag = 'C'; break; + } + + return "JR " + (not ? "" : "N") + flag + ", " + address.getName(); + } +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/LoadOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/LoadOpcode.java new file mode 100644 index 0000000..a782b65 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/LoadOpcode.java @@ -0,0 +1,26 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; + +public class LoadOpcode implements Opcode { + + private Argument from, to; + + public LoadOpcode(Argument from, Argument to) { + this.from = from; + this.to = to; + } + + @Override + public int run(Registers registers, MMU mmu) { + to.write(from.read(registers, mmu), registers, mmu); + + return to.getCycles() + from.getCycles(); + } + + @Override + public String toString() { + return "LD " + to.getName() + ", " + from.getName(); + } +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/Opcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/Opcode.java new file mode 100644 index 0000000..71cdaa5 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/Opcode.java @@ -0,0 +1,10 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; + +public interface Opcode { + + public int run(Registers registers, MMU mmu); + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/PopOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/PopOpcode.java new file mode 100644 index 0000000..ce4daca --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/PopOpcode.java @@ -0,0 +1,32 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; + +public class PopOpcode implements Opcode { + + private final Argument register; + + public PopOpcode(Argument register) { + this.register = register; + } + + @Override + public int run(Registers registers, MMU mmu) { + int value = mmu.read(registers.getSP()); + registers.incSP(); + + value |= mmu.read(registers.getSP()) << 8; + registers.incSP(); + + register.write(value, registers, mmu); + + return 8; + } + + @Override + public String toString() { + return "POP " + register.getName(); + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/PushOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/PushOpcode.java new file mode 100644 index 0000000..fee0abf --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/PushOpcode.java @@ -0,0 +1,35 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; +import playingcoffee.log.Log; + +public class PushOpcode implements Opcode { + + private final Argument register; + + public PushOpcode(Argument register) { + this.register = register; + } + + @Override + public int run(Registers registers, MMU mmu) { + int value = register.read(registers, mmu); + + registers.decSP(); + mmu.write(value >> 8, registers.getSP()); + + registers.decSP(); + mmu.write(value, registers.getSP()); + + Log.info("Pushing 0x%4x to the stack.", value); + + return 8; + } + + @Override + public String toString() { + return "PUSH " + register.getName(); + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/RestartOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/RestartOpcode.java new file mode 100644 index 0000000..8ce0964 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/RestartOpcode.java @@ -0,0 +1,26 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; + +public class RestartOpcode implements Opcode { + + private int address; + + public RestartOpcode(int address) { + this.address = address; + } + + @Override + public int run(Registers registers, MMU mmu) { + registers.decSP(); + mmu.write(registers.getPC() >> 8, registers.getSP()); + registers.decSP(); + mmu.write(registers.getPC(), registers.getSP()); + + registers.setPC(address); + + return 12; + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ReturnOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ReturnOpcode.java new file mode 100644 index 0000000..5c4d903 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/ReturnOpcode.java @@ -0,0 +1,67 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.InterruptManager; +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; +import playingcoffee.log.Log; + +public class ReturnOpcode implements Opcode { + + private int conditionFlag; + private boolean not; + private boolean fromInterupt; + + private final InterruptManager interruptManager; + + public ReturnOpcode(int conditionFlag, boolean not, boolean fromInterupt, final InterruptManager interruptManager) { + this.conditionFlag = conditionFlag; + this.not = not; + this.fromInterupt = fromInterupt; + this.interruptManager = interruptManager; + } + + public boolean canExecute(Registers registers) { + return (conditionFlag == 0) || (registers.getFlags().get(conditionFlag) ^ not); + } + + @Override + public int run(Registers registers, MMU mmu) { + int addressToJump = mmu.read(registers.getSP()); + registers.incSP(); + + addressToJump |= mmu.read(registers.getSP()) << 8; + registers.incSP(); + + if (canExecute(registers)) { + Log.info("Returning from 0x%4x to 0x%4x", registers.getPC(), addressToJump); + + registers.setPC(addressToJump); + + if (fromInterupt) { + interruptManager.enable(); + } + + return 16; + } + + return 4; + } + + @Override + public String toString() { + if (conditionFlag == 0) + return "RET" + (fromInterupt ? "I" : ""); + + char flag = ' '; + switch (conditionFlag) { + case Flags.ZERO: flag = 'Z'; break; + case Flags.NEGATIVE: flag = 'N'; break; + case Flags.HALF_CARRY: flag = 'H'; break; + case Flags.CARRY: flag = 'C'; break; + } + + return "RET" + (fromInterupt ? "I" : "") + " " + (not ? "" : "N") + flag; + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/RotateAOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/RotateAOpcode.java new file mode 100644 index 0000000..f74c024 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/RotateAOpcode.java @@ -0,0 +1,68 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; + +public class RotateAOpcode implements Opcode { + + private boolean withCarry; + private int direction; + + public static final int LEFT = -1; + public static final int RIGHT = 1; + + public RotateAOpcode(boolean withCarry, int direction) { + this.withCarry = withCarry; + this.direction = direction; + } + + @Override + public int run(Registers registers, MMU mmu) { + if (direction == LEFT) { + if (withCarry) { + int value = registers.getA(); + int result = (value << 1) | ((value >> 7) & 1); + + registers.getFlags().set(Flags.ZERO | Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, (value & 0x80) != 0); + + registers.setA(result); + } else { + int value = registers.getA(); + int result = (value << 1) | (registers.getFlags().get(Flags.CARRY) ? 1 : 0); + + registers.getFlags().set(Flags.ZERO | Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, (value & 0x80) != 0); + + registers.setA(result); + } + } else { + if (withCarry) { + int value = registers.getA(); + int result = (value >> 1) | ((value & 1) << 7); + + registers.getFlags().set(Flags.ZERO | Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, (value & 1) != 0); + + registers.setA(result); + } else { + int value = registers.getA(); + int result = (value >> 1) | ((registers.getFlags().get(Flags.CARRY) ? 1 : 0) << 7); + + registers.getFlags().set(Flags.ZERO | Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, (value & 1) != 0); + + registers.setA(result); + } + } + + return 0; + } + + @Override + public String toString() { + return "R" + (direction == LEFT ? "L" : "R") + (withCarry ? "C" : "" + "A"); + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/SetCarryOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/SetCarryOpcode.java new file mode 100644 index 0000000..9b570b6 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/SetCarryOpcode.java @@ -0,0 +1,17 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; + +public class SetCarryOpcode implements Opcode { + + @Override + public int run(Registers registers, MMU mmu) { + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, true); + + return 0; + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/BitOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/BitOpcode.java new file mode 100644 index 0000000..2053d6a --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/BitOpcode.java @@ -0,0 +1,33 @@ +package playingcoffee.core.opcode.prefixed; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; +import playingcoffee.core.opcode.Argument; +import playingcoffee.core.opcode.Opcode; + +public class BitOpcode implements Opcode { + + private int bit; + private Argument argument; + + public BitOpcode(int bit, Argument argument) { + this.bit = bit; + this.argument = argument; + } + + @Override + public int run(Registers registers, MMU mmu) { + registers.getFlags().set(Flags.ZERO, (argument.read(registers, mmu) & (1 << bit)) == 0); + registers.getFlags().set(Flags.NEGATIVE, false); + registers.getFlags().set(Flags.HALF_CARRY, true); + + return argument.getCycles(); + } + + @Override + public String toString() { + return "BIT " + bit + ", " + argument.getName(); + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/ResetBitOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/ResetBitOpcode.java new file mode 100644 index 0000000..525480e --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/ResetBitOpcode.java @@ -0,0 +1,25 @@ +package playingcoffee.core.opcode.prefixed; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; +import playingcoffee.core.opcode.Argument; +import playingcoffee.core.opcode.Opcode; + +public class ResetBitOpcode implements Opcode { + + private int bit; + private Argument argument; + + public ResetBitOpcode(int bit, Argument argument) { + this.bit = bit; + this.argument = argument; + } + + @Override + public int run(Registers registers, MMU mmu) { + argument.write(argument.read(registers, mmu) & (~(1 << bit)), registers, mmu); + + return argument.getCycles() * 2; + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/RotateOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/RotateOpcode.java new file mode 100644 index 0000000..b13a21a --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/RotateOpcode.java @@ -0,0 +1,54 @@ +package playingcoffee.core.opcode.prefixed; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; +import playingcoffee.core.opcode.Argument; +import playingcoffee.core.opcode.Opcode; + +public class RotateOpcode implements Opcode { + + private boolean withCarry; + private Argument register; + private int direction; + + public static final int LEFT = -1; + public static final int RIGHT = 1; + + public RotateOpcode(boolean withCarry, Argument register, int direction) { + this.withCarry = withCarry; + this.register = register; + this.direction = direction; + } + + @Override + public int run(Registers registers, MMU mmu) { + if (direction == LEFT) { + int value = register.read(registers, mmu); + int result = (value << 1) | (!withCarry ? (registers.getFlags().get(Flags.CARRY) ? 1 : 0) : (value >> 7) & 1); + + registers.getFlags().set(Flags.ZERO, result == 0); + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, (value & 0x80) != 0); + + register.write(result, registers, mmu); + + } else { + int value = register.read(registers, mmu); + int result = (value >> 1) | ((!withCarry ? (registers.getFlags().get(Flags.CARRY) ? 1 : 0) : (value & 0x1)) << 7); + + registers.getFlags().set(Flags.ZERO, result == 0); + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, (value & 1) != 0); + + register.write(result, registers, mmu); + } + + return register.getCycles(); + } + + @Override + public String toString() { + return "R" + (direction == LEFT ? "L" : "R") + (withCarry ? "C " : " " + register.getName()); + } +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/SetBitOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/SetBitOpcode.java new file mode 100644 index 0000000..f53b6c5 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/SetBitOpcode.java @@ -0,0 +1,25 @@ +package playingcoffee.core.opcode.prefixed; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; +import playingcoffee.core.opcode.Argument; +import playingcoffee.core.opcode.Opcode; + +public class SetBitOpcode implements Opcode { + + private int bit; + private Argument argument; + + public SetBitOpcode(int bit, Argument argument) { + this.bit = bit; + this.argument = argument; + } + + @Override + public int run(Registers registers, MMU mmu) { + argument.write(argument.read(registers, mmu) | (1 << bit), registers, mmu); + + return argument.getCycles() * 2; + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/ShiftOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/ShiftOpcode.java new file mode 100644 index 0000000..f75e9bd --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/ShiftOpcode.java @@ -0,0 +1,57 @@ +package playingcoffee.core.opcode.prefixed; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; +import playingcoffee.core.opcode.Argument; +import playingcoffee.core.opcode.Opcode; + +public class ShiftOpcode implements Opcode { + + private ShiftType type; + private Argument register; + + public ShiftOpcode(ShiftType type, Argument register) { + this.type = type; + this.register = register; + } + + public enum ShiftType { + SLA, SRA, SRL + } + + @Override + public int run(Registers registers, MMU mmu) { + int value, result; + + switch (type) { + case SLA: + value = register.read(registers, mmu); + result = value << 1; + + registers.getFlags().set(Flags.ZERO, result == 0); + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, (value & 0x80) != 0); + break; + case SRA: + value = register.read(registers, mmu); + result = (value >> 1) | (value & 0x80); + + registers.getFlags().set(Flags.ZERO, result == 0); + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, (value & 0x1) != 0); + break; + case SRL: + value = register.read(registers, mmu); + result = (value >> 1); + + registers.getFlags().set(Flags.ZERO, result == 0); + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, (value & 0x1) != 0); + break; + } + + return register.getCycles() * 2; + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/SwapOpcode.java b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/SwapOpcode.java new file mode 100644 index 0000000..b31cac8 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/core/opcode/prefixed/SwapOpcode.java @@ -0,0 +1,25 @@ +package playingcoffee.core.opcode.prefixed; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; +import playingcoffee.core.opcode.Argument; +import playingcoffee.core.opcode.Opcode; + +public class SwapOpcode implements Opcode { + + private Argument register; + + public SwapOpcode(Argument register) { + this.register = register; + } + + @Override + public int run(Registers registers, MMU mmu) { + int value = register.read(registers, mmu); + + register.write(((value & 0xF0) >> 4) | ((value & 0xF) << 4), registers, mmu); + + return register.getCycles() * 2; + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/log/Log.java b/playing-coffee - Copy/src/main/java/playingcoffee/log/Log.java new file mode 100644 index 0000000..1bb6478 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/log/Log.java @@ -0,0 +1,73 @@ +package playingcoffee.log; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.time.LocalTime; + +public class Log { + + public static final String ANSI_RESET = "\033[0m"; + public static final String ANSI_BLACK = "\u001B[30m"; + public static final String ANSI_RED = "\u001B[31m"; + public static final String ANSI_GREEN = "\u001B[32m"; + public static final String ANSI_YELLOW = "\u001B[33m"; + public static final String ANSI_BLUE = "\u001B[34m"; + public static final String ANSI_PURPLE = "\u001B[35m"; + public static final String ANSI_CYAN = "\u001B[36m"; + public static final String ANSI_WHITE = "\u001B[37m"; + + private static FileWriter fileWriter; + + private static boolean useColors = false; + + public static void init() { + try { + File file = new File("log.txt"); + file.createNewFile(); + + fileWriter = new FileWriter(file); + } catch (IOException e) { + e.printStackTrace(); + System.exit(-1); + } + info("Initialized logger"); + } + + public static void close() { + try { + fileWriter.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void info(String format, Object... args) { + log("Info", ANSI_RESET, format, args); + } + + public static void warn(String format, Object... args) { + log("Warning", ANSI_YELLOW, format, args); + } + + public static void error(String format, Object... args) { + log("Error", ANSI_RED, format, args); + } + + public static void fatal(String format, Object... args) { + log("Fatal", ANSI_RED, format, args); + } + + private static void log(String type, String ansiColor, String format, Object... args) { + String finalMessage = "[" + LocalTime.now() + "] " + type + ": " + String.format(format, args); + + ansiColor = useColors ? ansiColor : ""; + + System.out.println(ansiColor + finalMessage); + try { + fileWriter.write(finalMessage + "\n"); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/ppu/OAM.java b/playing-coffee - Copy/src/main/java/playingcoffee/ppu/OAM.java new file mode 100644 index 0000000..8ef0dcd --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/ppu/OAM.java @@ -0,0 +1,52 @@ +package playingcoffee.ppu; + +import playingcoffee.core.MemorySpace; +import playingcoffee.log.Log; + +public class OAM implements MemorySpace { + + public OAMEntry entries[]; + + public OAM() { + entries = new OAMEntry[40]; + } + + public int read(int address) { + int entry = (address - 0xFE00) / 4; + + switch (address % 4) { + case 0: return entries[entry].x; + case 1: return entries[entry].y; + case 2: return entries[entry].tileNumber; + case 3: return entries[entry].flags; + } + + Log.error("how in pete's holy christmas tree did we get here?"); + throw new IllegalArgumentException("Invalid address."); + } + + public void write(int value, int address) { + int entry = (address - 0xFE00) / 4; + + switch (address % 4) { + case 0: entries[entry].x = value; + case 1: entries[entry].y = value; + case 2: entries[entry].tileNumber = value; + case 3: entries[entry].flags = value; + } + + Log.error("how in pete's holy christmas tree did we get here?"); + throw new IllegalArgumentException("Invalid address."); + } + + @Override + public boolean inMemorySpace(int address) { + return (address >= 0xFE00 && address <= 0xFE9F); + } + + public class OAMEntry { + public int x, y; + public int tileNumber; + public int flags; + } +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/ppu/PPU.java b/playing-coffee - Copy/src/main/java/playingcoffee/ppu/PPU.java new file mode 100644 index 0000000..1a071a7 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/ppu/PPU.java @@ -0,0 +1,73 @@ +package playingcoffee.ppu; + +import playingcoffee.core.InterruptManager; +import playingcoffee.core.MMU; + +public class PPU { + + private final MMU mmu; + private final InterruptManager interruptManager; + + private PPURegisters registers; + private VRAM vram; + + private int clockCount = 0; + + public PPU(final MMU mmu, final InterruptManager interruptManager) { + this.mmu = mmu; + this.interruptManager = interruptManager; + + registers = new PPURegisters(); + vram = new VRAM(); + + this.mmu.connectMemorySpace(registers); + this.mmu.connectMemorySpace(vram); + } + + public void OAMSeach() { + registers.setLCDCMode(2); + } + + public void pixelTransfer() { + registers.setLCDCMode(3); + } + + public void HBlank() { + registers.setLCDCMode(0); + } + + public void VBlank() { + registers.setLCDCMode(1); + } + + public void clock() { + registers.lcdcYCoord = clockCount / 114; + + if (clockCount == 114 * 144) + interruptManager.requestInterrupt(InterruptManager.VBLANK); + + if (clockCount < 114 * 144) { + if (clockCount % 114 < 20) OAMSeach(); + else if (clockCount % 114 < 43) pixelTransfer(); + else HBlank(); + + } else { + VBlank(); + } + + clockCount++; + + if (clockCount == 17556) { + clockCount = 0; + } + } + + public PPURegisters getRegisters() { + return registers; + } + + public VRAM getVram() { + return vram; + } + +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/ppu/PPURegisters.java b/playing-coffee - Copy/src/main/java/playingcoffee/ppu/PPURegisters.java new file mode 100644 index 0000000..54695a8 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/ppu/PPURegisters.java @@ -0,0 +1,61 @@ +package playingcoffee.ppu; + +import playingcoffee.core.MemorySpace; + +public class PPURegisters implements MemorySpace { + + public int lcdControl; // 0xFF40 + public int lcdcStatus; // 0xFF41 + public int scrollY, scrollX; // 0xFF42 - 0xFF43 + public int lcdcYCoord; // 0xFF44 + public int lyCompare; // 0xFF45 + public int dmaTransferStart; // 0xFF46 + public int bgPalette; // 0xFF47 + public int objPalette0, objPalette1; // 0xFF48 - 0xFF49 + public int windowY, windowX; // 0xFF4A - 0xFF4B + + public void setLCDCMode(int mode) { + lcdcStatus = (lcdcStatus & 0xFC) | mode; + } + + public int read(int address) { + switch (address) { + case 0xFF40: return lcdControl; + case 0xFF41: return lcdcStatus; + case 0xFF42: return scrollY; + case 0xFF43: return scrollX; + case 0xFF44: return lcdcYCoord; + case 0xFF45: return lyCompare; + case 0xFF46: return dmaTransferStart; // TODO: Do DMA (I have no idea how to do it though...) + case 0xFF47: return bgPalette; + case 0xFF48: return objPalette0; + case 0xFF49: return objPalette1; + case 0xFF4A: return windowY; + case 0xFF4B: return windowX; + } + + throw new IllegalArgumentException("Invalid address"); + } + + public void write(int value, int address) { + switch (address) { + case 0xFF40: lcdControl = value; return; + case 0xFF41: lcdcStatus = value; return; + case 0xFF42: scrollY = value; return; + case 0xFF43: scrollX = value; return; + case 0xFF45: lyCompare = value; return; + case 0xFF46: dmaTransferStart = value; return; // TODO: Do DMA (I have no idea how to do it though...) + case 0xFF47: bgPalette = value; return; + case 0xFF48: objPalette0 = value; return; + case 0xFF49: objPalette1 = value; return; + case 0xFF4A: windowY = value; return; + case 0xFF4B: windowX = value; return; + } + + throw new IllegalArgumentException("Invalid address"); + } + + public boolean inMemorySpace(int address) { + return (address >= 0xFF40 && address <= 0xFF4B); + } +} diff --git a/playing-coffee - Copy/src/main/java/playingcoffee/ppu/VRAM.java b/playing-coffee - Copy/src/main/java/playingcoffee/ppu/VRAM.java new file mode 100644 index 0000000..c314446 --- /dev/null +++ b/playing-coffee - Copy/src/main/java/playingcoffee/ppu/VRAM.java @@ -0,0 +1,35 @@ +package playingcoffee.ppu; + +import playingcoffee.core.MemorySpace; + +public class VRAM implements MemorySpace { + + public int[] background0; + public int[] background1; + + public VRAM() { + background0 = new int[32 * 32]; + background1 = new int[32 * 32]; + } + + @Override + public int read(int address) { + if (address >= 0x9800 && address <= 0x9BFF) return background0[address % (32 * 32)]; + if (address >= 0x9C00 && address <= 0x9FFF) return background1[address % (32 * 32)]; + + return 0; + } + + @Override + public void write(int value, int address) { + if (address >= 0x9800 && address <= 0x9BFF) background0[address % (32 * 32)] = value; + if (address >= 0x9C00 && address <= 0x9FFF) background1[address % (32 * 32)] = value; + + } + + @Override + public boolean inMemorySpace(int address) { + return address >= 0x9800 && address <= 0x9FFF; + } + +} diff --git a/playing-coffee - Copy/src/test/java/playingcoffee/test/GameBoyTest.java b/playing-coffee - Copy/src/test/java/playingcoffee/test/GameBoyTest.java new file mode 100644 index 0000000..8f2adce --- /dev/null +++ b/playing-coffee - Copy/src/test/java/playingcoffee/test/GameBoyTest.java @@ -0,0 +1,26 @@ +package playingcoffee.test; + +import java.io.IOException; + +import org.junit.Test; + +public class GameBoyTest { + + @Test + public void testCPU() throws IOException { + /*Log.init(); + + + byte[] bootRom = Files.readAllBytes(Paths.get("dmg_boot.bin")); + + MMU mmu = new MMU(); + CPU cpu = new CPU(mmu); + + for (int i = 0; i < 0x100; i++) { + mmu.write(Byte.toUnsignedInt(bootRom[i]), i); + } + + while (true) cpu.clock();*/ + } + +} diff --git a/playing-coffee old/.classpath b/playing-coffee old/.classpath new file mode 100644 index 0000000..51a8bba --- /dev/null +++ b/playing-coffee old/.classpath @@ -0,0 +1,6 @@ + + + + + + diff --git a/playing-coffee old/.project b/playing-coffee old/.project new file mode 100644 index 0000000..e526655 --- /dev/null +++ b/playing-coffee old/.project @@ -0,0 +1,17 @@ + + + playing-coffee + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/playing-coffee old/.settings/org.eclipse.jdt.core.prefs b/playing-coffee old/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 0000000..3a21537 --- /dev/null +++ b/playing-coffee old/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,11 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/playing-coffee old/DMG_ROM.bin b/playing-coffee old/DMG_ROM.bin new file mode 100644 index 0000000000000000000000000000000000000000..afa0ee4792c2ba80afb6b0c1962e249e195e6fc0 GIT binary patch literal 256 zcmV+b0ssCn{{OEb|DQ6;d?5QFCjSl*K7caf3_kPXGCp^AKK$TE5d;At5P%xZl>p6_ z026!uG$8X4*Z>9y8WSQT1t9r88R`(3AupK@3_U0aG7TX4E)N*o0-PxJD zyJur=4(!a`+?k)dxidaILb;{6r9wO*1OXAK02&j%ApRqL{xl%<1{rvUBLyJ)h9Lev G0pL*XS7L7f literal 0 HcmV?d00001 diff --git a/playing-coffee old/bin/playingcoffee/core/CPU.class b/playing-coffee old/bin/playingcoffee/core/CPU.class new file mode 100644 index 0000000000000000000000000000000000000000..b006975ee99d568b959efc716c1f01c9966267d6 GIT binary patch literal 4191 zcmaKv33yc1703T?_Kc6@O$gbBkOh!rK(c@!Bq|{gGMHpSR+1<%nY;`WCNps|A&Irr zVr#3dUF=TxHLcjHKn5#HTU)zmciY<9y|!9gyV1Q`@tku95^yx%_kQ=R_uYHXeRFRf zdEx%k0M_FMCoCHB$D_fSNGu$RkBpdRbts-NtD8D{oUmyyMuP`~)zM%qTpbu1HA6`a z*0Hfk4TY^4vfA2u>NR8~%y48PX(lE#EXx$udCpJ4j?hdfYEq=;*HGYZ@9OUCY3lX| z+V?i~H??lx+Z5<&@77=$BzIm%=k{j*?(N$y^tl>B(MT+kyo$zFS=Fn-))XH$v!EeY z$0g8N%G0q3x`v$ANX%@X92+td-NB)#NinVQP%zpXOhi~u8yyYl4!xC`CcyKo>UG14 zVjTs@;WsSRkp(wP%XHY`aH5Qi85ZgM?A<6uF()k7;e?CEo!lRxmB_fXR1jJ%J!~e= z=W8gdtjaXJ^ATJO%4kY2F|7`*qFq)zi(bA)$7OKS;*N*hs79?5HJNt<%~wYq*3l3X zW^kCAN`I9<3zuV~6B{nrC0#RA`7s?=U=x)iK1my9Nvmw4NW8iu5s4+clGMjoy$dwD zf@U)r+HVe*#m7Tbv(05S(<^K0rrmB_iL0HUihAZZpg)#0!)8Lq7Bp&b9t=h&&AA^S8PR_{g=wmY#+!{Q9@*U^j}wEf0;NYqI~<$^9~i1axf{K!Y0gQb2J#1E(W*@4N)Cq9G<7<+mSFwBGcxunm2Ea zxQ=nz1ZE=PLW1hl6FU%#PsJ7%TWVd3q>jn+(`j*GioSxEMu&!0QoIItS9~%NGMgj( z{krJOSjz=QIgQFnziW2n@&`zgg}*Cl>C>dPh<^^Gr5^)o*_(|V$}<4RL$Hwzj&_fR z2i*NuXQ1C^=z)H_;R^IS7)~;|=rKeyST$Tm9x;pEhFf@vP-o{NMz-iVEE)_CcWUH{ zb+Isy%}W^hq8G4OC}BlnC>DB!OW9e%SSETYi)A8~OG<^v%Y`e%vQl`da21DFinB`S z6RsAnVdrIxwW3$ESR-PsWY>wjPPkqymkT!tH*)wD;@l+MEUXtcu=7gBRialfqA_gjrEOE&Pn|PT^7EUBb@_Kc^Dz7Udq{y~1O{dct|y0HVoFjqO^yV_Ei=6HKl!BY2Q%VBP#My z>GGRO`Ib@0oqSsben$mUWcV-^nlfl_{`0)M0eA6MFsmG%>*J)t6hDrrAc z%Fjjlg^K&7iu;vv{aQ7@QNAZ7@VCO>sko<<=V_(=UNz6iT>hYxKT7zs!aoWBtaARM z{C`!i_?v3ZDBs_uhkr=;KULhnl;__{`;Q9$uQL8m8K0A^=Oyd^!WWePEcuVaV;KBX z&n6pV!EZgQE(ZPDQ~eC5$jXx8=U%*vgxM0GL-KJfzL$;^3ziTEkWb8}Qzr+5bmE+V zM>CMCMX^{rjXaBiC6*!NTTYWWGpxD*`uk9$7+6z%)Ka6GeXOL}w7UlLc zsBr8=xuXrsrDqHEPFE@vs`Ctc=rm+ovF{{`eAYR5eXHkC;yZ=X{*x%5L&f~q^6A6` zR?-Q`*Vs#Gl$CT^R!P74su!#vKmQfW=}I?tid&U_S2eX7`Ru+EsJMp&HD!kw*GM}`LI*G1ROXYtcU_cRlQG$M?$pK|bkg%I8|ls7BdDXMga^@o0;>mX`{uAZ zg|!XGus(OQ?Lk=kt={|4(3Qg0KA$&*79YiVZNs!Uyckv~RIywh)>EN2padIHi%lfe z)4j7{!GbiVXHr57)k@b34Qf_?LK)%@o+I!W_&T@CkhXt8YGxaur?usEU7|4mMmYG-K( zO0}}8*}aRY@^m#;^~clc{W*5N{zlFrwFT~^RQfjrxH0|YUa(FRv|@)AtQ9+rExc+X zmDyzdWQ`8K>>5?b1vmr`Oqy31jo42^i_l7pVmA#liunB8Nk8|Ik1d${3fNbirJt2o Om~|0PdYYzT!OV-fxmA<^ literal 0 HcmV?d00001 diff --git a/playing-coffee old/bin/playingcoffee/core/MMU.class b/playing-coffee old/bin/playingcoffee/core/MMU.class new file mode 100644 index 0000000000000000000000000000000000000000..1df082d6eb8dda016e6cc0abd6afb508e9bee4c6 GIT binary patch literal 1133 zcmZuw*-{fh6g`~;CJd8+LH2+kE=j;d1=p~YajRH~vDjXCu$p8DgTo9p3@GpZh)=xu zLX{$ADIeg+_y>xoCsB&Rs-)9>``mNRy?yiJ_t#?pQ<&2b66o2o<(}o#tM1yGY38b~ zXXeV~B@GdQ&JDRMbGCHqxytH>S@i|Np4=1&trP{?ZCBO`uItac424taWr4_Jw`NAs z&f6#D=db50gd1)4V?mAfsSLjxi!l+bFZYozHK0ZF4ERbs^(Lr zQ!9<^{L1-Ix)C}u<(P0UZR3Tl|F@~!euKe}Fii?$}@2cjO zZ@G?vYlsSTY_U83f-kFY7Cl)t87Wzg`EqA-)$|tSs?A8EiG%+!s3_DC`i}cXxv#l#K!^`Ww{k7XqXUaowgjypVbk@9RrhyhS?H+ zTHbn=PpXzwv8X1YWmnoeJZrL6^UQ7fil`o|MikSO)fe0yuWG7C5QuW(vZ{?3B~_T? zNt3F}+(o$85ars&(;?65{$@fCe}{2|_z^Ca!ZZ6wWIkaaIi8$6LbCEcB$DIDA5MPm zlY8P2mk*c^Tt|j45aus1OjaO{7{@SyPM$j$PhcEf$Z=*;yvxRRdeaRn;&@v~m;6e8E&Earc}483P9l zgaTvnpuk=d_rXAaV8m!7G>r({!~G`N3!+s+qCxqCl~W#_d_aOThzZ+B{fK zzePSlU-Hs(lGAhg&=1fL)%Jg9c9FBr`Y=0l?_B=(H+N=d`RCui{wAUY+6$8*X!59O z{nBXFtL<9Nw$s&i$4>9q^+va6ce-H;3W^?DCsw*?wd(2Z%AsBD2@)1s5v_(+L93$e zquFQ&XiY)J5u8T%NKhzWTHScDCn%C@x4J#6)hk=gV>k2;jZ1wH?6v$p_$^|Z21Za`qZQ_g^;_ZCQf)EST8Sbw`WyrA-=QF*%XVvFj_7hAG6O1(UAyC)d{5bh*MWg_KwU*(ARTM20|V(mLKT65bQD|%2GRir6@kIXZu_`Xwet<$ z)Y1O&O!3L2kFk0_A){n)JuxzPlVp!#${wYZJ?bZWR8RIOne0(8^w4(tG1*_ID0)HT zGG+p&?7ac|8tnH6>=^712J8Cz>|B#XiJUIbj0UzOMssNh8 zq8R5EeS#iE6{+Z>^Sl5utP`ZhM;zS%>L3gwwb4q0Xg2*kc z$X%_-q7qrUAaYwP@<1z+Q6kG1M3P#ONA%dK$qvfIwT>m@(cxF)^dGi1m5i%_++$Uo zQMs2XoSHNa;vK1~iXZD2KSrx^e+6YP)t__q=g&{3(w~R^0`%|t=;s{$wM+FEUHv7m z{u1=>L;rz~{<@=|zf?ct>Mwism!ZD`{YO6f8;*XVzlrBB>RU>A&tg`}cwf|FA8rL( zXJGR}$vAfH9A-p%^SGfl20%jnjS9)Ti*JZcEy%PNWK#?BwH{>P)F_ZGq}%oZ;meaj zN?MRPFUXD-fgc6r64BBsDG{1xkUIrfquy~m$FA0P5&ww` zmm+cC2k@g1vyM|Zq{@NI+kH>-X6EhNU%!9+1n>&aYnT&QKab>>Fb@3W^i-*?pJ;~j zyt}7^e5B&6xm|-Lur>oGP<<1|VYVZ%)Eaz}7qT14IOrZ^n&e);eI#J@k`v{if_WD< z9D&!-JX2BSfM7?;s4?sY8SCNrwxcG=(KX?X3gtc0-n%X)xs9wQl U;jD?LJjVk+V!Z literal 0 HcmV?d00001 diff --git a/playing-coffee old/bin/playingcoffee/core/cpu/Argument$2.class b/playing-coffee old/bin/playingcoffee/core/cpu/Argument$2.class new file mode 100644 index 0000000000000000000000000000000000000000..d8e55fcad42984a1af011ef454af4cfb98cdee36 GIT binary patch literal 953 zcmbtSO>fgc5Pj3wNorjBK`8}Vnzkt z;Zh_H`~ZFwV%BjAhg3OmncW$^dGF26{`&pnCxBOYQNavh^CA>iDhmAg^i;~WA8QU5 zS^J$1vZ0L9dZz-5urUQjD8EsWO7{pWjsCf~6zxz%LHjV(Oz!oXM+B=IpGXHK%(}4Q z5MED0_NOR$hHAdABKavB9!vdM9EXyy)Q^1;9to{XUKFkLOeKWvX`JLjQ!M6kbVmGb-S{l{3mJu}xc7aORt<0N-%HS(?6mmPwn=iNa}7Vl0+u^HSjq6Gm%FdWzH*{bztR{ z>TYX|zN7k$YcsgV(I9|DxE$U5Ud0^lbH-{|DEhm68eY3~gT-sU{M)>oH`fZuQ8PpF z2&*}vjmLPx9fYS?D+mubx0t8gTED^iUpo%h!FZcHjHTTXs<342j#fgc6r64BBsDG{1xkT_q-}ttk%&Z*I2fv;2&6~}hqm;-b+@jOV^?dti2uZe zOOZJ61Nc#hS;r|HQsuzq?Y^gZGxPTCuirm@0(gmMHOvWYoJaCY7zciGda6{{Pc*}M z-hHcse5B&6xl@BBus#DOP<O5Ps3|>E^+58FaI{L70r!Oa@j3wDAy+*dZvhQWEYnx5!iNtX^XEuN{YVV2j<&f^~b0DlHki6V__UYHAzUq(^b literal 0 HcmV?d00001 diff --git a/playing-coffee old/bin/playingcoffee/core/cpu/Argument$4.class b/playing-coffee old/bin/playingcoffee/core/cpu/Argument$4.class new file mode 100644 index 0000000000000000000000000000000000000000..cb8caf986b7d31075e03f3d56ed661f32c3fd888 GIT binary patch literal 953 zcmbtSO>fgc5Pj3wN$R?M6etB+nzo^J8qr87QV)izD1a0x;m|_wTX*X&a_nkt7xACC za48Z8egHoTG3z>oL#iCOjCV$F-h1PjU%!9+1klA+4Kswb^GJMAaTp}0r&4x;L~}UL zJFj(^k7S%RyERyZ)hRGS^`(kcwntd>2A{-*=tLq8JBOKOa=+g?B3QlTL^`No)`bm+ zusaDkn4;txs@nq<%lG-{Sn3brIFf{gK@y1QNN8p9vS?*zDkW@8<7{?)wx^{yA-wW# zLLbObrJ2;JKM64$e)L;?2Mf4g!#zUdhB>ZTTs*)c%Z4(0L#TMIK4DgmH3;jIRLp9w z8Q64XG|CA}lc2^B!TzjO#{XyUKgQzinYo_2SVe;!r-ft7E4FH1h$v?PV*AcvlIuXe zQ)VOc;}hC3=fFFA8wXL6@;MA;c9!t3cKdNGbuSWWD%1T2cpSb=6wyYN^BQL_U=@}6 zc6*t=qyCL+GnnIO5Wq9I9Npqw$2@Lv#%j1z_V@T}cD6f_3`}Ra!E3$E?+o)zsFo&gvCx U6waDF4$6Ty`Q@;t84~;W1Nd^;Q2+n{ literal 0 HcmV?d00001 diff --git a/playing-coffee old/bin/playingcoffee/core/cpu/Argument$5.class b/playing-coffee old/bin/playingcoffee/core/cpu/Argument$5.class new file mode 100644 index 0000000000000000000000000000000000000000..32ba433b0842355ceeb5d7cf799b61e037f6b3c8 GIT binary patch literal 953 zcmbtST~8B16g`*jwq;%XKt)uL0!3SzxCxP9vPv)wCQVu&KzSdQVOg@>C9}I2|C2Ai zVB&*6z#nD2Gu@&OC4KPa&fJ@G&pCJQ{QCXlCxBOYQNs*j^&%Euq9hE`(^DzEAXOYL z3h$i?i=j;N=5`GhVPy)8P<<06QNBx9X!Xy-_NOTMn(9tJO5}%Pcr4XNaU4s+d_N6Dd?Zw)^Rj5=XHiC2pT^nT_Sv42;)L+F zbrbqPhEbMFmHCqpgTW`i-E%OH2Q}O$G;WyVn#ILKEU;`S^L;|4)$S2y)mVeDHc3UV z=9+;`SBAraus8{-4H4|mD$4nNcK>56-kzT8nTr)P*l}hYTP?Fy`%=UO3lQ6P4%0#f z@_nQ?GB^H0FZFleo$V(%cMzUpsU$q$++v<;XZZ@tf9*J22X?sowPD>Hp-M~I?wGY&vg+C@)>yrQ Vb>pmw4L(N$Kj)jnntDj=;SU(l*?j;2 literal 0 HcmV?d00001 diff --git a/playing-coffee old/bin/playingcoffee/core/cpu/Argument.class b/playing-coffee old/bin/playingcoffee/core/cpu/Argument.class new file mode 100644 index 0000000000000000000000000000000000000000..e29cc3d96eed84ad5d35a943314124833656c25b GIT binary patch literal 2270 zcmbtVZCBf57`~pSZAumxbfX(E222oIV3pT7iggS+tx(WqR5N@FC3InFTGMpOcmIUP zPk!?FZMGiEv1jOKe~G`sc|U0?9kK)G_#wHIC-=*Bzg_wB@86CA%%dQ}$1qefwL`N0b2~GdUD?=9DGaxEF5o}3oHGr}aFPtdL^R9bUoGzG zBCZg;bI^^>gbIB@~P`4BoJj5iIZWOQ)D+ZQlg2tkrC-87t)AtA^?&&%jU z7sKTZ!_qe^g3K|q8W;5;$PBy3&VV|ZucbxnMz=Z&(X z+vUVr8kx*?BATXo`Pw!dikJK!eyDZisHE9taE5AeQ-_ZYfQ&%TT_J|NHfR~5}9&%Ny+w{{QooRf$?7O_EF zablY`O+Bxftwl|JlhaF%QM5wXWVo8P2&b_(ta*F9G(1)o@!>1co31&qhcXmAA_)6M zyP!D?ciSO&8@kiB7A;h5N1m31b!7QUeS9hr?>p~CsD+BwmL3^86-UcG&1fZeltgq% z_zYi&`1}>T${|fmLB^NR7&RH^T?E-V&pB& zso3;w?o{aIb7Le4ot(BT-FDN_%WESPQy}IS@AYvK14t4lxsoWxmBbjXBmy8!AD>&G zyPwkly%R6tGP=o6550X4Kiz?R17S6MRj8x4hQ0;{)bOCcj+-?MH87%v-w4z(TEkca z<7zk>tRq&#WCPP`I4;)lRt+8P@oYpgdMOTY!5~q7WXDDqq@z~^zI_{h}3X%dmBiVfx@iuo( zzr4Fxa%BR`q(e-Gr04FZX4Q~9!n^8H;CRt5B!9&9zQEvYX6hHLPX3D2cL+>9|5jjw zz5X9w{?)*`8qWA%U`rJyl_Pvq$M)~!RdCaZ5CRbRE4YuFSj8|sN0D+<8Fqmuo2W=D ziBG5_jC4Dp5>g)?)9Ez`FR4L9_DrN@W^~^HdVKT)Ajbej+R+o9% zGs3_TKAn0_Uz*iz7KQy=$qw3(R_Za*;U$y8EOvz4A7HPw>*lcbz1ZUvnGcKTb|8Ot k2YjR;;E4;wz=12D!Xz0Z=wXt$2Wodyj(Bq1lhaN457=4@y#N3J literal 0 HcmV?d00001 diff --git a/playing-coffee old/bin/playingcoffee/core/cpu/Registers.class b/playing-coffee old/bin/playingcoffee/core/cpu/Registers.class new file mode 100644 index 0000000000000000000000000000000000000000..865bc0cb4fd01717003868cfb9f5697430b4a1b3 GIT binary patch literal 2113 zcmai!TTc@~7>3{3o@hIq%1J0l0Y#`t6;M$M2nB?s(Z(9C-7ZU6Qro5n5^wxP-k69c zUibt2QQ|kV-Ic7f*^8OC`*yx(zWL_x`_Io`05)(OgT|0)*7Q$Sy=pYh&rLIDG+NX( zySaB})oORlRy&3;!_cLErRQpTy_$PlxipOq1JkLUP^(ZgsGU(Wsa;U3F=$OPwRV#s znz!m!XNMuQw0z1CE;P=}1VR|$7{n06V9~0ZZ@M2VX6wCPsSzVpY#4g&RBu`0yJvD=NUC60j=w zUTotIoiL3wkxh&6YfO0$!muQa+oO8>NaOKX9u$Tr{;U}D7hvX?a;za pds!v3qAckT-?8U~&EA3~-K&yx^D3xec>z1{!ZHD{Bl+IG{tK?t&(Ht> literal 0 HcmV?d00001 diff --git a/playing-coffee old/bin/playingcoffee/ui/CPUDebugger$1.class b/playing-coffee old/bin/playingcoffee/ui/CPUDebugger$1.class new file mode 100644 index 0000000000000000000000000000000000000000..431e33d1a2b5aff769d1c6d0c05361dbb64e6e9d GIT binary patch literal 870 zcmaJ=T~8B16g|@}-MTDN+Jc}66)SFAWi{cg#+M3-W)Wkt3HmhM4$I(nw%HHC#Q)B7ciw$? zCHjfyiLm}$hsIDk;a|AzbLH9l{S)EFItmQs>(lp%QoQdAhQbS}Wc-pLZ#8-hS|=C? z19{Av&{1TVccc<;lVM+k2jd%MCvdsn%ZQ=oOs}p@dBQ~LC^M{13pKES z*>qYov4lGeMee#Hit5i8wymjI+6^ad^vsSRV}TB-M^`{UD;I_e6Xg3`{)1 zQ<8MZrD9mJKHbREjj3j@ol0)LR!W2&pGT31PyW zo+luX>}d@Rf@l&@3sq`o8CzMR8pROUBvFxK9->YNRcLsGP1-#sKm9LV9qSqeTV&=4 TtVydz&#Fa0fI)$2OZ5E%0;a%l literal 0 HcmV?d00001 diff --git a/playing-coffee old/bin/playingcoffee/ui/CPUDebugger$2.class b/playing-coffee old/bin/playingcoffee/ui/CPUDebugger$2.class new file mode 100644 index 0000000000000000000000000000000000000000..de14b9a9fe795bdd831959eec036200aa00547e1 GIT binary patch literal 937 zcmah{?QRl56g>kJ$~qRTO8saHRaBtbZHYfjFeV0T(uCF~Rel+D7%p_`cDLDGYU-o- z7AAhhA3lK3;xnjsw)h!?o6OG4&6#u0J@vqH z+mG&1Z!{cISY=E`ptz?FmFp>g=sxJ}k&bi73M?d1^&)oZko?%at78-R9TUamQz(#) zcTH4X5ChSs|RnRd{pJOoZk~z+5;Zu#lr;qe`#3+TYHr9*tn~9ubuf0=*$GCWru)YB zp!nNZtyuHe*L7ceLBzwiN%3ybmsqtD9Xkw0dFCZQlN&w{Q`=KfL=h_7_B5|4h+@%p zp9eX`D-VaGCz7#FB@w5w{1&qD9y6~K%qjk783R1VEdOP4c5oVJ_$F``r7=UFcgtCB zyn+0Hxp!FjdAjuK+n3|ZRZE+OrJdm&c!D`RWm}&m^rd5pF+G!^js_DiC$CsW8rS)l U!VRqOo?*{jtnta#VO8??9q@wOfB*mh literal 0 HcmV?d00001 diff --git a/playing-coffee old/bin/playingcoffee/ui/CPUDebugger.class b/playing-coffee old/bin/playingcoffee/ui/CPUDebugger.class new file mode 100644 index 0000000000000000000000000000000000000000..db6a71ac12b0b877baf578ba73d8e318af33f549 GIT binary patch literal 6620 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 literal 0 HcmV?d00001 diff --git a/playing-coffee old/src/playingcoffee/core/CPU.java b/playing-coffee old/src/playingcoffee/core/CPU.java new file mode 100644 index 0000000..ac2ab94 --- /dev/null +++ b/playing-coffee old/src/playingcoffee/core/CPU.java @@ -0,0 +1,112 @@ +// Basic CPU implementation. +// This will be changed in the future. + +package playingcoffee.core; + +public class CPU { + + private final MMU mmu; + + private Registers registers; + + private int cycles = 0; + + public static final int[] INSTRUCTION_CYCLE_COUNT = { + 4, 12, 8, 8, 4, 4, 8, 4, 20, 8, 8, 8, 4, 4, 8, 4, + 4, 12, 8, 8, 4, 4, 8, 4, 12, 8, 8, 8, 4, 4, 8, 4, + 8, 12, 8, 8, 12, 12, 12, 4, 8, 8, 8, 8, 4, 4, 8, 4, + 8, 12, 8, 8, 12, 12, 12, 4, 8, 8, 8, 8, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 4, 4, 4, 4, 4, 4, 8, 4, 4, 4, 4, 4, 4, 4, 8, 4, + 8, 12, 12, 16, 12, 16, 8, 16, 8, 16, 12, 4, 12, 24, 8, 16, + 8, 12, 12, -1, 12, 16, 8, 16, 8, 16, 12, -1, 12, -1, 8, 16, + 12, 12, 8, -1, -1, 16, 8, 16, 16, 4, 16, -1, -1, -1, 8, 16, + 12, 12, 8, 4, -1, 16, 8, 16, 12, 8, 16, 4, -1, -1, 8, 16 + }; + + public static final int[] PREFIXED_CYCLE_COUNT = { + 8, 8, 8, 8, 8, 8, 16, 8, 8, 8, 8, 8, 8, 8, 16, 8, + }; + + public CPU(final MMU mmu) { + this.mmu = mmu; + + registers = new Registers(); + } + + public Registers getRegisters() { + return registers; + } + + public int getCycles() { + return cycles; + } + + public void cycle() { + if (cycles == 0) { + + int opcode = mmu.read(registers.pc++); + System.out.printf("Fetched opcode: 0x%02x\n", opcode); + + decodeOpcode(opcode); + } + cycles--; + } + + public void decodeOpcode(int opcode) { + // Apply cycle count + cycles += INSTRUCTION_CYCLE_COUNT[opcode]; + + switch (opcode) { + case 0x00: // NOP + break; + + case 0x21: // LD HL, d16 + registers.writeL(mmu.read(registers.pc++)); + registers.writeH(mmu.read(registers.pc++)); + break; + + case 0x31: // LD SP, d16 + registers.sp = mmu.read(registers.pc++) | (mmu.read(registers.pc++) << 8); + break; + + case 0x32: // LD (HL-), A + mmu.write(registers.readA(), registers.readHL()); + registers.writeHL(registers.readHL() - 1); + break; + + case 0xAF: // XOR A + registers.writeA(registers.readA() ^ registers.readA()); + registers.setFlag(Registers.FLAG_Z, registers.readA() == 0); + break; + + case 0xCB: // CB - prefix + decodePrefixedOpcode(mmu.read(registers.pc++)); + break; + + default: + System.err.printf("Unknown opcode: 0x%2x\n", opcode); + } + } + + public void decodePrefixedOpcode(int opcode) { + cycles += INSTRUCTION_CYCLE_COUNT[opcode & 0xF]; + + switch (opcode) { + case 0x7C: + registers.setFlag(Registers.FLAG_Z, (registers.readH() & (1 << 7)) == 0); + registers.setFlag(Registers.FLAG_N, false); + registers.setFlag(Registers.FLAG_H, true); + break; + + default: + System.err.printf("Unknown opcode: 0xcb%2x\n", opcode); + } + } +} diff --git a/playing-coffee old/src/playingcoffee/core/MMU.java b/playing-coffee old/src/playingcoffee/core/MMU.java new file mode 100644 index 0000000..e7b98a8 --- /dev/null +++ b/playing-coffee old/src/playingcoffee/core/MMU.java @@ -0,0 +1,34 @@ +package playingcoffee.core; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +public class MMU { + + private int ram[]; + + public void loadBootRom() { + byte[] data; + try { + data = Files.readAllBytes(Paths.get("DMG_ROM.bin")); + for (int i = 0; i < data.length; i++) { + ram[i] = Byte.toUnsignedInt(data[i]); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + public MMU() { + ram = new int[0x10000]; + } + + public void write(int value, int address) { + ram[address] = value; + } + + public int read(int address) { + return ram[address]; + } +} diff --git a/playing-coffee old/src/playingcoffee/core/Registers.java b/playing-coffee old/src/playingcoffee/core/Registers.java new file mode 100644 index 0000000..1554b58 --- /dev/null +++ b/playing-coffee old/src/playingcoffee/core/Registers.java @@ -0,0 +1,129 @@ +package playingcoffee.core; + +public class Registers { + + private int a, f; + private int b, c; + private int d, e; + private int h, l; + + public int pc; + public int sp; + + public static final int FLAG_Z = 1 << 7; + public static final int FLAG_N = 1 << 6; + public static final int FLAG_H = 1 << 5; + public static final int FLAG_C = 1 << 4; + + public void setFlag(int flag, boolean value) { + if (value) + f |= flag; + else + f &= ~flag; + } + + public boolean getFlag(int flag) { + return (f & flag) != 0; + } + + public int readAF() { + return a << 8 | f; + } + + public int readBC() { + return b << 8 | c; + } + + public int readDE() { + return d << 8 | e; + } + + public int readHL() { + return h << 8 | l; + } + + public void writeAF(int af) { + a = (af >> 8) & 0xFF; + f = af & 0xFF; + } + + public void writeBC(int bc) { + b = (bc >> 8) & 0xFF; + c = bc & 0xFF; + } + + public void writeDE(int de) { + d = (de >> 8) & 0xFF; + e = de & 0xFF; + } + + public void writeHL(int hl) { + h = (hl >> 8) & 0xFF; + l = hl & 0xFF; + } + + public int readA() { + return a; + } + + public void writeA(int a) { + this.a = a; + } + + public int readF() { + return f; + } + + public void writeF(int f) { + this.f = f; + } + + public int readB() { + return b; + } + + public void writeB(int b) { + this.b = b; + } + + public int readC() { + return c; + } + + public void writeC(int c) { + this.c = c; + } + + public int readD() { + return d; + } + + public void writeD(int d) { + this.d = d; + } + + public int readE() { + return e; + } + + public void writeE(int e) { + this.e = e; + } + + public int readH() { + return h; + } + + public void writeH(int h) { + this.h = h; + } + + public int readL() { + return l; + } + + public void writeL(int l) { + this.l = l; + } + +} diff --git a/playing-coffee old/src/playingcoffee/core/cpu/Argument.java b/playing-coffee old/src/playingcoffee/core/cpu/Argument.java new file mode 100644 index 0000000..6d12ee3 --- /dev/null +++ b/playing-coffee old/src/playingcoffee/core/cpu/Argument.java @@ -0,0 +1,72 @@ +package playingcoffee.core.cpu; + +import playingcoffee.core.MMU; + +public enum Argument { + + A { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getA(); + } + + @Override + public void write(Registers registers, MMU mmu, int value) { + registers.setA(value); + } + }, B { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getB(); + } + + @Override + public void write(Registers registers, MMU mmu, int value) { + registers.setB(value); + } + }, C { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getC(); + } + + @Override + public void write(Registers registers, MMU mmu, int value) { + registers.setC(value); + } + }, D { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getD(); + } + + @Override + public void write(Registers registers, MMU mmu, int value) { + registers.setD(value); + } + }, E { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getE(); + } + + @Override + public void write(Registers registers, MMU mmu, int value) { + registers.setE(value); + } + }; + + private String label; + + public abstract int read(Registers registers, MMU mmu); + public abstract void write(Registers registers, MMU mmu, int value); + + public Argument parse(String argument) { + for (Argument arg : values()) + if (arg.label.equals(argument)) + return arg; + + throw new IllegalArgumentException(String.format("Invalid argument: %s", argument)); + } + +} diff --git a/playing-coffee old/src/playingcoffee/core/cpu/Registers.java b/playing-coffee old/src/playingcoffee/core/cpu/Registers.java new file mode 100644 index 0000000..adc77b7 --- /dev/null +++ b/playing-coffee old/src/playingcoffee/core/cpu/Registers.java @@ -0,0 +1,33 @@ +package playingcoffee.core.cpu; + +public class Registers { + + private int a, b, c, d, e, h, l; + + private int pc, sp; + + public int getA() { return a & 0xFF; } + public int getB() { return b & 0xFF; } + public int getC() { return c & 0xFF; } + public int getD() { return d & 0xFF; } + public int getE() { return e & 0xFF; } + public int getH() { return h & 0xFF; } + public int getL() { return l & 0xFF; } + + public void setA(int value) { a = value & 0xFF; } + public void setB(int value) { b = value & 0xFF; } + public void setC(int value) { c = value & 0xFF; } + public void setD(int value) { d = value & 0xFF; } + public void setE(int value) { e = value & 0xFF; } + public void setH(int value) { h = value & 0xFF; } + public void setL(int value) { l = value & 0xFF; } + + public int getPC() { return pc & 0xFFFF; } + public int getSP() { return sp & 0xFFFF; } + + public void setPC(int value) { pc = value & 0xFFFF; } + public void setSP(int value) { sp = value & 0xFFFF; } + + public void incPC(int value) { setPC(pc + value); } + public void incSP(int value) { setSP(sp + value); } +} diff --git a/playing-coffee old/src/playingcoffee/ui/CPUDebugger.java b/playing-coffee old/src/playingcoffee/ui/CPUDebugger.java new file mode 100644 index 0000000..c9a71da --- /dev/null +++ b/playing-coffee old/src/playingcoffee/ui/CPUDebugger.java @@ -0,0 +1,158 @@ +package playingcoffee.ui; + +import java.awt.EventQueue; +import java.awt.event.ActionEvent; +import java.awt.event.ActionListener; + +import javax.swing.DefaultListModel; +import javax.swing.GroupLayout; +import javax.swing.GroupLayout.Alignment; +import javax.swing.JButton; +import javax.swing.JFrame; +import javax.swing.JLabel; +import javax.swing.JList; +import javax.swing.LayoutStyle.ComponentPlacement; +import javax.swing.UIManager; +import javax.swing.UnsupportedLookAndFeelException; + +import playingcoffee.core.CPU; +import playingcoffee.core.MMU; +import playingcoffee.core.Registers; + +import javax.swing.JCheckBox; + +public class CPUDebugger { + + private JFrame frmCpuDebugger; + private JList registerList; + + private JCheckBox chckbxZero; + private JCheckBox chckbxNegative; + private JCheckBox chckbxHalfCarry; + private JCheckBox chckbxCarry; + + private MMU mmu; + private CPU cpu; + + public static void main(String[] args) { + EventQueue.invokeLater(new Runnable() { + public void run() { + try { + CPUDebugger window = new CPUDebugger(); + window.frmCpuDebugger.setVisible(true); + } catch (Exception e) { + e.printStackTrace(); + } + } + }); + } + + public CPUDebugger() { + initialize(); + + mmu = new MMU(); + cpu = new CPU(mmu); + + mmu.loadBootRom(); + + updateValues(); + } + + private void updateValues() { + DefaultListModel model = new DefaultListModel(); + model.addElement("AF: 0x" + Integer.toHexString(cpu.getRegisters().readAF())); + model.addElement("BC: 0x" + Integer.toHexString(cpu.getRegisters().readBC())); + model.addElement("DE: 0x" + Integer.toHexString(cpu.getRegisters().readDE())); + model.addElement("HL: 0x" + Integer.toHexString(cpu.getRegisters().readHL())); + model.addElement("PC: 0x" + Integer.toHexString(cpu.getRegisters().pc)); + model.addElement("SP: 0x" + Integer.toHexString(cpu.getRegisters().sp)); + + chckbxZero.setSelected(cpu.getRegisters().getFlag(Registers.FLAG_Z)); + chckbxNegative.setSelected(cpu.getRegisters().getFlag(Registers.FLAG_N)); + chckbxHalfCarry.setSelected(cpu.getRegisters().getFlag(Registers.FLAG_H)); + chckbxCarry.setSelected(cpu.getRegisters().getFlag(Registers.FLAG_C)); + + registerList.setModel(model); + } + + private void initialize() { + try { + UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); + } catch (ClassNotFoundException | InstantiationException | IllegalAccessException + | UnsupportedLookAndFeelException e) { + e.printStackTrace(); + } + + frmCpuDebugger = new JFrame(); + frmCpuDebugger.setTitle("CPU Debugger"); + frmCpuDebugger.setBounds(100, 100, 350, 350); + frmCpuDebugger.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + JButton btnStep = new JButton("Step"); + btnStep.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + do { cpu.cycle(); } while (cpu.getCycles() != 0); + updateValues(); + } + }); + + JButton btnReset = new JButton("Reset"); + + JLabel lblRegisters = new JLabel("Registers:"); + + registerList = new JList(); + + chckbxZero = new JCheckBox("Zero"); + chckbxNegative = new JCheckBox("Negative"); + chckbxHalfCarry = new JCheckBox("Half Carry"); + chckbxCarry = new JCheckBox("Carry"); + + GroupLayout groupLayout = new GroupLayout(frmCpuDebugger.getContentPane()); + groupLayout.setHorizontalGroup( + groupLayout.createParallelGroup(Alignment.LEADING) + .addGroup(groupLayout.createSequentialGroup() + .addContainerGap() + .addGroup(groupLayout.createParallelGroup(Alignment.LEADING) + .addComponent(lblRegisters) + .addComponent(btnStep) + .addComponent(registerList, GroupLayout.PREFERRED_SIZE, 148, GroupLayout.PREFERRED_SIZE)) + .addGroup(groupLayout.createParallelGroup(Alignment.LEADING) + .addGroup(groupLayout.createSequentialGroup() + .addPreferredGap(ComponentPlacement.RELATED, 52, Short.MAX_VALUE) + .addComponent(btnReset) + .addContainerGap()) + .addGroup(groupLayout.createSequentialGroup() + .addPreferredGap(ComponentPlacement.UNRELATED) + .addGroup(groupLayout.createParallelGroup(Alignment.LEADING) + .addComponent(chckbxZero) + .addComponent(chckbxNegative) + .addComponent(chckbxHalfCarry) + .addComponent(chckbxCarry)) + .addGap(73)))) + ); + groupLayout.setVerticalGroup( + groupLayout.createParallelGroup(Alignment.LEADING) + .addGroup(groupLayout.createSequentialGroup() + .addContainerGap() + .addComponent(lblRegisters) + .addPreferredGap(ComponentPlacement.RELATED) + .addGroup(groupLayout.createParallelGroup(Alignment.LEADING) + .addGroup(groupLayout.createSequentialGroup() + .addComponent(registerList, GroupLayout.PREFERRED_SIZE, 195, GroupLayout.PREFERRED_SIZE) + .addGap(51) + .addGroup(groupLayout.createParallelGroup(Alignment.BASELINE) + .addComponent(btnReset) + .addComponent(btnStep))) + .addGroup(groupLayout.createSequentialGroup() + .addComponent(chckbxZero) + .addPreferredGap(ComponentPlacement.UNRELATED) + .addComponent(chckbxNegative) + .addPreferredGap(ComponentPlacement.UNRELATED) + .addComponent(chckbxHalfCarry) + .addPreferredGap(ComponentPlacement.UNRELATED) + .addComponent(chckbxCarry))) + .addContainerGap(GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)) + ); + frmCpuDebugger.getContentPane().setLayout(groupLayout); + } +} diff --git a/playing-coffee/.gitignore b/playing-coffee/.gitignore new file mode 100644 index 0000000..b83d222 --- /dev/null +++ b/playing-coffee/.gitignore @@ -0,0 +1 @@ +/target/ diff --git a/playing-coffee/.settings/org.eclipse.m2e.core.prefs b/playing-coffee/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 0000000..f897a7f --- /dev/null +++ b/playing-coffee/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/playing-coffee/dmg_boot.bin b/playing-coffee/dmg_boot.bin new file mode 100644 index 0000000000000000000000000000000000000000..afa0ee4792c2ba80afb6b0c1962e249e195e6fc0 GIT binary patch literal 256 zcmV+b0ssCn{{OEb|DQ6;d?5QFCjSl*K7caf3_kPXGCp^AKK$TE5d;At5P%xZl>p6_ z026!uG$8X4*Z>9y8WSQT1t9r88R`(3AupK@3_U0aG7TX4E)N*o0-PxJD zyJur=4(!a`+?k)dxidaILb;{6r9wO*1OXAK02&j%ApRqL{xl%<1{rvUBLyJ)h9Lev G0pL*XS7L7f literal 0 HcmV?d00001 diff --git a/playing-coffee/gb-test-roms-master.zip b/playing-coffee/gb-test-roms-master.zip new file mode 100644 index 0000000000000000000000000000000000000000..b66c88fe50e813a2e3df4f9e2a349e9d607e0ce8 GIT binary patch literal 331384 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 HpAz^7^EI)n literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/cpu_instrs/readme.txt b/playing-coffee/roms/cpu_instrs/readme.txt new file mode 100644 index 0000000..6f94955 --- /dev/null +++ b/playing-coffee/roms/cpu_instrs/readme.txt @@ -0,0 +1,119 @@ +Game Boy CPU Instruction Behavior Test +-------------------------------------- +This ROM tests the behavior of all CPU instructions except STOP and the +11 illegal opcodes. The tests are fairly thorough, running instructions +with boundary data and verifying both the result and that other +registers are not modified. Instructions which perform the same +operation on different registers are each tested just as thoroughly, in +case an emulator implements each independently. Some sub-tests take half +minute to complete. + +Failed instructions are listed as + + [CB] opcode + +Some errors cannot of course be diagnosed properly, since the test +framework itself relies on basic instruction behavior being correct. + + +Internal operation +------------------ +The main tests use a framework that runs each instruction in a loop, +varying the register values on input and examining them on output. +Rather than keep a table of correct values, it simply calculates a +CRC-32 checksum of all the output, then compares this with the correct +value. Instructions are divided into several groups, each with a +different set of input values suited for their behavior; for example, +the bit test instructions are fed $01, $02, $04 ... $40, $80, to ensure +each bit is handled properly, while the arithmetic instructions are fed +$01, $0F, $10, $7F, $FF, to exercise carry and half-carry. A few +instructions require a custom test due to their uniqueness. + + +Multi-ROM +--------- +In the main directory is a single ROM which runs all the tests. It +prints a test's number, runs the test, then "ok" if it passes, otherwise +a failure code. Once all tests have completed it either reports that all +tests passed, or prints the number of failed tests. Finally, it makes +several beeps. If a test fails, it can be run on its own by finding the +corresponding ROM in individual/. + +Ths compact format on screen is to avoid having the results scroll off +the top, so the test can be started and allowed to run without having to +constantly monitor the display. + +Currently there is no well-defined way for an emulator test rig to +programatically find the result of the test; contact me if you're trying +to do completely automated testing of your emulator. One simple approach +is to take a screenshot after all tests have run, or even just a +checksum of one, and compare this with a previous run. + + +Failure codes +------------- +Failed tests may print a failure code, and also short description of the +problem. For more information about a failure code, look in the +corresponding source file in source/; the point in the code where +"set_test n" occurs is where that failure code will be generated. +Failure code 1 is a general failure of the test; any further information +will be printed. + +Note that once a sub-test fails, no further tests for that file are run. + + +Console output +-------------- +Information is printed on screen in a way that needs only minimum LCD +support, and won't hang if LCD output isn't supported at all. +Specifically, while polling LY to wait for vblank, it will time out if +it takes too long, so LY always reading back as the same value won't +hang the test. It's also OK if scrolling isn't supported; in this case, +text will appear starting at the top of the screen. + +Everything printed on screen is also sent to the game link port by +writing the character to SB, then writing $81 to SC. This is useful for +tests which print lots of information that scrolls off screen. + + +Source code +----------- +Source code is included for all tests, in source/. It can be used to +build the individual test ROMs. Code for the multi test isn't included +due to the complexity of putting everything together. + +Code is written for the wla-dx assembler. To assemble a particular test, +execute + + wla -o "source_filename.s" test.o + wlalink linkfile test.gb + +Test code uses a common shell framework contained in common/. + + +Internal framework operation +---------------------------- +Tests use a common framework for setting things up, reporting results, +and ending. All files first include "shell.inc", which sets up the ROM +header and shell code, and includes other commonly-used modules. + +One oddity is that test code is first copied to internal RAM at $D000, +then executed there. This allows self-modification, and ensures the code +is executed the same way it is on my devcart, which doesn't have a +rewritable ROM as most do. + +Some macros are used to simplify common tasks: + + Macro Behavior + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + wreg addr,data Writes data to addr using LDH + lda addr Loads byte from addr into A using LDH + sta addr Stores A at addr using LDH + delay n Delays n cycles, where NOP = 1 cycle + delay_msec n Delays n milliseconds + set_test n,"Cause" Sets failure code and optional string + +Routines and macros are documented where they are defined. + +-- +Shay Green diff --git a/playing-coffee/roms/cpu_instrs/source/01-special.s b/playing-coffee/roms/cpu_instrs/source/01-special.s new file mode 100644 index 0000000..776d685 --- /dev/null +++ b/playing-coffee/roms/cpu_instrs/source/01-special.s @@ -0,0 +1,78 @@ +; Tests instructions that don't fit template + +.include "shell.inc" + +main: + set_test 2,"JR negative" + ld a,0 + jp jr_neg + inc a +- inc a + inc a + cp 2 + jp nz,test_failed + jp + +jr_neg: + jr - ++ + + set_test 3,"JR positive" + ld a,0 + jr + + inc a ++ inc a + inc a + cp 2 + jp nz,test_failed + + + set_test 4,"LD PC,HL" + ld hl,+ + ld a,0 + ld pc,hl + inc a ++ inc a + inc a + cp 2 + jp nz,test_failed + + + set_test 5,"POP AF" + ld bc,$1200 +- push bc + pop af + push af + pop de + ld a,c + and $F0 + cp e + jp nz,test_failed + inc b + inc c + jr nz,- + + + set_test 6,"DAA" + ; Test all combinations of A and flags (256*16 total) + ld de,0 +- push de + pop af + daa + + push af + call update_crc + pop hl + ld a,l + call update_crc + + inc d + jr nz,- + + ld a,e + add $10 + ld e,a + jr nz,- + + check_crc $6A9F8D8A + + jp tests_passed diff --git a/playing-coffee/roms/cpu_instrs/source/02-interrupts.s b/playing-coffee/roms/cpu_instrs/source/02-interrupts.s new file mode 100644 index 0000000..de18b34 --- /dev/null +++ b/playing-coffee/roms/cpu_instrs/source/02-interrupts.s @@ -0,0 +1,73 @@ +; Tests DI, EI, and HALT (STOP proved untestable) + +.include "shell.inc" + +main: + wreg IE,$04 + + set_test 2,"EI" + ei + ld bc,0 + push bc + pop bc + inc b + wreg IF,$04 +interrupt_addr: + dec b + jp nz,test_failed + ld hl,sp-2 + ldi a,(hl) + cp interrupt_addr + jp nz,test_failed + lda IF + and $04 + jp nz,test_failed + + set_test 3,"DI" + di + ld bc,0 + push bc + pop bc + wreg IF,$04 + ld hl,sp-2 + ldi a,(hl) + or (hl) + jp nz,test_failed + lda IF + and $04 + jp z,test_failed + + set_test 4,"Timer doesn't work" + wreg TAC,$05 + wreg TIMA,0 + wreg IF,0 + delay 500 + lda IF + delay 500 + and $04 + jp nz,test_failed + delay 500 + lda IF + and $04 + jp z,test_failed + pop af + + set_test 5,"HALT" + wreg TAC,$05 + wreg TIMA,0 + wreg IF,0 + halt ; timer interrupt will exit halt + nop ; avoids DMG bug + lda IF + and $04 + jp z,test_failed + + jp tests_passed + +.bank 0 slot 0 +.org $50 + inc a + ret diff --git a/playing-coffee/roms/cpu_instrs/source/03-op sp,hl.s b/playing-coffee/roms/cpu_instrs/source/03-op sp,hl.s new file mode 100644 index 0000000..9531d51 --- /dev/null +++ b/playing-coffee/roms/cpu_instrs/source/03-op sp,hl.s @@ -0,0 +1,102 @@ +; Tests SP/HL instructions + +;.define PRINT_CHECKSUMS 1 +.include "shell.inc" +.include "instr_test.s" + +instrs: + .byte $33,0,0 ; INC SP + .byte $3B,0,0 ; DEC SP + .byte $39,0,0 ; ADD HL,SP + .byte $F9,0,0 ; LD SP,HL + .byte $E8,$01,0 ; ADD SP,1 + .byte $E8,$FF,0 ; ADD SP,-1 + .byte $F8,$01,0 ; LD HL,SP+1 + .byte $F8,$FF,0 ; LD HL,SP-1 +instrs_end: + +test_instr: + ; C = flags register + ld c,$00 + call test + ld c,$F0 + call test + ret + +test: + ; Go through each value for HL + ld hl,values +hl_loop: + ld e,(hl) + inc hl + ld d,(hl) + inc hl + push hl + + ; Go through each value for SP + ld hl,values +values_loop: + push bc + push de + push hl + + push bc + pop af + + ; Switch stack + ld (temp),sp + ld a,(hl+) + ld h,(hl) + ld l,a +; call print_regs + ld sp,hl + + ; Set registers + ld h,d + ld l,e + ld a,$12 + ld bc,$5691 + ld de,$9ABC + + jp instr +instr_done: + ; Save new SP and switch to yet another stack + ld (temp+2),sp + ld sp,$DF70 + + call checksum_af_bc_de_hl + + ; Checksum SP + ld a,(temp+2) + call update_crc_fast + ld a,(temp+3) + call update_crc_fast + + ldsp temp + + pop hl + pop de + pop bc + inc hl + inc hl + ld a,l + cp taken ; JP NZ,taken + .byte $C3,taken ; JP taken + .byte $CA,taken ; JP Z,taken + .byte $D2,taken ; JP NC,taken + .byte $DA,taken ; JP C,taken + + .byte $C4,taken ; CALL NZ,taken + .byte $CC,taken ; CALL Z,taken + .byte $CD,taken ; CALL taken + .byte $D4,taken ; CALL NC,taken + .byte $DC,taken ; CALL C,taken + + ; RET cond + ; INC A + .byte $C0,$3C,0 ; RET NZ + .byte $C8,$3C,0 ; RET Z + .byte $C9,$3C,0 ; RET + .byte $D0,$3C,0 ; RET NC + .byte $D8,$3C,0 ; RET C + .byte $D9,$3C,0 ; RETI + + ; RST + ; can only easily test this one on devcart + .byte $C7,0,0 ; RST $00 +.ifndef BUILD_DEVCART + .byte $CF,0,0 ; RST $08 + .byte $D7,0,0 ; RST $10 + .byte $DF,0,0 ; RST $18 + .byte $E7,0,0 ; RST $20 + .byte $EF,0,0 ; RST $28 + .byte $F7,0,0 ; RST $30 + .byte $FF,0,0 ; RST $38 +.endif + +instrs_end: + +test_instr: + wreg IE,0 ; disable interrupts, since RETI does EI + + ; Go through all 16 combinations of flags + ld bc,$1200 +- + ; Fill 4 bytes of new stack + ld a,$12 + ld ($DF80-2),a + ld a,$34 + ld ($DF80-3),a + ld a,$56 + ld ($DF80-4),a + ld a,$78 + ld ($DF80-5),a + + ; Set AF + push bc + pop af + + ; Switch to new stack + ld (temp),sp + ld sp,$DF80 + + ; Set return address + ld de,instr+3 + push de + + jp instr +instr_done: + inc a +taken: + di ; RETI enables interrupts + + ; Save new SP and switch to yet another stack + ld (temp+2),sp + ld sp,$DF70 + + ; Checksum A and SP + call update_crc_fast + ld a,(temp+2) + call update_crc_fast + ld a,(temp+3) + call update_crc_fast + + ; Checksum 4 bytes of stack + ld a,($DF80-2) + call update_crc_fast + ld a,($DF80-3) + call update_crc_fast + ld a,($DF80-4) + call update_crc_fast + ld a,($DF80-5) + call update_crc_fast + + ldsp temp + + ld a,c + add $10 + ld c,a + jr nz,- + + ret + +checksums: + .byte $EC,$A4,$94,$79,$C4,$00,$96,$2C,$C4,$64,$90,$33,$77,$C7,$0A,$D4 + .byte $77,$A3,$0C,$CB,$79,$E7,$7E,$AE,$DA,$DC,$03,$F7,$4F,$9F,$E9,$20 + .byte $72,$12,$DA,$01,$44,$6A,$4D,$8F,$D1,$79,$30,$4C,$AA,$37,$F2,$6A + .byte $97,$EA,$56,$5F,$32,$28,$C7,$D1,$49,$66,$05,$F7,$80,$0F,$BA,$8E + .byte $41,$E2,$A4,$9A,$2D,$2D,$8C,$72,$A5,$13,$76,$A8,$64,$FE,$68,$BC + .byte $2D,$2D,$8C,$72,$50,$96,$24,$27,$50,$96,$24,$27,$50,$96,$24,$27 + .byte $50,$96,$24,$27,$50,$96,$24,$27,$50,$96,$24,$27,$50,$96,$24,$27 + .byte $50,$96,$24,$27 + +.include "multi_custom.s" diff --git a/playing-coffee/roms/cpu_instrs/source/08-misc instrs.s b/playing-coffee/roms/cpu_instrs/source/08-misc instrs.s new file mode 100644 index 0000000..5c11c8e --- /dev/null +++ b/playing-coffee/roms/cpu_instrs/source/08-misc instrs.s @@ -0,0 +1,110 @@ +; Tests miscellaneous instructions + +;.define PRINT_CHECKSUMS 1 +.include "shell.inc" +.include "instr_test.s" + +instrs: + .byte $F0,$91,0 ; LDH A,($91) + .byte $E0,$91,0 ; LDH ($91),A + .byte $F2,$00,0 ; LDH A,(C) + .byte $E2,$00,0 ; LDH (C),A + .byte $FA,$91,$FF ; LD A,($FF91) + .byte $EA,$91,$FF ; LD ($FF91),A + .byte $08,$91,$FF ; LD ($FF91),SP + .byte $01,$23,$01 ; LD BC,$0123 + .byte $11,$23,$01 ; LD DE,$0123 + .byte $21,$23,$01 ; LD HL,$0123 + .byte $31,$23,$01 ; LD SP,$0123 + .byte $F5,0,0 ; PUSH AF + .byte $C5,0,0 ; PUSH BC + .byte $D5,0,0 ; PUSH DE + .byte $E5,0,0 ; PUSH HL + .byte $F1,0,0 ; POP AF + .byte $C1,0,0 ; POP BC + .byte $D1,0,0 ; POP DE + .byte $E1,0,0 ; POP HL +instrs_end: + +test_instr: + ; C = flags register + ld c,$00 + call test + ld c,$10 + call test + ld c,$E0 + call test + ld c,$F0 + call test + ret + +test: + ; Fill RAM + ld a,$FE + ld ($FF90),a + ld a,$DC + ld ($FF91),a + ld a,$BA + ld ($FF92),a + + ; Fill stack + ld a,$13 + ld ($DF80),a + ld a,$57 + ld ($DF80-1),a + ld a,$9B + ld ($DF80-2),a + ld a,$DF + ld ($DF80-3),a + + ; Set registers + ld b,$12 + push bc + ld bc,$5691 + ld de,$9ABC + ld hl,$DEF0 + pop af + + ; Switch stack + ld (temp),sp + ld sp,$DF80-2 + + jp instr +instr_done: + ; Save new SP and switch to another stack + ld (temp+2),sp + ld sp,$DF70 + + call checksum_af_bc_de_hl + + ; Checksum SP + ld a,(temp+2) + call update_crc_fast + ld a,(temp+3) + call update_crc_fast + + ; Checksum RAM + ld a,($FF90) + call update_crc_fast + ld a,($FF91) + call update_crc_fast + ld a,($FF92) + call update_crc_fast + + ; Checksum stack + ld a,($DF80) + call update_crc_fast + ld a,($DF80-1) + call update_crc_fast + ld a,($DF80-2) + call update_crc_fast + ld a,($DF80-3) + call update_crc_fast + + ; Restore SP + ldsp temp + + ret + +checksums: + .byte $4D,$FF,$15,$97,$6D,$A7,$35,$65,$4D,$FF,$15,$97,$6D,$A7,$35,$65,$4D,$FF,$15,$97,$6D,$A7,$35,$65,$AD,$FA,$5E,$41,$D0,$78,$79,$C1,$AF,$66,$99,$34,$0D,$E1,$97,$99,$6F,$D0,$6F,$5D,$C3,$1F,$A3,$8A,$C2,$F1,$9C,$F3,$C1,$C3,$DC,$78,$C0,$2D,$E3,$01,$8F,$C4,$0F,$44,$95,$22,$6A,$39,$61,$C5,$AB,$55,$FB,$DF,$2C,$52, diff --git a/playing-coffee/roms/cpu_instrs/source/09-op r,r.s b/playing-coffee/roms/cpu_instrs/source/09-op r,r.s new file mode 100644 index 0000000..432c4e7 --- /dev/null +++ b/playing-coffee/roms/cpu_instrs/source/09-op r,r.s @@ -0,0 +1,269 @@ +; Tests most register instructions. +; Takes 10 seconds. + +;.define PRINT_CHECKSUMS 1 +.include "shell.inc" +.include "instr_test.s" + +instrs: + .byte $00,0,0 ; NOP + .byte $2F,0,0 ; CPL + .byte $37,0,0 ; SCF + .byte $3F,0,0 ; CCF + + .byte $B0,0,0 ; OR B + .byte $B1,0,0 ; OR C + .byte $B2,0,0 ; OR D + .byte $B3,0,0 ; OR E + .byte $B4,0,0 ; OR H + .byte $B5,0,0 ; OR L + .byte $B7,0,0 ; OR A + + .byte $B8,0,0 ; CP B + .byte $B9,0,0 ; CP C + .byte $BA,0,0 ; CP D + .byte $BB,0,0 ; CP E + .byte $BC,0,0 ; CP H + .byte $BD,0,0 ; CP L + .byte $BF,0,0 ; CP A + + .byte $80,0,0 ; ADD B + .byte $81,0,0 ; ADD C + .byte $82,0,0 ; ADD D + .byte $83,0,0 ; ADD E + .byte $84,0,0 ; ADD H + .byte $85,0,0 ; ADD L + .byte $87,0,0 ; ADD A + + .byte $88,0,0 ; ADC B + .byte $89,0,0 ; ADC C + .byte $8A,0,0 ; ADC D + .byte $8B,0,0 ; ADC E + .byte $8C,0,0 ; ADC H + .byte $8D,0,0 ; ADC L + .byte $8F,0,0 ; ADC A + + .byte $90,0,0 ; SUB B + .byte $91,0,0 ; SUB C + .byte $92,0,0 ; SUB D + .byte $93,0,0 ; SUB E + .byte $94,0,0 ; SUB H + .byte $95,0,0 ; SUB L + .byte $97,0,0 ; SUB A + + .byte $98,0,0 ; SBC B + .byte $99,0,0 ; SBC C + .byte $9A,0,0 ; SBC D + .byte $9B,0,0 ; SBC E + .byte $9C,0,0 ; SBC H + .byte $9D,0,0 ; SBC L + .byte $9F,0,0 ; SBC A + + .byte $A0,0,0 ; AND B + .byte $A1,0,0 ; AND C + .byte $A2,0,0 ; AND D + .byte $A3,0,0 ; AND E + .byte $A4,0,0 ; AND H + .byte $A5,0,0 ; AND L + .byte $A7,0,0 ; AND A + + .byte $A8,0,0 ; XOR B + .byte $A9,0,0 ; XOR C + .byte $AA,0,0 ; XOR D + .byte $AB,0,0 ; XOR E + .byte $AC,0,0 ; XOR H + .byte $AD,0,0 ; XOR L + .byte $AF,0,0 ; XOR A + + .byte $05,0,0 ; DEC B + .byte $0D,0,0 ; DEC C + .byte $15,0,0 ; DEC D + .byte $1D,0,0 ; DEC E + .byte $25,0,0 ; DEC H + .byte $2D,0,0 ; DEC L + .byte $3D,0,0 ; DEC A + + .byte $04,0,0 ; INC B + .byte $0C,0,0 ; INC C + .byte $14,0,0 ; INC D + .byte $1C,0,0 ; INC E + .byte $24,0,0 ; INC H + .byte $2C,0,0 ; INC L + .byte $3C,0,0 ; INC A + + .byte $07,0,0 ; RLCA + .byte $17,0,0 ; RLA + .byte $0F,0,0 ; RRCA + .byte $1F,0,0 ; RRA + + .byte $CB,$00,0 ; RLC B + .byte $CB,$01,0 ; RLC C + .byte $CB,$02,0 ; RLC D + .byte $CB,$03,0 ; RLC E + .byte $CB,$04,0 ; RLC H + .byte $CB,$05,0 ; RLC L + .byte $CB,$07,0 ; RLC A + + .byte $CB,$08,0 ; RRC B + .byte $CB,$09,0 ; RRC C + .byte $CB,$0A,0 ; RRC D + .byte $CB,$0B,0 ; RRC E + .byte $CB,$0C,0 ; RRC H + .byte $CB,$0D,0 ; RRC L + .byte $CB,$0F,0 ; RRC A + + .byte $CB,$10,0 ; RL B + .byte $CB,$11,0 ; RL C + .byte $CB,$12,0 ; RL D + .byte $CB,$13,0 ; RL E + .byte $CB,$14,0 ; RL H + .byte $CB,$15,0 ; RL L + .byte $CB,$17,0 ; RL A + + .byte $CB,$18,0 ; RR B + .byte $CB,$19,0 ; RR C + .byte $CB,$1A,0 ; RR D + .byte $CB,$1B,0 ; RR E + .byte $CB,$1C,0 ; RR H + .byte $CB,$1D,0 ; RR L + .byte $CB,$1F,0 ; RR A + + .byte $CB,$20,0 ; SLA B + .byte $CB,$21,0 ; SLA C + .byte $CB,$22,0 ; SLA D + .byte $CB,$23,0 ; SLA E + .byte $CB,$24,0 ; SLA H + .byte $CB,$25,0 ; SLA L + .byte $CB,$27,0 ; SLA A + + .byte $CB,$28,0 ; SRA B + .byte $CB,$29,0 ; SRA C + .byte $CB,$2A,0 ; SRA D + .byte $CB,$2B,0 ; SRA E + .byte $CB,$2C,0 ; SRA H + .byte $CB,$2D,0 ; SRA L + .byte $CB,$2F,0 ; SRA A + + .byte $CB,$30,0 ; SWAP B + .byte $CB,$31,0 ; SWAP C + .byte $CB,$32,0 ; SWAP D + .byte $CB,$33,0 ; SWAP E + .byte $CB,$34,0 ; SWAP H + .byte $CB,$35,0 ; SWAP L + .byte $CB,$37,0 ; SWAP A + + .byte $CB,$38,0 ; SRL B + .byte $CB,$39,0 ; SRL C + .byte $CB,$3A,0 ; SRL D + .byte $CB,$3B,0 ; SRL E + .byte $CB,$3C,0 ; SRL H + .byte $CB,$3D,0 ; SRL L + .byte $CB,$3F,0 ; SRL A +instrs_end: + +test_instr: + ld c,$00 + call test + ld c,$F0 + call test + ret + +test: + ; Go through each value for A + ld hl,values +a_loop: + ld b,(hl) + push hl + + ; Go through each value for other registers + ld hl,values +values_loop: + push bc + push hl + + push bc + + ; BC + ld a,(hl+) + ld b,a + ld a,(hl+) + ld c,a + + ; HL + ld a,(hl+) + ld d,a + ld a,(hl+) + ld e,a + push de + + ; DE + ld a,(hl+) + ld d,a + ld a,(hl+) + ld e,a + + pop hl + pop af + +; call print_regs + jp instr +instr_done: + + ; Checksum registers + call checksum_af_bc_de_hl + + pop hl + pop bc + inc hl + ld a,l + cp 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 ?@1~S_Jk8EuOnO>t1U_)GPUYcV}PSJ688I zDZyK&1&Z3Yf=R|a=4{O6sIrFR>wY_q1954MiRzbDIv)c%zSNhFNh$uKIuubR9$M#% zQVgthyMM7>6t%8xc-*ZoU#;c~Eo^b8>ieF1Te%=ANe|UbtGOeuN<>ma4(yn>d z&PyDPc+NXzR}RnDiJDL@@*5U1@Exf>ZKHTq^DiA1#(zox`Vp^snt$Nkdp#BumLA0V yczj1Ga3U7XIy~nG6}avCt6hL`&VKmF{r{ZIvhq4Tb=k}k+hUx5zmKoWJ@6mAT&*eq literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/cpu_instrs/source/common/console.s b/playing-coffee/roms/cpu_instrs/source/common/console.s new file mode 100644 index 0000000..5307f6b --- /dev/null +++ b/playing-coffee/roms/cpu_instrs/source/common/console.s @@ -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 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 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 diff --git a/playing-coffee/roms/cpu_instrs/source/common/crc_fast.s b/playing-coffee/roms/cpu_instrs/source/common/crc_fast.s new file mode 100644 index 0000000..d1088b0 --- /dev/null +++ b/playing-coffee/roms/cpu_instrs/source/common/crc_fast.s @@ -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 diff --git a/playing-coffee/roms/cpu_instrs/source/common/delay.s b/playing-coffee/roms/cpu_instrs/source/common/delay.s new file mode 100644 index 0000000..65eb9c0 --- /dev/null +++ b/playing-coffee/roms/cpu_instrs/source/common/delay.s @@ -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 diff --git a/playing-coffee/roms/cpu_instrs/source/common/gb.inc b/playing-coffee/roms/cpu_instrs/source/common/gb.inc new file mode 100644 index 0000000..31bbf14 --- /dev/null +++ b/playing-coffee/roms/cpu_instrs/source/common/gb.inc @@ -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 diff --git a/playing-coffee/roms/cpu_instrs/source/common/instr_test.s b/playing-coffee/roms/cpu_instrs/source/common/instr_test.s new file mode 100644 index 0000000..5ed6e2c --- /dev/null +++ b/playing-coffee/roms/cpu_instrs/source/common/instr_test.s @@ -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+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,- + + 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 diff --git a/playing-coffee/roms/cpu_instrs/source/common/macros.inc b/playing-coffee/roms/cpu_instrs/source/common/macros.inc new file mode 100644 index 0000000..c413bd5 --- /dev/null +++ b/playing-coffee/roms/cpu_instrs/source/common/macros.inc @@ -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 diff --git a/playing-coffee/roms/cpu_instrs/source/common/multi_custom.s b/playing-coffee/roms/cpu_instrs/source/common/multi_custom.s new file mode 100644 index 0000000..4dbae9d --- /dev/null +++ b/playing-coffee/roms/cpu_instrs/source/common/multi_custom.s @@ -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 diff --git a/playing-coffee/roms/cpu_instrs/source/common/numbers.s b/playing-coffee/roms/cpu_instrs/source/common/numbers.s new file mode 100644 index 0000000..6d6faf8 --- /dev/null +++ b/playing-coffee/roms/cpu_instrs/source/common/numbers.s @@ -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 diff --git a/playing-coffee/roms/cpu_instrs/source/common/printing.s b/playing-coffee/roms/cpu_instrs/source/common/printing.s new file mode 100644 index 0000000..ad9d811 --- /dev/null +++ b/playing-coffee/roms/cpu_instrs/source/common/printing.s @@ -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 diff --git a/playing-coffee/roms/cpu_instrs/source/common/runtime.s b/playing-coffee/roms/cpu_instrs/source/common/runtime.s new file mode 100644 index 0000000..90cd24f --- /dev/null +++ b/playing-coffee/roms/cpu_instrs/source/common/runtime.s @@ -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 diff --git a/playing-coffee/roms/cpu_instrs/source/common/testing.s b/playing-coffee/roms/cpu_instrs/source/common/testing.s new file mode 100644 index 0000000..c102f78 --- /dev/null +++ b/playing-coffee/roms/cpu_instrs/source/common/testing.s @@ -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 diff --git a/playing-coffee/roms/cpu_instrs/source/linkfile b/playing-coffee/roms/cpu_instrs/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/playing-coffee/roms/cpu_instrs/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/playing-coffee/roms/cpu_instrs/source/shell.inc b/playing-coffee/roms/cpu_instrs/source/shell.inc new file mode 100644 index 0000000..277a9b7 --- /dev/null +++ b/playing-coffee/roms/cpu_instrs/source/shell.inc @@ -0,0 +1,21 @@ +.incdir "common" + +; GBS music file +.ifdef BUILD_GBS + .include "build_gbs.s" +.endif + +; Devcart +.ifdef BUILD_DEVCART + .include "build_devcart.s" +.endif + +; Sub-test in a multi-test ROM +.ifdef BUILD_MULTI + .include "build_multi.s" +.endif + +; GB ROM (default) +.ifndef RUNTIME_INCLUDED + .include "build_rom.s" +.endif diff --git a/playing-coffee/roms/dmg_sound/dmg_sound.gb b/playing-coffee/roms/dmg_sound/dmg_sound.gb new file mode 100644 index 0000000000000000000000000000000000000000..fe9131044031c45f404642f5dc8a9ec43ecd2065 GIT binary patch literal 65536 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 diff --git a/playing-coffee/roms/dmg_sound/rom_singles/01-registers.gb b/playing-coffee/roms/dmg_sound/rom_singles/01-registers.gb new file mode 100644 index 0000000000000000000000000000000000000000..c1fa6c5d2c003d96130d6cc426d75b9b99f4ba6e GIT binary patch literal 32768 zcmeI4-)~db9l(#B#H7J-5}LBxG>~I{cy30>23=l*!!OA zBvKjw0P6SXo_l_Me}2w6*Ka1|OJ1~q`{F06dZCf5t{EcZ#7P=SeM`p+FA~>B7s7K_ zuYNdj_H6&NVb}EwS3jC~?d+LvuzL`zA$FL(_WImC)^G|5AOR$R1dsp{Kmter2_OL^ zfCP{L5oAyb`e+Uf1qM*4Y7BU7Kc zNsp^l+wZ>562Wkh01`j~NB{{S0VIF~kN^@u0!RP}AOR$R1dsp{Kmter2_OL^fCP{L z5C7nMs z1?yr0)=bvKcWdkv->#KFE}_o!QWEdzxNCiCW!*<+zDr3*hmU|M)?ze+r!^q`AR9MY zi=H_fUC>2DZ7#X5VWyH;}OVfJw)^f zL{m!_x_i1wE%>3I?TF2$bPLR4HF=RF2-)d^tEbVMUy=l+8`9tM{Kd4i(Z27%Ho0rx zf$iI9AlyfX14n}Nz|JS>aCG2saBDOe=p(ah2{+EQ(gjPSr3Qo_`@*`|o}#Bbc#kwfiH(!RSD^pYGiMJvwwK^3zaX_^XF$Kvjd!9=80ES+;|}IVG&P!A3~+v~YQyo&pVCZl#Dr`VBRXbH1E3tfi)&JQ6&yisYwtf<@La=Oru|VIF zA&5cfYj34(cFo;fY4{7IShHoM?4keFt5eYTu4?#WRIGsS%~0EfL=VmD$#0U%yA|;| zQF#q|6++%6WU#Va?!M02V&+Pnyx1X=i?+YMN36s4kLo%5%o) zwyVp&7@PlO@6q{pdXLSI zKXZJZZl)i5DjT9(p4&1wdnr}Y&2mHfC5<1g9FZ=WnHY#2 zO-a*cX4iM58)jzrlhSqml4j2A;7_GXb4}$2pFO)OD{BX{W=3+F8JJk@>O1rP@wBUf zA5Ujx2nM2dY^{A+UL5U>!SJX)n>ROZ>?|a6BZbjiyfBt4>DI}zSI8_sq3IB7o*UZ! zz|c?-ZVe;asmww$=l9k<;r*x7B-VCE!olU0(OhI5T znA~a>N1={E9p5TaU#YfqNiQbP(ly1=KhwtI*cn=19Dj$_Xrmgxnbtl?cS63aW2fln z`ls87Q6M>6VwF3YU=lMff9?9}w!9_MS zd0W@Y+X7!Q_vu-CpgiWn@DmU96+GEQxQ2;YNnxW(A|sNL1WzfWsOJgs|Ge zc#cgLp3kwr7i^R44+Z-q3lwT587b6GvSfiw>Uncv96&#x>)+T-MnFCe;$JHb&+43I zwawER+xpuzu~S}0ad-Kb)J*BTd3u55=9<%|vT*3$$hygxi|~Iz`wLeETGm>a$bo}b zbL?i}WR5>QEq&ijUUAe8Z=s8u>gY}JLv_f}3?v8G38>`WwT@n8Hlvpj@BuQtddtaa zhmvvBw>|jQh7DjRRR_NcCjUW}y=?S=#JAYT#$N5%v^CXY^py0sB|A&bB_R}Xzr5@6 zrh6jSS^gP)pj4*b2EJ$7Ed8l#dw3Xr$b=RjpN4SZ4OTMwg_F5#J!HVB`<0xd^|3m- z$R;2+_z77O28k;#7`saPWg#5#cpiPUWg@q(ZNqW!adY<4%I887g-9e63Rz!}P&ot- zAre#kj<`hBkR&VFpOg0Pc3(t&oH(6MOU~5}dhISU=!4cbNQmmM_*Gbvqy!kO7FkxH zlUN!NqA0}faDu@e;#b2*RMk%;p)Vz1EEW#~(^++NC ziXvVpe#;*SSmPt1ZfguKg@3=W0}w?KexO+XRKH&$VCzbG9_*=p5xQ6xAym15zx9^d zdeS4Zci2hH8plYpFO*%`2SUnIio7)RI~drv@IwAXkXA%K%<>CkH38#xIS$x`4$LzL zFaccPUm`y60Q(5^p$r1NkjUR>4gDa0T>yZ#DlhDb{Hx=*V%Y#A;;dQ#s`C&JY_fHJT#_UUJRQLIOwt2_OL^fCP{L5UrhqDf1$yg(cqUptpAU4vY;*$gR>W~InZbkl@Td*8YK z1C%t|bkGXHp7j%r$fRGJ2Ok*3iOZr;emdzizC~$1u%( zl+x?oN$E@^KlVN!+e_0@XugM48v@(HWiicmddFqT>PT#`QQNATFl9=k=1D+M$CeV0 zL0+37qQ@YbQo7jM-bzZr552X=J3A?tbc}Y>;0P>2J z`bLlki6rlyP*^!%o;b&lIuOPR)_yCg|2IiClY(@+KH zK`<{TBEk;sb9QHsXFq+}-sS4+b@os*9oX+W;OM4Z4!g}w2Rt^kbImZD?1 z>b$DItt7ACWpjcJ>b5&vwr+bjwRxz`IYd3ZeRfqj7vZfrh5clWkta)^@_6ih{T}Y$ z!kY*bBiGV^SsRP@m1)+jpREg>5pIdVQ~ad7==SvFy9<-ku&i}y36g+*B%-c#iTgK9qA zy#uzF9RMz$cVQ7+&Oy6-g&)9ke?NdhErLM4h2mI+WmiRq>WZ^o<%{!i+neo99tZ8( zZ*w~Bjy$8~WaRquIs0S|9@QGyweAZnLh-oz3r@NmuC5<~)53~Dof06uY&mDP9mdv{ z*^bH=)WYIHFR$i*8!fyVnPRj%*XN=I)cG6>1wY{FbvU5xAM|FXTICQ|&78VH-6wLjUe_q)OSrk_MVI41AK$-h zUHiHJ!j{9me~uWL&9SKEKH(86;(B3G)Dln^1-2n6_?_rXnR%(fOr|w|ixBl2_Rrks z>z3M4*Rc3OQ+)E+)DbS1)}A*cfxx#ZkQh#kBt|7Aba~!5ntitU+s!XF|K3y)Yl}S^ zt7|aV{J^{L$-be5cRLR)jDGL%0^LqOHf6WCcfPu_fA-R3T2eBV7p_jp??mB|(R^E} zwzIM&@-Iawk?SK(XXqU`6?8@96A}4m5&2|9Ziz^W{8pr(1CQyDGAVl^lak^U=9M8~ zRyibGQ4R|qC_dq`azwbKguF0qXi}I`LQj8FxS@nPz9n3juTCkkJ@VU;^tI|tWt%o! z9ySNxi7FvMuY_P=y`lT;`|m{zmGXO$uo;4ZsC6x+Z_m#1NLq*%ScccT@1u6jb(cquL{*%*5Pu(cPS?s zj=L5|;ywN|jq5-YAi!Ukp2<=z(}1dy3bZJxVYo-&9&O>N7w?xMX(<&rM>nK~|3a%$ zBWGzvYV=)N5{ysDol{s*R__M#OOt9|;7yS?MdcS_ zX?bxvn3`4~uXIKerv^^oSz9lBZomexEA;4AFbM}7SnKurQbx8^(WNqaN1gEvr^oJg z+8osGcDvlbE(>T_Ws(q9njDR@@#N7s`$tkU&ig&I_rdfpr4PVfY$LP9rhBj8pr(#O_&&^+T9)!~|*OKEg z2p9f>mC*d+sd%^oGGNqrGOpXWyNoWeF~|+hrTNJ)W9CHS>9lm2Pj;D1PdrgO7Js~M z%VF?wd-hUsvk>@21mbWw)cwPealnrdfmtm&pFl*1V77*jlls>BHkbG$(d+fx_`x&R z%=v+Q!T&2xDJKKU{Xs{o+6R~1zlGZYi2UMy z;8OjG7K=c@)=Xvr?1>g0I+ybha$LaQW>sx7Y3JGNw8X3Sy`-ki5iYI+A(;uQIp4MD z7}!_wLjHu4w(@+K>KFQ=ANuuXA7JM?FwS(q1aN_WfwX}KSVy1_S3kgW@ci4hK6k_a)f!jh3k(2!yjE2Jxp9aG7Fn$y zgCLl>p6VXB^b3L>!h(6$8*k-G7oG`$4Tf)|ns2_OL^fCP{L5QVM literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/dmg_sound/rom_singles/03-trigger.gb b/playing-coffee/roms/dmg_sound/rom_singles/03-trigger.gb new file mode 100644 index 0000000000000000000000000000000000000000..1b0f03213d12cb67c481134896b04f7739472e41 GIT binary patch literal 32768 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 literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/dmg_sound/rom_singles/10-wave trigger while on.gb b/playing-coffee/roms/dmg_sound/rom_singles/10-wave trigger while on.gb new file mode 100644 index 0000000000000000000000000000000000000000..a7c7ddc03b556fa3e70e82218ab34398331fe5ea GIT binary patch literal 32768 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 disabled clocks as well + ld a,1 + call end + + call begin + wchn 4,$40 ; enable length + wchn 1,-2 ; length = 2 + wchn 4,$40 ; enabled -> enabled doesn't clock + wchn 4,$00 ; enabled -> disabled doesn't clock + ld a,2 + call end +.else + set_test 4,"Anything besides enabling shouldnt't clock" + call begin + wchn 4,$40 ; enable length + wchn 1,-2 ; length = 2 + wchn 4,$40 ; enabled -> enabled doesn't clock + wchn 4,$00 ; enabled -> disabled doesn't clock + wchn 4,$00 ; disabled -> disabled doesn't clock + ld a,2 + call end +.endif + + set_test 5,"If clock makes length zero, should disable chan" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + lda chan_mask + ld b,a + lda NR52 ; channel now disabled + and b + jp nz,test_failed + + set_test 6,"If length already reached zero, shouldn't clock" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + wchn 4,0 + wchn 4,$40 ; no clock; length still 0 + wchn 4,0 + wchn 4,$40 ; no clock; length still 0 + lda chan_maxlen; end triggers channel, which loads it with max length + call end + + set_test 7,"Trigger should un-freeze length that reached zero" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + wchn 4,$00 ; disable + wchn 4,$80 ; trigger unfreezes length, so it takes on maximum value + delay_clocks 8192 + wchn 4,$40 ; enable + delay_apu 2 ; clock length by 2 + lda chan_maxlen + sub 2 + call end_nodelay + + set_test 8,"Trigger that un-freezes enabled length should clock it" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + wchn 4,$00 ; disable + wchn 4,$C0 ; trigger unfreezes length, and since enabled, clocks it + lda chan_maxlen + dec a + call end_nodelay + + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + wchn 4,$C0 ; trigger unfreezes length, and since enabled, clocks it + lda chan_maxlen + dec a + call end_nodelay + + set_test 9,"Triggering that clocks length of 1 ","should clock twice and shouldn't freeze" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$C0 ; trigger and enable + ; First length counter is enabled, which clocks it to 0 and freezes it + ; Trigger unfreezes length counter, which clocks it AGAIN + ; The result is the same as the previous test, which enables separately + lda chan_maxlen + dec a + call end_nodelay + + set_test 10,"Trigger shouldn't otherwise affect length" + call begin + wchn 1,0 ; length = max + delay_clocks 8192 + wchn 4,$80 ; trigger + lda chan_maxlen + call end_nodelay + +.ifndef CGB_02 + call begin + wchn 1,0 ; length = max + wchn 4,$80 ; trigger + lda chan_maxlen + call end + + call begin + wchn 1,-2 ; length = 2 + wchn 4,$80 ; trigger + ld a,2 + call end +.endif + + set_test 11,"Disabled DAC shouldn't stop other trigger effects" + call begin + wchn 0,$00 ; disable wave DAC + wchn 2,$07 ; disable square/noise DAC + wchn 1,-1 + wchn 4,$C0 ; clocks length, which becomes max + wchn 0,$80 ; enable wave DAC + wchn 2,$08 ; enable square/noise DAC + wchn 4,$80 ; trigger + lda chan_maxlen + dec a + call end + + set_test 12,"Other trigger effects should still occur when disabled" + call sync_apu + wchn 0,0 + wchn 4,0 + wchn 1,-1 + wchn 4,$40 ; len = 0 + wchn 4,0 + wchn 4,$40 ; len = 0 + wchn 4,$80 ; len = max + wchn 4,$40 ; len = max-1 + wchn 4,0 + wchn 4,$40 ; len = max-2 + wchn 0,$80 ; enable now + wchn 4,$C0 + lda chan_maxlen + sub 3 + call delay_apu_cycles + lda chan_mask + ld b,a + lda NR52 + and b + jp z,test_failed + delay_apu 1 + lda NR52 + and b + jp nz,test_failed + + ret diff --git a/playing-coffee/roms/dmg_sound/source/04-sweep.s b/playing-coffee/roms/dmg_sound/source/04-sweep.s new file mode 100644 index 0000000..17ac4e4 --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/04-sweep.s @@ -0,0 +1,120 @@ +; Calc = calculation of new frequency and check for > $7FF +; Update = modification of frequency with new calculated value +.include "shell.inc" +.include "apu.s" + +begin: + call sync_sweep + wreg NR14,$40 + wreg NR11,-$21 + wreg NR12,$08 + ret + +should_be_almost_off: + lda NR52 + and $01 + jp z,test_failed + delay_apu 1 +should_be_off: + lda NR52 + and $01 + jp nz,test_failed + ret + +main: + set_test 2,"If shift>0, calculates on trigger" + call begin + wreg NR10,$01 + wreg NR13,$FF + wreg NR14,$C7 + call should_be_off + call begin + wreg NR10,$11 + wreg NR13,$FF + wreg NR14,$C7 + call should_be_off + + set_test 3,"If shift=0, doesn't calculate on trigger" + call begin + wreg NR10,$10 + wreg NR13,$FF + wreg NR14,$C7 + delay_apu 1 + call should_be_almost_off + + set_test 4,"If period=0, doesn't calculate" + call begin + wreg NR10,$00 + wreg NR13,$FF + wreg NR14,$C7 + delay_apu $20 + call should_be_almost_off + + set_test 5,"After updating frequency, calculates a second time" + call begin + wreg NR10,$11 + wreg NR13,$00 + wreg NR14,$C5 + delay_apu 1 + call should_be_almost_off + + set_test 6,"If calculation>$7FF, disables channel" + call begin + wreg NR10,$02 + wreg NR13,$67 + wreg NR14,$C6 + call should_be_off + + set_test 7,"If calculation<=$7FF, doesn't disable channel" + call begin + wreg NR10,$01 + wreg NR13,$55 + wreg NR14,$C5 + delay_apu $20 + call should_be_almost_off + + set_test 8,"If shift=0 and period>0, trigger enables" + call begin + wreg NR10,$10 + wreg NR13,$FF + wreg NR14,$C3 + delay_apu 2 + wreg NR10,$11 + delay_apu 1 + call should_be_almost_off + + set_test 9,"If shift>0 and period=0, trigger enables" + call begin + wreg NR10,$01 + wreg NR13,$FF + wreg NR14,$C3 + delay_apu 15 + wreg NR10,$11 + call should_be_almost_off + + set_test 10,"If shift=0 and period=0, trigger disables" + call begin + wreg NR10,$08 + wreg NR13,$FF + wreg NR14,$C3 + wreg NR10,$11 + delay_apu $20 + call should_be_almost_off + + set_test 11,"If shift=0, doesn't update" + call begin + wreg NR10,$10 + wreg NR13,$FF + wreg NR14,$C3 + delay_apu $20 + call should_be_almost_off + + set_test 12,"If period=0, doesn't update" + call begin + wreg NR10,$01 + wreg NR13,$00 + wreg NR14,$C5 + delay_apu $20 + call should_be_almost_off + + jp tests_passed diff --git a/playing-coffee/roms/dmg_sound/source/05-sweep details.s b/playing-coffee/roms/dmg_sound/source/05-sweep details.s new file mode 100644 index 0000000..1519b68 --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/05-sweep details.s @@ -0,0 +1,121 @@ +; Calc = calculation of new frequency and check for > $7FF +; Update = modification of frequency with new calculated value +.include "shell.inc" +.include "apu.s" + +begin: + call sync_sweep + wreg NR14,$40 + wreg NR11,-$20 + wreg NR12,$08 + ret + +should_be_almost_off: + lda NR52 + and $01 + jp z,test_failed + delay_apu 1 +should_be_off: + lda NR52 + and $01 + jp nz,test_failed + ret + +main: + set_test 2,"Timer treats period 0 as 8" + call begin + wreg NR10,$11 + wreg NR13,$00 + wreg NR14,$C2 + delay_apu 1 + wreg NR10,$01 ; sweep enabled + delay_apu 3 + wreg NR10,$11 ; non-zero period so calc will occur when timer reloads + delay_apu $11 + call should_be_almost_off + + set_test 3,"Makes private copy of frequency on trigger" + call begin + wreg NR10,$12 + wreg NR13,$04 + wreg NR14,$80 + wreg NR13,$00 + delay_apu $39 + call should_be_almost_off + + set_test 4,"Exiting negate mode after calculation disables channel" + call begin + wreg NR10,$09 ; since shift > 0, calculates sweep value at init + wreg NR13,$00 + wreg NR14,$C0 + delay_apu 2 + wreg NR10,$10 ; neg->pos, so disables channel + call should_be_off + + set_test 5,"Ending negate after it maybe changed freq disables chan" + call begin + wreg NR10,$10 ; enable sweep + wreg NR13,$00 + wreg NR14,$C0 + delay_apu 2 + wreg NR10,$18 ; negate mode + delay_apu 2 + wreg NR10,$10 ; neg->pos, so disables channel + call should_be_off + + set_test 6,"Ending negate mode any other way doesn't disable channel" + call begin + wreg NR10,$1F ; use negate mode once + wreg NR14,$C0 + delay_apu 2 + wreg NR10,$18 ; since period > 0, doesn't calculate at init + wreg NR13,$00 + wreg NR14,$C0 + delay_apu 1 ; no sweep clock here + wreg NR10,$10 ; pos mode before neg mode ever used + delay_apu 1 ; sweep clock occurs here + wreg NR10,$0F ; now let neg mode be seen once, but period = 0 so no calculation is made + delay_apu 2 ; sweep clock occurs here + wreg NR10,$10 ; doesn't affect channel + delay_apu 2 ; sweep clock occurs here + wreg NR10,$1F ; let neg mode get used + delay_apu 18 + wreg NR10,$79 ; period and shift can be changed without channel disabling + delay_apu 5 + call should_be_almost_off + + set_test 7,"Subtract mode uses two's complement" + call begin + delay 2048 ; avoids extra length clocking on CGB-02 + wreg NR10,$1C + wreg NR13,$B0 + wreg NR14,$85 + delay_apu 2 + wreg NR10,$01 + wreg NR14,$C5 + delay_apu $1F + call should_be_almost_off + + set_test 8,"Subtract mode uses two's complement (upper bound)" + call begin + wreg NR10,$1C + wreg NR13,$B1 + wreg NR14,$85 + delay_apu 2 + wreg NR10,$01 + wreg NR14,$C5 + call should_be_off + + set_test 9,"Update channel frequency only when period is reloaded" + call begin + wreg NR10,$74 + wreg NR13,$06 + wreg NR14,$85 + delay_apu 14 ; just reloaded + wreg NR13,$06 + delay_apu 13 ; if 14, fails + wreg NR10,$11 + wreg NR14,$85 ; just before next reload, so freq is still $506 + call should_be_almost_off + + jp tests_passed diff --git a/playing-coffee/roms/dmg_sound/source/06-overflow on trigger.s b/playing-coffee/roms/dmg_sound/source/06-overflow on trigger.s new file mode 100644 index 0000000..2ebc236 --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/06-overflow on trigger.s @@ -0,0 +1,64 @@ +; Finds highest and lowest frequencies that don't overflow +; immediately on trigger, for NR10 values of $00-$07 + +.include "shell.inc" +.include "apu.s" + +main: + + ; DMG-06: + ; 0555 0666 071C 0787 07C1 07E0 07F0 + + wreg NR12,8 + ld d,$01 +shift_loop: + ld a,d + sta NR10 + ld bc,$87FF +- ld a,c + sta NR13 + ld a,b + sta NR14 + delay_clocks 40 + lda NR52 + and 1 + jr nz,+ + dec bc + bit 6,b + jr z,- ++ res 7,b + call print_bc + inc d + bit 3,d + jr z,shift_loop + call print_newline + check_crc $F604603B + + ; DMG-05, DMG-06, DMG-09, CGB-04, CGB-05: + ; 0556 0667 071D 0788 07C2 07E1 07F1 + + wreg NR12,8 + ld d,$01 +shift_loop2: + ld a,d + sta NR10 + ld bc,$8000 +- ld a,c + sta NR13 + ld a,b + sta NR14 + delay_clocks 40 + lda NR52 + and 1 + jr z,+ + inc bc + bit 6,b + jr z,- ++ res 7,b + call print_bc + inc d + bit 3,d + jr z,shift_loop2 + check_crc $5A1697EE + + jp tests_passed diff --git a/playing-coffee/roms/dmg_sound/source/07-len sweep period sync.s b/playing-coffee/roms/dmg_sound/source/07-len sweep period sync.s new file mode 100644 index 0000000..6b3ed7e --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/07-len sweep period sync.s @@ -0,0 +1,106 @@ +; Tests length and sweep periods, and synchronization between the two +.include "shell.inc" +.include "apu.s" + +test_timing: + ; Time how long until next length clock +- inc de + ld a,(NR52) + and $01 + jr nz,- + + ;call print_de + + ; Error if not in 0...4 range + ld a,d + cp 0 + jp nz,test_failed + ld a,e + cp 5 + jp nc,test_failed + ret + +main: + + set_test 2,"Length period is wrong" + call sync_apu + wreg NR14,$40 ; avoids extra length clock + wreg NR11,$3F ; length = $01 + wreg NR12,$08 ; silent without disabling channel + wreg NR14,$C0 ; start length + ld de,-$170 + call test_timing + + set_test 3,"Sweep period is wrong" + call sync_sweep + wreg NR10,$10 ; sweep period = 1 + wreg NR12,$08 ; silent without disabling channel + wreg NR13,$FF ; max freq + wreg NR14,$87 ; start + ld de,-$2E4 + call test_timing + + set_test 4,"Sweep clock is synchronized with length" + call sync_sweep + wreg NR14,$40 ; avoids extra length clock + wreg NR11,$3F ; length = $01 + wreg NR12,$08 ; silent without disabling channel + wreg NR14,$C0 ; start length + ld de,-$170 + call test_timing + + set_test 5,"Powering up APU MODs next frame time with 8192" + call sync_apu + ld de,-$16F + call test_power + + call sync_apu + ld de,-$B5 + call test_power_off + + call sync_apu + delay_clocks 8192 + ld de,-$B5 + call test_power + + call sync_apu + delay_clocks 8192 + ld de,-$B5 + call test_power_off + + call sync_apu + ld de,-$B5 + wreg NR52,$00 ; power off + delay_clocks 8192 + call test_power + + set_test 6,"Powering up APU resets 128 Hz sweep divider" + call sync_sweep + ld de,-$229 + call test_power2 + + call sync_sweep + delay_apu 1 + ld de,-$229 + call test_power2 + + jp tests_passed + +test_power_off: + wreg NR52,$00 ; power off +test_power: + wreg NR52,$80 ; power on + wreg NR14,$40 + wreg NR11,-1 ; length = 1 + wreg NR12,8 + wreg NR14,$C0 + jp test_timing + +test_power2: + wreg NR52,$00 ; power off + wreg NR52,$80 ; power on + wreg NR10,$11 + wreg NR12,8 + wreg NR13,$00 + wreg NR14,$84 + jp test_timing diff --git a/playing-coffee/roms/dmg_sound/source/08-len ctr during power.s b/playing-coffee/roms/dmg_sound/source/08-len ctr during power.s new file mode 100644 index 0000000..1ea218a --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/08-len ctr during power.s @@ -0,0 +1,84 @@ +; On CGB, length counters are reset when powered up. +; On DMG, they are unaffected, and not clocked. + +;.define REQUIRE_DMG 1 +;.define REQUIRE_CGB 1 +.include "shell.inc" +.include "apu.s" + +enable_len_ctrs: + wreg NR22,8 + wreg NR24,$C0 + wreg NR12,8 + wreg NR14,$C0 + wreg NR30,$80 + wreg NR34,$C0 + wreg NR42,8 + wreg NR44,$C0 + ret + +main: + call sync_apu + + ld a,0 + call fill_apu_regs + + ; Load length counters + wreg NR41,-$33 + wreg NR31,-$44 + wreg NR11,-$11 + wreg NR21,-$22 + + delay_clocks 8192 + call enable_len_ctrs + + ; Power down. Comment out to see what would + ; happen if length counters did run. + wreg NR52,$00 + + ; Try to enable length counters + call enable_len_ctrs + + ; Give plenty of time for them to be clocked + delay_msec 250 + + ; Power back on and wait a bit longer + wreg NR52,$80 + ;call enable_len_ctrs ; can't do this here + delay_clocks 2048 + + ; Get values from length counters + wreg NR22,8 + wreg NR24,$C0 + ld a,$02 + call get_len_a + push af + + wreg NR12,8 + wreg NR14,$C0 + ld a,$01 + call get_len_a + push af + + wreg NR30,$80 + wreg NR34,$C0 + ld a,$04 + call get_len_a + push af + + wreg NR42,8 + wreg NR44,$C0 + ld a,$08 + call get_len_a + + ; Print them + call print_a + pop af + call print_a + pop af + call print_a + pop af + call print_a + + check_crc_dmg_cgb $32F0CFBB,$3CF589B4 + jp tests_passed diff --git a/playing-coffee/roms/dmg_sound/source/09-wave read while on.s b/playing-coffee/roms/dmg_sound/source/09-wave read while on.s new file mode 100644 index 0000000..2d06a77 --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/09-wave read while on.s @@ -0,0 +1,41 @@ +; Reads from wave RAM while playing, each time 2 +; clocks later. + +;.define REQUIRE_DMG 1 +;.define REQUIRE_CGB 1 +.include "shell.inc" +.include "apu.s" + +main: + wreg NR51,0 ; mute sound + loop_n_times test,69 + check_crc_dmg_cgb $118A3620,$270DA9A3 + jp tests_passed + +test: + add $99 + ld b,a + + ; Reload wave and have its first + ; sample read occur 2 clocks earlier + ; each loop iteration + ld hl,wave + call load_wave + wreg NR30,$80 ; enable + wreg NR32,$00 ; silent + ld a,b + sta NR33 ; period + wreg NR34,$87 ; start + + ; Read from wave + wreg NR33,-2 ; period = 4 + delay_clocks 176 + lda WAVE + + call print_a + + ret + +wave: + .byte $00,$11,$22,$33,$44,$55,$66,$77 + .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF diff --git a/playing-coffee/roms/dmg_sound/source/10-wave trigger while on.s b/playing-coffee/roms/dmg_sound/source/10-wave trigger while on.s new file mode 100644 index 0000000..d7ec063 --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/10-wave trigger while on.s @@ -0,0 +1,49 @@ +; Retriggers wave without stopping first + +;.define REQUIRE_DMG 1 +;.define REQUIRE_CGB 1 +.include "shell.inc" +.include "apu.s" + +main: + wreg NR51,0 ; mute sound + loop_n_times test,69 + check_crc_dmg_cgb $533D6D4D,$8130733A + jp tests_passed + +test: + add $99 + ld b,a + + ; Reload wave and have its first + ; sample read occur 2 clocks earlier + ; each loop iteration + ld hl,wave + call load_wave + wreg NR30,$80 ; enable + wreg NR32,$00 ; silent + ld a,b + sta NR33 ; period + wreg NR34,$87 ; start + + ; Retrigger wave + wreg NR33,-2 ; period = 4 + delay_clocks 168 + wreg NR34,$87 ; restart + delay_clocks 40 + + ; Print wave RAM + wreg NR30,0 + ld c,$30 +- ld a,($FF00+c) + call print_a + inc c + bit 6,c + jr z,- + call print_newline + + ret + +wave: + .byte $00,$11,$22,$33,$44,$55,$66,$77 + .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF diff --git a/playing-coffee/roms/dmg_sound/source/11-regs after power.s b/playing-coffee/roms/dmg_sound/source/11-regs after power.s new file mode 100644 index 0000000..56506bb --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/11-regs after power.s @@ -0,0 +1,52 @@ +; After powering sound off then on, NR12 and NR14 are 0 +; and NR44 is unchanged. While powered off, writes to +; NR41 are NOT ignored. + +.define REQUIRE_DMG 1 +.include "shell.inc" +.include "apu.s" + +main: + call sync_apu + ld a,$FF + call fill_apu_regs + + ; Power down for a moment + wreg NR52,$00 + wreg NR41,-$12 + wreg NR12,$F0 + delay_msec 100 + wreg NR52,$80 + + set_test 2,"Powering off should clear NR12" + call sync_apu + wreg NR14,$80 + lda NR52 + and $01 + jp nz,test_failed + + set_test 3,"Powering off should clear NR13" + call sync_apu + wreg NR10,$11 + wreg NR12,$08 + wreg NR14,$80 + delay_apu 20 + lda NR52 + and $01 + jp z,test_failed + + set_test 4,"Powering off shouldn't affect NR41" + call sync_apu + delay_clocks 8192 ; avoids extra length clocking + wreg NR42,$08 + wreg NR44,$C0 + delay_apu $11 + lda NR52 + and $08 + jp z,test_failed + delay_apu 1 + lda NR52 + and $08 + jp nz,test_failed + + jp tests_passed diff --git a/playing-coffee/roms/dmg_sound/source/12-wave write while on.s b/playing-coffee/roms/dmg_sound/source/12-wave write while on.s new file mode 100644 index 0000000..de4df7e --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/12-wave write while on.s @@ -0,0 +1,49 @@ +; Writes to wave RAM while playing + +.define REQUIRE_DMG 1 +;.define REQUIRE_CGB 1 +.include "shell.inc" +.include "apu.s" + +main: + loop_n_times test,69 + + ; CGB behaves erratically, so DMG-only for now + check_crc_dmg_cgb $3B4538A9,$2B27544E + jp tests_passed + +test: + add $99 + ld b,a + + ; Reload wave and have its first + ; sample read occur 2 clocks earlier + ; each loop iteration + ld hl,wave + call load_wave + wreg NR30,$80 ; enable + wreg NR32,$00 ; silent + ld a,b + sta NR33 ; period + wreg NR34,$87 ; start + + ; Write to wave + wreg NR33,-2 ; period = 4 + delay_clocks 168 + wreg WAVE,$F7 + + ; Print wave RAM + wreg NR30,0 + delay 1000 + ld hl,WAVE +- ld a,(hl+) + call print_a + bit 6,l + jr z,- + call print_newline + + ret + +wave: + .byte $00,$11,$22,$33,$44,$55,$66,$77 + .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF diff --git a/playing-coffee/roms/dmg_sound/source/common/build_gbs.s b/playing-coffee/roms/dmg_sound/source/common/build_gbs.s new file mode 100644 index 0000000..877754f --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/common/build_gbs.s @@ -0,0 +1,139 @@ +; Build as GBS music file + +.memoryMap + defaultSlot 0 + slot 0 $2000 size $2000 + slot 1 $C000 size $2000 +.endMe + +.romBankSize $2000 +.romBanks 2 + +.define RST_OFFSET $70 + +.ifndef GBS_TMA + .define GBS_TMA 0 +.endif + +.ifndef GBS_TAC + .define GBS_TAC 0 +.endif + +;;;; GBS music file header + +.ifndef CUSTOM_HEADER + .byte "GBS" + .byte 1,1,1 ; vers, song count, first song + .word load_addr, reset, gbs_play_, std_stack + .byte GBS_TMA,GBS_TAC ; timer +.endif + .org $10 + .ds $60,0 +load_addr: + .org RST_OFFSET+$70 ; space for RST vectors + .ds $148-RST_OFFSET-$70,0 + .org $150 ; wla insists on generating GB header + +gbs_play_: + jp gbs_play ; GBS spec disallows having gbs_play in RAM + +;;;; Shell + +.include "shell.s" + +.define gbs_idle nv_ram +.redefine nv_ram nv_ram+2 + +init_runtime: + ; Identify as DMG hardware + ld a,$01 + ld (gb_id),a + + ; Save return address + pop hl + ld a,l + ld (gbs_idle),a + ld a,h + ld (gbs_idle+1),a + + ; Delay 1/4 second to give time + ; for GBS player to interrupt with + ; play, if it's going to do so + delay_msec 250 + +.ifndef CUSTOM_PLAY +gbs_play: +.endif + ; Get return address + ld a,(gbs_idle) + ld l,a + ld a,(gbs_idle+1) + ld h,a + + ; If zero, then play interrupted init + ; call, or another play call, and we + ; can't run the program properly. + or l + jp z,internal_error + + setw gbs_idle,0 + jp hl + + +; 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 diff --git a/playing-coffee/roms/dmg_sound/source/common/build_rom.s b/playing-coffee/roms/dmg_sound/source/common/build_rom.s new file mode 100644 index 0000000..c1595b7 --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/common/build_rom.s @@ -0,0 +1,70 @@ +; Build as GB ROM + +.memoryMap + defaultSlot 0 + slot 0 $0000 size $4000 + slot 1 $C000 size $4000 +.endMe + +.romBankSize $4000 +.romBanks 2 + +.cartridgeType 3 ; MBC1+RAM+battery +.ramsize 02 ; 8K +.computeChecksum +.computeComplementCheck + +;;;; GB ROM header + + ; Reserve space for RST handlers + .org $70 + + ; Keep unused space filled, otherwise + ; wla moves code here + .ds $90,0 + + ; 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 + + ; Keep unused space filled, otherwise + ; wla moves code here + .org $150 + .ds $2150-$150,0 + +;;;; Shell + +.define NEED_CONSOLE 1 +.include "shell.s" + +init_runtime: + ret + +play_byte: + ret + +.ends diff --git a/playing-coffee/roms/dmg_sound/source/common/console.bin b/playing-coffee/roms/dmg_sound/source/common/console.bin new file mode 100644 index 0000000000000000000000000000000000000000..b02f2d37ee94c6570a3622734f2d704dfca44b0c GIT binary patch literal 768 zcmX|9F=`|+5G))_B$f+182koBX5hr&r*zbT5qlFY8w?8X8GehSdBVQJIPe4Zu1Z?m z2wEj|cXgKp3^N%Fwbp)6@0AO%)yuRSqFVOjFXoqd`4hj9QtEN1nR)XG+d{u=K$L$a zcxBdDa9GaHO?x1~9ZyqY!B>?@1~S_Jk8EuOnO>t1U_)GPUYcV}PSJ688I zDZyK&1&Z3Yf=R|a=4{O6sIrFR>wY_q1954MiRzbDIv)c%zSNhFNh$uKIuubR9$M#% zQVgthyMM7>6t%8xc-*ZoU#;c~Eo^b8>ieF1Te%=ANe|UbtGOeuN<>ma4(yn>d z&PyDPc+NXzR}RnDiJDL@@*5U1@Exf>ZKHTq^DiA1#(zox`Vp^snt$Nkdp#BumLA0V yczj1Ga3U7XIy~nG6}avCt6hL`&VKmF{r{ZIvhq4Tb=k}k+hUx5zmKoWJ@6mAT&*eq literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/dmg_sound/source/common/console.s b/playing-coffee/roms/dmg_sound/source/common/console.s new file mode 100644 index 0000000..403fa5c --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/common/console.s @@ -0,0 +1,285 @@ +; 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 + setb console_pos,console_width + setb console_mode,0 + setb console_scroll,-8 + 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 + wreg VBK,1 + ld a,0 + call @fill_nametable + + wreg VBK,0 + 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 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 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 diff --git a/playing-coffee/roms/dmg_sound/source/common/delay.s b/playing-coffee/roms/dmg_sound/source/common/delay.s new file mode 100644 index 0000000..cbcdcf3 --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/common/delay.s @@ -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 diff --git a/playing-coffee/roms/dmg_sound/source/common/gb.inc b/playing-coffee/roms/dmg_sound/source/common/gb.inc new file mode 100644 index 0000000..2d0118d --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/common/gb.inc @@ -0,0 +1,81 @@ +; Game Boy hardware addresses + +; $0000-$3FFF Fixed ROM bank +; $4000-$7FFF Switchable bank +; $8000-$9FFF VRAM +; $A000-$BFFF optional cartridge RAM +; $C000-$DFFF RAM +; $E000-$FDFF RAM mirror +; $FE00-$FE9F OAM +; $FEA0-$FEFF unused +; $FF00-$FF7F registers +; $FF80-$FFFE RAM +; $FFFF register + +; Memory +.define VRAM $8000 ; video memory +.define TILES $8000 ; tile images +.define BGMAP0 $9800 ; first 32x32 tilemap +.define BGMAP1 $9C00 ; second 32x32 tilemap +.define BRAM $A000 ; cart memory +.define WRAM $C000 ; internal memory +.define OAM $FE00 ; sprite memory +.define HRAM $FF80 ; fast memory for LDH + +; Registers + +.define RAMEN $0000 ; cartridge WRAM control +.define BANK $2000 ; bank select +.define P1 $FF00 ; controller + +; Game link I/O +.define SB $FF01 ; serial buffer +.define SC $FF02 ; serial control + +; 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 diff --git a/playing-coffee/roms/dmg_sound/source/common/macros.inc b/playing-coffee/roms/dmg_sound/source/common/macros.inc new file mode 100644 index 0000000..9d7cf44 --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/common/macros.inc @@ -0,0 +1,91 @@ +; General macros + +; Reads A from addr, from $FF00 to $FFFF +; Preserved: F, BC, DE, HL +; Time: 3 cycles +.macro lda ; addr + ldh a,(\1 - $FF00) +.endm + +; Writes A to addr, from $FF00 to $FFFF +; Preserved: AF, BC, DE, HL +; Time: 3 cycles +.macro sta ; addr + ldh (\1 - $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 + +; Writes byte to addr +; Preserved: F, BC, DE, HL +; Time: 6 cycles +.macro setb ; addr, data + ld a,\2 + ld (\1),a +.endm + +; Writes word to addr +; Preserved: F, BC, DE, HL +; Time: 12 cycles +.macro setw ; addr, data + ld a,<\2 + ld (\1),a + ld a,>\2 + ld (\1+1),a +.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 diff --git a/playing-coffee/roms/dmg_sound/source/common/numbers.s b/playing-coffee/roms/dmg_sound/source/common/numbers.s new file mode 100644 index 0000000..6d6faf8 --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/common/numbers.s @@ -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 diff --git a/playing-coffee/roms/dmg_sound/source/common/printing.s b/playing-coffee/roms/dmg_sound/source/common/printing.s new file mode 100644 index 0000000..bb9389b --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/common/printing.s @@ -0,0 +1,77 @@ +; 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 +;print_char_nocrc (defined by user) + + +; 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 diff --git a/playing-coffee/roms/dmg_sound/source/common/shell.s b/playing-coffee/roms/dmg_sound/source/common/shell.s new file mode 100644 index 0000000..3e588c7 --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/common/shell.s @@ -0,0 +1,261 @@ +; Common routines and runtime + +.define RUNTIME_INCLUDED 1 + +; A few bytes of RAM that aren't cleared +.define nv_ram_base $D800 +.define nv_ram nv_ram_base + +; Address of next normal variable +.define bss_base nv_ram_base+$80 +.define bss bss_base + +; Address of next direct-page ($FFxx) variable +.define dp_base $FF80 +.define dp dp_base + +; Top of stack +.define std_stack $DFFF+1 + +; Final exit result byte is written here +.define final_result $A000 + +; Text output is written here as zero-terminated string +.define text_out_base $A004 + +; 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 nv_ram +.redefine nv_ram nv_ram+1 + +; Copies C*$100 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 a,(hl+) + ld (de),a + inc e + jr nz,- + inc d + dec c + jr nz,- + + ld a,b + jp $C000 + + +.ifndef RST_OFFSET + .define RST_OFFSET 0 +.endif + +.ifndef CUSTOM_RESET + reset: + di + + ; Run code from $C000, as is done on devcart. This + ; ensures minimal difference in how it behaves. + ld hl,$4000 + ld c,$14 + jp copy_to_wram_then_run + + .bank 1 slot 1 + .org 0 + jp std_reset +.endif + +; returnOrg puts this code AFTER user code. +.section "runtime" returnOrg + + ; Catch user code running off end + jp internal_error + +; 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 + + ; Clear memory except very top of stack + ld bc,std_stack-bss_base - 2 + ld hl,bss_base + call clear_mem + ld bc,$FFFF-dp_base + ld hl,dp_base + call clear_mem + + ; Init hardware + wreg TAC,$00 + wreg IF,$00 + wreg IE,$00 + + wreg NR52,0 ; sound off + wreg NR52,$80 ; sound on + wreg NR51,$FF ; mono + wreg NR50,$77 ; volume + + call init_runtime + call init_text_out + call console_init + call init_testing + + .ifdef TEST_NAME + print_str TEST_NAME,newline,newline + .endif + + 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 + + call console_show + pop af + call play_byte + jp post_exit + ++ push af + call print_newline + 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 + + +; Clears BC bytes starting at HL +clear_mem: + ; If C>0, increment B + dec bc + inc c + inc b + + ld a,0 +- ld (hl+),a + dec c + jr nz,- + dec b + jr nz,- + ret + + +; Reports internal error and exits with code 255 +internal_error: + print_str "Internal error" + ld a,255 + jp exit + + +; build_devcart and build_multi customize this +.ifndef CUSTOM_PRINT + .define text_out_addr bss+0 + .redefine bss bss+2 + + ; Initializes text output to cartridge RAM + init_text_out: + ; Enable cartridge RAM and set text output pointer + setb RAMEN,$0A + setw text_out_addr,text_out_base + setb text_out_base-3,$DE + setb text_out_base-2,$B0 + setb text_out_base-1,$61 + setb text_out_base,0 + setb final_result,$80 + ret + + + ; Appends character to text output string + ; Preserved: AF, BC, DE, HL + write_text_out: + push hl + push af + ld a,(text_out_addr) + ld l,a + ld a,(text_out_addr+1) + ld h,a + inc hl + ld (hl),0 + ld a,l + ld (text_out_addr),a + ld a,h + ld (text_out_addr+1),a + dec hl + pop af + ld (hl),a + pop hl + ret + + print_char_nocrc: + call write_text_out + jp console_print +.endif + + +; only build_rom uses console +.ifdef NEED_CONSOLE + .include "console.s" +.else + console_init: + console_print: + console_flush: + console_normal: + console_inverse: + console_show: + console_set_mode: + ret +.endif + + +; build_devcart and build_multi need to customize this +.ifndef CUSTOM_EXIT + post_exit: + ld (final_result),a + forever: + wreg NR52,0 ; sound off +- jr - +.endif + + +.macro def_rst ARGS addr + .bank 0 slot 0 + .org addr+RST_OFFSET +.endm diff --git a/playing-coffee/roms/dmg_sound/source/common/testing.s b/playing-coffee/roms/dmg_sound/source/common/testing.s new file mode 100644 index 0000000..5dd63ce --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/common/testing.s @@ -0,0 +1,195 @@ +; Diagnostic and testing utilities + +.define test_code bss+0 +.define test_name bss+1 +.redefine bss bss+3 + + +; Sets test code and optional error text. +; Takes multiple strings due to wla's idiotic +; default limit of 63 chars per string. +; Preserved: AF, BC, DE, HL +.macro set_test ; code[,text[,text2[,text3]]] + push hl + call set_test_ + jr @set_test\@ + .byte \1 + .if NARGS > 1 + .byte \2 + .endif + .if NARGS > 2 + .byte \3 + .endif + .if NARGS > 3 + .byte \4 + .endif + .byte 0 +@set_test\@: + pop hl +.endm + +set_test_: + pop hl + push hl + push af + inc hl + inc hl + ldi a,(hl) + ld (test_code),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,(test_code) + 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,(test_code) + cp $FF ; if a = $FF then a = 1 + jr nz,+ + ld a,1 ++ 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 print_newline + call print_crc + .else + ld bc,(crc >> 16) ~ $FFFF + ld de,(crc & $FFFF) ~ $FFFF + call check_crc_ + .endif +.endm + +; Checks CRC, differing based on DMG or CGB build +.macro check_crc_dmg_cgb ARGS dmg, cgb + .ifdef REQUIRE_DMG + check_crc dmg + .else + .ifdef REQUIRE_CGB + check_crc cgb + .else + .printt "CGB or DMG must be specified" + .fail + .endif + .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 diff --git a/playing-coffee/roms/dmg_sound/source/linkfile b/playing-coffee/roms/dmg_sound/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/playing-coffee/roms/dmg_sound/source/readme.txt b/playing-coffee/roms/dmg_sound/source/readme.txt new file mode 100644 index 0000000..a0269c1 --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/readme.txt @@ -0,0 +1,82 @@ +Game Boy Tests Source Code +-------------------------- + +Building with wla-dx +-------------------- +To assemble a test ROM with wla-dx, use the following commands: + + wla -o source_filename_here.s test.o + wlalink linkfile test.gb + +To assemble as a GBS music file: + + wla -o source_filename_here.s test.o -DBUILD_GBS + wlalink linkfile test.gbs + +Note that some tests might only work when built as a ROM or GBS file, +but not both. + +Some tests might include a ROM/GBS that has all the tests combined. +Building such a multi-test is complex and the necessary files aren't +included. + + +Framework +--------- +Each test is in a single source file, and makes use of several library +source files from common/. This framework provides common services and +reduces code to only that which performs the actual test. Virtually all +tests include "shell.inc" at the beginning, which sets things up and +includes all the appropriate library files. + +The reset handler does minimal GB hardware initialization, clears RAM, +sets up the text console, then runs main. Main can exit by returning or +jumping to "exit" with an error code in A. Exit reports the code then +goes into an infinite loop. If the code is 0, it doesn't do anything, +otherwise it reports the code. Code 1 is reported as "Failed", and the +rest as "Error ". + +The default is to build a ROM. Defining BUILD_GBS will build as an GBS. +The other build types aren't supported due to their complexity. I load +the code into RAM at $C000 since my devcart requires it, and I don't +want the normal ROM to differ in any way from what I've tested. This +also allows easy self-modifying code. + +Several routines are available to print values and text to the console. +Most update a running CRC-32 checksum which can be checked with +check_crc, allowing ALL the output to be checked very easily. If the +checksum doesn't match, it is printed, so you can run the code on a GB +and paste the correct checksum into your code. + + +Macros +------ +Some macros are used to make common operations more convenient. The left +is equivalent to the right: + + Macro Equivalent + ------------------------------------- + lda addr ldh a,(addr-$FF00) + + sta addr ldh (addr-$FF00),a + + wreg addr,data ld a,data + ldh (addr-$FF00),a + + setb ld a,data + ld (addr),a + + setw setb addr+0,data + + for_loop routine,begin,end,step + calls routine with A set to successive values + + loop_n_times routine,count + calls routine with A from 0 to count-1 + + print_str "str" prints string + + +-- +Shay Green diff --git a/playing-coffee/roms/dmg_sound/source/shell.inc b/playing-coffee/roms/dmg_sound/source/shell.inc new file mode 100644 index 0000000..0d43faa --- /dev/null +++ b/playing-coffee/roms/dmg_sound/source/shell.inc @@ -0,0 +1,27 @@ +; Included at beginning of all programs +; that use standard shell + +; Get include files from common/ +.incdir "common" + +; Sub-test in a multi-test ROM +.ifdef BUILD_MULTI + .include "build_multi.s" +.else + +; GBS music file +.ifdef BUILD_GBS + .include "build_gbs.s" +.endif + +; Devcart +.ifdef BUILD_DEVCART + .include "build_devcart.s" +.endif + +; GB ROM (default) +.ifndef RUNTIME_INCLUDED + .include "build_rom.s" +.endif + +.endif ; .ifdef BUILD_MULTI diff --git a/playing-coffee/roms/drmario.gb b/playing-coffee/roms/drmario.gb new file mode 100644 index 0000000000000000000000000000000000000000..2f3dc699a35dea148246a5e0722ffdd9b529b51c GIT binary patch literal 32768 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 literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/halt_bug.gb b/playing-coffee/roms/halt_bug.gb new file mode 100644 index 0000000000000000000000000000000000000000..38e36625d805e68cb3b8cbf562d6dff500060bdd GIT binary patch literal 32768 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 literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/instr_timing/readme.txt b/playing-coffee/roms/instr_timing/readme.txt new file mode 100644 index 0000000..b64024f --- /dev/null +++ b/playing-coffee/roms/instr_timing/readme.txt @@ -0,0 +1,139 @@ +Game Boy CPU Instruction Timing Test +------------------------------------ +This ROM tests the timings of all CPU instructions except HALT, STOP, +and the 11 illegal opcodes. For conditional instructions, it tests taken +and not taken timings. This test requires proper timer operation (TAC, +TIMA, TMA). + +Failed instructions are listed as + + [CB] opcode:measured time-correct time + +Times are in terms of instruction cycles, where NOP takes one cycle. + + +Verified cycle timing tables +---------------------------- +The test internally uses a table of proper cycle times, which can be +used in an emulator to ensure proper timing. The only changes below are +removal of the .byte prefixes, and addition of commas at the ends, so +that they can be used without changes in most programming languages. For +added correctness assurance, the original tables can be found at the end +of the source code. + +Normal instructions: + + 1,3,2,2,1,1,2,1,5,2,2,2,1,1,2,1, + 0,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1, + 2,3,2,2,1,1,2,1,2,2,2,2,1,1,2,1, + 2,3,2,2,3,3,3,1,2,2,2,2,1,1,2,1, + 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, + 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, + 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, + 2,2,2,2,2,2,0,2,1,1,1,1,1,1,2,1, + 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, + 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, + 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, + 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, + 2,3,3,4,3,4,2,4,2,4,3,0,3,6,2,4, + 2,3,3,0,3,4,2,4,2,4,3,0,3,0,2,4, + 3,3,2,0,0,4,2,4,4,1,4,0,0,0,2,4, + 3,3,2,1,0,4,2,4,3,2,4,1,0,0,2,4 + +CB-prefixed instructions: + + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2, + 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2, + 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2, + 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, + 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + + +Internal operation +------------------ +Before each instruction is executed, the test sets up registers and +memory in such a way that the instruction will cleanly execute and then +end up at a common destination, without trashing anything important. The +timing itself is done by first synchronizing to the timer via a loop, +executing the instruction, then using a similar loop to determine how +many clocks elapsed. + + +Failure codes +------------- +Failed tests may print a failure code, and also short description of the +problem. For more information about a failure code, look in the +corresponding source file in source/; the point in the code where +"set_test n" occurs is where that failure code will be generated. +Failure code 1 is a general failure of the test; any further information +will be printed. + +Note that once a sub-test fails, no further tests for that file are run. + + +Console output +-------------- +Information is printed on screen in a way that needs only minimum LCD +support, and won't hang if LCD output isn't supported at all. +Specifically, while polling LY to wait for vblank, it will time out if +it takes too long, so LY always reading back as the same value won't +hang the test. It's also OK if scrolling isn't supported; in this case, +text will appear starting at the top of the screen. + +Everything printed on screen is also sent to the game link port by +writing the character to SB, then writing $81 to SC. This is useful for +tests which print lots of information that scrolls off screen. + + +Source code +----------- +Source code is included for all tests, in source/. It can be used to +build the individual test ROMs. Code for the multi test isn't included +due to the complexity of putting everything together. + +Code is written for the wla-dx assembler. To assemble a particular test, +execute + + wla -o "source_filename.s" test.o + wlalink linkfile test.gb + +Test code uses a common shell framework contained in common/. + + +Internal framework operation +---------------------------- +Tests use a common framework for setting things up, reporting results, +and ending. All files first include "shell.inc", which sets up the ROM +header and shell code, and includes other commonly-used modules. + +One oddity is that test code is first copied to internal RAM at $D000, +then executed there. This allows self-modification, and ensures the code +is executed the same way it is on my devcart, which doesn't have a +rewritable ROM as most do. + +Some macros are used to simplify common tasks: + + Macro Behavior + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + wreg addr,data Writes data to addr using LDH + lda addr Loads byte from addr into A using LDH + sta addr Stores A at addr using LDH + delay n Delays n cycles, where NOP = 1 cycle + delay_msec n Delays n milliseconds + set_test n,"Cause" Sets failure code and optional string + +Routines and macros are documented where they are defined. + +-- +Shay Green diff --git a/playing-coffee/roms/instr_timing/source/common/build_gbs.s b/playing-coffee/roms/instr_timing/source/common/build_gbs.s new file mode 100644 index 0000000..7ac8d3c --- /dev/null +++ b/playing-coffee/roms/instr_timing/source/common/build_gbs.s @@ -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 diff --git a/playing-coffee/roms/instr_timing/source/common/build_rom.s b/playing-coffee/roms/instr_timing/source/common/build_rom.s new file mode 100644 index 0000000..e1e220f --- /dev/null +++ b/playing-coffee/roms/instr_timing/source/common/build_rom.s @@ -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 diff --git a/playing-coffee/roms/instr_timing/source/common/console.bin b/playing-coffee/roms/instr_timing/source/common/console.bin new file mode 100644 index 0000000000000000000000000000000000000000..b02f2d37ee94c6570a3622734f2d704dfca44b0c GIT binary patch literal 768 zcmX|9F=`|+5G))_B$f+182koBX5hr&r*zbT5qlFY8w?8X8GehSdBVQJIPe4Zu1Z?m z2wEj|cXgKp3^N%Fwbp)6@0AO%)yuRSqFVOjFXoqd`4hj9QtEN1nR)XG+d{u=K$L$a zcxBdDa9GaHO?x1~9ZyqY!B>?@1~S_Jk8EuOnO>t1U_)GPUYcV}PSJ688I zDZyK&1&Z3Yf=R|a=4{O6sIrFR>wY_q1954MiRzbDIv)c%zSNhFNh$uKIuubR9$M#% zQVgthyMM7>6t%8xc-*ZoU#;c~Eo^b8>ieF1Te%=ANe|UbtGOeuN<>ma4(yn>d z&PyDPc+NXzR}RnDiJDL@@*5U1@Exf>ZKHTq^DiA1#(zox`Vp^snt$Nkdp#BumLA0V yczj1Ga3U7XIy~nG6}avCt6hL`&VKmF{r{ZIvhq4Tb=k}k+hUx5zmKoWJ@6mAT&*eq literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/instr_timing/source/common/console.s b/playing-coffee/roms/instr_timing/source/common/console.s new file mode 100644 index 0000000..5307f6b --- /dev/null +++ b/playing-coffee/roms/instr_timing/source/common/console.s @@ -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 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 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 diff --git a/playing-coffee/roms/instr_timing/source/common/delay.s b/playing-coffee/roms/instr_timing/source/common/delay.s new file mode 100644 index 0000000..65eb9c0 --- /dev/null +++ b/playing-coffee/roms/instr_timing/source/common/delay.s @@ -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 diff --git a/playing-coffee/roms/instr_timing/source/common/gb.inc b/playing-coffee/roms/instr_timing/source/common/gb.inc new file mode 100644 index 0000000..31bbf14 --- /dev/null +++ b/playing-coffee/roms/instr_timing/source/common/gb.inc @@ -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 diff --git a/playing-coffee/roms/instr_timing/source/common/macros.inc b/playing-coffee/roms/instr_timing/source/common/macros.inc new file mode 100644 index 0000000..c413bd5 --- /dev/null +++ b/playing-coffee/roms/instr_timing/source/common/macros.inc @@ -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 diff --git a/playing-coffee/roms/instr_timing/source/common/numbers.s b/playing-coffee/roms/instr_timing/source/common/numbers.s new file mode 100644 index 0000000..6d6faf8 --- /dev/null +++ b/playing-coffee/roms/instr_timing/source/common/numbers.s @@ -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 diff --git a/playing-coffee/roms/instr_timing/source/common/printing.s b/playing-coffee/roms/instr_timing/source/common/printing.s new file mode 100644 index 0000000..ad9d811 --- /dev/null +++ b/playing-coffee/roms/instr_timing/source/common/printing.s @@ -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 diff --git a/playing-coffee/roms/instr_timing/source/common/runtime.s b/playing-coffee/roms/instr_timing/source/common/runtime.s new file mode 100644 index 0000000..90cd24f --- /dev/null +++ b/playing-coffee/roms/instr_timing/source/common/runtime.s @@ -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 diff --git a/playing-coffee/roms/instr_timing/source/common/testing.s b/playing-coffee/roms/instr_timing/source/common/testing.s new file mode 100644 index 0000000..c102f78 --- /dev/null +++ b/playing-coffee/roms/instr_timing/source/common/testing.s @@ -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 diff --git a/playing-coffee/roms/instr_timing/source/common/timer.s b/playing-coffee/roms/instr_timing/source/common/timer.s new file mode 100644 index 0000000..32c713e --- /dev/null +++ b/playing-coffee/roms/instr_timing/source/common/timer.s @@ -0,0 +1,88 @@ +; Cycle-accurate timer + +; TIMA is incremented every 4 cycles. Loops +; check for increment within 3-cycle window, +; so when it occurs outside this, loop is +; exactly synchronized. Loop iterations are +; one more or less than 12 cycles, so they +; will never run more than 4 times. + +; Initializes timer +; Preserved: AF, BC, DE, HL +init_timer: + push af + di + lda IE ; disable timer interrupt + and ~$04 + sta IE + wreg TMA,0 ; max period + wreg TAC,$05 ; 262144 Hz + + ; Be sure timer doesn't expire + ; immediately or take too long + wreg IF,0 + wreg TIMA,-20 + delay 70 + lda IF + and $04 + jp nz,test_failed + lda IF + and $04 + jp z,test_failed + + pop af + ret + + +; Starts timer +; Preserved: AF, BC, DE, HL +start_timer: + push af + +- xor a ; 1 + sta TIMA ; 3 + lda TIMA ; 3 + or a ; 1 + jr nz,- ; 3 + + pop af + ret + + +; Stops timer and determines cycles since +; it was started. A = cycles (0 to 255). +; Preserved: BC, DE, HL +stop_timer: + push de + call stop_timer_word + ld a,e + sub 10 + pop de + ret + + +; Same as stop_timer, but with greater range. +; DE = cycles (0 to 1019). +; Preserved: BC, HL +stop_timer_word: + + ld d,0 + + ; Get main count (TIMA*4) + lda TIMA + sub 5 + add a + rl d + add a + rl d + ld e,a + + ; One iteration per remaining cycle +- xor a ; 1 + sta TIMA ; 3 + lda TIMA ; 3 + dec de ; 2 + or a ; 1 + jr nz,- ; 3 + + ret diff --git a/playing-coffee/roms/instr_timing/source/instr_timing.s b/playing-coffee/roms/instr_timing/source/instr_timing.s new file mode 100644 index 0000000..b6b7338 --- /dev/null +++ b/playing-coffee/roms/instr_timing/source/instr_timing.s @@ -0,0 +1,336 @@ +; Tests number of cycles taken by instructions +; except STOP, HALT, and illegals. + +.include "shell.inc" +.include "timer.s" + +.define saved_sp bss+0 +.define instr bss+2 ; 3-byte instr + JP instr_end +.define instr_addr bss+8 ; JP instr_end +.redefine bss bss+11 + +main: + call init_timer + call test_timer + set_test 0 + call test_main_ops + call test_cb_ops + jp tests_done + + +; Ensures timer works +test_timer: + call start_timer + call stop_timer + or a + ret z + set_test 2,"Timer doesn't work properly" + jp test_failed + + +; Tests main opcodes +test_main_ops: + ld l,0 +- ld h,>op_times + ld a,(hl) + cp 0 + call nz,@test_op + inc l + jr nz,- + ret + +@test_op: + ; Can't test the 8 RST instructions on devcart + ld a,l + cpl + and $C7 + jr nz,+ + ld a,(gb_id) + and gb_id_devcart + ret nz ++ + ; Test with flags set so that branches are + ; not taken + ld a,l ; e = (l & 0x08 ? 0 : 0xFF) + and $08 + add $F8 + ld e,a + call @copy_and_exec + ld d,0 + cp (hl) + jr z,+ + ld d,a + call print_failed_opcode ++ + + ; Time with branches not taken + ld a,e + cpl + ld e,a + call @copy_and_exec + ld h,>op_times_taken + cp (hl) + ret z + + ; If opcode already failed and timed the + ; same again, avoid re-reporting. + cp d + ret z + + call print_failed_opcode + ret + +@copy_and_exec: + push de + push hl + + ld h,>op_lens + ld c,(hl) + ld a,l + ld hl,instr + ld (hl+),a + dec c + jr z,@one_byte + ld a,0 + dec c + jr z,@two_bytes + ld a,instr_addr +@two_bytes: + ld (hl+),a +@one_byte: + ld a,e + call time_instruction + + pop hl + pop de + ret + + +; Tests CB opcodes +test_cb_ops: + ld hl,cb_op_times +- ld a,(hl) + cp 0 + call nz,@test_op_cb + inc l + jr nz,- + ret + +@test_op_cb: + ; Test with flags clear + ld e,$00 + call @copy_and_exec_cb + cp (hl) + jr nz,+ + + ; Test with flags set + ld e,$FF + call @copy_and_exec_cb + cp (hl) + jr nz,+ + + ret ++ print_str "CB " + call print_failed_opcode + ret + +@copy_and_exec_cb: + push hl + + ; Copy instr to exec space + ld a,l + ld hl,instr+1 + ld (hl+),a + ld a,$CB + ld (instr),a + call time_instruction + + pop hl + ret + + +; Reports failed opcode +; L -> opcode +; A -> cycles it took +; (HL) -> cycles it should have taken +; Preserved: HL +print_failed_opcode: + ; Print opcode + push af + ld a,l + call print_hex + ld a,':' + call print_char + pop af + + ; Print actual and correct times + call print_dec + ld a,'-' + call print_char + ld a,(hl) + call print_dec + ld a,' ' + call print_char + + ; Remember that failure occurred + set_test 1 + + ret + + +; Times instruction. +; HL -> address of byte just after instruction +; A -> flags when executing instruction +; A <- number of cycles instruction took +time_instruction: + ld c,a + + ; Write JP instr_end to HL and instr_addr + ld a,$C3 ; JP + ld (hl+),a + ld (instr_addr),a + + ld a,instr_end + ld (instr_addr+2),a + ld (hl),a + + ; Save sp + ld (saved_sp),sp + + ; Set regs and stack contents + push bc + ld bc,instr_addr + ld de,instr_addr + ld hl,instr_addr + call start_timer + pop af + push hl + + ; Environment instruction executes in: + ; 1 byte: OP + ; 2 byte: OP 00 + ; 3 byte: OP instr_addr + ; BC,DE,HL = instr_addr + ; Stack has instr_addr pushed on it. + ; Stack pointer can be trashed by instr. + ; instr_addr contains JP instr_end, that + ; can be trashed. Instructions which trash + ; this don't execute it. + + jp instr +instr_end: ; instruction jumps here when done + di + + ; Restore sp + ld sp,saved_sp + pop hl + ld sp,hl + + call stop_timer + sub 24 + ret + +.section "page_aligned" align 256 + +; Instruction lengths of opcodes. +; 0 for instructions not timed. +op_lens: + .byte 1,3,1,1,1,1,2,1,3,1,1,1,1,1,2,1 ; 0 + .byte 0,3,1,1,1,1,2,1,2,1,1,1,1,1,2,1 ; 1 + .byte 2,3,1,1,1,1,2,1,2,1,1,1,1,1,2,1 ; 2 + .byte 2,3,1,1,1,1,2,1,2,1,1,1,1,1,2,1 ; 3 + .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 4 + .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 5 + .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 6 + .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 7 + .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 8 + .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; 9 + .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; A + .byte 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1 ; B + .byte 1,1,3,3,3,1,2,1,1,1,3,0,3,3,2,1 ; C + .byte 1,1,3,0,3,1,2,1,1,1,3,0,3,0,2,1 ; D + .byte 2,1,1,0,0,1,2,1,2,1,3,0,0,0,2,1 ; E + .byte 2,1,1,1,0,1,2,1,2,1,3,1,0,0,2,1 ; F + +; Timings for main opcodes +op_times: + .byte 1,3,2,2,1,1,2,1,5,2,2,2,1,1,2,1 + .byte 0,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1 + .byte 2,3,2,2,1,1,2,1,2,2,2,2,1,1,2,1 + .byte 2,3,2,2,3,3,3,1,2,2,2,2,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 2,2,2,2,2,2,0,2,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 2,3,3,4,3,4,2,4,2,4,3,0,3,6,2,4 + .byte 2,3,3,0,3,4,2,4,2,4,3,0,3,0,2,4 + .byte 3,3,2,0,0,4,2,4,4,1,4,0,0,0,2,4 + .byte 3,3,2,1,0,4,2,4,3,2,4,1,0,0,2,4 + +; Timings when conditionals are taken +op_times_taken: + .byte 1,3,2,2,1,1,2,1,5,2,2,2,1,1,2,1 + .byte 0,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1 + .byte 3,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1 + .byte 3,3,2,2,3,3,3,1,3,2,2,2,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 2,2,2,2,2,2,0,2,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1 + .byte 5,3,4,4,6,4,2,4,5,4,4,0,6,6,2,4 + .byte 5,3,4,0,6,4,2,4,5,4,4,0,6,0,2,4 + .byte 3,3,2,0,0,4,2,4,4,1,4,0,0,0,2,4 + .byte 3,3,2,1,0,4,2,4,3,2,4,1,0,0,2,4 + +; Timings for CB-prefixed opcodes +cb_op_times: + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2 + .byte 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2 + .byte 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2 + .byte 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 + .byte 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 +.ends + +; RST handlers +.bank 0 slot 0 +.org $00 + jp instr_end +.org $08 + jp instr_end +.org $10 + jp instr_end +.org $18 + jp instr_end +.org $20 + jp instr_end +.org $28 + jp instr_end +.org $30 + jp instr_end +.org $38 + jp instr_end diff --git a/playing-coffee/roms/instr_timing/source/linkfile b/playing-coffee/roms/instr_timing/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/playing-coffee/roms/instr_timing/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/playing-coffee/roms/instr_timing/source/shell.inc b/playing-coffee/roms/instr_timing/source/shell.inc new file mode 100644 index 0000000..277a9b7 --- /dev/null +++ b/playing-coffee/roms/instr_timing/source/shell.inc @@ -0,0 +1,21 @@ +.incdir "common" + +; GBS music file +.ifdef BUILD_GBS + .include "build_gbs.s" +.endif + +; Devcart +.ifdef BUILD_DEVCART + .include "build_devcart.s" +.endif + +; Sub-test in a multi-test ROM +.ifdef BUILD_MULTI + .include "build_multi.s" +.endif + +; GB ROM (default) +.ifndef RUNTIME_INCLUDED + .include "build_rom.s" +.endif diff --git a/playing-coffee/roms/interrupt_time/interrupt_time.gb b/playing-coffee/roms/interrupt_time/interrupt_time.gb new file mode 100644 index 0000000000000000000000000000000000000000..1b17845e33205df4c97eaa140f8e6db2a1194ee0 GIT binary patch literal 32768 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 AjsO4v literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/mem_timing-2/mem_timing.gb b/playing-coffee/roms/mem_timing-2/mem_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..2665aa2d4f2b613b79eceef1a3365d238f919bb8 GIT binary patch literal 65536 zcmeI3dvH|OeaFx0v0x!BlA-FgOyKIVtF>gA#Sfu6>uc&bFpkS(oYXHe@iInB6$=Rq z8LdF91p=JbjRy~&)YKV@|FNgd3^Fr~h--%G-YY4-T_4x9w!E1Jd)JMaW<#h}%C(Jv z-G0y6)e8{)r(~SY@6P_tJ?Gr>{G4;I`2Bu|bo%#uv9H=?7F53f+hFj$BDNrRJL_SE ztccAoS@wg!WUgPn*K+pEnO_`!=bbH2x46!|cjlLe|L&c)@6y(ObL~^#d1&oJ>(&2lX@cpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y z5g-CYfCvx)B0vOg1c5artiR^wCF;e+Md}wJll5yX=CQQym`6*;+*(Jh%Cbka`LRFN zF2_o>{@5+$o;046YB_JHm*Yu`Z!gDlk%4EnwvcyLX~+5ZtCA=e4o23itZUh_E1vgH zJ#S!yFVcEqi$^_Y9qNQNh!K^!d^MjHMxOOv(5*p-ulTdV zeD$J;7ORud^-ebGdEL7(JBIf~A9?DzkZzrLH=FmN$h%KXT7x57w1uwjs-gUd!>wLu z^`xCFvcsEGFPreB^8V$e3Lq%cF2!!eyq01_w_-GT>O}pjdX|TN)X!Ij;<_clT*^#d zOcI51)Ct$BBJW7HBr1(5|31N@XN%t*T|4gsrC=%^9Uo2Gy|Hv`q2`M*%@JE$%#yLR zHOLQ^qu0e~utf4e+;QY`uHR@avZOoM(ktuEahpNMe6_biY5gKXQNMet&|g zqx&N(&-y-J_1IEYmNJb8D|uNJAFSk6Rc2WwFLICWJK_B%?j!t<+>bohq6RlLH8*Zn z+Z(sHZ!chRk=@j?xoJn!=I1swH+QPR*0yZ+*4DO0b#r4oDr~<4d3;kj50-;giI&9L zvDhM4zVx-ZckHOJgvvfT-GvMb>@`B8b!Nym>_35)WQ zDuocl~VItqgtkrzMX`d(@tKA9Vk9(7%-k*C#;mM-r%Ie{K-=$?f z_P*nTcbVKnpJukt=Q1JZHMODaF5XeKTRgYq5t~_or7$dIcWh5=Z>-0%2R|Aq?@6z& zTUYmJ-7(MnNJHe7NZB%9@l&Dmzg^RL{%7lVp6_{V*Ll^g{>qcCXuJK{+qcC}>q*N_ z6%6QA{7^Nbnwq^A$a|cZxqoddxqPCk=7f3yZ{?r5dC<+bx%mz^R@t(7mwU1fqRBy9 z=a0B`%MK|ccBc}zcPeM>UCJ-)F6AS8w{qGZ453)3uKe5{eBfba%pQDjt#Xd*Rd!@0 zKj2QDU6?9p$Qhb9J&242uFY?M_?FvMz~6EY`7jua+POUMW#6Se>qAI( zTJaIPva$x7V0U6qv@5YUn#5wAaw|D$tk6GJS=k-AVbk{Qjhk6qW9B)}M0eEh&A-q4 zpUOh9LJzjKG)_L*6K$Q?8{HB<;k^kp0RbV3r_ySU=0H_@w0pTdx(Bis(z9GleM;)x zNo%zG9rfnXJ^!Q@jqZJ0oj=<1Gd0)jtKxMYv(Qt6`HHReg*Co{#nzlncypXAlweUu zcwTvTl-E={o@Zhw)>LEL&x0(4q#>S}-%tK&E#FYh|Dl?nmap5^#;s4aH*IZd*-{{0 zpT4TA-V|O`DQAveZGPFq8?ZxTwdQcGjrk>0j%a3Mua@sD+K?MQlTs_uymd=>rSwv)`&gP1Kj(fTPo}E`u1S?_C;%x9dV0yR+|CupkwL9+|Y5abM(Q~ zLDkkR{;*em>ITkLW9xlVUC#Ep7_an}v%hksan}|n4oA_!-$u1B6R$=2%UaenOg4&G<2oOJTug z#TOVlz?Rf6X=n|8ofQ@q%5ttHsJFz$wl(11u#K^xKkX0Vk)njrVCM4q0*HjODaHZ; zmg6i$gR7W7*s>!S^fN`&SHeh(2VoX2R0^HQFI?hp4x_Jdpvx)J{y?Bhln;wyJ|D^l z#ZTNdXJLcrSNyVf9}~U!6fstGk00WqW~Lv^7yW;~C7>|{4TOV1))fqjAMyi%0NM>T z2ZO=p5c&^v1p*kCya$*T2(+L}hNq|&WPUMx#m}7LLOs;khPs%lxWa7cd}?@TXov-O zgu|#P#*51@`$GYl-qBny)96z4?-%U=qPRpqxMY7pzh7Z!YdCct?FIc}=;FB;LWT?a zTPmw9Wvj&OojEKd(;-&e&^$D?4veMx1HNq1@6^z~#EbbeK{_Djqse}e4u+9l;_Cu- zQ3v^&6HI^${VS{iJzyO{AJ;a(YZmiwkV!wvpA!IZ&y*MKiTP*JT>;r18Wd^418K4G zu#SR&3u)1fmbqj83V`xwhP1vvnD3GySz5y%^m zW02L5b&y9P--84pEs!u|3{n6ogVaFog^cF2+=={jUSu`PjjV$_3i%!+2x)=rfP`5t z&+Q-KuNeyde|^0Cwtha*e`=t=zjAl#b#>hQS7T(&y4u>>np8y1-N* zA>J;dbu8YQjlyjSPpoLm0VhvQ;!pIN#J!NO>myFaD|EQL2XWKkxylQr3e+*Ur{In$ z?ALBO2pYYhQ~$gpKV1S!&%?@U-*n6ATe|jJrZR91=O*yor%pOIAJw%F1yJEFhja5g z`fib$@>58otqlGTotsB=e)mAextX~a$aI{4p-boH$9mSexzym_F!)-7f7{>*;beKj zfQL!eK~y?7mmAl2Za!gL<=i}?3+Lw3#!Tnt-2=0on?E$*-29;d=jO-yEa&FO`W((p zv3kO}`7#`030@H^-{y91-Z{YU8sIAi_%XSAeJkqP8aHj8>EJx13kT=Z zI*tNhS8=hA`0M>}aB@>RI6pB_J<0DGFzW|w%yH7eIi}Baa89PAgR@<(`xOq(G5s12 zPHtM_GTHq%=5s`?5&eLfRnP zA?=XoARQ3s=Q%e+Dj;`4K7+)d<0qiwOCA34X&t@~I{ZGN!#6^Q|Ld#{ALgY{d3rOt z`wAJAx_c&?(cMKG8Qp!nzb5fJh}{p}eUN|Jucz|8&Q#i)qhyr#-%Kg*MF)AF4&6Pg zx#tVb9U2s-2m6C%4x86d-tll+d0!-ycbOxV_Yx@YNZnZFeWT9D%#8A$xfjSZPW5J# z_g5yB_y5p2GkAf)iw*v!P~J<0@-FKjDwX$h`t_Cf(yNvCjZ%46XDRPv=4|EtTS9sN zmQdbbnXSCPGMDl$mQN_}>!7^93FW$H(#LF_b@;++vq{;u=$|hAqphjE zai;dZL2B=+(B84FxY$U%QE2ZkX0-Poi~31EZkoR{ZA@}fd+(j4y-%j3_P#>#bM8Hn| zs*EmY@m<*{3=E;^+)hNf%ZQI~(M!h8@FdO$Y=nGQo)LIh=lALxAdknb_55M|?S64k zpuz5RvJK*(fIK0fl^o>H=)GAp11L%}gLbVm0(dxWX4omr3^GTW8A32KAa!HS42I61 z%$^a*+zVtn&Tlp38G(zFW`+k0zQy3r8oWawf|+5rFf+(Hh)OfVxBr(j0=us^GZ@m$ z@WL!J!;@E>5jZZ)49A6;;o@vF!^OGG3}W?!nc=4}Gq~iE&p0D6KHJPtYD}FGz>}F~ z2A2T{s6TCH7{A)gfT@e=OIbPB8G#czzr!?(%o=W7*UYeD@{GWW>z@&Le1JbOz#kjn z|0XBryQ^+%>*l6l=S(}p32A3|LD(6vuejJr{3pWBFrKk9>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1x zKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1x zKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1x zKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1x XKm>>Y5g-CYfCvx)B0vQGpAz^V^e*2# literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/mem_timing-2/readme.txt b/playing-coffee/roms/mem_timing-2/readme.txt new file mode 100644 index 0000000..f6577c1 --- /dev/null +++ b/playing-coffee/roms/mem_timing-2/readme.txt @@ -0,0 +1,114 @@ +Game Boy CPU Memory Access Timing Test +-------------------------------------- +These tests verify the timing of memory reads and writes made by +instructions, except stack and program counter accesses. These tests +require correct instruction timing and proper timer operation (TAC, +TIMA, TMA). + +The read and write tests list failing instructions as + + [CB] opcode:tested-correct + +The read-modify-write test lists failing instructions as + + [CB] opcode:tested read/tested write-correct read/correct write + +The values after the opcode refer to which instruction cycle the access +occurs on, with 1 being the first. If a time couldn't be determined due +to some other problem, it prints 0. + +For instructions which either read or write, but not both, the CPU makes +the access on the last cycle. For instructions which read, modify, then +write back, the CPU reads on the next-to-last cycle, and writes on the +last cycle. + + +Internal operation +------------------ +The tests have the timer increment TIMA every 64 cycles, synchronize +with this, delay a variable amount, then have the instruction under test +access the timer. By varying the delay in one-cycle increments, the +memory access made by the instruction can be made to fall before and +after a TIMA increment. By then examining the registers and value in +TIMA, it can be determined which occurred. + + +Multi-ROM +--------- +In the main directory is a single ROM/GBS which runs all the tests. It +prints a test's number, runs the test, then "ok" if it passes, otherwise +a failure code. Once all tests have completed it either reports that all +tests passed, or reports the number of the first failed test as the +result code (1 = first). Finally, it makes several beeps. If a test +fails, it can be run on its own by finding the corresponding ROM/GBS in +the singles directories. + +Ths compact format on screen is to avoid having the results scroll off +the top, so the test can be started and allowed to run without having to +constantly monitor the display. + + +Failure information +------------------- +For more information about a failure code or information printed, see +the test's source code in source/. To find failure code N, search for +"set_test N", which will usually be before the subtest which failed. + + +Flashes, clicks, other glitches +------------------------------- +Some tests might need to turn the screen off and on, or cause slight +audio clicks. This does not indicate failure, and should be ignored. +Only the test result reported at the end is important, unless stated +otherwise. + + +LCD support +----------- +Tests generally print information on screen. The tests will work fine if +run on an emulator with NO LCD support, or as an GBS which has no +inherent screen; in particular, the VBL wait routine has a timeout in +case LY doesn't reflect the current LCD line. The text printing will +also work if the LCD doesn't support scrolling. + + +Output to memory +---------------- +Text output and the final result are also written to memory at $A000, +allowing testing a very minimal emulator that supports little more than +CPU and RAM. To reliably indicate that the data is from a test and not +random data, $A001-$A003 are written with a signature: $DE,$B0,$61. If +this is present, then the text string and final result status are valid. + +$A000 holds the overall status. If the test is still running, it holds +$80, otherwise it holds the final result code. + +All text output is appended to a zero-terminated string at $A004. An +emulator could regularly check this string for any additional +characters, and output them, allowing real-time text output, rather than +just printing the final output at the end. + + +GBS versions +------------ +Many GBS-based tests require that the GBS player either not interrupt +the init routine with the play routine, or if they do, not interrupt the +play routine again if it hasn't returned yet. This is because many tests +need to run for a while without returning. + +In addition to the other text output methods described above, GBS builds +report essential information bytes audibly, including the final result. +A byte is reported as a series of tones. The code is in binary, with a +low tone for 0 and a high tone for 1. The first tone is always a zero. A +final code of 0 means passed, 1 means failure, and 2 or higher indicates +a specific reason as listed in the source code by the corresponding +set_code line. Examples: + +Tones Binary Decimal Meaning +- - - - - - - - - - - - - - - - - - - - +low 0 0 passed +low high 01 1 failed +low high low 010 2 error 2 + +-- +Shay Green diff --git a/playing-coffee/roms/mem_timing-2/rom_singles/01-read_timing.gb b/playing-coffee/roms/mem_timing-2/rom_singles/01-read_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..660298b1ee8ce2fd1128db5921d0312fabc30519 GIT binary patch literal 32768 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 J2>dsJe*@ghw{-vj literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/mem_timing-2/rom_singles/03-modify_timing.gb b/playing-coffee/roms/mem_timing-2/rom_singles/03-modify_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..7579c42719bb9dcb01556eb82204d93ccc482614 GIT binary patch literal 32768 zcmeI)UrZdw836DP4iJq#M3BqR+Uz!G^^fE=RWy^|ethQ>}w2~LqETz31 z1V`q8<9tAxxP7RSm&!O&%2TB(QXC#);<$3TKYL|`$EvFsZKROisX3~i9K;1}>|lD= zZ}zY&Reep=O5f=A+nwE+`DT9mM}9G-(wAG{f2|~Yyw^X`^=tL8&2<{4zzy|K+pzoG zFTwNSwb07)@(0sbuJrvR!#_Ovy`#sD9*^OYTu_P8 zH~!w2t4Y~NfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14c zz6gOM4KVL}uu;9cvtGR&PQv`U7rHz&62Cjly5-;iNZ0aw+cM}~)FXp*5-hR0;+kb%qavJAhL;Wsk8A;a%vcuR&$ zGF+7*EyHygmSp%yh7}oBW%x{nA_n1_&xt=K82(TDtas16NYA&77vEHGC)X3ADL#6& zMRko`UC15ZdwB2Oy}sh2S|tu7qGCLe6AvYxg6ya`?;P%j?AX>}J7mNEK0AVk!js6B zvs6IV^yQz6`|>}+?Z@$)0^*stcq)DZw;yE9ZmeCNuL>9II_(h!PME?ykA59zdBdw{ zcM<;{fX!V}aih9qCV6V9+P}Hm57#UHya49hg3bRbFFvh)=2S{zG|t~IpDCDjM10)TwF-uAy>Edb6%7ZPGv5}7u#-s^*h1;dVu?mdB5p4p5U(T(E8CH=GF_AQCoiOIgSl-3Pi*s?y8QlZ!c!+^ z6KOwA24}78uX@eDInf=)>WG=m*)1);e9RipPgs%sq*XA>cdrCl@lM>txyrkLqUZGK z-cyj}kX%o0#H>JD_0w&CW83AmLj$4Sdsil`fsIM4FFM!u0QN)#jM%(bQY(1{cD2`H z`|Y&}+$M3G+Ao)We>;#Um}{{s>Vs<&@2T}`lb6-nwW)ViS8^^c9!n5AdC=!EHNk_(o=0c?`Ew=21SJc|@!% zh-Xt`HYNVzEDrx1#q$HF`t^~MFZMs*AL^^C!?n-F<-_+%oIB5zkWZ3$bKY4$=C(yu zN_@*Gh`%o+mlteYWuaJMsUGQZxTinZdkV)u$$K@m1&4HA4s)BkibHd~x>>D$?zA`> zda-vX)Du*DhlU1*P_l$jye<#GtMXG8pUJ;u@sIKqGyLs*M-Y z-k3u4m#n^)4j9Mw5p2GGPIt5tk)#7v66mg&M4-{v1@jwJtJ zIHwviT?O+ZtK_kj7*0jLYi$qbZ11$?EB-=#piorX>clz2E?jDVGBk{zSJ_J#22K|v zj$-6CUboVYr5#kZ{77+Wlz%N#72H)Z32*490H8m6s>cQub; zjyu^K_dgv>7GMwz2Axkyuo%Q20OMLfi7?QE%&(NXj(+JkIH6#KepH9Pwu(m?v#1uZ)@)dnZ?iKTroZV@L2XN=fU-2 z{-1?34h#o~>N-SpUHD-FpT-Nh^FDZoV^BI(?XaM@G5rGAt0yE0-(r( z{jkp<_Qj>j5{2|?aVecngMKC&#g1~mSOQKyY~a+-1UsBMrj+>uavVgIMdrcc_ z2Pv(|^>8OI*7YdX8~qVvmwoU&SI`8xFh7G%%z*b1^|1^hUQn*T)2Rlq{VfTA`*M3Z zo?L&q9?_ifa6nm?9$1$T9^OYO;K8~~!^?YIKcK);L_VtyK>0k*2XC_T{CF7i%YIIJ zbQxsKjnl$*+NW=nA6+~Kzz>Z4c(S5t(!cjefCNZ@1W14cNPq-LfCNZ@1W14cNPq-L zfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@1W14cNPq-LfCNZ@ K1W4fjN#I|aZiWc} literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/mem_timing-2/source/01-read_timing.s b/playing-coffee/roms/mem_timing-2/source/01-read_timing.s new file mode 100644 index 0000000..5084d4b --- /dev/null +++ b/playing-coffee/roms/mem_timing-2/source/01-read_timing.s @@ -0,0 +1,150 @@ +; Tests timing of accesses made by +; memory read instructions + +.define ROM_NAME "foo" + +.include "shell.inc" +.include "tima_64.s" + +instructions: + ; last value is time of read + .byte $B6,$00,$00,2 ; OR (HL) + .byte $BE,$00,$00,2 ; CP (HL) + .byte $86,$00,$00,2 ; ADD (HL) + .byte $8E,$00,$00,2 ; ADC (HL) + .byte $96,$00,$00,2 ; SUB (HL) + .byte $9E,$00,$00,2 ; SBC (HL) + .byte $A6,$00,$00,2 ; AND (HL) + .byte $AE,$00,$00,2 ; XOR (HL) + .byte $46,$00,$00,2 ; LD B,(HL) + .byte $4E,$00,$00,2 ; LD C,(HL) + .byte $56,$00,$00,2 ; LD D,(HL) + .byte $5E,$00,$00,2 ; LD E,(HL) + .byte $66,$00,$00,2 ; LD H,(HL) + .byte $6E,$00,$00,2 ; LD L,(HL) + .byte $7E,$00,$00,2 ; LD A,(HL) + .byte $F2,$00,$00,2 ; LDH A,(C) + .byte $0A,$00,$00,2 ; LD A,(BC) + .byte $1A,$00,$00,2 ; LD A,(DE) + .byte $2A,$00,$00,2 ; LD A,(HL+) + .byte $3A,$00,$00,2 ; LD A,(HL-) + .byte $F0,tima_64,4 ; LD A,($0000) + + .byte $CB,$46,$00,3 ; BIT 0,(HL) + .byte $CB,$4E,$00,3 ; BIT 1,(HL) + .byte $CB,$56,$00,3 ; BIT 2,(HL) + .byte $CB,$5E,$00,3 ; BIT 3,(HL) + .byte $CB,$66,$00,3 ; BIT 4,(HL) + .byte $CB,$6E,$00,3 ; BIT 5,(HL) + .byte $CB,$76,$00,3 ; BIT 6,(HL) + .byte $CB,$7E,$00,3 ; BIT 7,(HL) +instructions_end: + +main: + call init_tima_64 + set_test 0 + + ; Test instructions + ld hl,instructions +- call @time_instr + cp (hl) + call nz,@print_failed + inc hl + ld a,l + cp 3-byte instruction +; HL <- HL + 3 +@time_instr: + ; Copy instr + ld a,(hl+) + ld (instr+0),a + ld a,(hl+) + ld (instr+1),a + ld a,(hl+) + ld (instr+2),a + push hl + + ; Find result when access doesn't occur + ld b,0 + call @time_access + ld c,a + + ; Test for accesses on each cycle + ld b,0 +- push bc + call @time_access + pop bc + cp c + jr nz,@found + inc b + ld a,b + cp 10 + jr nz,- + ld b,0 + +@found: + ld a,b + pop hl + ret + +; Tests for read +; B -> which cycle to test +; A <- timer value after test +@time_access: + call sync_tima_64 + ld a,9 + sub b + call delay_a_20_cycles + xor a ; clear flags + ld hl,tima_64 + ld (hl),$7F + ld bc,tima_64 + ld de,tima_64 + ld a,$7F +instr: + nop + nop + nop + + ; Add all registers together to yield + ; unique value that differs based on + ; read occurring before or after tima_64 + ; increments. + push af + add hl,bc + add hl,de + pop de + add hl,de + ld a,h + add l + ret diff --git a/playing-coffee/roms/mem_timing-2/source/02-write_timing.s b/playing-coffee/roms/mem_timing-2/source/02-write_timing.s new file mode 100644 index 0000000..acd6d9d --- /dev/null +++ b/playing-coffee/roms/mem_timing-2/source/02-write_timing.s @@ -0,0 +1,115 @@ +; Tests timing of accesses made by +; memory write instructions + +.include "shell.inc" +.include "tima_64.s" + +instructions: + ; last value is time of write + .byte $36,$FF,$00,3 ; LD (HL),n + .byte $70,$00,$00,2 ; LD (HL),B + .byte $71,$00,$00,2 ; LD (HL),C + .byte $72,$00,$00,2 ; LD (HL),D + .byte $73,$00,$00,2 ; LD (HL),E + .byte $74,$00,$00,2 ; LD (HL),H + .byte $75,$00,$00,2 ; LD (HL),L + .byte $77,$00,$00,2 ; LD (HL),A + .byte $02,$00,$00,2 ; LD (BC),A + .byte $12,$00,$00,2 ; LD (DE),A + .byte $22,$00,$00,2 ; LD (HL+),A + .byte $32,$00,$00,2 ; LD (HL-),A + .byte $E2,$00,$00,2 ; LDH (C),A + .byte $E0,tima_64,4 ; LD (nn),A +instructions_end: + +main: + call init_tima_64 + set_test 0 + + ; Test instructions + ld hl,instructions +- call @test_instr + cp (hl) + call nz,@print_failed + inc hl + ld a,l + cp 3-byte instruction +; HL <- HL + 3 +@test_instr: + ; Copy instr + ld a,(hl+) + ld (instr+0),a + ld a,(hl+) + ld (instr+1),a + ld a,(hl+) + ld (instr+2),a + push hl + + ; Test for writes on each cycle + ld b,0 +- push bc + call @time_write + pop bc + cp tima_64 + jr z,@no_write + jr @found +@no_write: + inc b + ld a,b + cp 10 + jr nz,- + ld b,0 + +@found: + ld a,b + pop hl + ret + +; Tests for write +; B -> which cycle to test +; A <- timer value after test +@time_write: + call sync_tima_64 + ld a,13 + sub b + call delay_a_20_cycles + ld hl,tima_64 + ld bc,tima_64 + ld de,tima_64 + ld a, 3-byte instruction +; HL <- HL + 3 +@time_instr: + ; Copy instr + ld a,(hl+) + ld (instr+0),a + ld a,(hl+) + ld (instr+1),a + ld a,(hl+) + ld (instr+2),a + push hl + + ; Find result when access doesn't occur + ld b,0 + call @time_access + + ; Find first access + call @find_next_access + ld d,b + + ; Find second access + call @find_next_access + ld e,b + + pop hl + ret + +; A -> current timer result +; B -> starting clock +; B <- clock next access occurs on +; A <- new timer result +@find_next_access: + ld c,a +- call @time_access + cp c + ret nz + inc b + ld a,b + cp 10 + jr c,- + + ; Couldn't find time, so return 0/0 + ld a,c + ld b,0 + ld d,b + ret + +; Tests for access +; B -> which cycle to test +; A <- timer value after test +@time_access: + call sync_tima_64 + ld hl,tima_64 + ld (hl),$7F + ld a,17 + sub b + call delay_a_20_cycles + xor a ; clear flags +instr: + nop + nop + nop + delay 32 + ld a,(tima_64) + ret diff --git a/playing-coffee/roms/mem_timing-2/source/common/build_gbs.s b/playing-coffee/roms/mem_timing-2/source/common/build_gbs.s new file mode 100644 index 0000000..877754f --- /dev/null +++ b/playing-coffee/roms/mem_timing-2/source/common/build_gbs.s @@ -0,0 +1,139 @@ +; Build as GBS music file + +.memoryMap + defaultSlot 0 + slot 0 $2000 size $2000 + slot 1 $C000 size $2000 +.endMe + +.romBankSize $2000 +.romBanks 2 + +.define RST_OFFSET $70 + +.ifndef GBS_TMA + .define GBS_TMA 0 +.endif + +.ifndef GBS_TAC + .define GBS_TAC 0 +.endif + +;;;; GBS music file header + +.ifndef CUSTOM_HEADER + .byte "GBS" + .byte 1,1,1 ; vers, song count, first song + .word load_addr, reset, gbs_play_, std_stack + .byte GBS_TMA,GBS_TAC ; timer +.endif + .org $10 + .ds $60,0 +load_addr: + .org RST_OFFSET+$70 ; space for RST vectors + .ds $148-RST_OFFSET-$70,0 + .org $150 ; wla insists on generating GB header + +gbs_play_: + jp gbs_play ; GBS spec disallows having gbs_play in RAM + +;;;; Shell + +.include "shell.s" + +.define gbs_idle nv_ram +.redefine nv_ram nv_ram+2 + +init_runtime: + ; Identify as DMG hardware + ld a,$01 + ld (gb_id),a + + ; Save return address + pop hl + ld a,l + ld (gbs_idle),a + ld a,h + ld (gbs_idle+1),a + + ; Delay 1/4 second to give time + ; for GBS player to interrupt with + ; play, if it's going to do so + delay_msec 250 + +.ifndef CUSTOM_PLAY +gbs_play: +.endif + ; Get return address + ld a,(gbs_idle) + ld l,a + ld a,(gbs_idle+1) + ld h,a + + ; If zero, then play interrupted init + ; call, or another play call, and we + ; can't run the program properly. + or l + jp z,internal_error + + setw gbs_idle,0 + jp hl + + +; 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 diff --git a/playing-coffee/roms/mem_timing-2/source/common/build_rom.s b/playing-coffee/roms/mem_timing-2/source/common/build_rom.s new file mode 100644 index 0000000..0f090aa --- /dev/null +++ b/playing-coffee/roms/mem_timing-2/source/common/build_rom.s @@ -0,0 +1,84 @@ +; Build as GB ROM + +.memoryMap + defaultSlot 0 + slot 0 $0000 size $4000 + slot 1 $C000 size $4000 +.endMe + +.romBankSize $4000 +.romBanks 2 + +.cartridgeType 3 ; MBC1+RAM+battery +.ramsize 02 ; 8K +.computeChecksum +.computeComplementCheck +.emptyfill $FF + +;;;; GB ROM header + +.org $134 + .ds 15,0 + + ; Reserve space for RST handlers + .org $70 + + ; Keep unused space filled, otherwise + ; wla moves code here + .ds $90,0 + + ; 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 + +.section "HEADER" OVERWRITE + ; Internal name + .ifdef ROM_NAME + .byte ROM_NAME + .else + .ifdef ROM_NAME_DEFAULT + .byte ROM_NAME_DEFAULT + .endif + .endif +.ends + + ; CGB/DMG requirements + .org $143 + .ifdef REQUIRE_CGB + .byte $C0 + .else + .ifndef REQUIRE_DMG + .byte $80 + .endif + .endif + .byte 0,0,0 + + .org $14A + .byte 0,0,0 + + ; Keep unused space filled, otherwise + ; wla moves code here + .org $150 + .ds $2150-$150,$FF + +;;;; Shell + +.define NEED_CONSOLE 1 +.include "shell.s" + +init_runtime: + ret + +play_byte: + ret + +.ends diff --git a/playing-coffee/roms/mem_timing-2/source/common/console.bin b/playing-coffee/roms/mem_timing-2/source/common/console.bin new file mode 100644 index 0000000000000000000000000000000000000000..b02f2d37ee94c6570a3622734f2d704dfca44b0c GIT binary patch literal 768 zcmX|9F=`|+5G))_B$f+182koBX5hr&r*zbT5qlFY8w?8X8GehSdBVQJIPe4Zu1Z?m z2wEj|cXgKp3^N%Fwbp)6@0AO%)yuRSqFVOjFXoqd`4hj9QtEN1nR)XG+d{u=K$L$a zcxBdDa9GaHO?x1~9ZyqY!B>?@1~S_Jk8EuOnO>t1U_)GPUYcV}PSJ688I zDZyK&1&Z3Yf=R|a=4{O6sIrFR>wY_q1954MiRzbDIv)c%zSNhFNh$uKIuubR9$M#% zQVgthyMM7>6t%8xc-*ZoU#;c~Eo^b8>ieF1Te%=ANe|UbtGOeuN<>ma4(yn>d z&PyDPc+NXzR}RnDiJDL@@*5U1@Exf>ZKHTq^DiA1#(zox`Vp^snt$Nkdp#BumLA0V yczj1Ga3U7XIy~nG6}avCt6hL`&VKmF{r{ZIvhq4Tb=k}k+hUx5zmKoWJ@6mAT&*eq literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/mem_timing-2/source/common/console.s b/playing-coffee/roms/mem_timing-2/source/common/console.s new file mode 100644 index 0000000..403fa5c --- /dev/null +++ b/playing-coffee/roms/mem_timing-2/source/common/console.s @@ -0,0 +1,285 @@ +; 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 + setb console_pos,console_width + setb console_mode,0 + setb console_scroll,-8 + 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 + wreg VBK,1 + ld a,0 + call @fill_nametable + + wreg VBK,0 + 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 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 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 diff --git a/playing-coffee/roms/mem_timing-2/source/common/delay.s b/playing-coffee/roms/mem_timing-2/source/common/delay.s new file mode 100644 index 0000000..cbcdcf3 --- /dev/null +++ b/playing-coffee/roms/mem_timing-2/source/common/delay.s @@ -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 diff --git a/playing-coffee/roms/mem_timing-2/source/common/gb.inc b/playing-coffee/roms/mem_timing-2/source/common/gb.inc new file mode 100644 index 0000000..2d0118d --- /dev/null +++ b/playing-coffee/roms/mem_timing-2/source/common/gb.inc @@ -0,0 +1,81 @@ +; Game Boy hardware addresses + +; $0000-$3FFF Fixed ROM bank +; $4000-$7FFF Switchable bank +; $8000-$9FFF VRAM +; $A000-$BFFF optional cartridge RAM +; $C000-$DFFF RAM +; $E000-$FDFF RAM mirror +; $FE00-$FE9F OAM +; $FEA0-$FEFF unused +; $FF00-$FF7F registers +; $FF80-$FFFE RAM +; $FFFF register + +; Memory +.define VRAM $8000 ; video memory +.define TILES $8000 ; tile images +.define BGMAP0 $9800 ; first 32x32 tilemap +.define BGMAP1 $9C00 ; second 32x32 tilemap +.define BRAM $A000 ; cart memory +.define WRAM $C000 ; internal memory +.define OAM $FE00 ; sprite memory +.define HRAM $FF80 ; fast memory for LDH + +; Registers + +.define RAMEN $0000 ; cartridge WRAM control +.define BANK $2000 ; bank select +.define P1 $FF00 ; controller + +; Game link I/O +.define SB $FF01 ; serial buffer +.define SC $FF02 ; serial control + +; 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 diff --git a/playing-coffee/roms/mem_timing-2/source/common/macros.inc b/playing-coffee/roms/mem_timing-2/source/common/macros.inc new file mode 100644 index 0000000..9d7cf44 --- /dev/null +++ b/playing-coffee/roms/mem_timing-2/source/common/macros.inc @@ -0,0 +1,91 @@ +; General macros + +; Reads A from addr, from $FF00 to $FFFF +; Preserved: F, BC, DE, HL +; Time: 3 cycles +.macro lda ; addr + ldh a,(\1 - $FF00) +.endm + +; Writes A to addr, from $FF00 to $FFFF +; Preserved: AF, BC, DE, HL +; Time: 3 cycles +.macro sta ; addr + ldh (\1 - $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 + +; Writes byte to addr +; Preserved: F, BC, DE, HL +; Time: 6 cycles +.macro setb ; addr, data + ld a,\2 + ld (\1),a +.endm + +; Writes word to addr +; Preserved: F, BC, DE, HL +; Time: 12 cycles +.macro setw ; addr, data + ld a,<\2 + ld (\1),a + ld a,>\2 + ld (\1+1),a +.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 diff --git a/playing-coffee/roms/mem_timing-2/source/common/numbers.s b/playing-coffee/roms/mem_timing-2/source/common/numbers.s new file mode 100644 index 0000000..6d6faf8 --- /dev/null +++ b/playing-coffee/roms/mem_timing-2/source/common/numbers.s @@ -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 diff --git a/playing-coffee/roms/mem_timing-2/source/common/printing.s b/playing-coffee/roms/mem_timing-2/source/common/printing.s new file mode 100644 index 0000000..bb9389b --- /dev/null +++ b/playing-coffee/roms/mem_timing-2/source/common/printing.s @@ -0,0 +1,77 @@ +; 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 +;print_char_nocrc (defined by user) + + +; 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 diff --git a/playing-coffee/roms/mem_timing-2/source/common/shell.s b/playing-coffee/roms/mem_timing-2/source/common/shell.s new file mode 100644 index 0000000..a045121 --- /dev/null +++ b/playing-coffee/roms/mem_timing-2/source/common/shell.s @@ -0,0 +1,266 @@ +; Common routines and runtime + +.define RUNTIME_INCLUDED 1 + +; A few bytes of RAM that aren't cleared +.define nv_ram_base $D800 +.define nv_ram nv_ram_base + +; Address of next normal variable +.define bss_base nv_ram_base+$80 +.define bss bss_base + +; Address of next direct-page ($FFxx) variable +.define dp_base $FF80 +.define dp dp_base + +; Top of stack +.define std_stack $DFFF+1 + +; Final exit result byte is written here +.define final_result $A000 + +; Text output is written here as zero-terminated string +.define text_out_base $A004 + +; 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 nv_ram +.redefine nv_ram nv_ram+1 + +; Copies C*$100 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 a,(hl+) + ld (de),a + inc e + jr nz,- + inc d + dec c + jr nz,- + + ld a,b + jp $C000 + + +.ifndef RST_OFFSET + .define RST_OFFSET 0 +.endif + +.ifndef CUSTOM_RESET + reset: + di + + ; Run code from $C000, as is done on devcart. This + ; ensures minimal difference in how it behaves. + ld hl,$4000 + ld c,$14 + jp copy_to_wram_then_run + + .bank 1 slot 1 + .org 0 + jp std_reset +.endif + +; returnOrg puts this code AFTER user code. +.section "runtime" returnOrg + + ; Catch user code running off end + jp internal_error + +; 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 b,a + and $EA + jr z,+ + ld b,0 ++ ld a,b + ld (gb_id),a + + ; Clear memory except very top of stack + ld bc,std_stack-bss_base - 2 + ld hl,bss_base + call clear_mem + ld bc,$FFFF-dp_base + ld hl,dp_base + call clear_mem + + ; Init hardware + wreg TAC,$00 + wreg IF,$00 + wreg IE,$00 + + wreg NR52,0 ; sound off + wreg NR52,$80 ; sound on + wreg NR51,$FF ; mono + wreg NR50,$77 ; volume + + call init_runtime + call init_text_out + call console_init + call init_testing + + .ifdef TEST_NAME + print_str TEST_NAME,newline,newline + .endif + + 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 + + call console_show + pop af + call play_byte + jp post_exit + ++ push af + call print_newline + 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 + + +; Clears BC bytes starting at HL +clear_mem: + ; If C>0, increment B + dec bc + inc c + inc b + + ld a,0 +- ld (hl+),a + dec c + jr nz,- + dec b + jr nz,- + ret + + +; Reports internal error and exits with code 255 +internal_error: + print_str "Internal error" + ld a,255 + jp exit + + +; build_devcart and build_multi customize this +.ifndef CUSTOM_PRINT + .define text_out_addr bss+0 + .redefine bss bss+2 + + ; Initializes text output to cartridge RAM + init_text_out: + ; Enable cartridge RAM and set text output pointer + setb RAMEN,$0A + setw text_out_addr,text_out_base + setb text_out_base-3,$DE + setb text_out_base-2,$B0 + setb text_out_base-1,$61 + setb text_out_base,0 + setb final_result,$80 + ret + + + ; Appends character to text output string + ; Preserved: AF, BC, DE, HL + write_text_out: + push hl + push af + ld a,(text_out_addr) + ld l,a + ld a,(text_out_addr+1) + ld h,a + inc hl + ld (hl),0 + ld a,l + ld (text_out_addr),a + ld a,h + ld (text_out_addr+1),a + dec hl + pop af + ld (hl),a + pop hl + ret + + print_char_nocrc: + call write_text_out + jp console_print +.endif + + +; only build_rom uses console +.ifdef NEED_CONSOLE + .include "console.s" +.else + console_init: + console_print: + console_flush: + console_normal: + console_inverse: + console_show: + console_set_mode: + ret +.endif + + +; build_devcart and build_multi need to customize this +.ifndef CUSTOM_EXIT + post_exit: + ld (final_result),a + forever: + wreg NR52,0 ; sound off +- jr - +.endif + + +.macro def_rst ARGS addr + .bank 0 slot 0 + .org addr+RST_OFFSET +.endm diff --git a/playing-coffee/roms/mem_timing-2/source/common/testing.s b/playing-coffee/roms/mem_timing-2/source/common/testing.s new file mode 100644 index 0000000..5dd63ce --- /dev/null +++ b/playing-coffee/roms/mem_timing-2/source/common/testing.s @@ -0,0 +1,195 @@ +; Diagnostic and testing utilities + +.define test_code bss+0 +.define test_name bss+1 +.redefine bss bss+3 + + +; Sets test code and optional error text. +; Takes multiple strings due to wla's idiotic +; default limit of 63 chars per string. +; Preserved: AF, BC, DE, HL +.macro set_test ; code[,text[,text2[,text3]]] + push hl + call set_test_ + jr @set_test\@ + .byte \1 + .if NARGS > 1 + .byte \2 + .endif + .if NARGS > 2 + .byte \3 + .endif + .if NARGS > 3 + .byte \4 + .endif + .byte 0 +@set_test\@: + pop hl +.endm + +set_test_: + pop hl + push hl + push af + inc hl + inc hl + ldi a,(hl) + ld (test_code),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,(test_code) + 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,(test_code) + cp $FF ; if a = $FF then a = 1 + jr nz,+ + ld a,1 ++ 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 print_newline + call print_crc + .else + ld bc,(crc >> 16) ~ $FFFF + ld de,(crc & $FFFF) ~ $FFFF + call check_crc_ + .endif +.endm + +; Checks CRC, differing based on DMG or CGB build +.macro check_crc_dmg_cgb ARGS dmg, cgb + .ifdef REQUIRE_DMG + check_crc dmg + .else + .ifdef REQUIRE_CGB + check_crc cgb + .else + .printt "CGB or DMG must be specified" + .fail + .endif + .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 diff --git a/playing-coffee/roms/mem_timing-2/source/common/tima_64.s b/playing-coffee/roms/mem_timing-2/source/common/tima_64.s new file mode 100644 index 0000000..5b6c3a1 --- /dev/null +++ b/playing-coffee/roms/mem_timing-2/source/common/tima_64.s @@ -0,0 +1,36 @@ +; Timer that's incremented every 64 cycles + +; Initializes timer for use by sync_tima_64 +init_tima_64: + wreg TMA,0 + wreg TAC,$07 + ret + +; Synchronizes to timer +; Preserved: AF, BC, DE, HL +sync_tima_64: + push af + push hl + + ; Coarse + ld hl,TIMA + ld a,0 + ld (hl),a +- or (hl) + jr z,- + + ; Fine +- delay 65-12 + xor a + ld (hl),a + or (hl) + delay 4 + jr z,- + + pop hl + pop af + ret + +; Read from this to get count that's incremented +; every 64 cycles. +.define tima_64 TIMA diff --git a/playing-coffee/roms/mem_timing-2/source/linkfile b/playing-coffee/roms/mem_timing-2/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/playing-coffee/roms/mem_timing-2/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/playing-coffee/roms/mem_timing-2/source/readme.txt b/playing-coffee/roms/mem_timing-2/source/readme.txt new file mode 100644 index 0000000..a0269c1 --- /dev/null +++ b/playing-coffee/roms/mem_timing-2/source/readme.txt @@ -0,0 +1,82 @@ +Game Boy Tests Source Code +-------------------------- + +Building with wla-dx +-------------------- +To assemble a test ROM with wla-dx, use the following commands: + + wla -o source_filename_here.s test.o + wlalink linkfile test.gb + +To assemble as a GBS music file: + + wla -o source_filename_here.s test.o -DBUILD_GBS + wlalink linkfile test.gbs + +Note that some tests might only work when built as a ROM or GBS file, +but not both. + +Some tests might include a ROM/GBS that has all the tests combined. +Building such a multi-test is complex and the necessary files aren't +included. + + +Framework +--------- +Each test is in a single source file, and makes use of several library +source files from common/. This framework provides common services and +reduces code to only that which performs the actual test. Virtually all +tests include "shell.inc" at the beginning, which sets things up and +includes all the appropriate library files. + +The reset handler does minimal GB hardware initialization, clears RAM, +sets up the text console, then runs main. Main can exit by returning or +jumping to "exit" with an error code in A. Exit reports the code then +goes into an infinite loop. If the code is 0, it doesn't do anything, +otherwise it reports the code. Code 1 is reported as "Failed", and the +rest as "Error ". + +The default is to build a ROM. Defining BUILD_GBS will build as an GBS. +The other build types aren't supported due to their complexity. I load +the code into RAM at $C000 since my devcart requires it, and I don't +want the normal ROM to differ in any way from what I've tested. This +also allows easy self-modifying code. + +Several routines are available to print values and text to the console. +Most update a running CRC-32 checksum which can be checked with +check_crc, allowing ALL the output to be checked very easily. If the +checksum doesn't match, it is printed, so you can run the code on a GB +and paste the correct checksum into your code. + + +Macros +------ +Some macros are used to make common operations more convenient. The left +is equivalent to the right: + + Macro Equivalent + ------------------------------------- + lda addr ldh a,(addr-$FF00) + + sta addr ldh (addr-$FF00),a + + wreg addr,data ld a,data + ldh (addr-$FF00),a + + setb ld a,data + ld (addr),a + + setw setb addr+0,data + + for_loop routine,begin,end,step + calls routine with A set to successive values + + loop_n_times routine,count + calls routine with A from 0 to count-1 + + print_str "str" prints string + + +-- +Shay Green diff --git a/playing-coffee/roms/mem_timing-2/source/shell.inc b/playing-coffee/roms/mem_timing-2/source/shell.inc new file mode 100644 index 0000000..0d43faa --- /dev/null +++ b/playing-coffee/roms/mem_timing-2/source/shell.inc @@ -0,0 +1,27 @@ +; Included at beginning of all programs +; that use standard shell + +; Get include files from common/ +.incdir "common" + +; Sub-test in a multi-test ROM +.ifdef BUILD_MULTI + .include "build_multi.s" +.else + +; GBS music file +.ifdef BUILD_GBS + .include "build_gbs.s" +.endif + +; Devcart +.ifdef BUILD_DEVCART + .include "build_devcart.s" +.endif + +; GB ROM (default) +.ifndef RUNTIME_INCLUDED + .include "build_rom.s" +.endif + +.endif ; .ifdef BUILD_MULTI diff --git a/playing-coffee/roms/mem_timing/individual/01-read_timing.gb b/playing-coffee/roms/mem_timing/individual/01-read_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..d0836aabd5807ad6781955db20358fd20a384b66 GIT binary patch literal 32768 zcmeH}Z)_9i9mgNr@g*2z65z!{NZ>-+C>SE#Rbv)&Pf-`ai>YLbIRXRC`u6eFLRP)m_eZa|#byq!L{vvf83%nnk0S>Q!n*a3gc$lm>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la z5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la z5CI}U1c<=@76DtGTM8#0>`Jg3fj`eP=gPdNeJVm`{VDbDzR?t0Ozq>=NZs*rqSKs` zmh4UFLdupbiSBf`TX$Y%KM|Q ztjs=pZbf}u{cNDQ`{jK<-8WWQwCc`OE$HTIA(yvl4i9Q}E#Nv{BT{(I6ag10XzF?& z@%xJS$o!kfL*kES{EGUIx+7LhvDFg2mgu*{2d3kRJ=ViDxUCmGOEgW(@v^+?P4bHO z3Sail@lU)Qzv)f!MX#8}v`LG9j5lIG1?WKer zaJ?drdPNLujGegp@$*(JBA&NO2_%C`l>Ke5CmzghykZbq2Zs&F+_lKhkY%-|F-=b<66^2kNHP3s=>S)!Fye zkUeS&*Rro#aa6l8`=0ux^TiX*PixH8Sh*eA3^kzvZ5nEpAJ-Y9DJ)xY#HBfH!)u^%32Y9W4}_at z;ih&TZ>YCn`{MT8&z)jm=U1WZdxOgANaHotvmEie!Wwh$bQTU4@NVoY^sx&uCeEV& zWvq!_+g96wU9a`H+WlIuD=G#4!#?&~rEOxL`rxs!df&kj_>t0yLvUI<53&EOu)3@T zYNI$b2m*hjO{Z(1sOUwdqkHQ+yLZEr6;|<9i@)01>*Zni`JVP!`LG=>`a@mi;kt7P zpMmK+(?~{s2nKtrUf&Kk-e^}h)EnwxL@XA(`sJZ-XC`n6B4PZ(OR>7v>y|oD zgQoU%yUMn2h1Cbz4pdjcC!jSMGnOuvpRPMM<>WI11AF##?Qna$cb`Ly`<2B<-#RWE z<1C#{`=7sbBaMqOuIVWy$C;7l3BB|x>p9SqJY#&9MWa!FUaSY}dSYxWi9R{Tn2~Cx zaQqnOc{psfL_)_64&!>$brw{ja5&6TMrPbFQjE*>JP%tw$g_NuM-_}mds68x=X4!;`Mu6GUC$sS!&59XSW2ePQ%sQ!>#$}FYZ<(tC(e(K;JO+#JZVMH2C-NqJAbx>#`orj|Hbrd|v*g;EhLb zkMD|phyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko v0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvzQ|4QIrKpA>a literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/mem_timing/individual/02-write_timing.gb b/playing-coffee/roms/mem_timing/individual/02-write_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..cfe7c0fa5716f723c3482f915ab9543679e269cc GIT binary patch literal 32768 zcmeI4Uu+b|8Nk20^Q|xDeAqyxDcG=QC+=ae*bUyAY&2tlfLy>|yIGpI;x=861pMLyX(bYLh6Id z6E{{?-hb`Fg`+>2NZq`=^1*9wTsZ$A{{pdD)Fa`T`OHgSnETSJhkC(E^$c(6Q$OF< zt=`HzV5MQyf#{b$b4rj15CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+Sp zM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+Sp zM1Tko0V43fMZi%Pm*d)nYedsUGa_< z&t>jION4;GxV#m)A`Shu*?s=n9LhY(!aiB}J-LqCw>Eo0-Lf|KuG+mee_l}umK*2YA4EOlmKOj&!Pm?2l!O={&ef2__Za9wzz&# zj9EU9iCq>C&yA2LMI@ZC7WCwcQ;SXKE0=tk@tan&74g)REbNh|$8$#xcR>x2+||@U z|FLJXdSD-BB9lI6JD6Jq9Wr>b_ zt4sZx0{NON&Mflom$4MAD-sWS;{lK3-I5rwy!CkCmLoeqkLBOWC~F5>FRQ-oiXWEN z!Mo8@y5-=({@6*wd970g9o}%OQ@yX*QZrrRrIOz~JMxW* zDcnJ6C6-EG)z$F+win}yUW4PGgMQwX63&R7^Htkjv%5Z&$9B!v}zILstq6%pB8<$}gbrKwtKl`E5uh zli|8lAKLY$U^0t+77v)I2fB$QgiKL}Aa83$V! z&(w7W=xe#PivCPprY`4Y3T-YJZ)a$>6GmnAN)+;;o`;@nu5xc509uxzZK%4!(SL{+ z>jz2NkoE8|F6w3x^**hD*rg4Qa|M|oE{xA0ixF@ikv_IbgqM@`XG2xT^*1E|?Az<5 zKUsfUFBqXebSQPn19f@ua33Xs6zVbzZ}(U~Ac5tAJgXsq_BiH)n;f2>6l0pS3&SIq zNyd_x7OpcseZ75j@f|?C?Z_{cwtr;m`1r{qPn{fpZhYcsm;BiJ)d-)fMtz=;f8{;- z!D%|rmBe>TB7I4I=UMCC^$C5^4-p^&M1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y z2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y M2oM1x@LwSCFNP9pv;Y7A literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/mem_timing/individual/03-modify_timing.gb b/playing-coffee/roms/mem_timing/individual/03-modify_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..28f1ae66df4cf2df43b3b164e9e10fae46848bcc GIT binary patch literal 32768 zcmeH}|8G;*6~K?}_$3X-B!H$naNwD=Q3zysWo6dpK1Ce@sZ$9*ZBwNfC(@GCcp%k z025#WOn?b60Vco%m;e)C0!)AjFaajO1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k z025#WOyK{EfUPbphZA>qC*W$}Z;RkuT=cXLWyq{Qs($J#O}W+7uD53Dj+d2P<}_)} ze{*mIXwA}0X|{B(H0OAQ8ARbj@^l_r!%#Wk51O!89rN^ z`{7hgeMtSNues-!`+u>2ynM~7JJZ#mo2`ai(WW^fsM*zk>v)aM{F9~(xR`>bZj7D& zSeEaZ|L{ae{?Qz`sQ#<&$eJm)S+d8Hy_S5}bUeA&x@Uv0^@3-~rfE4|MpV2>QTEP? z6>mzs?`6deZ(3aQ3K?8BX^Ho|!eieTpLm7CPl#LcNz+^UwmfLnSGP1elfhzpF<}Q> zuOK2`0Vg&^Pt0F`%8GW%r>tTElfg=q18uJ)?#>QpP@Qzj6|c81e7(fpIumQb5s4{NBeJzE%fd1*yzc7U&8L#y5gS7 zUF?4VEfE4rT;2#=B|~p*=76_0i~St-xdT-A{eBa-Z*At1x^->#U3K%?xp}o?ZSEa4 zWDlFtwd{Fo0IS`Ud0V~h{PUa553As6P-@4%1$z^FpEiTNS$asDC_NZA7UgeEnX}~M zi*nfXw2<6wYW}&AbW-Fo+ph&ZdGgFc-JV|bDC0kv^+v!|qg2?DXGYQ|Pjo^Vk=)bN zUgUDv-$D$jXzitT$E#=%4>u<+^ndPYg1n!fxF7)f>*c!h2tY9OEfPx#gxZ#(B)&#s z7l}R+-ym^_ght{862BqwDv8%gyh-9M5*J8ZCQ&4DmBccMzmr%cQ6upwi3SS!=0a;r z0m|_!3v$Hnoo*acZ`*HJ(z4H9?o~rmmlrF?_8r-`Z{I*;No|ui%^dCvRs9_(pT&CV z^#GKoHeLszocYh}WD7{S6(xYY2CARi1Jwrhrd?4$)@-?AAIJWqvNMd@yxEq)L? zT)>1;qL)Hty~izfU=5lY-{C6T9}cT`wVha98Lx@fVqCHOV(GEEbAuL-_4V!D+r886 z*|BE|W85iUyMG@FY9Jt;PWxA6x{<~QKxleO$qF#iBB2*wfY_l}a?JQHL?RJ?T{MPv zu_%luaZHW_7^!B;z)wQt&|#}35<0%*HZ%ZThoBNcheMDuM$Z^V3Iy4U9P0ET2f2ud zC^#R9rP4W!mD95d=_y^$()t`NOC)f;K@W|Z5=l~AdN#%cP?UtATroU8_)eCsI9M;n z|M{q{fj|d2!+@+|(1Y{3uA|>f+AxfC2IK2lUB|rqQ3p-eM=>PeS2QyqMd^zaDAdpn zZN||SOQn{BVy&@UEEd5ylgptY<%=!l$HN7_ekOg$*D)l;Pmv#lNR8rP^Wzz*lmPU# z+^C^HBSopxcS@nf1>^1V&33^MRj&jgWK|CrD*8i$BXp?k=Cgm z&5w(^kwZO}$Rc*K!Ff$VCWs5;3rJ!F+()F3Z5-jHss5y|rf~f&0)S&{J^7>hTY6Ua z{h>qBi3jR*@NgfAKooTfqqRn?ACSOumd>gVpf!*A;3oU$Cn`jO?ELV^Wn72|riJVD zPhM{wU3>?Su{>Q}iGzdBjhz@VCZGP*$a5p3r#k6U_eue6wiNWV4*GAxr5mSiU(d@w z=jBU_^!G$G?*D##XDpZi6JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C0!)AjFaajO z1egF5U;<2l2`~XBzyz286JP>NfC(@GCcp%k025#WOn?b60Vco%m;e)C0!)Aje5DEe E8=ZrgSO5S3 literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/mem_timing/mem_timing.gb b/playing-coffee/roms/mem_timing/mem_timing.gb new file mode 100644 index 0000000000000000000000000000000000000000..78766b579f74433332c546c9cf06f8b972a4c2d8 GIT binary patch literal 65536 zcmeI4Z*Ual9mk*Bz1ti~I1*s&0s%Gz(-1_?f0*jIGD=|_OG0NHu}&pNm7~ZJP$*nN za{QZ8+nHLO&RBg>tkV{!Z)EHX5Sd~2Zn?;MJdCt@J*T33VkllQa|d082zUKHdm;Rh zI@*pew7;GEJRQJ(XrGQBt$h+RnUV1Y&rJ=o!=5(|GHcI4M}LoxlZtm}AG?V%pC;*V zR&t7#)5|rfPo%8Rj@nY0UbYQ8VJB_Va#C*%G@9l4v)X6fKkKDtf4VI_Aw6+wu&y&U z_|byy!B;wZ2DdHVIH+B#eb8K<_V{&=UAHE8BvG{b@=~TZFK1M*ZmE)P<~JOTpjFVDpofI-^0*~x{bCn#Z=BwoXAUcDQD6_p!2hVNkh#}c+&7ay9zc2R)#g}n_*@9T782( zz42Ey{Pz-nQs&(?yi;;?na`GVcinKYQuytPI~KI$@a^8BxwFO*J$_j#mONQ4AO<#n z>5AyGN2*y4NG_zbCUCjWokHQ#y40tIoC&&aU!(i&Z|R0Tq0oF>(uOR(-mXK|SoCe2 zzRezwY!9+iC_XU|Y<<5r{aDM{gtg0VzQb;|ueGP^MHJp{kN-@styG8NN3}Y*t=G?> zxp2BZDCxgL`JajM9&uxg69Yu4v5x*daq82u2`d!{x{ii$+M%#Q^9wCw9DR%^e^?aq zYA0h@eglooWw-ltF_aR9f??>=PVM|~*_7LMxpJY<%os-aL9S@+(G=VSe^uqmo$BZT z(Q;Ol#$B7YhFIo!-NC}bsyEc?k@M{7LfP46mobGwo9P~VVI3>lWh=!Gdf|9HfYrO; zaPHZ0UUtEq)mb{Gc|r-x40^2_mShwbV6pU_={rMtrp3~?9uIyzckvW9v1BHZY%+jQMr6QmINBu?dAKt?!<`cWA?Z}b!AenSbC$i6J}9~h4>V-KM6NZz9FNZ$xqJkoBiq!8`-mpcf)ns zx#>Rbj9Ae>R_cI$g}w>Ku5Lcbw0)IHmX}j{DQJX=o|o)9_&A zw^iv++jG&*-TDXi0`mdCn^kYmEn2(qB;9;`Gdd%lr7e>OoW@C+)>4%=s#wxms`+%k zQ`|l4maYw0V4@aZ>PLqWqLtLZ(Bw?NA7jw9#)%na{Ea^CoIVMo%V8N;3Zq2k3Hz?1 z^_D2Sy19AAjQUBL$rGn-gpV`1BO_lIsyLX+qS2`P4H7Ly@nKBW!y%k6;p$IOiY8(w#j$km1llX9ov2d74N-Y|k z%R-T^^^r)3siM9bM_OEnvv{pqt02F&Ary_nS3KOSh;%3%?iJ!QB;T|8Hi<%Wb7%%)k+7;HBiU#7528iW6E zZ#ay8xpA23;cypRGF(Nq2n&hst0AU{1NBg64eDa3;)t{U!BVEbzn?|c$K$9d`impv z`a=OXy*@g}O~a+|9}?{VqBw*f9In4eD5Ns9l_?FPy+}xOU0fGksBnS5DQ>kXY_1r+ zBC(j8jcl{zAi6h+*>;-mF2l=`JCcp*% zDrUISOa)bG5$6;8AAD!0s!JldC{I2e-Rjoz0P%eCGM`h{aoH2f{`!rd#Y*pvF z6{}WsEw2{ec8=}Qx7hg3^k002o3fw(y$a7E+{n#pY2?mvo1uUR5CI}U1c(3;AOb{y z2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y z2oM1xKm>>Y5g-CYfCvzQZzut?OEIx@2;x{3^`qW9ix zZ?U)9+bVkRZU3F#I}5#c_M+Z9>*~E9G`J@_toL?f=)Do=_-lr%_kPhB)_V^cd|ZN0 zPVlA#f7O5@aYjPuy+s|Ix_a-j0j1*S=)GqoKBM=3&=7j>n-Z7ly|b6=y&p~+*bPx$ zOA$KN-Hb{5VcTI(!k&TcfbE361e*_Qhusfb0*k=9U~$+fST$@StQB?}tl(qvkgx1b z&u4PF9dC39{vZ|!+%@s;r*BF;lJG8{Tb}r ze*=3~?A=p*tC=YIT9i_`MN%u9_kWIT-bahgJNE9wJ9i&$Uhy*ue%`?E543szg3mH4 zMg)ie5g-CYfCvx)B0vO)z~>X7{Xgyh|JT3&*B%)D`+v=WtN#7J{fhnn=mTHP-~VsH zeqO#=1ZjoI-gH0&*N9+>2%1H3y$I%rKo`Mw5j-h^XGE|=1Up6Wk_dK-V800ZMQ~UI z846v4+LC?UA=SA-Sf7ld~06TAh$6K2o;CQaVB|KLFT%=uS;BcNyaHGtVH zxAEf30T>Y z5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y z5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y z5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y z5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y O5g-CYfCzlO2>cuVGucxB literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/mem_timing/readme.txt b/playing-coffee/roms/mem_timing/readme.txt new file mode 100644 index 0000000..98d76e2 --- /dev/null +++ b/playing-coffee/roms/mem_timing/readme.txt @@ -0,0 +1,122 @@ +Game Boy CPU Memory Access Timing Test +-------------------------------------- +This ROM tests the timing of memory reads and writes made by +instructions, except stack and program counter accesses. These tests +require correct instruction timing and proper timer operation (TAC, +TIMA, TMA). + +The read and write tests list failing instructions as + + [CB] opcode:tested-correct + +The read-modify-write test lists failing instructions as + + [CB] opcode:tested read/tested write-correct read/correct write + +The values after the opcode refer to which instruction cycle the access +occurs on, with 1 being the first. If a time couldn't be determined due +to some other problem, it prints 0. + +For instructions which either read or write, but not both, the CPU makes +the access on the last cycle. For instructions which read, modify, then +write back, the CPU reads on the next-to-last cycle, and writes on the +last cycle. + + +Internal operation +------------------ +The tests have the timer increment TIMA every 64 cycles, synchronize +with this, delay a variable amount, then have the instruction under test +access the timer. By varying the delay in one-cycle increments, the +memory access made by the instruction can be made to fall before and +after a TIMA increment. By then examining the registers and value in +TIMA, it can be determined which occurred. + + +Multi-ROM +--------- +In the main directory is a single ROM which runs all the tests. It +prints a test's number, runs the test, then "ok" if it passes, otherwise +a failure code. Once all tests have completed it either reports that all +tests passed, or prints the number of failed tests. Finally, it makes +several beeps. If a test fails, it can be run on its own by finding the +corresponding ROM in individual/. + +Ths compact format on screen is to avoid having the results scroll off +the top, so the test can be started and allowed to run without having to +constantly monitor the display. + +Currently there is no well-defined way for an emulator test rig to +programatically find the result of the test; contact me if you're trying +to do completely automated testing of your emulator. One simple approach +is to take a screenshot after all tests have run, or even just a +checksum of one, and compare this with a previous run. + + +Failure codes +------------- +Failed tests may print a failure code, and also short description of the +problem. For more information about a failure code, look in the +corresponding source file in source/; the point in the code where +"set_test n" occurs is where that failure code will be generated. +Failure code 1 is a general failure of the test; any further information +will be printed. + +Note that once a sub-test fails, no further tests for that file are run. + + +Console output +-------------- +Information is printed on screen in a way that needs only minimum LCD +support, and won't hang if LCD output isn't supported at all. +Specifically, while polling LY to wait for vblank, it will time out if +it takes too long, so LY always reading back as the same value won't +hang the test. It's also OK if scrolling isn't supported; in this case, +text will appear starting at the top of the screen. + +Everything printed on screen is also sent to the game link port by +writing the character to SB, then writing $81 to SC. This is useful for +tests which print lots of information that scrolls off screen. + + +Source code +----------- +Source code is included for all tests, in source/. It can be used to +build the individual test ROMs. Code for the multi test isn't included +due to the complexity of putting everything together. + +Code is written for the wla-dx assembler. To assemble a particular test, +execute + + wla -o "source_filename.s" test.o + wlalink linkfile test.gb + +Test code uses a common shell framework contained in common/. + + +Internal framework operation +---------------------------- +Tests use a common framework for setting things up, reporting results, +and ending. All files first include "shell.inc", which sets up the ROM +header and shell code, and includes other commonly-used modules. + +One oddity is that test code is first copied to internal RAM at $D000, +then executed there. This allows self-modification, and ensures the code +is executed the same way it is on my devcart, which doesn't have a +rewritable ROM as most do. + +Some macros are used to simplify common tasks: + + Macro Behavior + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + wreg addr,data Writes data to addr using LDH + lda addr Loads byte from addr into A using LDH + sta addr Stores A at addr using LDH + delay n Delays n cycles, where NOP = 1 cycle + delay_msec n Delays n milliseconds + set_test n,"Cause" Sets failure code and optional string + +Routines and macros are documented where they are defined. + +-- +Shay Green diff --git a/playing-coffee/roms/mem_timing/source/01-read_timing.s b/playing-coffee/roms/mem_timing/source/01-read_timing.s new file mode 100644 index 0000000..e119615 --- /dev/null +++ b/playing-coffee/roms/mem_timing/source/01-read_timing.s @@ -0,0 +1,148 @@ +; Tests timing of accesses made by +; memory read instructions + +.include "shell.inc" +.include "tima_64.s" + +instructions: + ; last value is time of read + .byte $B6,$00,$00,2 ; OR (HL) + .byte $BE,$00,$00,2 ; CP (HL) + .byte $86,$00,$00,2 ; ADD (HL) + .byte $8E,$00,$00,2 ; ADC (HL) + .byte $96,$00,$00,2 ; SUB (HL) + .byte $9E,$00,$00,2 ; SBC (HL) + .byte $A6,$00,$00,2 ; AND (HL) + .byte $AE,$00,$00,2 ; XOR (HL) + .byte $46,$00,$00,2 ; LD B,(HL) + .byte $4E,$00,$00,2 ; LD C,(HL) + .byte $56,$00,$00,2 ; LD D,(HL) + .byte $5E,$00,$00,2 ; LD E,(HL) + .byte $66,$00,$00,2 ; LD H,(HL) + .byte $6E,$00,$00,2 ; LD L,(HL) + .byte $7E,$00,$00,2 ; LD A,(HL) + .byte $F2,$00,$00,2 ; LDH A,(C) + .byte $0A,$00,$00,2 ; LD A,(BC) + .byte $1A,$00,$00,2 ; LD A,(DE) + .byte $2A,$00,$00,2 ; LD A,(HL+) + .byte $3A,$00,$00,2 ; LD A,(HL-) + .byte $F0,tima_64,4 ; LD A,($0000) + + .byte $CB,$46,$00,3 ; BIT 0,(HL) + .byte $CB,$4E,$00,3 ; BIT 1,(HL) + .byte $CB,$56,$00,3 ; BIT 2,(HL) + .byte $CB,$5E,$00,3 ; BIT 3,(HL) + .byte $CB,$66,$00,3 ; BIT 4,(HL) + .byte $CB,$6E,$00,3 ; BIT 5,(HL) + .byte $CB,$76,$00,3 ; BIT 6,(HL) + .byte $CB,$7E,$00,3 ; BIT 7,(HL) +instructions_end: + +main: + call init_tima_64 + set_test 0 + + ; Test instructions + ld hl,instructions +- call @time_instr + cp (hl) + call nz,@print_failed + inc hl + ld a,l + cp 3-byte instruction +; HL <- HL + 3 +@time_instr: + ; Copy instr + ld a,(hl+) + ld (instr+0),a + ld a,(hl+) + ld (instr+1),a + ld a,(hl+) + ld (instr+2),a + push hl + + ; Find result when access doesn't occur + ld b,0 + call @time_access + ld c,a + + ; Test for accesses on each cycle + ld b,0 +- push bc + call @time_access + pop bc + cp c + jr nz,@found + inc b + ld a,b + cp 10 + jr nz,- + ld b,0 + +@found: + ld a,b + pop hl + ret + +; Tests for read +; B -> which cycle to test +; A <- timer value after test +@time_access: + call sync_tima_64 + ld a,9 + sub b + call delay_a_20_cycles + xor a ; clear flags + ld hl,tima_64 + ld (hl),$7F + ld bc,tima_64 + ld de,tima_64 + ld a,$7F +instr: + nop + nop + nop + + ; Add all registers together to yield + ; unique value that differs based on + ; read occurring before or after tima_64 + ; increments. + push af + add hl,bc + add hl,de + pop de + add hl,de + ld a,h + add l + ret diff --git a/playing-coffee/roms/mem_timing/source/02-write_timing.s b/playing-coffee/roms/mem_timing/source/02-write_timing.s new file mode 100644 index 0000000..acd6d9d --- /dev/null +++ b/playing-coffee/roms/mem_timing/source/02-write_timing.s @@ -0,0 +1,115 @@ +; Tests timing of accesses made by +; memory write instructions + +.include "shell.inc" +.include "tima_64.s" + +instructions: + ; last value is time of write + .byte $36,$FF,$00,3 ; LD (HL),n + .byte $70,$00,$00,2 ; LD (HL),B + .byte $71,$00,$00,2 ; LD (HL),C + .byte $72,$00,$00,2 ; LD (HL),D + .byte $73,$00,$00,2 ; LD (HL),E + .byte $74,$00,$00,2 ; LD (HL),H + .byte $75,$00,$00,2 ; LD (HL),L + .byte $77,$00,$00,2 ; LD (HL),A + .byte $02,$00,$00,2 ; LD (BC),A + .byte $12,$00,$00,2 ; LD (DE),A + .byte $22,$00,$00,2 ; LD (HL+),A + .byte $32,$00,$00,2 ; LD (HL-),A + .byte $E2,$00,$00,2 ; LDH (C),A + .byte $E0,tima_64,4 ; LD (nn),A +instructions_end: + +main: + call init_tima_64 + set_test 0 + + ; Test instructions + ld hl,instructions +- call @test_instr + cp (hl) + call nz,@print_failed + inc hl + ld a,l + cp 3-byte instruction +; HL <- HL + 3 +@test_instr: + ; Copy instr + ld a,(hl+) + ld (instr+0),a + ld a,(hl+) + ld (instr+1),a + ld a,(hl+) + ld (instr+2),a + push hl + + ; Test for writes on each cycle + ld b,0 +- push bc + call @time_write + pop bc + cp tima_64 + jr z,@no_write + jr @found +@no_write: + inc b + ld a,b + cp 10 + jr nz,- + ld b,0 + +@found: + ld a,b + pop hl + ret + +; Tests for write +; B -> which cycle to test +; A <- timer value after test +@time_write: + call sync_tima_64 + ld a,13 + sub b + call delay_a_20_cycles + ld hl,tima_64 + ld bc,tima_64 + ld de,tima_64 + ld a, 3-byte instruction +; HL <- HL + 3 +@time_instr: + ; Copy instr + ld a,(hl+) + ld (instr+0),a + ld a,(hl+) + ld (instr+1),a + ld a,(hl+) + ld (instr+2),a + push hl + + ; Find result when access doesn't occur + ld b,0 + call @time_access + + ; Find first access + call @find_next_access + ld d,b + + ; Find second access + call @find_next_access + ld e,b + + pop hl + ret + +; A -> current timer result +; B -> starting clock +; B <- clock next access occurs on +; A <- new timer result +@find_next_access: + ld c,a +- call @time_access + cp c + ret nz + inc b + ld a,b + cp 10 + jr c,- + + ; Couldn't find time, so return 0/0 + ld a,c + ld b,0 + ld d,b + ret + +; Tests for access +; B -> which cycle to test +; A <- timer value after test +@time_access: + call sync_tima_64 + ld hl,tima_64 + ld (hl),$7F + ld a,17 + sub b + call delay_a_20_cycles + xor a ; clear flags +instr: + nop + nop + nop + delay 32 + ld a,(tima_64) + ret diff --git a/playing-coffee/roms/mem_timing/source/common/build_gbs.s b/playing-coffee/roms/mem_timing/source/common/build_gbs.s new file mode 100644 index 0000000..7ac8d3c --- /dev/null +++ b/playing-coffee/roms/mem_timing/source/common/build_gbs.s @@ -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 diff --git a/playing-coffee/roms/mem_timing/source/common/build_rom.s b/playing-coffee/roms/mem_timing/source/common/build_rom.s new file mode 100644 index 0000000..e1e220f --- /dev/null +++ b/playing-coffee/roms/mem_timing/source/common/build_rom.s @@ -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 diff --git a/playing-coffee/roms/mem_timing/source/common/console.bin b/playing-coffee/roms/mem_timing/source/common/console.bin new file mode 100644 index 0000000000000000000000000000000000000000..b02f2d37ee94c6570a3622734f2d704dfca44b0c GIT binary patch literal 768 zcmX|9F=`|+5G))_B$f+182koBX5hr&r*zbT5qlFY8w?8X8GehSdBVQJIPe4Zu1Z?m z2wEj|cXgKp3^N%Fwbp)6@0AO%)yuRSqFVOjFXoqd`4hj9QtEN1nR)XG+d{u=K$L$a zcxBdDa9GaHO?x1~9ZyqY!B>?@1~S_Jk8EuOnO>t1U_)GPUYcV}PSJ688I zDZyK&1&Z3Yf=R|a=4{O6sIrFR>wY_q1954MiRzbDIv)c%zSNhFNh$uKIuubR9$M#% zQVgthyMM7>6t%8xc-*ZoU#;c~Eo^b8>ieF1Te%=ANe|UbtGOeuN<>ma4(yn>d z&PyDPc+NXzR}RnDiJDL@@*5U1@Exf>ZKHTq^DiA1#(zox`Vp^snt$Nkdp#BumLA0V yczj1Ga3U7XIy~nG6}avCt6hL`&VKmF{r{ZIvhq4Tb=k}k+hUx5zmKoWJ@6mAT&*eq literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/mem_timing/source/common/console.s b/playing-coffee/roms/mem_timing/source/common/console.s new file mode 100644 index 0000000..5307f6b --- /dev/null +++ b/playing-coffee/roms/mem_timing/source/common/console.s @@ -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 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 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 diff --git a/playing-coffee/roms/mem_timing/source/common/delay.s b/playing-coffee/roms/mem_timing/source/common/delay.s new file mode 100644 index 0000000..65eb9c0 --- /dev/null +++ b/playing-coffee/roms/mem_timing/source/common/delay.s @@ -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 diff --git a/playing-coffee/roms/mem_timing/source/common/gb.inc b/playing-coffee/roms/mem_timing/source/common/gb.inc new file mode 100644 index 0000000..31bbf14 --- /dev/null +++ b/playing-coffee/roms/mem_timing/source/common/gb.inc @@ -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 diff --git a/playing-coffee/roms/mem_timing/source/common/macros.inc b/playing-coffee/roms/mem_timing/source/common/macros.inc new file mode 100644 index 0000000..c413bd5 --- /dev/null +++ b/playing-coffee/roms/mem_timing/source/common/macros.inc @@ -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 diff --git a/playing-coffee/roms/mem_timing/source/common/numbers.s b/playing-coffee/roms/mem_timing/source/common/numbers.s new file mode 100644 index 0000000..6d6faf8 --- /dev/null +++ b/playing-coffee/roms/mem_timing/source/common/numbers.s @@ -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 diff --git a/playing-coffee/roms/mem_timing/source/common/printing.s b/playing-coffee/roms/mem_timing/source/common/printing.s new file mode 100644 index 0000000..ad9d811 --- /dev/null +++ b/playing-coffee/roms/mem_timing/source/common/printing.s @@ -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 diff --git a/playing-coffee/roms/mem_timing/source/common/runtime.s b/playing-coffee/roms/mem_timing/source/common/runtime.s new file mode 100644 index 0000000..90cd24f --- /dev/null +++ b/playing-coffee/roms/mem_timing/source/common/runtime.s @@ -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 diff --git a/playing-coffee/roms/mem_timing/source/common/testing.s b/playing-coffee/roms/mem_timing/source/common/testing.s new file mode 100644 index 0000000..c102f78 --- /dev/null +++ b/playing-coffee/roms/mem_timing/source/common/testing.s @@ -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 diff --git a/playing-coffee/roms/mem_timing/source/common/tima_64.s b/playing-coffee/roms/mem_timing/source/common/tima_64.s new file mode 100644 index 0000000..1db7211 --- /dev/null +++ b/playing-coffee/roms/mem_timing/source/common/tima_64.s @@ -0,0 +1,33 @@ +; Timer that's incremented every 64 cycles + +; Initializes timer for use by sync_tima_64 +init_tima_64: + wreg TMA,0 + wreg TAC,$07 + ret + +; Synchronizes to timer +sync_tima_64: + push af + push hl + + ld a,0 + ld hl,TIMA + ld (hl),a +- or (hl) + jr z,- + +- delay 65-12 + xor a + ld (hl),a + or (hl) + delay 4 + jr z,- + + pop hl + pop af + ret + +; Read from this to get count that's incremented +; every 64 cycles. +.define tima_64 TIMA diff --git a/playing-coffee/roms/mem_timing/source/linkfile b/playing-coffee/roms/mem_timing/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/playing-coffee/roms/mem_timing/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/playing-coffee/roms/mem_timing/source/shell.inc b/playing-coffee/roms/mem_timing/source/shell.inc new file mode 100644 index 0000000..277a9b7 --- /dev/null +++ b/playing-coffee/roms/mem_timing/source/shell.inc @@ -0,0 +1,21 @@ +.incdir "common" + +; GBS music file +.ifdef BUILD_GBS + .include "build_gbs.s" +.endif + +; Devcart +.ifdef BUILD_DEVCART + .include "build_devcart.s" +.endif + +; Sub-test in a multi-test ROM +.ifdef BUILD_MULTI + .include "build_multi.s" +.endif + +; GB ROM (default) +.ifndef RUNTIME_INCLUDED + .include "build_rom.s" +.endif diff --git a/playing-coffee/roms/oam_bug/oam_bug.gb b/playing-coffee/roms/oam_bug/oam_bug.gb new file mode 100644 index 0000000000000000000000000000000000000000..42ff9ca21f9d5fc87488b621867192d77716eb26 GIT binary patch literal 65536 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 diff --git a/playing-coffee/roms/oam_bug/rom_singles/1-lcd_sync.gb b/playing-coffee/roms/oam_bug/rom_singles/1-lcd_sync.gb new file mode 100644 index 0000000000000000000000000000000000000000..8f3d856c9217a13eb9b6e22117a4bef976924f0e GIT binary patch literal 32768 zcmeI4-)~#h9l*bi6E`@H+d!qARiV93A(#*=hH0IpUCshjMAODvq-jih;hHqJ3%YZ% zBx@Y%7$@mAn6_z1dmu&H@;Xf#@W4vf3B|s)$yIt#Bhlz0kt(a zvGLyGrAw!W6TxejH{M_Tr}`1{P?-}u}!5{Up2AOb{y2oM1xKm>>Y z5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5P{o}z)%+~_ubj8e0s1`xt=s( z`I8U~1`nDiL$_fwR5B4D0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO) z01+SpM1Tm~HU!KE%j6hl3wJZyE_(@qKwg9o_&=q|!^(eLp&<#q+tk!7CnX}-vgGx@`)@JejZ&sGSDG&^SWe+;CA83Z zJo3HB4Gbr_OFE_bMOuHhL!df7(A@UcxzBv?wpMVm zqCJQDB&#?xtn56;Hk`BUJtxJkI8!K*4WZ_0 zoBQkjSW9tVF={@NcM2@v6j0F-JazG1Js0!~Jy(okF<7;4p!KEb?%ataiZfQZ;v7BN zSIyY7)j2y=owsYv-EV2&o<5tkuvU3YPR1uEM@~VRgL&TE$=GVR?cwkTY`=VVd`upI z=94*lY-ip+oxTvh13eJ}{-Sxkp|o%hx;opL0cU#-=RD4Z0onSkiRaVW&RkON+@AZJ z(z!i-J|D!XmUP1oYviK&r^MBGqDCML!vkX-|FaDNqmw^kQyzEnMD^M6!5OZ>HJ%My=Q zy-OUbtxG&pg(a)v>?|PWbN1<@$6yxiQ)vD`ZMtlU`|@V6Q1Bf7)SG-g>~lXP4({R2 z{0`Wg2lD6hc;tSa55as8#2T)@3N~=%2dj%VCOB{N8`W2A@vQ>;Q3zi4wNCddyZ5vy zH!P(1W8VSfILe>L3IBh#DI45VI>7;Rpv?)Z>s-O76@2Z7?)p8_D@efNw=5WDPb(IEd)~l?AXErlfihR4mcUPRyqTN^jxpGIXu7v$!py1U08u@x+ zy4(yTi~^R6zZj)Z*?Gk-wqple3B7Fl4nEYT?D9qItt{WSp*)(Q#rg}SM{CwqX*|~3 zd;k4ii}u}zdd^~w8|AfI9}6Z2Ffa_m{eBpA11|u^HPx45pc^cz6@LNU$GT%<`ZpmE z2)K5^ZuIL8!gvhV*f@Z$HdGxSF_y-F%@K`ic$40f05lC+d;ttN2&$eqqw6X#>Ce(A z%Li#l2Ux&|`arj8q%l`oOZlX%YFbL#r=?jmiuSs^msO$Sw$ z&s6Zqfqv*Sj=tEc9BC+S)z^!~BIsw*X>^qJ;!xduXyD3cjAO2hDP?|D#zBa3$UHdQ ze7dSKz*y_`EsUqDvUK@emeAya`3}2ohhb25?`wgiD<|PV%qZU42Y~vr7Tpt7AIJDE zUhE$vX-)RS-MlF4X_UL8Da0;)P|tnH1aV=01~JTl`-t>$j3Ycl_8)Ua745G{0Jt{o zWjxt`Q%-4aJPash$pdA1@Ngd`fgs8SPyQpdwzn9MWvsc z9=VJ&7QnL5PW$)|&7+I=0QiBCpG{V`ZoLYwo_>e`5g-CYfCvx)B0vO)01+SpM1Tko z0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko U0U|&IhyW2F0z`lad|3(n7h?{-Q~&?~ literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/oam_bug/rom_singles/2-causes.gb b/playing-coffee/roms/oam_bug/rom_singles/2-causes.gb new file mode 100644 index 0000000000000000000000000000000000000000..b997cda45a53b2fbdb472e590f2e4163c8bb7e44 GIT binary patch literal 32768 zcmeI4|4$p|9mhY$;1FY+kYvl1(c}y%b0}WiR!dRlJ|YPrsg@PCXq_}^+OdYqT6w@= z4W==!fi!FS%hsP(R%zqcs?wTDUD7mC1U}B|4W6oz5_FYHXLmKytQ6VJZcQk&_j%62 zE3E%Ow9lh^{M_ex-k&ee>z=zG4e)>7v>xAkpQ`G7Ez~%NU;;c)3)OYIUpx=q59j-q z78gI5ymIB#Q+?i>^NSx&{_)D?N7x2pakQOKfxka{^qmcBWD)@)Km>>Y5g-CYfCvx) zB0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOiOwfunUW-LkDg{$fY1{CPx! z>03T%_wLZTefQvIsAM8Q1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko z0U|&IhyW3|X9#G|&gR;8zMtzT{8nBu*jTFF;Ld3VsdkHfpN}NiEPp1EH<%~mgTipA zb#ByuXYLn%D~(e)b9LIDx{Xu+e4{jFjV^7G-GBBwd960e_iK6Us=qq6;K5s|CX?Y` z(*gk*(&@bIc?~@|Dvj#1#SFiyNnfux-XV7!Y2DL#Bp8$r$d5m97)?m?gNZsQZ0A2o zlw{8JvW_EPmVKivt1QbpJ2yltekocN$_^b~m-NtKJE;Q-&T=#t%8nghCp&iBmLZF# zvTPH|PIRx6o#?h@4?*FP+}Zxz3#aas)zxe24!gweu5K))q%Qt@W`la?<~4N*PxlW` z94pJApl^`ZyVqBY*1=Z)vEvW!U8}lfqesnGdj!fJ2mb4!eWda2)BKy$;^0`!!Otbw zHgRfp+NVaW9hm;BXV%aH`a)H3b$1ZnbG-j97?<9)*x!WaG5KG%Q50U+OEY=Hit^#a zs2$^f-y}lyX1jx4|3 z_1yBrkIpR156GW17aIp#f7v>azBYq73hvpN1V69iEtBt#ZhK7gdhH*UUbklW540J>ibxr2L`qxFNsHDQ=>sb&-LOWbYnC2C z*~pCa7fV0%UFkDR@A#f{lg}iqxkvdqEx)v-;BKo(Zb}BV^SY%=9!tlG8t=)=*Dq*Z zH@~1IgIEk!?b=uQa&UF5D}v^Tk>ar_H-1^J&P}{6JGF}m9+=fUvn{yan#h~- z=fX8n_Si%&x0>s#2ftXY{I?;dl7pxi(@aQ9$>?%J^Y5jJ9R##^K15| z9u7R!^XyRXklT%Wy_m3=Q=C5g9C2}S_<8({VbPe&;uXD_GPQE`}kk~*rrp0uaxQtf%;4XJ{~m*QB8c(?rb zXDr{O*;05x-kdMUem5UZTKS8CZ}$zSOM&=XNh}wCF^Zm9dDTo-;|`eYd&P9^c(_Vl zWs|tKG`>}s6=kSccrkS-Z`=^#{^sVrd+R36`|BIeV2<1AYsT8ALJ|!m2#3S=RJahv z3qWE@$Q6}94NE~Kc@7%(H?;Mu-vN)uW9z&P=-1$dfi@i51_0DhF{I*>B*idbsRe@y z-o)-&0Ez+?E)NE5hmhKLT2(_J34bYuwpfTk%p-YRIPYl)g=3g2rbJ!B4k=1h=wm__ z45D5Yml&DL(+To?ktHaJS}{FdcrQFld2qj&|Cv68frJ5KstQq66&KDcih^+? zVO3Sb5zMbd6$R_EM+KOo^kGWCRdiD!B+8dU;1Uh}&}RUBajT-mAh}#vNG6k@o{q)P zQPhhqWamSHZJ!SBw{1)*@`pqmgeaQGgU!yThC&ixtcAid##2M0bg?cXdoofmC2hIT_Rir9q@&NCM> zL0p($f;P;6&k^Zk8$ftrvHv#P459v}0DxmjFXD;)m+Yuw$HRca7Cg`v2M?d4AmBw? zq!E83B;3RUMgUQ9R&4;Kd8`K?vVDHMk`xqvc6#J8AW0r93w6q8e<&SYya&J!jQD6u z1*7~b*z@#51c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F w0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+Sp_a1@&0J=Xo=l}o! literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/oam_bug/rom_singles/3-non_causes.gb b/playing-coffee/roms/oam_bug/rom_singles/3-non_causes.gb new file mode 100644 index 0000000000000000000000000000000000000000..d545e832119ea12092a7729978fdc3e8452232f3 GIT binary patch literal 32768 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 b0z`la5CI}U1c(3;AOb{y2oQn)HGzKs4~-Dp literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/oam_bug/rom_singles/5-timing_bug.gb b/playing-coffee/roms/oam_bug/rom_singles/5-timing_bug.gb new file mode 100644 index 0000000000000000000000000000000000000000..47a5c021a6db95a4010407642191f4378e9ecbc3 GIT binary patch literal 32768 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 afCvx)B0vO)01+SpM1Tko0V43f5coF%-mLxr literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/oam_bug/rom_singles/7-timing_effect.gb b/playing-coffee/roms/oam_bug/rom_singles/7-timing_effect.gb new file mode 100644 index 0000000000000000000000000000000000000000..2212779ed9328f79b759653802280acdd3f4cc57 GIT binary patch literal 32768 zcmeI4Uu;`f8Nfg1kCQo$+pLv1R9SnStf`kJswtu~aCa1yXcZb=#tTS&+0EA6Qsy}! ztr~ZAJSQI2#$M=K!=w>S0)&J%h6G)y)cN|_CRgdH7HQ4yv;}N2XJEA<-ng{QBAf4A zw|y8q@mBF2-Q#n-yzR=x9C;Y4AfQw0G@#>17CfbUC)V zvhu;)#fwLui3P4*UioP5=NB)0m4AU)3gu>K!r!NV)BA-z5{Up2AOb{y2oM1xKm>>Y z5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5P^G;z`-ua^xxmDe7?O?xe+%Z z^H~sv0^7|egZE%Dlrj+@0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO) z01+SpM1Tm~GX%`l?E27`-d!KA{Z!eo_)Kxg63rP7#UY1(S5z`QCqB$nE$%M|p*E!s zEKY}REj|}=O1Ok8moo0sOoAG>zQi7Q#+wJo-?yu=_T<_)*Q*I2lHZEM(oLmTt-g5kugGk z@s5E;;!wsl#jN9)ro+E3lkI}qHp~2lx!fAw+!cl^o_80(I=|rXzsTK3m4CTHT^9FV zW2I_23Guhgw3Mv>JUWX3uNPCrnc{3QZ8`a?}Ua-#@*k>{dSRtM^O}{AggJv}9n6nlHD{5S|=PxpI5-xnoJZV*K4< z&Ei?3|GaXmYKh+&;(bGu4DpF9)N;giYQ=zR8Ha$5f&-_jv7diq* zF1#1a27DrzErc-{%-XxRp4#iI&tRX$p57}*=?ZW^<1rUdx)( z*?z3IBmS0h!}{ziP^^IMNwx)3wm4^cUVvb#juX7-ZP>S9uVe4yDeU#)Za!6fP#jti zmrTKO;y_j={>b2oW6vFn9erv{*TZUgMqt+~TFym#Np z1z|P-;w+9o3Dj}q+beT62KbrHZ&qHlMLow32jQHzWokg#e6UryX(7rVc()?VWBdi| z@ZWo@vcf&Z5e^swbw;eN;k;MNd)s$x`*lwb`Y~D+d|yOcj-M?Kp~koP$HjwYZ{A(% zDh^exH(3);t)?&)`HF=eC_BM9yTA5h<-Tf73Hii7a!&ORgI|kHl^Tx3KpxY@U-XhM zZ@g+3+OYzz1kc&t?fY7lO+JUUmE=p;Ce=`5`K984s&!Q=kN5RG^ibEF{lJc%(-`As zY3c44gUJRAqS2`P5sB8K_y91json$wJ<7sb;S6-|?~aV?-vqzk@9G7*(XKlH6A>IE z69BqeS9RQCEQt;qEgaTxlDsVeXc{zm{pfHAR6TZ5*HvKBo+VM18%ap|ncs`+{%$pz z#8^o!;gzzgX$h&HlxpEH>g)26Blr3vGOm1Xk71Be!c69h;qk$_v~0w|dNKYFV;TpB z4wAYK30;>Du4|fxe&bPH*Q0TauO&1M^KwTGxTeK0B;YQZ>7dH=nF?On&<<@T&=yOT zEeVCS+H#>#0R3b#iH0&?Y^oa%6sCJ>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la K5P|=Vz`p@31Fawc literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/oam_bug/rom_singles/8-instr_effect.gb b/playing-coffee/roms/oam_bug/rom_singles/8-instr_effect.gb new file mode 100644 index 0000000000000000000000000000000000000000..464a5f4e682a8a2becab8665ea09890f0b70df3c GIT binary patch literal 32768 zcmeI4Uu;`f8NiR@#LXPXjoV6`rfGYfw3(YFiVLDx!#m2_vTo2VL1;w+X}h7#&5Dc@ zlB#i6$90kpZF|}t1`8D$osSjeYBOnaj*}VUAlmVkFmRv2SxV%`*Hq>9WB*1wW}w zgu53e1FH)^3RroVf|>c0J#`zV{&ul8Wlb(`mfXJ$IQcPklAlw{)_kBbvFL$U+Egg; zr_`PviRANT+wvM%a?msy&sPh4lSX&eJ@&+*-oxKGB#n(qql2eT4UI*mljG9F*hqAk zhvb7vsKZ--cyt z{lJsodGx=T>i=T;)1yA0wqd%v;pk!M;qJhrM<3|z+4sds`%9HBhzY}kBJ{(nzVFY2 zy>`FGx2E|0DM8+14*qVMZ4ubF+t|h0gQt%@~$`z2KjzKGO3<&#|6g zbv7=97Pc?6?hdv*9b5VM$eES5`k!5y`R>^jX|MEAXSIE-`^Vj*`D<`CtcpA1R=Jsh zS0>#V-S(L0$JM`B)X5L2U6-WQvcX?e`9+mqR{0-PvFjGs)eReXE$ddAUr^JA6{7{~ z49#25(k1IGy@Zr?$P5YHI-g4NN{RZNc%Dd^MCXL?2HcRO4eqm;ZNZ7EItUTQ8)UzQq6 zGjB;wt(xYiHSI~Q3+io(y(xWa+}claMPfP`n^c0(BZDU+Lnpuo@Y|524se;X_q)kc&q#Z- zwB8`C+8U2V#RVUXNJC>|BV)j?3Ne_wNC>Mh&X{bjc-~|m79Df!*TuRyHdu7dF;c9b zW62_!GYZz)44^)54tE?Nlb|05^#{rmd4tbqwAXdrvFkHu?9G77{=QkTk1y$K#9VI4 z?APJgb?AOF?Irvf7+>^OVPxBivnB-ixyf!9FPMB$r{D9FS6%fJ-O~D=2I;l|B%g7$ z0Lzo?9CY%%DA@))3 zh_-lPQB42aFomog1 ze!+`gSi4|m8=(Rw`Cm0%+rHc&t+QFEEf04KqJkP|EIyw*ST?Q;<&nEU{+hAz!JwiMN+OYnJr$`$;30%EIqZs4qC{v=&i;h79cT-UC|@TYkH^;Ywt-!n zmyCvB42=?^gsWi%mMBet!&(alWq3*4kqD7xQs?r3!#)yLqNf!lOsKG@36RA?f+RfD zw;Wi1ZW z3-Ny#l^LPnAfYHEt|;Pxd0CdhZ!DrHN+bsH<+vNnjPs6cQHc(=c&~ z26kXG3bs(HXbF;CsVru*S)!ayB*0MQ3oUHN0|i??9XVjj5K_bs3qJr+G!X}y9Zv~| zDFI)Ll@;)(ghlFNU8GRs0`Yd)X1hqAsNPjaVzwM3Eul#E?miGwNy))GV%WvNzl|5_ zCxWyr>S1+uAd7?@1~S_Jk8EuOnO>t1U_)GPUYcV}PSJ688I zDZyK&1&Z3Yf=R|a=4{O6sIrFR>wY_q1954MiRzbDIv)c%zSNhFNh$uKIuubR9$M#% zQVgthyMM7>6t%8xc-*ZoU#;c~Eo^b8>ieF1Te%=ANe|UbtGOeuN<>ma4(yn>d z&PyDPc+NXzR}RnDiJDL@@*5U1@Exf>ZKHTq^DiA1#(zox`Vp^snt$Nkdp#BumLA0V yczj1Ga3U7XIy~nG6}avCt6hL`&VKmF{r{ZIvhq4Tb=k}k+hUx5zmKoWJ@6mAT&*eq literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/oam_bug/source/common/console.s b/playing-coffee/roms/oam_bug/source/common/console.s new file mode 100644 index 0000000..403fa5c --- /dev/null +++ b/playing-coffee/roms/oam_bug/source/common/console.s @@ -0,0 +1,285 @@ +; 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 + setb console_pos,console_width + setb console_mode,0 + setb console_scroll,-8 + 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 + wreg VBK,1 + ld a,0 + call @fill_nametable + + wreg VBK,0 + 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 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 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 diff --git a/playing-coffee/roms/oam_bug/source/common/delay.s b/playing-coffee/roms/oam_bug/source/common/delay.s new file mode 100644 index 0000000..cbcdcf3 --- /dev/null +++ b/playing-coffee/roms/oam_bug/source/common/delay.s @@ -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 diff --git a/playing-coffee/roms/oam_bug/source/common/gb.inc b/playing-coffee/roms/oam_bug/source/common/gb.inc new file mode 100644 index 0000000..2d0118d --- /dev/null +++ b/playing-coffee/roms/oam_bug/source/common/gb.inc @@ -0,0 +1,81 @@ +; Game Boy hardware addresses + +; $0000-$3FFF Fixed ROM bank +; $4000-$7FFF Switchable bank +; $8000-$9FFF VRAM +; $A000-$BFFF optional cartridge RAM +; $C000-$DFFF RAM +; $E000-$FDFF RAM mirror +; $FE00-$FE9F OAM +; $FEA0-$FEFF unused +; $FF00-$FF7F registers +; $FF80-$FFFE RAM +; $FFFF register + +; Memory +.define VRAM $8000 ; video memory +.define TILES $8000 ; tile images +.define BGMAP0 $9800 ; first 32x32 tilemap +.define BGMAP1 $9C00 ; second 32x32 tilemap +.define BRAM $A000 ; cart memory +.define WRAM $C000 ; internal memory +.define OAM $FE00 ; sprite memory +.define HRAM $FF80 ; fast memory for LDH + +; Registers + +.define RAMEN $0000 ; cartridge WRAM control +.define BANK $2000 ; bank select +.define P1 $FF00 ; controller + +; Game link I/O +.define SB $FF01 ; serial buffer +.define SC $FF02 ; serial control + +; 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 diff --git a/playing-coffee/roms/oam_bug/source/common/macros.inc b/playing-coffee/roms/oam_bug/source/common/macros.inc new file mode 100644 index 0000000..9d7cf44 --- /dev/null +++ b/playing-coffee/roms/oam_bug/source/common/macros.inc @@ -0,0 +1,91 @@ +; General macros + +; Reads A from addr, from $FF00 to $FFFF +; Preserved: F, BC, DE, HL +; Time: 3 cycles +.macro lda ; addr + ldh a,(\1 - $FF00) +.endm + +; Writes A to addr, from $FF00 to $FFFF +; Preserved: AF, BC, DE, HL +; Time: 3 cycles +.macro sta ; addr + ldh (\1 - $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 + +; Writes byte to addr +; Preserved: F, BC, DE, HL +; Time: 6 cycles +.macro setb ; addr, data + ld a,\2 + ld (\1),a +.endm + +; Writes word to addr +; Preserved: F, BC, DE, HL +; Time: 12 cycles +.macro setw ; addr, data + ld a,<\2 + ld (\1),a + ld a,>\2 + ld (\1+1),a +.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 diff --git a/playing-coffee/roms/oam_bug/source/common/numbers.s b/playing-coffee/roms/oam_bug/source/common/numbers.s new file mode 100644 index 0000000..6d6faf8 --- /dev/null +++ b/playing-coffee/roms/oam_bug/source/common/numbers.s @@ -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 diff --git a/playing-coffee/roms/oam_bug/source/common/ppu.s b/playing-coffee/roms/oam_bug/source/common/ppu.s new file mode 100644 index 0000000..e1d2a6a --- /dev/null +++ b/playing-coffee/roms/oam_bug/source/common/ppu.s @@ -0,0 +1,14 @@ +; Waits for LCD blanking period +; Preserved: BC, DE, HL +wait_vbl: + ; Return if off + lda LCDC + rla + ret nc + + ; Wait for start of vblank +- lda LY + cp 144 + jr nz,- + + ret diff --git a/playing-coffee/roms/oam_bug/source/common/printing.s b/playing-coffee/roms/oam_bug/source/common/printing.s new file mode 100644 index 0000000..bb9389b --- /dev/null +++ b/playing-coffee/roms/oam_bug/source/common/printing.s @@ -0,0 +1,77 @@ +; 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 +;print_char_nocrc (defined by user) + + +; 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 diff --git a/playing-coffee/roms/oam_bug/source/common/shell.s b/playing-coffee/roms/oam_bug/source/common/shell.s new file mode 100644 index 0000000..3e588c7 --- /dev/null +++ b/playing-coffee/roms/oam_bug/source/common/shell.s @@ -0,0 +1,261 @@ +; Common routines and runtime + +.define RUNTIME_INCLUDED 1 + +; A few bytes of RAM that aren't cleared +.define nv_ram_base $D800 +.define nv_ram nv_ram_base + +; Address of next normal variable +.define bss_base nv_ram_base+$80 +.define bss bss_base + +; Address of next direct-page ($FFxx) variable +.define dp_base $FF80 +.define dp dp_base + +; Top of stack +.define std_stack $DFFF+1 + +; Final exit result byte is written here +.define final_result $A000 + +; Text output is written here as zero-terminated string +.define text_out_base $A004 + +; 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 nv_ram +.redefine nv_ram nv_ram+1 + +; Copies C*$100 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 a,(hl+) + ld (de),a + inc e + jr nz,- + inc d + dec c + jr nz,- + + ld a,b + jp $C000 + + +.ifndef RST_OFFSET + .define RST_OFFSET 0 +.endif + +.ifndef CUSTOM_RESET + reset: + di + + ; Run code from $C000, as is done on devcart. This + ; ensures minimal difference in how it behaves. + ld hl,$4000 + ld c,$14 + jp copy_to_wram_then_run + + .bank 1 slot 1 + .org 0 + jp std_reset +.endif + +; returnOrg puts this code AFTER user code. +.section "runtime" returnOrg + + ; Catch user code running off end + jp internal_error + +; 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 + + ; Clear memory except very top of stack + ld bc,std_stack-bss_base - 2 + ld hl,bss_base + call clear_mem + ld bc,$FFFF-dp_base + ld hl,dp_base + call clear_mem + + ; Init hardware + wreg TAC,$00 + wreg IF,$00 + wreg IE,$00 + + wreg NR52,0 ; sound off + wreg NR52,$80 ; sound on + wreg NR51,$FF ; mono + wreg NR50,$77 ; volume + + call init_runtime + call init_text_out + call console_init + call init_testing + + .ifdef TEST_NAME + print_str TEST_NAME,newline,newline + .endif + + 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 + + call console_show + pop af + call play_byte + jp post_exit + ++ push af + call print_newline + 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 + + +; Clears BC bytes starting at HL +clear_mem: + ; If C>0, increment B + dec bc + inc c + inc b + + ld a,0 +- ld (hl+),a + dec c + jr nz,- + dec b + jr nz,- + ret + + +; Reports internal error and exits with code 255 +internal_error: + print_str "Internal error" + ld a,255 + jp exit + + +; build_devcart and build_multi customize this +.ifndef CUSTOM_PRINT + .define text_out_addr bss+0 + .redefine bss bss+2 + + ; Initializes text output to cartridge RAM + init_text_out: + ; Enable cartridge RAM and set text output pointer + setb RAMEN,$0A + setw text_out_addr,text_out_base + setb text_out_base-3,$DE + setb text_out_base-2,$B0 + setb text_out_base-1,$61 + setb text_out_base,0 + setb final_result,$80 + ret + + + ; Appends character to text output string + ; Preserved: AF, BC, DE, HL + write_text_out: + push hl + push af + ld a,(text_out_addr) + ld l,a + ld a,(text_out_addr+1) + ld h,a + inc hl + ld (hl),0 + ld a,l + ld (text_out_addr),a + ld a,h + ld (text_out_addr+1),a + dec hl + pop af + ld (hl),a + pop hl + ret + + print_char_nocrc: + call write_text_out + jp console_print +.endif + + +; only build_rom uses console +.ifdef NEED_CONSOLE + .include "console.s" +.else + console_init: + console_print: + console_flush: + console_normal: + console_inverse: + console_show: + console_set_mode: + ret +.endif + + +; build_devcart and build_multi need to customize this +.ifndef CUSTOM_EXIT + post_exit: + ld (final_result),a + forever: + wreg NR52,0 ; sound off +- jr - +.endif + + +.macro def_rst ARGS addr + .bank 0 slot 0 + .org addr+RST_OFFSET +.endm diff --git a/playing-coffee/roms/oam_bug/source/common/testing.s b/playing-coffee/roms/oam_bug/source/common/testing.s new file mode 100644 index 0000000..5dd63ce --- /dev/null +++ b/playing-coffee/roms/oam_bug/source/common/testing.s @@ -0,0 +1,195 @@ +; Diagnostic and testing utilities + +.define test_code bss+0 +.define test_name bss+1 +.redefine bss bss+3 + + +; Sets test code and optional error text. +; Takes multiple strings due to wla's idiotic +; default limit of 63 chars per string. +; Preserved: AF, BC, DE, HL +.macro set_test ; code[,text[,text2[,text3]]] + push hl + call set_test_ + jr @set_test\@ + .byte \1 + .if NARGS > 1 + .byte \2 + .endif + .if NARGS > 2 + .byte \3 + .endif + .if NARGS > 3 + .byte \4 + .endif + .byte 0 +@set_test\@: + pop hl +.endm + +set_test_: + pop hl + push hl + push af + inc hl + inc hl + ldi a,(hl) + ld (test_code),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,(test_code) + 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,(test_code) + cp $FF ; if a = $FF then a = 1 + jr nz,+ + ld a,1 ++ 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 print_newline + call print_crc + .else + ld bc,(crc >> 16) ~ $FFFF + ld de,(crc & $FFFF) ~ $FFFF + call check_crc_ + .endif +.endm + +; Checks CRC, differing based on DMG or CGB build +.macro check_crc_dmg_cgb ARGS dmg, cgb + .ifdef REQUIRE_DMG + check_crc dmg + .else + .ifdef REQUIRE_CGB + check_crc cgb + .else + .printt "CGB or DMG must be specified" + .fail + .endif + .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 diff --git a/playing-coffee/roms/oam_bug/source/linkfile b/playing-coffee/roms/oam_bug/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/playing-coffee/roms/oam_bug/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/playing-coffee/roms/oam_bug/source/readme.txt b/playing-coffee/roms/oam_bug/source/readme.txt new file mode 100644 index 0000000..a0269c1 --- /dev/null +++ b/playing-coffee/roms/oam_bug/source/readme.txt @@ -0,0 +1,82 @@ +Game Boy Tests Source Code +-------------------------- + +Building with wla-dx +-------------------- +To assemble a test ROM with wla-dx, use the following commands: + + wla -o source_filename_here.s test.o + wlalink linkfile test.gb + +To assemble as a GBS music file: + + wla -o source_filename_here.s test.o -DBUILD_GBS + wlalink linkfile test.gbs + +Note that some tests might only work when built as a ROM or GBS file, +but not both. + +Some tests might include a ROM/GBS that has all the tests combined. +Building such a multi-test is complex and the necessary files aren't +included. + + +Framework +--------- +Each test is in a single source file, and makes use of several library +source files from common/. This framework provides common services and +reduces code to only that which performs the actual test. Virtually all +tests include "shell.inc" at the beginning, which sets things up and +includes all the appropriate library files. + +The reset handler does minimal GB hardware initialization, clears RAM, +sets up the text console, then runs main. Main can exit by returning or +jumping to "exit" with an error code in A. Exit reports the code then +goes into an infinite loop. If the code is 0, it doesn't do anything, +otherwise it reports the code. Code 1 is reported as "Failed", and the +rest as "Error ". + +The default is to build a ROM. Defining BUILD_GBS will build as an GBS. +The other build types aren't supported due to their complexity. I load +the code into RAM at $C000 since my devcart requires it, and I don't +want the normal ROM to differ in any way from what I've tested. This +also allows easy self-modifying code. + +Several routines are available to print values and text to the console. +Most update a running CRC-32 checksum which can be checked with +check_crc, allowing ALL the output to be checked very easily. If the +checksum doesn't match, it is printed, so you can run the code on a GB +and paste the correct checksum into your code. + + +Macros +------ +Some macros are used to make common operations more convenient. The left +is equivalent to the right: + + Macro Equivalent + ------------------------------------- + lda addr ldh a,(addr-$FF00) + + sta addr ldh (addr-$FF00),a + + wreg addr,data ld a,data + ldh (addr-$FF00),a + + setb ld a,data + ld (addr),a + + setw setb addr+0,data + + for_loop routine,begin,end,step + calls routine with A set to successive values + + loop_n_times routine,count + calls routine with A from 0 to count-1 + + print_str "str" prints string + + +-- +Shay Green diff --git a/playing-coffee/roms/oam_bug/source/shell.inc b/playing-coffee/roms/oam_bug/source/shell.inc new file mode 100644 index 0000000..0d43faa --- /dev/null +++ b/playing-coffee/roms/oam_bug/source/shell.inc @@ -0,0 +1,27 @@ +; Included at beginning of all programs +; that use standard shell + +; Get include files from common/ +.incdir "common" + +; Sub-test in a multi-test ROM +.ifdef BUILD_MULTI + .include "build_multi.s" +.else + +; GBS music file +.ifdef BUILD_GBS + .include "build_gbs.s" +.endif + +; Devcart +.ifdef BUILD_DEVCART + .include "build_devcart.s" +.endif + +; GB ROM (default) +.ifndef RUNTIME_INCLUDED + .include "build_rom.s" +.endif + +.endif ; .ifdef BUILD_MULTI diff --git a/playing-coffee/roms/readme.txt b/playing-coffee/roms/readme.txt new file mode 100644 index 0000000..2c40e27 --- /dev/null +++ b/playing-coffee/roms/readme.txt @@ -0,0 +1,4 @@ +Blargg's Gameboy hardware test ROMs. Originally hosted at http://blargg.parodius.com/gb-tests/ +before parodious.com went down. +New official location: http://blargg.8bitalley.com/parodius/gb-tests/ + diff --git a/playing-coffee/roms/solitaire.gb b/playing-coffee/roms/solitaire.gb new file mode 100644 index 0000000000000000000000000000000000000000..8c5b8c6888cac8c3f832c7774495a293be49f21f GIT binary patch literal 65536 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 literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/supermarioland.gb b/playing-coffee/roms/supermarioland.gb new file mode 100644 index 0000000000000000000000000000000000000000..e5d712509040aa23d23e6d908b44da130c13f174 GIT binary patch literal 65536 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�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 literal 0 HcmV?d00001 diff --git a/playing-coffee/src/main/java/playingcoffee/application/Application.java b/playing-coffee/src/main/java/playingcoffee/application/Application.java new file mode 100644 index 0000000..37b7ab4 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/application/Application.java @@ -0,0 +1,111 @@ +package playingcoffee.application; + +import java.awt.Canvas; +import java.awt.Color; +import java.awt.Dimension; +import java.awt.Graphics; +import java.awt.image.BufferStrategy; + +import javax.swing.JFrame; + +public class Application extends Canvas implements Runnable { + + private static final long serialVersionUID = 1L; + + private boolean running; + private Thread thread; + + private GameBoy gameBoy; + + public Application() { + setPreferredSize(new Dimension(320, 288)); + + gameBoy = new GameBoy(); + gameBoy.start(); + } + + public void renderGameBoy() { + BufferStrategy bs = getBufferStrategy(); + if (bs == null) { + createBufferStrategy(2); + return; + } + + Graphics g = bs.getDrawGraphics(); + + g.setColor(Color.WHITE); + g.fillRect(0, 0, getWidth(), getHeight()); + + int lcdControl = (gameBoy.getMMU().read(0xFF40)); + + int tileDataOffset = ((lcdControl & 0x10) != 0) ? 0x8000 : 0x8800; + + for (int tile = 0; tile < 512; tile++) { + int tileIndex = tile; + for (int y = 0; y < 8; y++) { + for (int n = 0; n < 8; n++) { + int value = (((gameBoy.getMMU().read(0x8000 + y * 2 + tileIndex * 16 + 1) >> (7 - n)) & 1) << 1) | + (((gameBoy.getMMU().read(0x8000 + y * 2 + tileIndex * 16) >> (7 - n)) & 1)); + + g.setColor(new Color(value * 64, value * 64, value * 64)); + if (value != 0x0) + g.fillRect((n + (tile % 16) * 8), (y + (tile / 16) * 8), 1, 1); + } + } + } + + if ((lcdControl & 0x80) != 0) { + for (int tileY = 0; tileY < 32; tileY++) { + for (int tileX = 0; tileX < 32; tileX++) { + int tileIndex = gameBoy.getMMU().read(0x9800 + (tileX + tileY * 32)); + for (int y = 0; y < 8; y++) { + for (int n = 0; n < 8; n++) { + int value = (((gameBoy.getMMU().read(tileDataOffset + y * 2 + tileIndex * 16 + 1) >> (7 - n)) & 1) << 1) | + (((gameBoy.getMMU().read(tileDataOffset + y * 2 + tileIndex * 16) >> (7 - n)) & 1)); + + g.setColor(new Color(value * 64, value * 64, value * 64)); + if (value != 0x0) + g.fillRect((n + (tileX) * 8 - gameBoy.getPPU().getRegisters().scrollX) * 2, (y + (tileY) * 8 - gameBoy.getPPU().getRegisters().scrollY) * 2, 2, 2); + } + } + } + } + } + + g.dispose(); + + bs.show(); + } + + @Override + public void run() { + while (running) { + renderGameBoy(); + } + + gameBoy.stop(); + } + + public void start() { + if (running) return; + + running = true; + + thread = new Thread(this); + thread.start(); + } + + public static void main(String[] args) { + Application application = new Application(); + + JFrame frame = new JFrame("playing-coffee"); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + frame.setResizable(false); + frame.add(application); + frame.pack(); + frame.setLocationRelativeTo(null); + frame.setVisible(true); + + application.start(); + } +} diff --git a/playing-coffee/src/main/java/playingcoffee/application/GameBoy.java b/playing-coffee/src/main/java/playingcoffee/application/GameBoy.java new file mode 100644 index 0000000..25480f7 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/application/GameBoy.java @@ -0,0 +1,85 @@ +package playingcoffee.application; + +import playingcoffee.core.Cartridge; +import playingcoffee.core.InterruptManager; +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.CPU; +import playingcoffee.log.Log; +import playingcoffee.ppu.PPU; + +public class GameBoy implements Runnable { + + private Thread thread; + private boolean running = false; + + private InterruptManager interruptManager; + private PPU ppu; + private MMU mmu; + private CPU cpu; + + public GameBoy() { + interruptManager = new InterruptManager(); + mmu = new MMU(); + + ppu = new PPU(mmu, interruptManager); + cpu = new CPU(mmu, interruptManager); + + mmu.connectMemorySpace(interruptManager); + mmu.connectMemorySpace(new Cartridge("roms/kwirk.gb")); + } + + public void start() { + if (running) + return; + + running = true; + thread = new Thread(this); + thread.start(); + } + + public void stop() { + if (!running) + return; + + running = false; + try { + thread.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + public void run() { + Log.init(); + + while (running) { + for (int i = 0; i < 8192; i++) { + ppu.clock(); + cpu.clock(); + interruptManager.clock(); + } + + try { + Thread.sleep(1); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + Log.close(); + } + + public PPU getPPU() { + return ppu; + } + + public MMU getMMU() { + return mmu; + } + + public CPU getCPU() { + return cpu; + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/Cartridge.java b/playing-coffee/src/main/java/playingcoffee/core/Cartridge.java new file mode 100644 index 0000000..21c9dea --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/Cartridge.java @@ -0,0 +1,44 @@ +package playingcoffee.core; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; + +import playingcoffee.log.Log; + +public class Cartridge implements MemorySpace { + + private int[] rom; + + public Cartridge(String rom) { + byte[] bin = null; + + try { + bin = Files.readAllBytes(Paths.get(rom)); + } catch (IOException e) { + e.printStackTrace(); + } + + this.rom = new int[bin.length]; + + for (int i = 0; i < bin.length; i++) { + this.rom[i] = Byte.toUnsignedInt(bin[i]); + } + } + + @Override + public int read(int address) { + return rom[address]; + } + + @Override + public void write(int value, int address) { + Log.warn("Attempting to write to ROM at address: 0x%4x.", address); + } + + @Override + public boolean inMemorySpace(int address) { + return address >= 0x0000 && address <= 0x7FFF; + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/InterruptListener.java b/playing-coffee/src/main/java/playingcoffee/core/InterruptListener.java new file mode 100644 index 0000000..088c107 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/InterruptListener.java @@ -0,0 +1,7 @@ +package playingcoffee.core; + +public interface InterruptListener { + + public void interruptOccured(int type); + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/InterruptManager.java b/playing-coffee/src/main/java/playingcoffee/core/InterruptManager.java new file mode 100644 index 0000000..5bd5d61 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/InterruptManager.java @@ -0,0 +1,76 @@ +package playingcoffee.core; + +import java.util.ArrayList; +import java.util.List; + +public class InterruptManager implements MemorySpace { + + private boolean enabled; + + private int interruptEnable; + private int interruptFlag; + + private List listeners; + + public static final int VBLANK = 1 << 0; + public static final int LCD_STAT = 1 << 1; + public static final int TIMER = 1 << 2; + public static final int SERIAL = 1 << 3; + public static final int JOYPAD = 1 << 4; + + public InterruptManager() { + listeners = new ArrayList(); + } + + public void addListener(InterruptListener listener) { + listeners.add(listener); + } + + public void enable() { enabled = true; } + public void disable() { enabled = false; } + + public boolean isEnabled() { return enabled; } + + public void requestInterrupt(int type) { + interruptFlag |= type; + } + + public void clock() { + if (enabled) { + int toOccur = interruptFlag & interruptEnable; + + //System.out.println(toOccur); + + if (toOccur != 0) { + for (int i = 0; i < 8; i++) { + if ((toOccur & (1 << i)) != 0) { + for (InterruptListener listener : listeners) { + listener.interruptOccured(1 << i); + } + interruptFlag &= ~(1 << i); + } + } + } + } + } + + @Override + public int read(int address) { + if (address == 0xFFFF) return interruptEnable; + if (address == 0xFF0F) return interruptFlag; + + return 0; + } + + @Override + public void write(int value, int address) { + if (address == 0xFFFF) interruptEnable = value; + if (address == 0xFF0F) interruptFlag = value; + } + + @Override + public boolean inMemorySpace(int address) { + return address == 0xFFFF || address == 0xFF0F; + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/MMU.java b/playing-coffee/src/main/java/playingcoffee/core/MMU.java new file mode 100644 index 0000000..fecec2e --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/MMU.java @@ -0,0 +1,93 @@ +package playingcoffee.core; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.List; + +public class MMU { + + private int[] memory; + private int[] bootRom; + + private List memorySpaces; + + public MMU() { + memory = new int[0x10000]; + + memorySpaces = new ArrayList(); + + loadBootROM(); + } + + public void connectMemorySpace(MemorySpace memorySpace) { + memorySpaces.add(memorySpace); + } + + // TODO: Move the ROM into a separate file. + + public void loadROM(String rom) { + byte[] bin = null; + + try { + bin = Files.readAllBytes(Paths.get(rom)); + } catch (IOException e) { + e.printStackTrace(); + } + + for (int i = 0; i < bin.length; i++) { + memory[i] = Byte.toUnsignedInt(bin[i]); + } + } + + // TODO: Move the boot ROM to a separate file. + private void loadBootROM() { + bootRom = new int[0x100]; + + byte[] bin = null; + + try { + bin = Files.readAllBytes(Paths.get("dmg_boot.bin")); + } catch (IOException e) { + e.printStackTrace(); + } + + for (int i = 0; i < 0x100; i++) { + bootRom[i] = Byte.toUnsignedInt(bin[i]); + } + } + + public int read(int address) { + if (address == 0xFF00) return 0xF; // TODO: Remove + + if (address >= 0x00 && address <= 0xFF && read(0xFF50) == 0) + return bootRom[address]; + + for (MemorySpace memorySpace : memorySpaces) { + if (memorySpace.inMemorySpace(address)) { + return memorySpace.read(address); + } + } + + return memory[address]; + } + + public void write(int value, int address) { + if (address == 0xFF00) return; // TODO: Remove + + if (address >= 0x00 && address <= 0xFF && read(0xFF50) == 0) + bootRom[address] = value & 0xFF; + + for (MemorySpace memorySpace : memorySpaces) { + if (memorySpace.inMemorySpace(address)) { + memorySpace.write(value & 0xFF, address); + } + } + memory[address] = value & 0xFF; + } + + public int[] getMemory() { + return memory; + } +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/MemorySpace.java b/playing-coffee/src/main/java/playingcoffee/core/MemorySpace.java new file mode 100644 index 0000000..c2ce195 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/MemorySpace.java @@ -0,0 +1,9 @@ +package playingcoffee.core; + +public interface MemorySpace { + + public int read(int address); + public void write(int value, int address); + public boolean inMemorySpace(int address); + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/cpu/CPU.java b/playing-coffee/src/main/java/playingcoffee/core/cpu/CPU.java new file mode 100644 index 0000000..2c25bf1 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/cpu/CPU.java @@ -0,0 +1,343 @@ +package playingcoffee.core.cpu; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Map.Entry; + +import playingcoffee.core.InterruptListener; +import playingcoffee.core.InterruptManager; +import playingcoffee.core.MMU; +import playingcoffee.core.opcode.ALU16Opcode; +import playingcoffee.core.opcode.ALU16Opcode.ALU16Type; +import playingcoffee.core.opcode.ALUOpcode; +import playingcoffee.core.opcode.ALUOpcode.ALUType; +import playingcoffee.core.opcode.Argument; +import playingcoffee.core.opcode.CallOpcode; +import playingcoffee.core.opcode.ComplementOpcode; +import playingcoffee.core.opcode.FlipCarryOpcode; +import playingcoffee.core.opcode.InterruptOpcode; +import playingcoffee.core.opcode.JumpOpcode; +import playingcoffee.core.opcode.JumpRelativeOpcode; +import playingcoffee.core.opcode.LoadOpcode; +import playingcoffee.core.opcode.Opcode; +import playingcoffee.core.opcode.PopOpcode; +import playingcoffee.core.opcode.PushOpcode; +import playingcoffee.core.opcode.RestartOpcode; +import playingcoffee.core.opcode.ReturnOpcode; +import playingcoffee.core.opcode.RotateAOpcode; +import playingcoffee.core.opcode.SetCarryOpcode; +import playingcoffee.core.opcode.prefixed.BitOpcode; +import playingcoffee.core.opcode.prefixed.ResetBitOpcode; +import playingcoffee.core.opcode.prefixed.RotateOpcode; +import playingcoffee.core.opcode.prefixed.SetBitOpcode; +import playingcoffee.core.opcode.prefixed.ShiftOpcode; +import playingcoffee.core.opcode.prefixed.ShiftOpcode.ShiftType; +import playingcoffee.core.opcode.prefixed.SwapOpcode; +import playingcoffee.log.Log; + +public class CPU implements InterruptListener { + + private final MMU mmu; + private final InterruptManager interruptManager; + + private Registers registers; + + private int cycles; + + Opcode[] opcodes; + Opcode[] prefixedOpcodes; + + public CPU(final MMU mmu, final InterruptManager interruptManager) { + this.mmu = mmu; + this.interruptManager = interruptManager; + + registers = new Registers(); + + opcodes = new Opcode[0x100]; + prefixedOpcodes = new Opcode[0x100]; + + loadOpcodes(); + loadPrefixedOpcodes(); + + interruptManager.addListener(this); + } + + private void loadOpcodes() { + // NOP + opcodes[0x00] = new Opcode() { + @Override + public int run(Registers registers, MMU mmu) { + return 0; + } + + @Override + public String toString() { + return "NOP"; + } + }; + + // Load Opcodes + for (Entry val : indexedList(0x01, 0x10, Argument.BC, Argument.DE, Argument.HL, Argument.SP)) { + opcodes[val.getKey()] = new LoadOpcode(Argument.D16, val.getValue()); + } + + for (Entry val : indexedList(0x02, 0x10, Argument._BC, Argument._DE, Argument._HL_INC, Argument._HL_DEC)) { + opcodes[val.getKey()] = new LoadOpcode(Argument.A, val.getValue()); + } + for (Entry val : indexedList(0x06, 0x10, Argument.B, Argument.D, Argument.H, Argument._HL)) { + opcodes[val.getKey()] = new LoadOpcode(Argument.D8, val.getValue()); + } + for (Entry val : indexedList(0x0A, 0x10, Argument._BC, Argument._DE, Argument._HL_INC, Argument._HL_DEC)) { + opcodes[val.getKey()] = new LoadOpcode(val.getValue(), Argument.A); + } + for (Entry val : indexedList(0x0E, 0x10, Argument.C, Argument.E, Argument.L, Argument.A)) { + opcodes[val.getKey()] = new LoadOpcode(Argument.D8, val.getValue()); + } + + for (Entry val : indexedList(0x40, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + for (Entry row : indexedList(val.getKey(), 0x10, Argument.B, Argument.D, Argument.H, Argument._HL)) { + opcodes[row.getKey()] = new LoadOpcode(val.getValue(), row.getValue()); + } + } + for (Entry val : indexedList(0x48, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + for (Entry row : indexedList(val.getKey(), 0x10, Argument.C, Argument.E, Argument.L, Argument.A)) { + opcodes[row.getKey()] = new LoadOpcode(val.getValue(), row.getValue()); + } + } + + opcodes[0x08] = new LoadOpcode(Argument.SP, Argument._D16_SHORT); + + opcodes[0xE0] = new LoadOpcode(Argument.A, Argument._D8); + opcodes[0xF0] = new LoadOpcode(Argument._D8, Argument.A); + + opcodes[0xE2] = new LoadOpcode(Argument.A, Argument._C); + opcodes[0xF2] = new LoadOpcode(Argument._C, Argument.A); + + opcodes[0xF9] = new LoadOpcode(Argument.HL, Argument.SP); + + opcodes[0xEA] = new LoadOpcode(Argument.A, Argument._D16); + opcodes[0xFA] = new LoadOpcode(Argument._D16, Argument.A); + + // POP and PUSH + for (Entry val : indexedList(0xC1, 0x10, Argument.BC, Argument.DE, Argument.HL, Argument.AF)) { + opcodes[val.getKey()] = new PopOpcode(val.getValue()); + } + for (Entry val : indexedList(0xC5, 0x10, Argument.BC, Argument.DE, Argument.HL, Argument.AF)) { + opcodes[val.getKey()] = new PushOpcode(val.getValue()); + } + + // TODO: Override 0x76 with HALT + + // Jumps, Calls and Returns + opcodes[0x20] = new JumpRelativeOpcode(Flags.ZERO, Argument.I8, true); + opcodes[0x30] = new JumpRelativeOpcode(Flags.CARRY, Argument.I8, true); + + opcodes[0x18] = new JumpRelativeOpcode(0, Argument.I8, false); + opcodes[0x28] = new JumpRelativeOpcode(Flags.ZERO, Argument.I8, false); + opcodes[0x38] = new JumpRelativeOpcode(Flags.CARRY, Argument.I8, false); + + opcodes[0xC3] = new JumpOpcode(0, Argument.D16, false); + opcodes[0xC2] = new JumpOpcode(Flags.ZERO, Argument.D16, true); + opcodes[0xD2] = new JumpOpcode(Flags.CARRY, Argument.D16, true); + + opcodes[0xE9] = new JumpOpcode(0, Argument.HL, false); + + opcodes[0xCA] = new JumpOpcode(Flags.ZERO, Argument.D16, false); + opcodes[0xDA] = new JumpOpcode(Flags.CARRY, Argument.D16, false); + + opcodes[0xC4] = new CallOpcode(Flags.ZERO, Argument.D16, true); + opcodes[0xD4] = new CallOpcode(Flags.CARRY, Argument.D16, true); + + opcodes[0xCC] = new CallOpcode(Flags.ZERO, Argument.D16, false); + opcodes[0xDC] = new CallOpcode(Flags.CARRY, Argument.D16, false); + + opcodes[0xCD] = new CallOpcode(0, Argument.D16, false); + + opcodes[0xC0] = new ReturnOpcode(Flags.ZERO, true, false, interruptManager); + opcodes[0xD0] = new ReturnOpcode(Flags.CARRY, true, false, interruptManager); + + opcodes[0xC8] = new ReturnOpcode(Flags.ZERO, false, false, interruptManager); + opcodes[0xD8] = new ReturnOpcode(Flags.CARRY, false, false, interruptManager); + + opcodes[0xC9] = new ReturnOpcode(0, false, false, interruptManager); + opcodes[0xD9] = new ReturnOpcode(0, false, true, interruptManager); + + // Bit Operations + + opcodes[0x07] = new RotateAOpcode(true, RotateAOpcode.LEFT); + opcodes[0x17] = new RotateAOpcode(false, RotateAOpcode.LEFT); + opcodes[0x0F] = new RotateAOpcode(true, RotateAOpcode.RIGHT); + opcodes[0x1F] = new RotateAOpcode(false, RotateAOpcode.RIGHT); + + // 16-bit ALU Opcodes + for (Entry val : indexedList(0x03, 0x10, Argument.BC, Argument.DE,Argument.HL, Argument.SP)) { + opcodes[val.getKey()] = new ALU16Opcode(ALU16Type.INC, val.getValue()); + opcodes[val.getKey() + 8] = new ALU16Opcode(ALU16Type.DEC, val.getValue()); + } + for (Entry val : indexedList(0x09, 0x10, Argument.BC, Argument.DE,Argument.HL, Argument.SP)) { + opcodes[val.getKey()] = new ALU16Opcode(ALU16Type.ADD, val.getValue()); + } + + // ALU Opcodes + for (Entry val : indexedList(0x04, 8, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + opcodes[val.getKey()] = new ALUOpcode(ALUType.INC, val.getValue()); + opcodes[val.getKey() + 1] = new ALUOpcode(ALUType.DEC, val.getValue()); + } + + for (Entry val : indexedList(0x80, 8, ALUType.ADD, ALUType.ADC, ALUType.SUB, ALUType.SBC, ALUType.AND, ALUType.XOR, ALUType.OR, ALUType.CP)) { + for (Entry row : indexedList(val.getKey(), 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + opcodes[row.getKey()] = new ALUOpcode(val.getValue(), row.getValue()); + } + } + for (Entry val : indexedList(0xC6, 8, ALUType.ADD, ALUType.ADC, ALUType.SUB, ALUType.SBC, ALUType.AND, ALUType.XOR, ALUType.OR, ALUType.CP)) { + opcodes[val.getKey()] = new ALUOpcode(val.getValue(), Argument.D8); + } + + // Restarts + for (Entry val : indexedList(0xC7, 8, 0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38)) { + opcodes[val.getKey()] = new RestartOpcode(val.getValue()); + } + + // Interrupts + opcodes[0xF3] = new InterruptOpcode(false, interruptManager); + opcodes[0xFB] = new InterruptOpcode(true, interruptManager); + + // Misc + opcodes[0x2F] = new ComplementOpcode(); + opcodes[0x37] = new SetCarryOpcode(); + opcodes[0x3F] = new FlipCarryOpcode(); + } + + public void loadPrefixedOpcodes() { + for (Entry val : indexedList(0x00, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + prefixedOpcodes[val.getKey() + 0x00] = new RotateOpcode(true, val.getValue(), RotateOpcode.LEFT); + prefixedOpcodes[val.getKey() + 0x08] = new RotateOpcode(true, val.getValue(), RotateOpcode.RIGHT); + prefixedOpcodes[val.getKey() + 0x10] = new RotateOpcode(false, val.getValue(), RotateOpcode.LEFT); + prefixedOpcodes[val.getKey() + 0x18] = new RotateOpcode(false, val.getValue(), RotateOpcode.RIGHT); + } + + for (Entry val : indexedList(0x40, 8, 0, 1, 2, 3, 4, 5, 6, 7)) { + for (Entry row : indexedList(val.getKey(), 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + prefixedOpcodes[row.getKey()] = new BitOpcode(val.getValue(), row.getValue()); + } + } + + for (Entry val : indexedList(0x30, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + prefixedOpcodes[val.getKey()] = new SwapOpcode(val.getValue()); + } + + // Reset Bit + for (Entry val : indexedList(0x80, 8, 0, 1, 2, 3, 4, 5, 6, 7)) { + for (Entry row : indexedList(val.getKey(), 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + prefixedOpcodes[row.getKey()] = new ResetBitOpcode(val.getValue(), row.getValue()); + } + } + + // Set Bit + for (Entry val : indexedList(0xC0, 8, 0, 1, 2, 3, 4, 5, 6, 7)) { + for (Entry row : indexedList(val.getKey(), 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + prefixedOpcodes[row.getKey()] = new SetBitOpcode(val.getValue(), row.getValue()); + } + } + + for (Entry val : indexedList(0x20, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + prefixedOpcodes[val.getKey()] = new ShiftOpcode(ShiftType.SLA, val.getValue()); + } + for (Entry val : indexedList(0x28, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + prefixedOpcodes[val.getKey()] = new ShiftOpcode(ShiftType.SRA, val.getValue()); + } + for (Entry val : indexedList(0x38, 1, Argument.B, Argument.C, Argument.D, Argument.E, Argument.H, Argument.L, Argument._HL, Argument.A)) { + prefixedOpcodes[val.getKey()] = new ShiftOpcode(ShiftType.SRL, val.getValue()); + } + } + + @SafeVarargs + private static Iterable> indexedList(int start, int step, T... values) { + Map map = new LinkedHashMap<>(); + int i = start; + for (T e : values) { + map.put(i, e); + i += step; + } + return map.entrySet(); + } + + public void clock() { + if (cycles == 0) { + //Log.info("PC: 0x%4x", registers.getPC()); + + if (registers.getPC() == 0x2EFC) { + Log.info("Breakpoint."); + } + + int opcodeValue = mmu.read(registers.getPC()); + registers.incPC(); + + if (opcodeValue == 0xCB) { + int prefixedValue = mmu.read(registers.getPC()); + registers.incPC(); + + runPrefixedOpcode(prefixedValue); + + } else { + runOpcode(opcodeValue); + } + } + + cycles--; + } + + private void runOpcode(int opcodeValue) { + Opcode opcode = opcodes[opcodeValue]; + + if (opcode == null) { + Log.error("Unimplemented opcode 0x%2x at 0x%4x!", opcodeValue, registers.getPC()); + + Log.close(); + + throw new IllegalStateException(); + + //System.exit(0); + } + + //Log.info("Executing opcode: 0x%2x (%s)", opcodeValue, opcode.toString()); + cycles += opcode.run(registers, mmu) + 4; // Adding 4 because we fetch the instruction. + } + + private void runPrefixedOpcode(int opcodeValue) { + Opcode opcode = prefixedOpcodes[opcodeValue]; + + if (opcode == null) { + Log.error("Unimplemented prefixed opcode 0x%2x!", opcodeValue); + + Log.close(); + + throw new IllegalStateException(); + + //System.exit(0); + } + + //Log.info("Executing prefixed opcode: 0x%2x (%s)", opcodeValue, opcode.toString()); + cycles += opcode.run(registers, mmu) + 8; // Adding 8 because we fetch the 0xCB prefix and the instruction. + } + + @Override + public void interruptOccured(int types) { + /*if ((types & InterruptManager.VBLANK) != 0) { + interruptManager.disable(); + + registers.decSP(); + mmu.write(registers.getPC() >> 8, registers.getSP()); + registers.decSP(); + mmu.write(registers.getPC(), registers.getSP()); + + Log.info("Pushing 0x%4x to the stack.", registers.getPC()); + + registers.setPC(0x40); + + cycles += 12; + }*/ + } + + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/cpu/Flags.java b/playing-coffee/src/main/java/playingcoffee/core/cpu/Flags.java new file mode 100644 index 0000000..c4e121a --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/cpu/Flags.java @@ -0,0 +1,29 @@ +package playingcoffee.core.cpu; + +public class Flags { + + private int f; // Technically the "correct" notation + + public static final int ZERO = 1 << 7; + public static final int NEGATIVE = 1 << 6; + public static final int HALF_CARRY = 1 << 5; + public static final int CARRY = 1 << 4; + + public void set(int value) { + f = value & 0xFF; + } + + public int get() { + return f; + } + + public void set(int flag, boolean value) { + if (value) f |= flag; + else f &= ~flag; + } + + public boolean get(int flag) { + return (f & flag) != 0; + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/cpu/Registers.java b/playing-coffee/src/main/java/playingcoffee/core/cpu/Registers.java new file mode 100644 index 0000000..82c97f6 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/cpu/Registers.java @@ -0,0 +1,57 @@ +package playingcoffee.core.cpu; + +public class Registers { + + private int a, b, c, d, e, h, l; + + private int pc, sp; + + private Flags flags; + + public Registers() { + flags = new Flags(); + } + + public int getA() { return a; } + public int getB() { return b; } + public int getC() { return c; } + public int getD() { return d; } + public int getE() { return e; } + public int getH() { return h; } + public int getL() { return l; } + + public void setA(int value) { a = value & 0xFF; } + public void setB(int value) { b = value & 0xFF; } + public void setC(int value) { c = value & 0xFF; } + public void setD(int value) { d = value & 0xFF; } + public void setE(int value) { e = value & 0xFF; } + public void setH(int value) { h = value & 0xFF; } + public void setL(int value) { l = value & 0xFF; } + + public Flags getFlags() { return flags; } + + public int getAF() { return (a << 8) | flags.get(); } + public int getBC() { return (b << 8) | c; } + public int getDE() { return (d << 8) | e; } + public int getHL() { return (h << 8) | l; } + + public int getPC() { return pc; } + public int getSP() { return sp; } + + public void setAF(int value) { flags.set(value >> 8); setA(value); } + public void setBC(int value) { setB(value >> 8); setC(value); } + public void setDE(int value) { setD(value >> 8); setE(value); } + public void setHL(int value) { setH(value >> 8); setL(value); } + + public void setPC(int value) { pc = value & 0xFFFF; } + public void setSP(int value) { sp = value & 0xFFFF; } + + public void addPC(int value) { setPC(getPC() + value); }; + public void addSP(int value) { setSP(getSP() + value); }; + + public void incPC() { addPC(1); } + public void incSP() { addSP(1); } + + public void decPC() { addPC(-1); } + public void decSP() { addSP(-1); } +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/ALU16Opcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/ALU16Opcode.java new file mode 100644 index 0000000..b4fef41 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/ALU16Opcode.java @@ -0,0 +1,57 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; +import playingcoffee.log.Log; + +public class ALU16Opcode implements Opcode { + + private final ALU16Type type; + private final Argument register; + + public ALU16Opcode(ALU16Type type, Argument register) { + this.type = type; + this.register = register; + } + + @Override + public int run(Registers registers, MMU mmu) { + int result = 0; + + switch (type) { + case INC: + register.write(register.read(registers, mmu) + 1, registers, mmu); + break; + case DEC: + register.write(register.read(registers, mmu) - 1, registers, mmu); + break; + case ADD: + int value = register.read(registers, mmu); + result = registers.getHL() + value; + + registers.getFlags().set(Flags.NEGATIVE, false); + registers.getFlags().set(Flags.HALF_CARRY, ((value & 0xF) + (registers.getHL() & 0xF) > 0xF)); + registers.getFlags().set(Flags.CARRY, (result & 0xFFFF0000) != 0); + + register.write(result, registers, mmu); + + break; + default: + Log.error("wtf!? how did we get here?!?!?"); + break; + } + + return register.getCycles() + 4; + } + + public enum ALU16Type { + INC, DEC, ADD + } + + @Override + public String toString() { + return type.name() + " " + register.getName(); + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/ALUOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/ALUOpcode.java new file mode 100644 index 0000000..8cfa92a --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/ALUOpcode.java @@ -0,0 +1,134 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; +import playingcoffee.log.Log; + +public class ALUOpcode implements Opcode { + + private final ALUType type; + private final Argument register; + + public ALUOpcode(ALUType type, Argument register) { + this.type = type; + this.register = register; + } + + @Override + public int run(Registers registers, MMU mmu) { + int result = 0; + int registerValue = register.read(registers, mmu); + + switch (type) { + case ADD: + result = registers.getA() + registerValue; + + registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0); + registers.getFlags().set(Flags.NEGATIVE, false); + registers.getFlags().set(Flags.HALF_CARRY, (registers.getA() & 0xF) + (registerValue & 0xF) > 0xF); + registers.getFlags().set(Flags.CARRY, (result & 0xFF00) > 0); + + break; + case ADC: + int value = registerValue + (registers.getFlags().get(Flags.CARRY) ? 1 : 0); + + result = registers.getA() + value; + + registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0); + registers.getFlags().set(Flags.NEGATIVE, false); + registers.getFlags().set(Flags.HALF_CARRY, (registers.getA() & 0xF) + (value & 0xF) > 0xF); + registers.getFlags().set(Flags.CARRY, (result & 0xFF00) > 0); + + break; + case AND: + result = registers.getA() & registerValue; + + registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0); + registers.getFlags().set(Flags.NEGATIVE, false); + registers.getFlags().set(Flags.HALF_CARRY, true); + registers.getFlags().set(Flags.CARRY, false); + + break; + case CP: + result = registers.getA(); + + registers.getFlags().set(Flags.ZERO, registers.getA() == registerValue); + registers.getFlags().set(Flags.NEGATIVE, true); + registers.getFlags().set(Flags.HALF_CARRY, (registerValue & 0xF) > (registers.getA() & 0xF)); + registers.getFlags().set(Flags.CARRY, registerValue > registers.getA()); + + break; + case OR: + result = registers.getA() | registerValue; + + registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0); + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY | Flags.CARRY, false); + + break; + case SBC: + value = registerValue + (registers.getFlags().get(Flags.CARRY) ? 1 : 0); + + registers.getFlags().set(Flags.ZERO, value == registers.getA()); + registers.getFlags().set(Flags.NEGATIVE, true); + registers.getFlags().set(Flags.HALF_CARRY, (value & 0xF) > (registers.getA() & 0xF)); + registers.getFlags().set(Flags.CARRY, value > registers.getA()); + + result = registers.getA() - value; + + break; + case SUB: + registers.getFlags().set(Flags.ZERO, registerValue == registers.getA()); + registers.getFlags().set(Flags.NEGATIVE, true); + registers.getFlags().set(Flags.HALF_CARRY, registerValue > (registers.getA() & 0xF)); + registers.getFlags().set(Flags.CARRY, registerValue > registers.getA()); + + result = registers.getA() - registerValue; + + break; + case XOR: + result = registers.getA() ^ registerValue; + + registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0); + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY | Flags.CARRY, false); + + break; + case INC: + result = registerValue + 1; + + registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0); + registers.getFlags().set(Flags.NEGATIVE, false); + registers.getFlags().set(Flags.HALF_CARRY, (result & 0x10) != 0); + + register.write(result, registers, mmu); + break; + case DEC: + result = registerValue - 1; + + registers.getFlags().set(Flags.ZERO, (result & 0xFF) == 0); + registers.getFlags().set(Flags.NEGATIVE, true); + registers.getFlags().set(Flags.HALF_CARRY, (registerValue & 0x0F) == 0x0F); + + register.write(result, registers, mmu); + break; + default: + Log.error("wtf!? how did we get here?!?!?"); + break; + } + + if (type != ALUType.INC && type != ALUType.DEC) + registers.setA(result); + + return 0; + } + + public enum ALUType { + ADD, ADC, SUB, SBC, AND, XOR, OR, CP, INC, DEC + } + + @Override + public String toString() { + return type.name() + " A, " + register.getName(); + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/Argument.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/Argument.java new file mode 100644 index 0000000..6250bad --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/Argument.java @@ -0,0 +1,348 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; +import playingcoffee.log.Log; + +public enum Argument { + + // Note: The '_' prefix, specifies the value located at the memory address the register is pointing to. + // Example: _HL <=> memory[HL] + + A { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getA(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setA(value); + } + }, B { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getB(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setB(value); + } + }, C { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getC(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setC(value); + } + }, D { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getD(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setD(value); + } + }, E { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getE(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setE(value); + } + }, H { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getH(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setH(value); + } + }, L { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getL(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setL(value); + } + }, AF { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getAF(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setAF(value); + } + }, BC { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getBC(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setBC(value); + } + }, DE { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getDE(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setDE(value); + } + }, HL { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getHL(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setHL(value); + } + }, SP { + @Override + public int read(Registers registers, MMU mmu) { + return registers.getSP(); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + registers.setSP(value); + } + }, _BC("(BC)", 4) { + @Override + public int read(Registers registers, MMU mmu) { + return mmu.read(registers.getBC()); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + mmu.write(value, registers.getBC()); + } + }, _DE("(DE)", 4) { + @Override + public int read(Registers registers, MMU mmu) { + return mmu.read(registers.getDE()); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + mmu.write(value, registers.getDE()); + } + }, _HL("(HL)", 4) { + @Override + public int read(Registers registers, MMU mmu) { + return mmu.read(registers.getHL()); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + mmu.write(value, registers.getHL()); + } + }, _HL_INC("(HL+)", 4) { + @Override + public int read(Registers registers, MMU mmu) { + int value = mmu.read(registers.getHL()); + registers.setHL(registers.getHL() + 1); + return value; + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + int address = registers.getHL(); + registers.setHL(registers.getHL() + 1); + mmu.write(value, address); + } + }, _HL_DEC("(HL-)", 4) { + @Override + public int read(Registers registers, MMU mmu) { + int value = mmu.read(registers.getHL()); + registers.setHL(registers.getHL() - 1); + return value; + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + int address = registers.getHL(); + registers.setHL(registers.getHL() - 1); + mmu.write(value, address); + } + }, D8(4) { + @Override + public int read(Registers registers, MMU mmu) { + int value = mmu.read(registers.getPC()); + registers.incPC(); + + return value; + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + Log.warn("Why are you writing to this argument?"); + } + }, D16(8) { + @Override + public int read(Registers registers, MMU mmu) { + int value = mmu.read(registers.getPC()); + + registers.incPC(); + + value |= mmu.read(registers.getPC()) << 8; + + registers.incPC(); + + return value; + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + Log.warn("Why are you writing to this argument?"); + } + }, I8(4) { + + @Override + public int read(Registers registers, MMU mmu) { + byte relativeAddress = (byte) mmu.read(registers.getPC()); + + registers.incPC(); + + return relativeAddress; + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + Log.warn("Why are you writing to this argument?"); + } + + }, _C("(C)", 4) { // memory[0xFF00 + C] + @Override + public int read(Registers registers, MMU mmu) { + int value = mmu.read(0xFF00 + registers.getC()); + + return value; + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + mmu.write(value, 0xFF00 + registers.getC()); + } + }, _D8("(D8)", 8) { // memory[0xFF00 + memory[PC++]] + @Override + public int read(Registers registers, MMU mmu) { + int value = mmu.read(0xFF00 + mmu.read(registers.getPC())); + + registers.incPC(); + + return value; + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + mmu.write(value, 0xFF00 + mmu.read(registers.getPC())); + + registers.incPC(); + } + }, _D16("(D16)", 12) { + @Override + public int read(Registers registers, MMU mmu) { + int address = mmu.read(registers.getPC()); + + registers.incPC(); + + address |= mmu.read(registers.getPC()) << 8; + + registers.incPC(); + + return mmu.read(address); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + int address = mmu.read(registers.getPC()); + + registers.incPC(); + + address |= mmu.read(registers.getPC()) << 8; + + registers.incPC(); + + mmu.write(value, address); + } + }, _D16_SHORT("(D16 (16 bits))", 16) { + @Override + public int read(Registers registers, MMU mmu) { + int address = mmu.read(registers.getPC()); + + registers.incPC(); + + address |= mmu.read(registers.getPC()) << 8; + + registers.incPC(); + + return (mmu.read(address) << 8) | mmu.read(address); + } + + @Override + public void write(int value, Registers registers, MMU mmu) { + int address = mmu.read(registers.getPC()); + + registers.incPC(); + + address |= mmu.read(registers.getPC()) << 8; + + registers.incPC(); + + mmu.write(value >> 8, address); + mmu.write(value, address + 1); + } + }; + + private String name; + private int cycles; + + Argument(String name, int cycles) { + this.name = name; + this.cycles = cycles; + } + + Argument(int cycles) { + this.name = name(); + this.cycles = cycles; + } + + Argument() { + this.name = name(); + this.cycles = 0; + } + + public abstract int read(Registers registers, MMU mmu); + public abstract void write(int value, Registers registers, MMU mmu); + + public String getName() { + return name; + } + + public int getCycles() { + return cycles; + } +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/CallOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/CallOpcode.java new file mode 100644 index 0000000..2a0e2ad --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/CallOpcode.java @@ -0,0 +1,60 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; +import playingcoffee.log.Log; + +public class CallOpcode implements Opcode { + + private int conditionFlag; + private Argument address; + private boolean not; + + public CallOpcode(int conditionFlag, Argument address, boolean not) { + this.conditionFlag = conditionFlag; + this.address = address; + this.not = not; + } + + public boolean canExecute(Registers registers) { + return (conditionFlag == 0) || (registers.getFlags().get(conditionFlag) ^ not); + } + + @Override + public int run(Registers registers, MMU mmu) { + int addressToJump = address.read(registers, mmu); + + if (canExecute(registers)) { + registers.decSP(); + mmu.write(registers.getPC() >> 8, registers.getSP()); + registers.decSP(); + mmu.write(registers.getPC(), registers.getSP()); + + Log.info("Pushing 0x%4x to the stack.", registers.getPC()); + + registers.setPC(addressToJump); + + return 12 + address.getCycles(); // 2 memory writes and additional cycle + } + + return address.getCycles(); + } + + @Override + public String toString() { + if (conditionFlag == 0) + return "CALL " + address.getName(); + + char flag = ' '; + switch (conditionFlag) { + case Flags.ZERO: flag = 'Z'; break; + case Flags.NEGATIVE: flag = 'N'; break; + case Flags.HALF_CARRY: flag = 'H'; break; + case Flags.CARRY: flag = 'C'; break; + } + + return "CALL " + (not ? "" : "N") + flag + ", " + address.getName(); + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/ComplementOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/ComplementOpcode.java new file mode 100644 index 0000000..ece82c6 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/ComplementOpcode.java @@ -0,0 +1,18 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; + +public class ComplementOpcode implements Opcode { + + @Override + public int run(Registers registers, MMU mmu) { + registers.setA(~registers.getA()); + + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false); + + return 0; + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/FlipCarryOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/FlipCarryOpcode.java new file mode 100644 index 0000000..0b32d4a --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/FlipCarryOpcode.java @@ -0,0 +1,17 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; + +public class FlipCarryOpcode implements Opcode { + + @Override + public int run(Registers registers, MMU mmu) { + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, !registers.getFlags().get(Flags.CARRY)); + + return 0; + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/InterruptOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/InterruptOpcode.java new file mode 100644 index 0000000..9c398f6 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/InterruptOpcode.java @@ -0,0 +1,29 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.InterruptManager; +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; + +public class InterruptOpcode implements Opcode { + + private boolean enable; + private InterruptManager interruptManager; + + public InterruptOpcode(boolean enable, InterruptManager interruptManager) { + this.enable = enable; + this.interruptManager = interruptManager; + } + + @Override + public int run(Registers registers, MMU mmu) { + if (enable) + interruptManager.enable(); + else + interruptManager.disable(); + + return 0; + } + + + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/JumpOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/JumpOpcode.java new file mode 100644 index 0000000..5444f41 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/JumpOpcode.java @@ -0,0 +1,50 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; + +public class JumpOpcode implements Opcode { + + private int conditionFlag; + private Argument address; + private boolean not; + + public JumpOpcode(int conditionFlag, Argument address, boolean not) { + this.conditionFlag = conditionFlag; + this.address = address; + this.not = not; + } + + public boolean canExecute(Registers registers) { + return (conditionFlag == 0) || (registers.getFlags().get(conditionFlag) ^ not); + } + + @Override + public int run(Registers registers, MMU mmu) { + int addressToJump = address.read(registers, mmu); + + if (canExecute(registers)) { + registers.setPC(addressToJump); + return address.getCycles() + 4; + } + + return address.getCycles(); + } + + @Override + public String toString() { + if (conditionFlag == 0) + return "JP " + address.getName(); + + char flag = ' '; + switch (conditionFlag) { + case Flags.ZERO: flag = 'Z'; break; + case Flags.NEGATIVE: flag = 'N'; break; + case Flags.HALF_CARRY: flag = 'H'; break; + case Flags.CARRY: flag = 'C'; break; + } + + return "JP " + (not ? "" : "N") + flag + ", " + address.getName(); + } +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/JumpRelativeOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/JumpRelativeOpcode.java new file mode 100644 index 0000000..3a804a8 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/JumpRelativeOpcode.java @@ -0,0 +1,51 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; + +public class JumpRelativeOpcode implements Opcode { + + private int conditionFlag; + private Argument address; + private boolean not; + + public JumpRelativeOpcode(int conditionFlag, Argument address, boolean not) { + this.conditionFlag = conditionFlag; + this.address = address; + this.not = not; + } + + public boolean canExecute(Registers registers) { + return (conditionFlag == 0) || (registers.getFlags().get(conditionFlag) ^ not); + } + + @Override + public int run(Registers registers, MMU mmu) { + int relativeValue = address.read(registers, mmu); + + int addressToJump = registers.getPC() + relativeValue; + if (canExecute(registers)) { + registers.setPC(addressToJump); + return address.getCycles() + 4; + } + + return address.getCycles(); + } + + @Override + public String toString() { + if (conditionFlag == 0) + return "JR " + address.getName(); + + char flag = ' '; + switch (conditionFlag) { + case Flags.ZERO: flag = 'Z'; break; + case Flags.NEGATIVE: flag = 'N'; break; + case Flags.HALF_CARRY: flag = 'H'; break; + case Flags.CARRY: flag = 'C'; break; + } + + return "JR " + (not ? "" : "N") + flag + ", " + address.getName(); + } +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/LoadOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/LoadOpcode.java new file mode 100644 index 0000000..a782b65 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/LoadOpcode.java @@ -0,0 +1,26 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; + +public class LoadOpcode implements Opcode { + + private Argument from, to; + + public LoadOpcode(Argument from, Argument to) { + this.from = from; + this.to = to; + } + + @Override + public int run(Registers registers, MMU mmu) { + to.write(from.read(registers, mmu), registers, mmu); + + return to.getCycles() + from.getCycles(); + } + + @Override + public String toString() { + return "LD " + to.getName() + ", " + from.getName(); + } +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/Opcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/Opcode.java new file mode 100644 index 0000000..71cdaa5 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/Opcode.java @@ -0,0 +1,10 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; + +public interface Opcode { + + public int run(Registers registers, MMU mmu); + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/PopOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/PopOpcode.java new file mode 100644 index 0000000..ce4daca --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/PopOpcode.java @@ -0,0 +1,32 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; + +public class PopOpcode implements Opcode { + + private final Argument register; + + public PopOpcode(Argument register) { + this.register = register; + } + + @Override + public int run(Registers registers, MMU mmu) { + int value = mmu.read(registers.getSP()); + registers.incSP(); + + value |= mmu.read(registers.getSP()) << 8; + registers.incSP(); + + register.write(value, registers, mmu); + + return 8; + } + + @Override + public String toString() { + return "POP " + register.getName(); + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/PushOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/PushOpcode.java new file mode 100644 index 0000000..fee0abf --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/PushOpcode.java @@ -0,0 +1,35 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; +import playingcoffee.log.Log; + +public class PushOpcode implements Opcode { + + private final Argument register; + + public PushOpcode(Argument register) { + this.register = register; + } + + @Override + public int run(Registers registers, MMU mmu) { + int value = register.read(registers, mmu); + + registers.decSP(); + mmu.write(value >> 8, registers.getSP()); + + registers.decSP(); + mmu.write(value, registers.getSP()); + + Log.info("Pushing 0x%4x to the stack.", value); + + return 8; + } + + @Override + public String toString() { + return "PUSH " + register.getName(); + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/RestartOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/RestartOpcode.java new file mode 100644 index 0000000..8ce0964 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/RestartOpcode.java @@ -0,0 +1,26 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; + +public class RestartOpcode implements Opcode { + + private int address; + + public RestartOpcode(int address) { + this.address = address; + } + + @Override + public int run(Registers registers, MMU mmu) { + registers.decSP(); + mmu.write(registers.getPC() >> 8, registers.getSP()); + registers.decSP(); + mmu.write(registers.getPC(), registers.getSP()); + + registers.setPC(address); + + return 12; + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/ReturnOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/ReturnOpcode.java new file mode 100644 index 0000000..5c4d903 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/ReturnOpcode.java @@ -0,0 +1,67 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.InterruptManager; +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; +import playingcoffee.log.Log; + +public class ReturnOpcode implements Opcode { + + private int conditionFlag; + private boolean not; + private boolean fromInterupt; + + private final InterruptManager interruptManager; + + public ReturnOpcode(int conditionFlag, boolean not, boolean fromInterupt, final InterruptManager interruptManager) { + this.conditionFlag = conditionFlag; + this.not = not; + this.fromInterupt = fromInterupt; + this.interruptManager = interruptManager; + } + + public boolean canExecute(Registers registers) { + return (conditionFlag == 0) || (registers.getFlags().get(conditionFlag) ^ not); + } + + @Override + public int run(Registers registers, MMU mmu) { + int addressToJump = mmu.read(registers.getSP()); + registers.incSP(); + + addressToJump |= mmu.read(registers.getSP()) << 8; + registers.incSP(); + + if (canExecute(registers)) { + Log.info("Returning from 0x%4x to 0x%4x", registers.getPC(), addressToJump); + + registers.setPC(addressToJump); + + if (fromInterupt) { + interruptManager.enable(); + } + + return 16; + } + + return 4; + } + + @Override + public String toString() { + if (conditionFlag == 0) + return "RET" + (fromInterupt ? "I" : ""); + + char flag = ' '; + switch (conditionFlag) { + case Flags.ZERO: flag = 'Z'; break; + case Flags.NEGATIVE: flag = 'N'; break; + case Flags.HALF_CARRY: flag = 'H'; break; + case Flags.CARRY: flag = 'C'; break; + } + + return "RET" + (fromInterupt ? "I" : "") + " " + (not ? "" : "N") + flag; + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/RotateAOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/RotateAOpcode.java new file mode 100644 index 0000000..f74c024 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/RotateAOpcode.java @@ -0,0 +1,68 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; + +public class RotateAOpcode implements Opcode { + + private boolean withCarry; + private int direction; + + public static final int LEFT = -1; + public static final int RIGHT = 1; + + public RotateAOpcode(boolean withCarry, int direction) { + this.withCarry = withCarry; + this.direction = direction; + } + + @Override + public int run(Registers registers, MMU mmu) { + if (direction == LEFT) { + if (withCarry) { + int value = registers.getA(); + int result = (value << 1) | ((value >> 7) & 1); + + registers.getFlags().set(Flags.ZERO | Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, (value & 0x80) != 0); + + registers.setA(result); + } else { + int value = registers.getA(); + int result = (value << 1) | (registers.getFlags().get(Flags.CARRY) ? 1 : 0); + + registers.getFlags().set(Flags.ZERO | Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, (value & 0x80) != 0); + + registers.setA(result); + } + } else { + if (withCarry) { + int value = registers.getA(); + int result = (value >> 1) | ((value & 1) << 7); + + registers.getFlags().set(Flags.ZERO | Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, (value & 1) != 0); + + registers.setA(result); + } else { + int value = registers.getA(); + int result = (value >> 1) | ((registers.getFlags().get(Flags.CARRY) ? 1 : 0) << 7); + + registers.getFlags().set(Flags.ZERO | Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, (value & 1) != 0); + + registers.setA(result); + } + } + + return 0; + } + + @Override + public String toString() { + return "R" + (direction == LEFT ? "L" : "R") + (withCarry ? "C" : "" + "A"); + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/SetCarryOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/SetCarryOpcode.java new file mode 100644 index 0000000..9b570b6 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/SetCarryOpcode.java @@ -0,0 +1,17 @@ +package playingcoffee.core.opcode; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; + +public class SetCarryOpcode implements Opcode { + + @Override + public int run(Registers registers, MMU mmu) { + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, true); + + return 0; + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/BitOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/BitOpcode.java new file mode 100644 index 0000000..2053d6a --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/BitOpcode.java @@ -0,0 +1,33 @@ +package playingcoffee.core.opcode.prefixed; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; +import playingcoffee.core.opcode.Argument; +import playingcoffee.core.opcode.Opcode; + +public class BitOpcode implements Opcode { + + private int bit; + private Argument argument; + + public BitOpcode(int bit, Argument argument) { + this.bit = bit; + this.argument = argument; + } + + @Override + public int run(Registers registers, MMU mmu) { + registers.getFlags().set(Flags.ZERO, (argument.read(registers, mmu) & (1 << bit)) == 0); + registers.getFlags().set(Flags.NEGATIVE, false); + registers.getFlags().set(Flags.HALF_CARRY, true); + + return argument.getCycles(); + } + + @Override + public String toString() { + return "BIT " + bit + ", " + argument.getName(); + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/ResetBitOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/ResetBitOpcode.java new file mode 100644 index 0000000..525480e --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/ResetBitOpcode.java @@ -0,0 +1,25 @@ +package playingcoffee.core.opcode.prefixed; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; +import playingcoffee.core.opcode.Argument; +import playingcoffee.core.opcode.Opcode; + +public class ResetBitOpcode implements Opcode { + + private int bit; + private Argument argument; + + public ResetBitOpcode(int bit, Argument argument) { + this.bit = bit; + this.argument = argument; + } + + @Override + public int run(Registers registers, MMU mmu) { + argument.write(argument.read(registers, mmu) & (~(1 << bit)), registers, mmu); + + return argument.getCycles() * 2; + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/RotateOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/RotateOpcode.java new file mode 100644 index 0000000..b13a21a --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/RotateOpcode.java @@ -0,0 +1,54 @@ +package playingcoffee.core.opcode.prefixed; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; +import playingcoffee.core.opcode.Argument; +import playingcoffee.core.opcode.Opcode; + +public class RotateOpcode implements Opcode { + + private boolean withCarry; + private Argument register; + private int direction; + + public static final int LEFT = -1; + public static final int RIGHT = 1; + + public RotateOpcode(boolean withCarry, Argument register, int direction) { + this.withCarry = withCarry; + this.register = register; + this.direction = direction; + } + + @Override + public int run(Registers registers, MMU mmu) { + if (direction == LEFT) { + int value = register.read(registers, mmu); + int result = (value << 1) | (!withCarry ? (registers.getFlags().get(Flags.CARRY) ? 1 : 0) : (value >> 7) & 1); + + registers.getFlags().set(Flags.ZERO, result == 0); + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, (value & 0x80) != 0); + + register.write(result, registers, mmu); + + } else { + int value = register.read(registers, mmu); + int result = (value >> 1) | ((!withCarry ? (registers.getFlags().get(Flags.CARRY) ? 1 : 0) : (value & 0x1)) << 7); + + registers.getFlags().set(Flags.ZERO, result == 0); + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, (value & 1) != 0); + + register.write(result, registers, mmu); + } + + return register.getCycles(); + } + + @Override + public String toString() { + return "R" + (direction == LEFT ? "L" : "R") + (withCarry ? "C " : " " + register.getName()); + } +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/SetBitOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/SetBitOpcode.java new file mode 100644 index 0000000..f53b6c5 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/SetBitOpcode.java @@ -0,0 +1,25 @@ +package playingcoffee.core.opcode.prefixed; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; +import playingcoffee.core.opcode.Argument; +import playingcoffee.core.opcode.Opcode; + +public class SetBitOpcode implements Opcode { + + private int bit; + private Argument argument; + + public SetBitOpcode(int bit, Argument argument) { + this.bit = bit; + this.argument = argument; + } + + @Override + public int run(Registers registers, MMU mmu) { + argument.write(argument.read(registers, mmu) | (1 << bit), registers, mmu); + + return argument.getCycles() * 2; + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/ShiftOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/ShiftOpcode.java new file mode 100644 index 0000000..f75e9bd --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/ShiftOpcode.java @@ -0,0 +1,57 @@ +package playingcoffee.core.opcode.prefixed; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Flags; +import playingcoffee.core.cpu.Registers; +import playingcoffee.core.opcode.Argument; +import playingcoffee.core.opcode.Opcode; + +public class ShiftOpcode implements Opcode { + + private ShiftType type; + private Argument register; + + public ShiftOpcode(ShiftType type, Argument register) { + this.type = type; + this.register = register; + } + + public enum ShiftType { + SLA, SRA, SRL + } + + @Override + public int run(Registers registers, MMU mmu) { + int value, result; + + switch (type) { + case SLA: + value = register.read(registers, mmu); + result = value << 1; + + registers.getFlags().set(Flags.ZERO, result == 0); + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, (value & 0x80) != 0); + break; + case SRA: + value = register.read(registers, mmu); + result = (value >> 1) | (value & 0x80); + + registers.getFlags().set(Flags.ZERO, result == 0); + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, (value & 0x1) != 0); + break; + case SRL: + value = register.read(registers, mmu); + result = (value >> 1); + + registers.getFlags().set(Flags.ZERO, result == 0); + registers.getFlags().set(Flags.NEGATIVE | Flags.HALF_CARRY, false); + registers.getFlags().set(Flags.CARRY, (value & 0x1) != 0); + break; + } + + return register.getCycles() * 2; + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/SwapOpcode.java b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/SwapOpcode.java new file mode 100644 index 0000000..b31cac8 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/core/opcode/prefixed/SwapOpcode.java @@ -0,0 +1,25 @@ +package playingcoffee.core.opcode.prefixed; + +import playingcoffee.core.MMU; +import playingcoffee.core.cpu.Registers; +import playingcoffee.core.opcode.Argument; +import playingcoffee.core.opcode.Opcode; + +public class SwapOpcode implements Opcode { + + private Argument register; + + public SwapOpcode(Argument register) { + this.register = register; + } + + @Override + public int run(Registers registers, MMU mmu) { + int value = register.read(registers, mmu); + + register.write(((value & 0xF0) >> 4) | ((value & 0xF) << 4), registers, mmu); + + return register.getCycles() * 2; + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/log/Log.java b/playing-coffee/src/main/java/playingcoffee/log/Log.java new file mode 100644 index 0000000..1bb6478 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/log/Log.java @@ -0,0 +1,73 @@ +package playingcoffee.log; + +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.time.LocalTime; + +public class Log { + + public static final String ANSI_RESET = "\033[0m"; + public static final String ANSI_BLACK = "\u001B[30m"; + public static final String ANSI_RED = "\u001B[31m"; + public static final String ANSI_GREEN = "\u001B[32m"; + public static final String ANSI_YELLOW = "\u001B[33m"; + public static final String ANSI_BLUE = "\u001B[34m"; + public static final String ANSI_PURPLE = "\u001B[35m"; + public static final String ANSI_CYAN = "\u001B[36m"; + public static final String ANSI_WHITE = "\u001B[37m"; + + private static FileWriter fileWriter; + + private static boolean useColors = false; + + public static void init() { + try { + File file = new File("log.txt"); + file.createNewFile(); + + fileWriter = new FileWriter(file); + } catch (IOException e) { + e.printStackTrace(); + System.exit(-1); + } + info("Initialized logger"); + } + + public static void close() { + try { + fileWriter.close(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static void info(String format, Object... args) { + log("Info", ANSI_RESET, format, args); + } + + public static void warn(String format, Object... args) { + log("Warning", ANSI_YELLOW, format, args); + } + + public static void error(String format, Object... args) { + log("Error", ANSI_RED, format, args); + } + + public static void fatal(String format, Object... args) { + log("Fatal", ANSI_RED, format, args); + } + + private static void log(String type, String ansiColor, String format, Object... args) { + String finalMessage = "[" + LocalTime.now() + "] " + type + ": " + String.format(format, args); + + ansiColor = useColors ? ansiColor : ""; + + System.out.println(ansiColor + finalMessage); + try { + fileWriter.write(finalMessage + "\n"); + } catch (IOException e) { + e.printStackTrace(); + } + } +} diff --git a/playing-coffee/src/main/java/playingcoffee/ppu/OAM.java b/playing-coffee/src/main/java/playingcoffee/ppu/OAM.java new file mode 100644 index 0000000..8ef0dcd --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/ppu/OAM.java @@ -0,0 +1,52 @@ +package playingcoffee.ppu; + +import playingcoffee.core.MemorySpace; +import playingcoffee.log.Log; + +public class OAM implements MemorySpace { + + public OAMEntry entries[]; + + public OAM() { + entries = new OAMEntry[40]; + } + + public int read(int address) { + int entry = (address - 0xFE00) / 4; + + switch (address % 4) { + case 0: return entries[entry].x; + case 1: return entries[entry].y; + case 2: return entries[entry].tileNumber; + case 3: return entries[entry].flags; + } + + Log.error("how in pete's holy christmas tree did we get here?"); + throw new IllegalArgumentException("Invalid address."); + } + + public void write(int value, int address) { + int entry = (address - 0xFE00) / 4; + + switch (address % 4) { + case 0: entries[entry].x = value; + case 1: entries[entry].y = value; + case 2: entries[entry].tileNumber = value; + case 3: entries[entry].flags = value; + } + + Log.error("how in pete's holy christmas tree did we get here?"); + throw new IllegalArgumentException("Invalid address."); + } + + @Override + public boolean inMemorySpace(int address) { + return (address >= 0xFE00 && address <= 0xFE9F); + } + + public class OAMEntry { + public int x, y; + public int tileNumber; + public int flags; + } +} diff --git a/playing-coffee/src/main/java/playingcoffee/ppu/PPU.java b/playing-coffee/src/main/java/playingcoffee/ppu/PPU.java new file mode 100644 index 0000000..1a071a7 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/ppu/PPU.java @@ -0,0 +1,73 @@ +package playingcoffee.ppu; + +import playingcoffee.core.InterruptManager; +import playingcoffee.core.MMU; + +public class PPU { + + private final MMU mmu; + private final InterruptManager interruptManager; + + private PPURegisters registers; + private VRAM vram; + + private int clockCount = 0; + + public PPU(final MMU mmu, final InterruptManager interruptManager) { + this.mmu = mmu; + this.interruptManager = interruptManager; + + registers = new PPURegisters(); + vram = new VRAM(); + + this.mmu.connectMemorySpace(registers); + this.mmu.connectMemorySpace(vram); + } + + public void OAMSeach() { + registers.setLCDCMode(2); + } + + public void pixelTransfer() { + registers.setLCDCMode(3); + } + + public void HBlank() { + registers.setLCDCMode(0); + } + + public void VBlank() { + registers.setLCDCMode(1); + } + + public void clock() { + registers.lcdcYCoord = clockCount / 114; + + if (clockCount == 114 * 144) + interruptManager.requestInterrupt(InterruptManager.VBLANK); + + if (clockCount < 114 * 144) { + if (clockCount % 114 < 20) OAMSeach(); + else if (clockCount % 114 < 43) pixelTransfer(); + else HBlank(); + + } else { + VBlank(); + } + + clockCount++; + + if (clockCount == 17556) { + clockCount = 0; + } + } + + public PPURegisters getRegisters() { + return registers; + } + + public VRAM getVram() { + return vram; + } + +} diff --git a/playing-coffee/src/main/java/playingcoffee/ppu/PPURegisters.java b/playing-coffee/src/main/java/playingcoffee/ppu/PPURegisters.java new file mode 100644 index 0000000..54695a8 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/ppu/PPURegisters.java @@ -0,0 +1,61 @@ +package playingcoffee.ppu; + +import playingcoffee.core.MemorySpace; + +public class PPURegisters implements MemorySpace { + + public int lcdControl; // 0xFF40 + public int lcdcStatus; // 0xFF41 + public int scrollY, scrollX; // 0xFF42 - 0xFF43 + public int lcdcYCoord; // 0xFF44 + public int lyCompare; // 0xFF45 + public int dmaTransferStart; // 0xFF46 + public int bgPalette; // 0xFF47 + public int objPalette0, objPalette1; // 0xFF48 - 0xFF49 + public int windowY, windowX; // 0xFF4A - 0xFF4B + + public void setLCDCMode(int mode) { + lcdcStatus = (lcdcStatus & 0xFC) | mode; + } + + public int read(int address) { + switch (address) { + case 0xFF40: return lcdControl; + case 0xFF41: return lcdcStatus; + case 0xFF42: return scrollY; + case 0xFF43: return scrollX; + case 0xFF44: return lcdcYCoord; + case 0xFF45: return lyCompare; + case 0xFF46: return dmaTransferStart; // TODO: Do DMA (I have no idea how to do it though...) + case 0xFF47: return bgPalette; + case 0xFF48: return objPalette0; + case 0xFF49: return objPalette1; + case 0xFF4A: return windowY; + case 0xFF4B: return windowX; + } + + throw new IllegalArgumentException("Invalid address"); + } + + public void write(int value, int address) { + switch (address) { + case 0xFF40: lcdControl = value; return; + case 0xFF41: lcdcStatus = value; return; + case 0xFF42: scrollY = value; return; + case 0xFF43: scrollX = value; return; + case 0xFF45: lyCompare = value; return; + case 0xFF46: dmaTransferStart = value; return; // TODO: Do DMA (I have no idea how to do it though...) + case 0xFF47: bgPalette = value; return; + case 0xFF48: objPalette0 = value; return; + case 0xFF49: objPalette1 = value; return; + case 0xFF4A: windowY = value; return; + case 0xFF4B: windowX = value; return; + } + + throw new IllegalArgumentException("Invalid address"); + } + + public boolean inMemorySpace(int address) { + return (address >= 0xFF40 && address <= 0xFF4B); + } +} diff --git a/playing-coffee/src/main/java/playingcoffee/ppu/VRAM.java b/playing-coffee/src/main/java/playingcoffee/ppu/VRAM.java new file mode 100644 index 0000000..c314446 --- /dev/null +++ b/playing-coffee/src/main/java/playingcoffee/ppu/VRAM.java @@ -0,0 +1,35 @@ +package playingcoffee.ppu; + +import playingcoffee.core.MemorySpace; + +public class VRAM implements MemorySpace { + + public int[] background0; + public int[] background1; + + public VRAM() { + background0 = new int[32 * 32]; + background1 = new int[32 * 32]; + } + + @Override + public int read(int address) { + if (address >= 0x9800 && address <= 0x9BFF) return background0[address % (32 * 32)]; + if (address >= 0x9C00 && address <= 0x9FFF) return background1[address % (32 * 32)]; + + return 0; + } + + @Override + public void write(int value, int address) { + if (address >= 0x9800 && address <= 0x9BFF) background0[address % (32 * 32)] = value; + if (address >= 0x9C00 && address <= 0x9FFF) background1[address % (32 * 32)] = value; + + } + + @Override + public boolean inMemorySpace(int address) { + return address >= 0x9800 && address <= 0x9FFF; + } + +} diff --git a/playing-coffee/src/test/java/playingcoffee/test/GameBoyTest.java b/playing-coffee/src/test/java/playingcoffee/test/GameBoyTest.java new file mode 100644 index 0000000..8f2adce --- /dev/null +++ b/playing-coffee/src/test/java/playingcoffee/test/GameBoyTest.java @@ -0,0 +1,26 @@ +package playingcoffee.test; + +import java.io.IOException; + +import org.junit.Test; + +public class GameBoyTest { + + @Test + public void testCPU() throws IOException { + /*Log.init(); + + + byte[] bootRom = Files.readAllBytes(Paths.get("dmg_boot.bin")); + + MMU mmu = new MMU(); + CPU cpu = new CPU(mmu); + + for (int i = 0; i < 0x100; i++) { + mmu.write(Byte.toUnsignedInt(bootRom[i]), i); + } + + while (true) cpu.clock();*/ + } + +}

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¥c;}{QEQ%JA?wec_EeE@9-?l!#sbNoBH@9> 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℞Uq9I0$W&P?N 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 + 4.0.0 + sixtenhugosson + playing-coffee + 0.0.1-SNAPSHOT + playing-coffee + A GameBoy emulator written in Java. + + + junit + junit + 4.13.1 + + + \ No newline at end of file diff --git a/playing-coffee/roms/cgb_sound/cgb_sound.gb b/playing-coffee/roms/cgb_sound/cgb_sound.gb new file mode 100644 index 0000000000000000000000000000000000000000..dc50471b00315edade129fc438517975a7036465 GIT binary patch literal 65536 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 diff --git a/playing-coffee/roms/cgb_sound/rom_singles/01-registers.gb b/playing-coffee/roms/cgb_sound/rom_singles/01-registers.gb new file mode 100644 index 0000000000000000000000000000000000000000..be180baf816f1cdbf2a7ac3dc36e72adf8636f43 GIT binary patch literal 32768 zcmeI4?{5?59l#$uiAjUwBs68$G?2#xJU1ib=tQto_gP>FG;JjmX)W4|?YzbpGI@5I zWW<1DoTQuDsV~-d&6w6~>n8QhI`(3eA+0fIJ3YdKhE}L6mF?Ws(Pq)irn?qGu=jn= zNu;v<1E}98cfRNQJl~(6=Xvhlg^2B zFI>O=-t_tN1J8zCH!fX&fBMz)=f1-3AvO!L9sUUE{MEueb0{JKB!C2v01`j~NB{{S z0VIF~kN^@u0!RP}AOR$R1dsp{Kmter2_OL^fCP{L5>2+;iF>2|J zXi`7>_R82hV(bZ8($o0?*5pcT&D71?w$a-WPsKszk9qAqmJ3h0_M)5w1Z`~Dcm(R& z3>iHF+0@ddp57i(3vuWtyQ8_JZh={@7B7kfB|BYm^)`C*E1F<*Q~GP3znGFb?FSF< zl)Dce-nEkk!u@nCa4bjKkQj*L<8^@D(wg5?98 z1^QPUK@P%Rdp%{dYwqSs!=ItVn(gCd5B-l`9fH1hUBe%S#SFyW26vm#=%sl*@l{fJ zt0I0*RbGW&g^;%h8LF(ddylfVoW52kFL%h~vh6Q#6Kk{my}Hjnv4n~^5o^nCi$jRQa7Vg-aIbl%oEae^Q82i8I!J>5yxl z8``13$Vd=Q4I|o_^im@0_trh({fE>f=JrLx!PS|GY-DLNI}ktXeE=*00{nt{xkByC z2Bu~)vBNA*z~)DBVl+HFgf^kU*X-B6tP6KyO`o}=}}sW)kjHlgubDec`<7u35x zdWL?gf4q|z1(LPJ*SQl}{_nKy6mcgi(19(s4(=wnD{ysT zsY|%p9~cY;`@sj$Ta{=hxcr{A+UuRQjBIJ7%XRd&rSbJJoX9Yotl;o)WLTVTfWsOJ zgs|GeRF=&Yp3kzs6>Kx?_XYb53lwT*7%9}wutb5(=y`K#3P3-f9q8;Kh33?m3~ahzXWV4cMfgAA`4d+Ko~*Sn zorM5D%d%UA(^>xXob+usdBssXww*3-t)sWZhw7-K8AuMYQ*e|2f7j9L%x3g40x>|R zPj5Lr=TOp)`nCsO-?9b#Bzu+aEUpk%5)I$Y~x?jmU zS|6*U%WN8IgHOncGH6_T!Pr~UuL|Xe$Mfi;Ez{XeZCg%4j9a-YYp)AQG$N5uC}h1s zLgf%#gh)*BJ7N-1Lz1jyeoWeX+IJM&=;1~I8ZOd|9)6uL;?qKRV6W16&H*v ziUNM4AyrjFQHZa^6b16Ko(f@#5{8fjX2DD)ev!W9Cl1lT4s3?N7D^Q@PBM$-Yne=j zsK?@QFckSh^IP#i!IF=KdMp`2iuiuv2Ox?j;y|$)zv>|{O67~Bm(GU7J1ORwe^@TrCe^rhtmOpSPWWfVuvGHIX z1pybxB8>R2NWx4kFaU^&y=nodjzd1M$kzUGNs=t=tnk2PNRph87U(HAe^K4K&?f|5 zFyh52mG!lwV2$Gs5WB literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/cgb_sound/rom_singles/02-len ctr.gb b/playing-coffee/roms/cgb_sound/rom_singles/02-len ctr.gb new file mode 100644 index 0000000000000000000000000000000000000000..eb5ca82b76bba016c588efbb905a21fce786102f GIT binary patch literal 32768 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 literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/cgb_sound/rom_singles/06-overflow on trigger.gb b/playing-coffee/roms/cgb_sound/rom_singles/06-overflow on trigger.gb new file mode 100644 index 0000000000000000000000000000000000000000..b2d5c75a2fbb2e3f5530bb065ebf257325a390db GIT binary patch literal 32768 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 literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/cgb_sound/rom_singles/09-wave read while on.gb b/playing-coffee/roms/cgb_sound/rom_singles/09-wave read while on.gb new file mode 100644 index 0000000000000000000000000000000000000000..1e4a78cdc9ed1408864a96ff3a47e359edec58b4 GIT binary patch literal 32768 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 literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/cgb_sound/rom_singles/10-wave trigger while on.gb b/playing-coffee/roms/cgb_sound/rom_singles/10-wave trigger while on.gb new file mode 100644 index 0000000000000000000000000000000000000000..56f0e901ea1f0ecb0b984536a14eb8870babf5e5 GIT binary patch literal 32768 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 disabled clocks as well + ld a,1 + call end + + call begin + wchn 4,$40 ; enable length + wchn 1,-2 ; length = 2 + wchn 4,$40 ; enabled -> enabled doesn't clock + wchn 4,$00 ; enabled -> disabled doesn't clock + ld a,2 + call end +.else + set_test 4,"Anything besides enabling shouldnt't clock" + call begin + wchn 4,$40 ; enable length + wchn 1,-2 ; length = 2 + wchn 4,$40 ; enabled -> enabled doesn't clock + wchn 4,$00 ; enabled -> disabled doesn't clock + wchn 4,$00 ; disabled -> disabled doesn't clock + ld a,2 + call end +.endif + + set_test 5,"If clock makes length zero, should disable chan" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + lda chan_mask + ld b,a + lda NR52 ; channel now disabled + and b + jp nz,test_failed + + set_test 6,"If length already reached zero, shouldn't clock" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + wchn 4,0 + wchn 4,$40 ; no clock; length still 0 + wchn 4,0 + wchn 4,$40 ; no clock; length still 0 + lda chan_maxlen; end triggers channel, which loads it with max length + call end + + set_test 7,"Trigger should un-freeze length that reached zero" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + wchn 4,$00 ; disable + wchn 4,$80 ; trigger unfreezes length, so it takes on maximum value + delay_clocks 8192 + wchn 4,$40 ; enable + delay_apu 2 ; clock length by 2 + lda chan_maxlen + sub 2 + call end_nodelay + + set_test 8,"Trigger that un-freezes enabled length should clock it" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + wchn 4,$00 ; disable + wchn 4,$C0 ; trigger unfreezes length, and since enabled, clocks it + lda chan_maxlen + dec a + call end_nodelay + + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + wchn 4,$C0 ; trigger unfreezes length, and since enabled, clocks it + lda chan_maxlen + dec a + call end_nodelay + + set_test 9,"Triggering that clocks length of 1 ","should clock twice and shouldn't freeze" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$C0 ; trigger and enable + ; First length counter is enabled, which clocks it to 0 and freezes it + ; Trigger unfreezes length counter, which clocks it AGAIN + ; The result is the same as the previous test, which enables separately + lda chan_maxlen + dec a + call end_nodelay + + set_test 10,"Trigger shouldn't otherwise affect length" + call begin + wchn 1,0 ; length = max + delay_clocks 8192 + wchn 4,$80 ; trigger + lda chan_maxlen + call end_nodelay + +.ifndef CGB_02 + call begin + wchn 1,0 ; length = max + wchn 4,$80 ; trigger + lda chan_maxlen + call end + + call begin + wchn 1,-2 ; length = 2 + wchn 4,$80 ; trigger + ld a,2 + call end +.endif + + set_test 11,"Disabled DAC shouldn't stop other trigger effects" + call begin + wchn 0,$00 ; disable wave DAC + wchn 2,$07 ; disable square/noise DAC + wchn 1,-1 + wchn 4,$C0 ; clocks length, which becomes max + wchn 0,$80 ; enable wave DAC + wchn 2,$08 ; enable square/noise DAC + wchn 4,$80 ; trigger + lda chan_maxlen + dec a + call end + + set_test 12,"Other trigger effects should still occur when disabled" + call sync_apu + wchn 0,0 + wchn 4,0 + wchn 1,-1 + wchn 4,$40 ; len = 0 + wchn 4,0 + wchn 4,$40 ; len = 0 + wchn 4,$80 ; len = max + wchn 4,$40 ; len = max-1 + wchn 4,0 + wchn 4,$40 ; len = max-2 + wchn 0,$80 ; enable now + wchn 4,$C0 + lda chan_maxlen + sub 3 + call delay_apu_cycles + lda chan_mask + ld b,a + lda NR52 + and b + jp z,test_failed + delay_apu 1 + lda NR52 + and b + jp nz,test_failed + + ret diff --git a/playing-coffee/roms/cgb_sound/source/04-sweep.s b/playing-coffee/roms/cgb_sound/source/04-sweep.s new file mode 100644 index 0000000..17ac4e4 --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/04-sweep.s @@ -0,0 +1,120 @@ +; Calc = calculation of new frequency and check for > $7FF +; Update = modification of frequency with new calculated value +.include "shell.inc" +.include "apu.s" + +begin: + call sync_sweep + wreg NR14,$40 + wreg NR11,-$21 + wreg NR12,$08 + ret + +should_be_almost_off: + lda NR52 + and $01 + jp z,test_failed + delay_apu 1 +should_be_off: + lda NR52 + and $01 + jp nz,test_failed + ret + +main: + set_test 2,"If shift>0, calculates on trigger" + call begin + wreg NR10,$01 + wreg NR13,$FF + wreg NR14,$C7 + call should_be_off + call begin + wreg NR10,$11 + wreg NR13,$FF + wreg NR14,$C7 + call should_be_off + + set_test 3,"If shift=0, doesn't calculate on trigger" + call begin + wreg NR10,$10 + wreg NR13,$FF + wreg NR14,$C7 + delay_apu 1 + call should_be_almost_off + + set_test 4,"If period=0, doesn't calculate" + call begin + wreg NR10,$00 + wreg NR13,$FF + wreg NR14,$C7 + delay_apu $20 + call should_be_almost_off + + set_test 5,"After updating frequency, calculates a second time" + call begin + wreg NR10,$11 + wreg NR13,$00 + wreg NR14,$C5 + delay_apu 1 + call should_be_almost_off + + set_test 6,"If calculation>$7FF, disables channel" + call begin + wreg NR10,$02 + wreg NR13,$67 + wreg NR14,$C6 + call should_be_off + + set_test 7,"If calculation<=$7FF, doesn't disable channel" + call begin + wreg NR10,$01 + wreg NR13,$55 + wreg NR14,$C5 + delay_apu $20 + call should_be_almost_off + + set_test 8,"If shift=0 and period>0, trigger enables" + call begin + wreg NR10,$10 + wreg NR13,$FF + wreg NR14,$C3 + delay_apu 2 + wreg NR10,$11 + delay_apu 1 + call should_be_almost_off + + set_test 9,"If shift>0 and period=0, trigger enables" + call begin + wreg NR10,$01 + wreg NR13,$FF + wreg NR14,$C3 + delay_apu 15 + wreg NR10,$11 + call should_be_almost_off + + set_test 10,"If shift=0 and period=0, trigger disables" + call begin + wreg NR10,$08 + wreg NR13,$FF + wreg NR14,$C3 + wreg NR10,$11 + delay_apu $20 + call should_be_almost_off + + set_test 11,"If shift=0, doesn't update" + call begin + wreg NR10,$10 + wreg NR13,$FF + wreg NR14,$C3 + delay_apu $20 + call should_be_almost_off + + set_test 12,"If period=0, doesn't update" + call begin + wreg NR10,$01 + wreg NR13,$00 + wreg NR14,$C5 + delay_apu $20 + call should_be_almost_off + + jp tests_passed diff --git a/playing-coffee/roms/cgb_sound/source/05-sweep details.s b/playing-coffee/roms/cgb_sound/source/05-sweep details.s new file mode 100644 index 0000000..1519b68 --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/05-sweep details.s @@ -0,0 +1,121 @@ +; Calc = calculation of new frequency and check for > $7FF +; Update = modification of frequency with new calculated value +.include "shell.inc" +.include "apu.s" + +begin: + call sync_sweep + wreg NR14,$40 + wreg NR11,-$20 + wreg NR12,$08 + ret + +should_be_almost_off: + lda NR52 + and $01 + jp z,test_failed + delay_apu 1 +should_be_off: + lda NR52 + and $01 + jp nz,test_failed + ret + +main: + set_test 2,"Timer treats period 0 as 8" + call begin + wreg NR10,$11 + wreg NR13,$00 + wreg NR14,$C2 + delay_apu 1 + wreg NR10,$01 ; sweep enabled + delay_apu 3 + wreg NR10,$11 ; non-zero period so calc will occur when timer reloads + delay_apu $11 + call should_be_almost_off + + set_test 3,"Makes private copy of frequency on trigger" + call begin + wreg NR10,$12 + wreg NR13,$04 + wreg NR14,$80 + wreg NR13,$00 + delay_apu $39 + call should_be_almost_off + + set_test 4,"Exiting negate mode after calculation disables channel" + call begin + wreg NR10,$09 ; since shift > 0, calculates sweep value at init + wreg NR13,$00 + wreg NR14,$C0 + delay_apu 2 + wreg NR10,$10 ; neg->pos, so disables channel + call should_be_off + + set_test 5,"Ending negate after it maybe changed freq disables chan" + call begin + wreg NR10,$10 ; enable sweep + wreg NR13,$00 + wreg NR14,$C0 + delay_apu 2 + wreg NR10,$18 ; negate mode + delay_apu 2 + wreg NR10,$10 ; neg->pos, so disables channel + call should_be_off + + set_test 6,"Ending negate mode any other way doesn't disable channel" + call begin + wreg NR10,$1F ; use negate mode once + wreg NR14,$C0 + delay_apu 2 + wreg NR10,$18 ; since period > 0, doesn't calculate at init + wreg NR13,$00 + wreg NR14,$C0 + delay_apu 1 ; no sweep clock here + wreg NR10,$10 ; pos mode before neg mode ever used + delay_apu 1 ; sweep clock occurs here + wreg NR10,$0F ; now let neg mode be seen once, but period = 0 so no calculation is made + delay_apu 2 ; sweep clock occurs here + wreg NR10,$10 ; doesn't affect channel + delay_apu 2 ; sweep clock occurs here + wreg NR10,$1F ; let neg mode get used + delay_apu 18 + wreg NR10,$79 ; period and shift can be changed without channel disabling + delay_apu 5 + call should_be_almost_off + + set_test 7,"Subtract mode uses two's complement" + call begin + delay 2048 ; avoids extra length clocking on CGB-02 + wreg NR10,$1C + wreg NR13,$B0 + wreg NR14,$85 + delay_apu 2 + wreg NR10,$01 + wreg NR14,$C5 + delay_apu $1F + call should_be_almost_off + + set_test 8,"Subtract mode uses two's complement (upper bound)" + call begin + wreg NR10,$1C + wreg NR13,$B1 + wreg NR14,$85 + delay_apu 2 + wreg NR10,$01 + wreg NR14,$C5 + call should_be_off + + set_test 9,"Update channel frequency only when period is reloaded" + call begin + wreg NR10,$74 + wreg NR13,$06 + wreg NR14,$85 + delay_apu 14 ; just reloaded + wreg NR13,$06 + delay_apu 13 ; if 14, fails + wreg NR10,$11 + wreg NR14,$85 ; just before next reload, so freq is still $506 + call should_be_almost_off + + jp tests_passed diff --git a/playing-coffee/roms/cgb_sound/source/06-overflow on trigger.s b/playing-coffee/roms/cgb_sound/source/06-overflow on trigger.s new file mode 100644 index 0000000..2ebc236 --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/06-overflow on trigger.s @@ -0,0 +1,64 @@ +; Finds highest and lowest frequencies that don't overflow +; immediately on trigger, for NR10 values of $00-$07 + +.include "shell.inc" +.include "apu.s" + +main: + + ; DMG-06: + ; 0555 0666 071C 0787 07C1 07E0 07F0 + + wreg NR12,8 + ld d,$01 +shift_loop: + ld a,d + sta NR10 + ld bc,$87FF +- ld a,c + sta NR13 + ld a,b + sta NR14 + delay_clocks 40 + lda NR52 + and 1 + jr nz,+ + dec bc + bit 6,b + jr z,- ++ res 7,b + call print_bc + inc d + bit 3,d + jr z,shift_loop + call print_newline + check_crc $F604603B + + ; DMG-05, DMG-06, DMG-09, CGB-04, CGB-05: + ; 0556 0667 071D 0788 07C2 07E1 07F1 + + wreg NR12,8 + ld d,$01 +shift_loop2: + ld a,d + sta NR10 + ld bc,$8000 +- ld a,c + sta NR13 + ld a,b + sta NR14 + delay_clocks 40 + lda NR52 + and 1 + jr z,+ + inc bc + bit 6,b + jr z,- ++ res 7,b + call print_bc + inc d + bit 3,d + jr z,shift_loop2 + check_crc $5A1697EE + + jp tests_passed diff --git a/playing-coffee/roms/cgb_sound/source/07-len sweep period sync.s b/playing-coffee/roms/cgb_sound/source/07-len sweep period sync.s new file mode 100644 index 0000000..6b3ed7e --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/07-len sweep period sync.s @@ -0,0 +1,106 @@ +; Tests length and sweep periods, and synchronization between the two +.include "shell.inc" +.include "apu.s" + +test_timing: + ; Time how long until next length clock +- inc de + ld a,(NR52) + and $01 + jr nz,- + + ;call print_de + + ; Error if not in 0...4 range + ld a,d + cp 0 + jp nz,test_failed + ld a,e + cp 5 + jp nc,test_failed + ret + +main: + + set_test 2,"Length period is wrong" + call sync_apu + wreg NR14,$40 ; avoids extra length clock + wreg NR11,$3F ; length = $01 + wreg NR12,$08 ; silent without disabling channel + wreg NR14,$C0 ; start length + ld de,-$170 + call test_timing + + set_test 3,"Sweep period is wrong" + call sync_sweep + wreg NR10,$10 ; sweep period = 1 + wreg NR12,$08 ; silent without disabling channel + wreg NR13,$FF ; max freq + wreg NR14,$87 ; start + ld de,-$2E4 + call test_timing + + set_test 4,"Sweep clock is synchronized with length" + call sync_sweep + wreg NR14,$40 ; avoids extra length clock + wreg NR11,$3F ; length = $01 + wreg NR12,$08 ; silent without disabling channel + wreg NR14,$C0 ; start length + ld de,-$170 + call test_timing + + set_test 5,"Powering up APU MODs next frame time with 8192" + call sync_apu + ld de,-$16F + call test_power + + call sync_apu + ld de,-$B5 + call test_power_off + + call sync_apu + delay_clocks 8192 + ld de,-$B5 + call test_power + + call sync_apu + delay_clocks 8192 + ld de,-$B5 + call test_power_off + + call sync_apu + ld de,-$B5 + wreg NR52,$00 ; power off + delay_clocks 8192 + call test_power + + set_test 6,"Powering up APU resets 128 Hz sweep divider" + call sync_sweep + ld de,-$229 + call test_power2 + + call sync_sweep + delay_apu 1 + ld de,-$229 + call test_power2 + + jp tests_passed + +test_power_off: + wreg NR52,$00 ; power off +test_power: + wreg NR52,$80 ; power on + wreg NR14,$40 + wreg NR11,-1 ; length = 1 + wreg NR12,8 + wreg NR14,$C0 + jp test_timing + +test_power2: + wreg NR52,$00 ; power off + wreg NR52,$80 ; power on + wreg NR10,$11 + wreg NR12,8 + wreg NR13,$00 + wreg NR14,$84 + jp test_timing diff --git a/playing-coffee/roms/cgb_sound/source/08-len ctr during power.s b/playing-coffee/roms/cgb_sound/source/08-len ctr during power.s new file mode 100644 index 0000000..1ea218a --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/08-len ctr during power.s @@ -0,0 +1,84 @@ +; On CGB, length counters are reset when powered up. +; On DMG, they are unaffected, and not clocked. + +;.define REQUIRE_DMG 1 +;.define REQUIRE_CGB 1 +.include "shell.inc" +.include "apu.s" + +enable_len_ctrs: + wreg NR22,8 + wreg NR24,$C0 + wreg NR12,8 + wreg NR14,$C0 + wreg NR30,$80 + wreg NR34,$C0 + wreg NR42,8 + wreg NR44,$C0 + ret + +main: + call sync_apu + + ld a,0 + call fill_apu_regs + + ; Load length counters + wreg NR41,-$33 + wreg NR31,-$44 + wreg NR11,-$11 + wreg NR21,-$22 + + delay_clocks 8192 + call enable_len_ctrs + + ; Power down. Comment out to see what would + ; happen if length counters did run. + wreg NR52,$00 + + ; Try to enable length counters + call enable_len_ctrs + + ; Give plenty of time for them to be clocked + delay_msec 250 + + ; Power back on and wait a bit longer + wreg NR52,$80 + ;call enable_len_ctrs ; can't do this here + delay_clocks 2048 + + ; Get values from length counters + wreg NR22,8 + wreg NR24,$C0 + ld a,$02 + call get_len_a + push af + + wreg NR12,8 + wreg NR14,$C0 + ld a,$01 + call get_len_a + push af + + wreg NR30,$80 + wreg NR34,$C0 + ld a,$04 + call get_len_a + push af + + wreg NR42,8 + wreg NR44,$C0 + ld a,$08 + call get_len_a + + ; Print them + call print_a + pop af + call print_a + pop af + call print_a + pop af + call print_a + + check_crc_dmg_cgb $32F0CFBB,$3CF589B4 + jp tests_passed diff --git a/playing-coffee/roms/cgb_sound/source/09-wave read while on.s b/playing-coffee/roms/cgb_sound/source/09-wave read while on.s new file mode 100644 index 0000000..2d06a77 --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/09-wave read while on.s @@ -0,0 +1,41 @@ +; Reads from wave RAM while playing, each time 2 +; clocks later. + +;.define REQUIRE_DMG 1 +;.define REQUIRE_CGB 1 +.include "shell.inc" +.include "apu.s" + +main: + wreg NR51,0 ; mute sound + loop_n_times test,69 + check_crc_dmg_cgb $118A3620,$270DA9A3 + jp tests_passed + +test: + add $99 + ld b,a + + ; Reload wave and have its first + ; sample read occur 2 clocks earlier + ; each loop iteration + ld hl,wave + call load_wave + wreg NR30,$80 ; enable + wreg NR32,$00 ; silent + ld a,b + sta NR33 ; period + wreg NR34,$87 ; start + + ; Read from wave + wreg NR33,-2 ; period = 4 + delay_clocks 176 + lda WAVE + + call print_a + + ret + +wave: + .byte $00,$11,$22,$33,$44,$55,$66,$77 + .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF diff --git a/playing-coffee/roms/cgb_sound/source/10-wave trigger while on.s b/playing-coffee/roms/cgb_sound/source/10-wave trigger while on.s new file mode 100644 index 0000000..d7ec063 --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/10-wave trigger while on.s @@ -0,0 +1,49 @@ +; Retriggers wave without stopping first + +;.define REQUIRE_DMG 1 +;.define REQUIRE_CGB 1 +.include "shell.inc" +.include "apu.s" + +main: + wreg NR51,0 ; mute sound + loop_n_times test,69 + check_crc_dmg_cgb $533D6D4D,$8130733A + jp tests_passed + +test: + add $99 + ld b,a + + ; Reload wave and have its first + ; sample read occur 2 clocks earlier + ; each loop iteration + ld hl,wave + call load_wave + wreg NR30,$80 ; enable + wreg NR32,$00 ; silent + ld a,b + sta NR33 ; period + wreg NR34,$87 ; start + + ; Retrigger wave + wreg NR33,-2 ; period = 4 + delay_clocks 168 + wreg NR34,$87 ; restart + delay_clocks 40 + + ; Print wave RAM + wreg NR30,0 + ld c,$30 +- ld a,($FF00+c) + call print_a + inc c + bit 6,c + jr z,- + call print_newline + + ret + +wave: + .byte $00,$11,$22,$33,$44,$55,$66,$77 + .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF diff --git a/playing-coffee/roms/cgb_sound/source/11-regs after power.s b/playing-coffee/roms/cgb_sound/source/11-regs after power.s new file mode 100644 index 0000000..6379ad7 --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/11-regs after power.s @@ -0,0 +1,51 @@ +; After powering sound off then on, NR12, NR14, and NR44 +; are clear. +.define REQUIRE_CGB 1 +.include "shell.inc" +.include "apu.s" + +main: + call sync_apu + + ld a,$FF + call fill_apu_regs + + ; Power down for a moment + wreg NR52,$00 + wreg NR41,-$12 + wreg NR12,$F0 + delay_msec 100 + wreg NR52,$80 + + set_test 2,"Powering off should clear NR12" + call sync_apu + wreg NR14,$80 + lda NR52 + and $01 + jp nz,test_failed + + set_test 3,"Powering off should clear NR13" + call sync_apu + wreg NR10,$11 + wreg NR12,$08 + wreg NR14,$80 + delay_apu 20 + lda NR52 + and $01 + jp z,test_failed + + set_test 4,"Powering off should clear NR41" + call sync_apu + delay_clocks 8192 ; avoids extra length clocking + wreg NR42,$08 + wreg NR44,$C0 + delay_apu 63 + lda NR52 + and $08 + jp z,test_failed + delay_apu 1 + lda NR52 + and $08 + jp nz,test_failed + + jp tests_passed diff --git a/playing-coffee/roms/cgb_sound/source/12-wave.s b/playing-coffee/roms/cgb_sound/source/12-wave.s new file mode 100644 index 0000000..ba3185d --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/12-wave.s @@ -0,0 +1,112 @@ +; Tests wave channel timer reload and phase rest on trigger, +; and access to wave RAM while playing. +.define REQUIRE_CGB 1 +.include "shell.inc" +.include "apu.s" + +main: + ld hl,wave + call load_wave + wreg NR32,0 + + set_test 2,"Timer period or phase resetting is wrong" + wreg NR30,$80 + wreg NR33,$00 + wreg NR34,$80 + delay_clocks 1024 + wreg NR34,$80 + ld c,$31 + ld de,-$FE + call test_wave + + set_test 3,"Current byte readable at any wave addr" + wreg NR30,$80 + wreg NR33,$00 + wreg NR34,$80 + ld c,$3C + ld de,-$FE + call test_wave + + set_test 5,"Normal access when chan disabled" + wreg NR30,$80 + wreg NR33,$00 + wreg NR34,$80 + wreg NR30,$00 ; disable chan + wreg NR30,$80 ; DAC on + ld c,$31 + ld de,0 + call test_wave + + set_test 6,"Write test" + wreg NR30,$80 + wreg NR33,$F0 + wreg NR34,$87 + delay_clocks 256 + wreg $FF30,$BC + wreg NR30,0 + ld a,($FF34) + cp $BC + jp nz,test_failed + + set_test 7,"Timer period change" + wreg NR30,$80 + wreg NR33,$00 + wreg NR34,$87 + wreg NR33,$F0 + ld c,$30 + ld de,-$E + call test_wave + + set_test 8,"Frequency 0 is valid" + wreg NR30,$80 + wreg NR33,$00 + wreg NR34,$80 + ld c,$30 + ld de,-$FE + call test_wave + + set_test 9,"Maintains phase properly when vol = 0" + wreg NR30,$80 + wreg NR32,0 + wreg NR33,$00 + wreg NR34,$87 + ld c,$30 + ld de,-$1E + call test_wave + + set_test 10,"Maintains phase properly when stereo = 0" + wreg NR51,$00 + wreg NR30,$80 + wreg NR33,$00 + wreg NR34,$87 + ld c,$30 + ld de,-$1E + call test_wave + + jp tests_passed + +test_wave: +- inc de ; 8 + ld a,($FF00+c) ; 8 + or a ; 4 + jr z,- ; 12 + + ;call print_a + ;call print_de + + cp $11 + jp nz,test_failed + + + ; Error if not in 0...4 range + ld a,d + cp 0 + jp nz,test_failed + ld a,e + cp 5 + jp nc,test_failed + ret + +wave: + .byte $00,$11,$22,$33,$44,$55,$66,$77 + .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF diff --git a/playing-coffee/roms/cgb_sound/source/common/build_gbs.s b/playing-coffee/roms/cgb_sound/source/common/build_gbs.s new file mode 100644 index 0000000..877754f --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/common/build_gbs.s @@ -0,0 +1,139 @@ +; Build as GBS music file + +.memoryMap + defaultSlot 0 + slot 0 $2000 size $2000 + slot 1 $C000 size $2000 +.endMe + +.romBankSize $2000 +.romBanks 2 + +.define RST_OFFSET $70 + +.ifndef GBS_TMA + .define GBS_TMA 0 +.endif + +.ifndef GBS_TAC + .define GBS_TAC 0 +.endif + +;;;; GBS music file header + +.ifndef CUSTOM_HEADER + .byte "GBS" + .byte 1,1,1 ; vers, song count, first song + .word load_addr, reset, gbs_play_, std_stack + .byte GBS_TMA,GBS_TAC ; timer +.endif + .org $10 + .ds $60,0 +load_addr: + .org RST_OFFSET+$70 ; space for RST vectors + .ds $148-RST_OFFSET-$70,0 + .org $150 ; wla insists on generating GB header + +gbs_play_: + jp gbs_play ; GBS spec disallows having gbs_play in RAM + +;;;; Shell + +.include "shell.s" + +.define gbs_idle nv_ram +.redefine nv_ram nv_ram+2 + +init_runtime: + ; Identify as DMG hardware + ld a,$01 + ld (gb_id),a + + ; Save return address + pop hl + ld a,l + ld (gbs_idle),a + ld a,h + ld (gbs_idle+1),a + + ; Delay 1/4 second to give time + ; for GBS player to interrupt with + ; play, if it's going to do so + delay_msec 250 + +.ifndef CUSTOM_PLAY +gbs_play: +.endif + ; Get return address + ld a,(gbs_idle) + ld l,a + ld a,(gbs_idle+1) + ld h,a + + ; If zero, then play interrupted init + ; call, or another play call, and we + ; can't run the program properly. + or l + jp z,internal_error + + setw gbs_idle,0 + jp hl + + +; 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 diff --git a/playing-coffee/roms/cgb_sound/source/common/build_rom.s b/playing-coffee/roms/cgb_sound/source/common/build_rom.s new file mode 100644 index 0000000..f369b92 --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/common/build_rom.s @@ -0,0 +1,70 @@ +; Build as GB ROM + +.memoryMap + defaultSlot 0 + slot 0 $0000 size $4000 + slot 1 $C000 size $4000 +.endMe + +.romBankSize $4000 +.romBanks 2 + +.cartridgeType 2 ; MBC1+RAM +.ramsize 02 ; 8K +.computeChecksum +.computeComplementCheck + +;;;; GB ROM header + + ; Reserve space for RST handlers + .org $70 + + ; Keep unused space filled, otherwise + ; wla moves code here + .ds $90,0 + + ; 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 + + ; Keep unused space filled, otherwise + ; wla moves code here + .org $150 + .ds $2150-$150,0 + +;;;; Shell + +.define NEED_CONSOLE 1 +.include "shell.s" + +init_runtime: + ret + +play_byte: + ret + +.ends diff --git a/playing-coffee/roms/cgb_sound/source/common/console.bin b/playing-coffee/roms/cgb_sound/source/common/console.bin new file mode 100644 index 0000000000000000000000000000000000000000..b02f2d37ee94c6570a3622734f2d704dfca44b0c GIT binary patch literal 768 zcmX|9F=`|+5G))_B$f+182koBX5hr&r*zbT5qlFY8w?8X8GehSdBVQJIPe4Zu1Z?m z2wEj|cXgKp3^N%Fwbp)6@0AO%)yuRSqFVOjFXoqd`4hj9QtEN1nR)XG+d{u=K$L$a zcxBdDa9GaHO?x1~9ZyqY!B>?@1~S_Jk8EuOnO>t1U_)GPUYcV}PSJ688I zDZyK&1&Z3Yf=R|a=4{O6sIrFR>wY_q1954MiRzbDIv)c%zSNhFNh$uKIuubR9$M#% zQVgthyMM7>6t%8xc-*ZoU#;c~Eo^b8>ieF1Te%=ANe|UbtGOeuN<>ma4(yn>d z&PyDPc+NXzR}RnDiJDL@@*5U1@Exf>ZKHTq^DiA1#(zox`Vp^snt$Nkdp#BumLA0V yczj1Ga3U7XIy~nG6}avCt6hL`&VKmF{r{ZIvhq4Tb=k}k+hUx5zmKoWJ@6mAT&*eq literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/cgb_sound/source/common/console.s b/playing-coffee/roms/cgb_sound/source/common/console.s new file mode 100644 index 0000000..403fa5c --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/common/console.s @@ -0,0 +1,285 @@ +; 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 + setb console_pos,console_width + setb console_mode,0 + setb console_scroll,-8 + 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 + wreg VBK,1 + ld a,0 + call @fill_nametable + + wreg VBK,0 + 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 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 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 diff --git a/playing-coffee/roms/cgb_sound/source/common/delay.s b/playing-coffee/roms/cgb_sound/source/common/delay.s new file mode 100644 index 0000000..cbcdcf3 --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/common/delay.s @@ -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 diff --git a/playing-coffee/roms/cgb_sound/source/common/gb.inc b/playing-coffee/roms/cgb_sound/source/common/gb.inc new file mode 100644 index 0000000..2d0118d --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/common/gb.inc @@ -0,0 +1,81 @@ +; Game Boy hardware addresses + +; $0000-$3FFF Fixed ROM bank +; $4000-$7FFF Switchable bank +; $8000-$9FFF VRAM +; $A000-$BFFF optional cartridge RAM +; $C000-$DFFF RAM +; $E000-$FDFF RAM mirror +; $FE00-$FE9F OAM +; $FEA0-$FEFF unused +; $FF00-$FF7F registers +; $FF80-$FFFE RAM +; $FFFF register + +; Memory +.define VRAM $8000 ; video memory +.define TILES $8000 ; tile images +.define BGMAP0 $9800 ; first 32x32 tilemap +.define BGMAP1 $9C00 ; second 32x32 tilemap +.define BRAM $A000 ; cart memory +.define WRAM $C000 ; internal memory +.define OAM $FE00 ; sprite memory +.define HRAM $FF80 ; fast memory for LDH + +; Registers + +.define RAMEN $0000 ; cartridge WRAM control +.define BANK $2000 ; bank select +.define P1 $FF00 ; controller + +; Game link I/O +.define SB $FF01 ; serial buffer +.define SC $FF02 ; serial control + +; 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 diff --git a/playing-coffee/roms/cgb_sound/source/common/macros.inc b/playing-coffee/roms/cgb_sound/source/common/macros.inc new file mode 100644 index 0000000..9d7cf44 --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/common/macros.inc @@ -0,0 +1,91 @@ +; General macros + +; Reads A from addr, from $FF00 to $FFFF +; Preserved: F, BC, DE, HL +; Time: 3 cycles +.macro lda ; addr + ldh a,(\1 - $FF00) +.endm + +; Writes A to addr, from $FF00 to $FFFF +; Preserved: AF, BC, DE, HL +; Time: 3 cycles +.macro sta ; addr + ldh (\1 - $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 + +; Writes byte to addr +; Preserved: F, BC, DE, HL +; Time: 6 cycles +.macro setb ; addr, data + ld a,\2 + ld (\1),a +.endm + +; Writes word to addr +; Preserved: F, BC, DE, HL +; Time: 12 cycles +.macro setw ; addr, data + ld a,<\2 + ld (\1),a + ld a,>\2 + ld (\1+1),a +.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 diff --git a/playing-coffee/roms/cgb_sound/source/common/numbers.s b/playing-coffee/roms/cgb_sound/source/common/numbers.s new file mode 100644 index 0000000..6d6faf8 --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/common/numbers.s @@ -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 diff --git a/playing-coffee/roms/cgb_sound/source/common/printing.s b/playing-coffee/roms/cgb_sound/source/common/printing.s new file mode 100644 index 0000000..bb9389b --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/common/printing.s @@ -0,0 +1,77 @@ +; 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 +;print_char_nocrc (defined by user) + + +; 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 diff --git a/playing-coffee/roms/cgb_sound/source/common/shell.s b/playing-coffee/roms/cgb_sound/source/common/shell.s new file mode 100644 index 0000000..3e588c7 --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/common/shell.s @@ -0,0 +1,261 @@ +; Common routines and runtime + +.define RUNTIME_INCLUDED 1 + +; A few bytes of RAM that aren't cleared +.define nv_ram_base $D800 +.define nv_ram nv_ram_base + +; Address of next normal variable +.define bss_base nv_ram_base+$80 +.define bss bss_base + +; Address of next direct-page ($FFxx) variable +.define dp_base $FF80 +.define dp dp_base + +; Top of stack +.define std_stack $DFFF+1 + +; Final exit result byte is written here +.define final_result $A000 + +; Text output is written here as zero-terminated string +.define text_out_base $A004 + +; 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 nv_ram +.redefine nv_ram nv_ram+1 + +; Copies C*$100 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 a,(hl+) + ld (de),a + inc e + jr nz,- + inc d + dec c + jr nz,- + + ld a,b + jp $C000 + + +.ifndef RST_OFFSET + .define RST_OFFSET 0 +.endif + +.ifndef CUSTOM_RESET + reset: + di + + ; Run code from $C000, as is done on devcart. This + ; ensures minimal difference in how it behaves. + ld hl,$4000 + ld c,$14 + jp copy_to_wram_then_run + + .bank 1 slot 1 + .org 0 + jp std_reset +.endif + +; returnOrg puts this code AFTER user code. +.section "runtime" returnOrg + + ; Catch user code running off end + jp internal_error + +; 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 + + ; Clear memory except very top of stack + ld bc,std_stack-bss_base - 2 + ld hl,bss_base + call clear_mem + ld bc,$FFFF-dp_base + ld hl,dp_base + call clear_mem + + ; Init hardware + wreg TAC,$00 + wreg IF,$00 + wreg IE,$00 + + wreg NR52,0 ; sound off + wreg NR52,$80 ; sound on + wreg NR51,$FF ; mono + wreg NR50,$77 ; volume + + call init_runtime + call init_text_out + call console_init + call init_testing + + .ifdef TEST_NAME + print_str TEST_NAME,newline,newline + .endif + + 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 + + call console_show + pop af + call play_byte + jp post_exit + ++ push af + call print_newline + 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 + + +; Clears BC bytes starting at HL +clear_mem: + ; If C>0, increment B + dec bc + inc c + inc b + + ld a,0 +- ld (hl+),a + dec c + jr nz,- + dec b + jr nz,- + ret + + +; Reports internal error and exits with code 255 +internal_error: + print_str "Internal error" + ld a,255 + jp exit + + +; build_devcart and build_multi customize this +.ifndef CUSTOM_PRINT + .define text_out_addr bss+0 + .redefine bss bss+2 + + ; Initializes text output to cartridge RAM + init_text_out: + ; Enable cartridge RAM and set text output pointer + setb RAMEN,$0A + setw text_out_addr,text_out_base + setb text_out_base-3,$DE + setb text_out_base-2,$B0 + setb text_out_base-1,$61 + setb text_out_base,0 + setb final_result,$80 + ret + + + ; Appends character to text output string + ; Preserved: AF, BC, DE, HL + write_text_out: + push hl + push af + ld a,(text_out_addr) + ld l,a + ld a,(text_out_addr+1) + ld h,a + inc hl + ld (hl),0 + ld a,l + ld (text_out_addr),a + ld a,h + ld (text_out_addr+1),a + dec hl + pop af + ld (hl),a + pop hl + ret + + print_char_nocrc: + call write_text_out + jp console_print +.endif + + +; only build_rom uses console +.ifdef NEED_CONSOLE + .include "console.s" +.else + console_init: + console_print: + console_flush: + console_normal: + console_inverse: + console_show: + console_set_mode: + ret +.endif + + +; build_devcart and build_multi need to customize this +.ifndef CUSTOM_EXIT + post_exit: + ld (final_result),a + forever: + wreg NR52,0 ; sound off +- jr - +.endif + + +.macro def_rst ARGS addr + .bank 0 slot 0 + .org addr+RST_OFFSET +.endm diff --git a/playing-coffee/roms/cgb_sound/source/common/testing.s b/playing-coffee/roms/cgb_sound/source/common/testing.s new file mode 100644 index 0000000..5dd63ce --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/common/testing.s @@ -0,0 +1,195 @@ +; Diagnostic and testing utilities + +.define test_code bss+0 +.define test_name bss+1 +.redefine bss bss+3 + + +; Sets test code and optional error text. +; Takes multiple strings due to wla's idiotic +; default limit of 63 chars per string. +; Preserved: AF, BC, DE, HL +.macro set_test ; code[,text[,text2[,text3]]] + push hl + call set_test_ + jr @set_test\@ + .byte \1 + .if NARGS > 1 + .byte \2 + .endif + .if NARGS > 2 + .byte \3 + .endif + .if NARGS > 3 + .byte \4 + .endif + .byte 0 +@set_test\@: + pop hl +.endm + +set_test_: + pop hl + push hl + push af + inc hl + inc hl + ldi a,(hl) + ld (test_code),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,(test_code) + 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,(test_code) + cp $FF ; if a = $FF then a = 1 + jr nz,+ + ld a,1 ++ 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 print_newline + call print_crc + .else + ld bc,(crc >> 16) ~ $FFFF + ld de,(crc & $FFFF) ~ $FFFF + call check_crc_ + .endif +.endm + +; Checks CRC, differing based on DMG or CGB build +.macro check_crc_dmg_cgb ARGS dmg, cgb + .ifdef REQUIRE_DMG + check_crc dmg + .else + .ifdef REQUIRE_CGB + check_crc cgb + .else + .printt "CGB or DMG must be specified" + .fail + .endif + .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 diff --git a/playing-coffee/roms/cgb_sound/source/linkfile b/playing-coffee/roms/cgb_sound/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/playing-coffee/roms/cgb_sound/source/readme.txt b/playing-coffee/roms/cgb_sound/source/readme.txt new file mode 100644 index 0000000..a0269c1 --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/readme.txt @@ -0,0 +1,82 @@ +Game Boy Tests Source Code +-------------------------- + +Building with wla-dx +-------------------- +To assemble a test ROM with wla-dx, use the following commands: + + wla -o source_filename_here.s test.o + wlalink linkfile test.gb + +To assemble as a GBS music file: + + wla -o source_filename_here.s test.o -DBUILD_GBS + wlalink linkfile test.gbs + +Note that some tests might only work when built as a ROM or GBS file, +but not both. + +Some tests might include a ROM/GBS that has all the tests combined. +Building such a multi-test is complex and the necessary files aren't +included. + + +Framework +--------- +Each test is in a single source file, and makes use of several library +source files from common/. This framework provides common services and +reduces code to only that which performs the actual test. Virtually all +tests include "shell.inc" at the beginning, which sets things up and +includes all the appropriate library files. + +The reset handler does minimal GB hardware initialization, clears RAM, +sets up the text console, then runs main. Main can exit by returning or +jumping to "exit" with an error code in A. Exit reports the code then +goes into an infinite loop. If the code is 0, it doesn't do anything, +otherwise it reports the code. Code 1 is reported as "Failed", and the +rest as "Error ". + +The default is to build a ROM. Defining BUILD_GBS will build as an GBS. +The other build types aren't supported due to their complexity. I load +the code into RAM at $C000 since my devcart requires it, and I don't +want the normal ROM to differ in any way from what I've tested. This +also allows easy self-modifying code. + +Several routines are available to print values and text to the console. +Most update a running CRC-32 checksum which can be checked with +check_crc, allowing ALL the output to be checked very easily. If the +checksum doesn't match, it is printed, so you can run the code on a GB +and paste the correct checksum into your code. + + +Macros +------ +Some macros are used to make common operations more convenient. The left +is equivalent to the right: + + Macro Equivalent + ------------------------------------- + lda addr ldh a,(addr-$FF00) + + sta addr ldh (addr-$FF00),a + + wreg addr,data ld a,data + ldh (addr-$FF00),a + + setb ld a,data + ld (addr),a + + setw setb addr+0,data + + for_loop routine,begin,end,step + calls routine with A set to successive values + + loop_n_times routine,count + calls routine with A from 0 to count-1 + + print_str "str" prints string + + +-- +Shay Green diff --git a/playing-coffee/roms/cgb_sound/source/shell.inc b/playing-coffee/roms/cgb_sound/source/shell.inc new file mode 100644 index 0000000..0d43faa --- /dev/null +++ b/playing-coffee/roms/cgb_sound/source/shell.inc @@ -0,0 +1,27 @@ +; Included at beginning of all programs +; that use standard shell + +; Get include files from common/ +.incdir "common" + +; Sub-test in a multi-test ROM +.ifdef BUILD_MULTI + .include "build_multi.s" +.else + +; GBS music file +.ifdef BUILD_GBS + .include "build_gbs.s" +.endif + +; Devcart +.ifdef BUILD_DEVCART + .include "build_devcart.s" +.endif + +; GB ROM (default) +.ifndef RUNTIME_INCLUDED + .include "build_rom.s" +.endif + +.endif ; .ifdef BUILD_MULTI diff --git a/playing-coffee/roms/cpu_instrs.gb b/playing-coffee/roms/cpu_instrs.gb new file mode 100644 index 0000000000000000000000000000000000000000..7b06221b23dcd84644e7910bb8ba91a957c9ba04 GIT binary patch literal 65536 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 MfFZyT_;UpQ3qRkrD*ylh literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/cpu_instrs/cpu_instrs.gb b/playing-coffee/roms/cpu_instrs/cpu_instrs.gb new file mode 100644 index 0000000000000000000000000000000000000000..7b06221b23dcd84644e7910bb8ba91a957c9ba04 GIT binary patch literal 65536 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 MfFZyT_;UpQ3qRkrD*ylh literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/cpu_instrs/individual/01-special.gb b/playing-coffee/roms/cpu_instrs/individual/01-special.gb new file mode 100644 index 0000000000000000000000000000000000000000..ad3e9984f967b77b7ffdf768842ce3c04517d059 GIT binary patch literal 32768 zcmeI)+iw(A7y$5Zx80$Z?a~4dM`&TnrIv<M@$w=uCK3`2FB)GYyojYQGo9TnBOD?LkTIcgH`+#5Y&z&DBJTQ~SxVyN5AgC$ z=FIFj=klHNn@isc@IT+|W#Mz*8^+l>SX?y#V-STpsA*XJ+UpRze71LXdiska6BGOP z^v157oxXhJ{fY6rv_FZBU>pjr8QZ`6Q{6v4+|~eQtiEk&qx|dgI{D|M4Q4ZR*bx55 z&zvSm0wh2JBtQZrKmsH{0wh2JBtQZrKmsH{0wh2JBtQZrKmsH{0wh2JBtQZrKmsH{ z0wh2JBtQZrKmsH{0wh2JBtQZrKmsH{0wh2JBtQZrKmsH{0wh2J|1AMqp1c%MZmd?| zT}pr&#L%`X4^^m7ti5y zCSPXpCX27N_$kxz`37sD5B~JBzQxO?<@iZ9;}5Zde}GN<2iX^Xie2=F*?B*k#AZVl zoAR>{Ji@N}+0O0k3g2b=XYS>9S>EiDQf)k(Ta;7mkn3kz)X(CD#j(BP7oM_Wwfre7 zr{H98CDNwq!^(}(t|W#-PGQDhySA-Z?G6_sxIhWlGQ*#*f!Q%Qf?3pUPEsm&skr9=F1Tw-yRWvJlB@(WNuEg@IFD_~FOp-tK+7 zU+NjGD10`ls%j}bId0i!C)tmcR#n)lzi*&tp%n`)Gz#i#s~_!@cRaZ9p+}+6Ty1Oc z?H{UZB0IWv$Xm8TAp}dao_~I5c76gAR7Yhm=pT_fw`_qz1HeV^4j-Kg!+2OOdk1s% zPs`th^miIwev$uT*>76e(5hdnk|$cF`OeZ?vTr%OK64%1+4{_J8z0DHb~7A`0sjWi zZ^X)Y)|MAnB6YFR)vgtrT)s2Io@|D9rRx0~)I1g%H=eOAhGQp*We23>8 z$9HdJb_6FQ-oDnR8NYd@+g4g6-{zI%)>^(b<9na9-`%^vP#K7iWpKiH#8I*{*PGm2 z4X#0xn^(Bf@;f8)4Q(Z^u7K|yX2oXM;?isR2R!GZXx!J*vSCBRO1EjnnuD0*TH*Yy z4-69v)`;Tp19W)FTnCQ>a7>kNDq@ye<;q_>vnn+`= zw4Rbgtm=A7v`>p>ih}J8v5T`NMdKo`*l(T{5Lpx^=8EZY$KRr7B@eC_^FQ0GYrt@T zv|&KXFvO16bzR4ClL^Bx5=qRjr*s|X6`Xa@biEf-0@h+T15`16rh+6k?1z2&u`e!F zY-z~Nl`iFSIWPv(Y3wNGi<=tc!v;Y-n7Aj1F{Q|_ig6H8Y$6YCK|VuO8Q@r#N^>}# zp^B-CdNGBH3+7uDbXx@-V)arOl0lq=`gkID>plRKOkKGds*;A|2fVm`5YoC>j~3*` z*hpjCsHBix^ug>A`q7 zpooPB#^S-l`zQor7>hLGzru)a;szr?N<6CpK;=5l2XAul{KObjM86DisA72iicAJRtiVPC5ef0$kIf%WY58UCBiFHDI~ zGb`PCbAn;$KmsH{0wh2JBtQZrKmsH{0wh2JBtQZrKmsH{0wh2JBtQZrKmsH{0wh2J tBtQZrKmsH{0wh2JBtQZrKmsH{0wh2JBtQZrKmsH{0wh2JB=Bz&_#K5wPf-8> literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/cpu_instrs/individual/02-interrupts.gb b/playing-coffee/roms/cpu_instrs/individual/02-interrupts.gb new file mode 100644 index 0000000000000000000000000000000000000000..20895940ad31ca0229cb4ff8bb7c7ef79ca8db10 GIT binary patch literal 32768 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 literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/cpu_instrs/individual/04-op r,imm.gb b/playing-coffee/roms/cpu_instrs/individual/04-op r,imm.gb new file mode 100644 index 0000000000000000000000000000000000000000..58ca7b8ac3c2b16efc9c8e401e790671c00022a2 GIT binary patch literal 32768 zcmeI4ZEO?g9l-x~k|Pbqu|w1yAuneVtPr4hD}zWgk1{}@y`p91iI-s1C}m)=&Ci}PS^)m|6_7tDiNZ`%vs1o!oe z{kL!2xc1Vyb332yci*{q~E)78){-D{jD zeGra=>Vuv*I6wB8Q-VZ*2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F z0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F z0z`la5P|t3>m;#d?jCJ;y z(NpT*EU`-esoHf)x?9lnDOH~z(_3PCdrUv8YL>nv_Rt1z3kfTxm(`eNg~hCOK+ISN z#T(Wk@tPG8-?WCrD^?1k^xKxD-l*yzZjSq=?kNHK+9yU_V%t^wK0^d!2$)W zC96Ld6P;zq=)J0jxn}io^-Is}-MeEuWEjMs8!uKH!OogBoj1jLKDU0Lf5*d_8e^a+ z7(1hG%Jj_o)`E(%V#KuN$4O6#bXy?OM>2rSBUr#FwTmp7AScq2w?mH!p0%GrK}Nw%0)h zkvx>7cI2|&euXTWYc*Ih*JfzAs*0^xdYiG+J6sl^>G-cVwl^OteOR31&o=GqtPeOXaO2HrJ}~XrpNYEHs`Z3v(ffb)Do!P z+_fG^n0+B z5|)BxFz2h%{Rg@U+jqG#k@o{&g|{*V?UW?oZv3NIRXH*&=H{Lau8qe>PJ04Q^gX@R zU)#nvetKcR^RN4UTbS?sYkNEPbnNcfzf5`~-m4DehkrHTzm^|4-eC^sNAWt6AH(Zt z6i*cWujBfb^ZKjfm2Cr%@!d;w<)$aQa(A&8C$o51+?!-uax;^-Snf~aYSfn|^^@bB z3o0fK$P}k1^_yxbEeXZD{4O(8S}L85l#9$_8(ElkdIBLL52QCyMV)>fbq^iM0=vqSBu;_VBs}j(Shq;o*Gw z@Zp+M-x&S=YewCKwf@_yyY6kMDgJiFvDoHKU4Obf)p$0ycGVk4JAV=A{mHY*l}~-S z_D9V>aDQ%k@6{uz>&dF$uk$4wt@oEBRy6qNu722&r(+5wdJ4g-;aBZTj|*pCX83~lE@ZY zjASj=z$9OjAvAufMw(#@vAPWI=!!CGOuS&OEogXTwip9`|B@x%g+@!$qC*&CI&br zlwdii;FKsv(P5Li#x2(j$dNKZ2`(*|q zI*2L?L==Vp@VP9@=r~cUaTKDX_?o>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0{;a9e+RrWAo2hJ literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/cpu_instrs/individual/05-op rp.gb b/playing-coffee/roms/cpu_instrs/individual/05-op rp.gb new file mode 100644 index 0000000000000000000000000000000000000000..1c19d922f728bd11dcd8b289813fd0409f438157 GIT binary patch literal 32768 zcmeI4Uu;v?8Nfd~$t48iI3#12{2@0ucp<>J9fC!gIm##kRUOS<)=E`rZ#B5I!%6~Z zV2I;7$;7&8539O|@lu8+wVJfSkhbwpo33MQb7w9pT|pPENv{WHnnfeK)m?)L_P%o+ z*w$`&m^7){z9XM|&UeoD=X}3=&bfIC!2fwkG`p{UpsH7v!jhVUFaaJ|3iVB$uYM1_ z?_C+ZGc|Sd_{EERzd7i=du8gq<0mej-^e~eEQYen8B;yq_{7|2Ufj_Hn%CF6tX;a_ zvQ+vwoB*v0`V-*(%&(jlNCb!g5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la z5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la z5CI}U1c(3;_}?OskS1@`$#b1DTy?#D8T7N4ZT7xVqZPMEe|3b4u}ov{R!54u9Wi{` zD3|n4bI?U#6)R?qS>x7(ZYQtjx+W^y*FUxX>Gh`q^_jlRicD*#?0X?R^TCcIGv@}5 z&P+TrG9#^)ZU-u>4y@b1Za8}_UerhPZZndvF~wS@i*A<4yG-3KH(YvFGh8Mvfl2di zgVSb>wDuQUtTEowde2Jtin=kQ8O?E{BX0QP#sy8cjWzLyHrOpD?YL3V;<_CcbM_H2 zYabP->=E&%9TBhFqvAC?8Ai1uaWQQtw>~L;XeYPr6z>|(YIf$!#>RN@PGh;D&z)M7 zk`pe|PKq8oi33Z#yU)M7EADMDcEwXNE(S9ZHq^c*&y5X)Q9Pn&bGF~#o3AxT^L3b@ zfVt$1eR0uUfvmAp({ZgiV^aJ2iw6(x*$r6+iG7o$S~J*Fx25Nn*vRL$4Gr#jI8$d1 zl>~Ed^lZ;!v_uH_MD?=kDmSzXu?==%4C^@7i48pQNA(uIzC!Gxw5%|8L0VcEKQGl6 zCeBGUi2=A{nHwHAD)fk-`bM}qFcZ%`zOHMB6$uxLq_cp~U0<@m`aAY6_fMY8{+@er{j?R>Vg;<#RyQkR&|_B1r|{{?0ihnvd>FQg&7wOc(IAmY21y*@|1CA zvS&rr#0A;X?6h%9D`zC3bdUG4qvhq&g-AIi{W@_Xp0gKPL3VVZ+3-S>M;4kqy3pjv zLX+42b5nf@KpVhXfEs{i06)NDfE5610K8zVO?iF%Vev-+A9~lW)bswQ{W)y$5MEC{ z<=^h-1I1`IflwQhcn~39mp5Dpj!)d=(Tc^Wwjo)lP2y z>F+*wZtaQJQ+ zn+6Z#QR7y|?KCbJpXeo-D|MKuddz`IU9G0j@})XyjHQFkg)StwDu>hz_Ev3K3Q1KO9#S1^tFYs;Y*<7+;Af3a-m(6<~@oh#>)I z(M*LPU%nUwffuwxn_;xYRC$R)YNmW6l}dqnC>ljWzFsUrCmt#|@}baXN5+smevtb? zh`jJPSe$rjFen20x>24%e`=5~ozL?nRJmZhm5$j;=;zrBZU{Sa7<_%9)T8$RP}UTA zUJMEh{X2Luf8eAQo)2^4qO3+yZkHp7o!j6z6Oakw!uTTeVFY}SNFU2E!VB^I`y4Td z`gb`1Y^(a*AJ4xkM-;~&I^;6vfimBC_#Qa{FUmX&|F=urjK5$65aD~(0Z<*s_25f( z_K#N-Wp3w$M=ryn=)q;7p7P-v)vb$t0OM+k|Ft1+T0b-_9T;xle}34Oi;Y=so7MmQ zTb4MMHl9x#8#8!}17i8nxpAD(g9s1-B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U z1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CJ0a HUn1}~7IqB+ literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/cpu_instrs/individual/06-ld r,r.gb b/playing-coffee/roms/cpu_instrs/individual/06-ld r,r.gb new file mode 100644 index 0000000000000000000000000000000000000000..d497bfd1275361bc847fa94dc87b43729f180b5d GIT binary patch literal 32768 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 literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb b/playing-coffee/roms/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb new file mode 100644 index 0000000000000000000000000000000000000000..5c8d20bb3a1a0458f29afe40b8d3ca125b13a391 GIT binary patch literal 32768 zcmeI4Z){W76~K?3#H7JEf3-bALS7(vAxefRTZy#StF%K!)0(C02P#!X7!@8ZwUQ7J zIK=jxWa_$U8m%_9>pqlK>)0P{FeYsjvJZ9~+j%pOkq+1ck>t4$P%WC-pf?f&_Rf6{ ztm~$Hm^2Ah=jq*h-?{hvx##!pz0aR4DPUgG|EVo_e~9Zywe`%$vV3|OEG^m#!(fMH zP+HmW+IPWm`E>WS$;pdvkBsbkvDpE*sC2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko z0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko z0U|&IhyW2F0z`la5CJ0a*&+}TM=zAP?l!pKjOFY&C@01ZHmw$gGLMV@FonEY6=LTK zJsHLDsTCnVmlpmn;FB~I0a#uS>Vx`_KCBqg^D~XZ`KQ{x(YB@SsIxTDo>-o!ZE#g= z^Ui*}xqtTku7k6~&kf9qYs9P0{HngzJ*~aTiEu{or>vSURip{UOcAUslCo%ukt-W} zKBQVSoB|UU+A_0Es`1cAhESybBGht1oX;rgY)GvRtMy@Zbyz(WQVex%_`VH3W}-$| z&4=EBKfCbo6OZ@BIbc6m|kSCTWv z>eVf&V$Gi_!31rX%Z&O;Sg__Hsdk1Goa>A_8hU2?-n~0_K$1b^mCknMDrtPi4d?x^_=AlH#E}0bw+v+%Mg~~bv*G0uk(1#T%yy~wxOiK+wLU+c0*!&b1WP%VJQgPlV#(`k z*a5w%@lZnjVMq;x)$WAa6*5?n+7M#qx>5N`QGYXL=7OFKb^mB4awK8!8NVCKJ8ei%3$6GwMT!qd3D`D z?%Sf_MAw!Cw%DDAvv5$72syrM-4D?E4Qlgaa*<&5c{L!-~5(Q-7fidltK>J_G+ zyk5R5$`SNMor)O!a&*yfEiv*W|R}64R_;Tg`&cdVK4{x ztZ{8TK2pjT;aJymTb-p1eB&opxGMgA-*5Bsoqus}Q(x1brUR|w2a(QDPdfP99_PjM z;L#>Mm>$Ab%7I&tYFLiec z7t6gVT#f3pG4=Rp^YVg;1(MmhDfLPymk@2)dH$62=bjZ$`EoJw50S&+8Dp^-B>jtx zdKVk)Uu^K;VuJ&V4Nm;;rix&Za7{N`B$q{Mw@9P@Si~Ruvp@EMKX%C<8}r9b`(vs< zcFrHW?2m;c7?(hi;H(6i1Q#WU1N;TxZvfK(*8%e<*9WI7?@XPK%uPwRO0ARYd#BYyV_EAYuZzq&VD|XU+Go}SW(|`) zS*-%_yt(_W)Rt|JHpeHvCYRg}mY@2;6Cb|!leV_+_x-$j|1WpsUiuS%e&)@l zFDzD~mX&vkyvLhu=}o!UMI)@JSK{W`GBy;)Z_K`UBMdn}9l`c{jy$%kCba@j&s3ep z=2G>Vx^`T6p%H#;E5-$Xx2Z&&whBCVUS{9I0{^{~h?C5c>cYbpgP^~zyi(ac zZgWR%rM1<+TD1y2g-b;II;!j9>O)i*~v$IY%vy(6O>!_R5(S`a70Kd8<1 zGe+YIttGcad?b?-o6FSY<3{F>ZC~y_fSa3JsawWz!dRo1=uEa=i1(J^^Hp;Bv=ag}TS;+I!@$fZc94^&E9*SLcAg#qfCJGq)_n!F)0P?rt{&0UZQn8GN$LZ|rxw z-RRfrk!9KA#rSTY+l})wTQ@MbyBk9Sj-r_i5}&>xfsJ2ihc>-vi>dM}0I}KJg;*>G z^8P>o4f%ZWlFWFhV9NVFkC`%th_mC(Vnw^_k!%3i2w_rxB&2Y_72?OG5e8$8;11Q(Ie2AUf zU_Y}V6U2q_1!%_z_#TlyUcCs>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&Ih`?u;z&`;#5pP@o literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/cpu_instrs/individual/08-misc instrs.gb b/playing-coffee/roms/cpu_instrs/individual/08-misc instrs.gb new file mode 100644 index 0000000000000000000000000000000000000000..4da139be35c0f82db12136c2b65b2571122a026b GIT binary patch literal 32768 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 literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/cpu_instrs/individual/10-bit ops.gb b/playing-coffee/roms/cpu_instrs/individual/10-bit ops.gb new file mode 100644 index 0000000000000000000000000000000000000000..8988458ea0eff185d7f3bd78ff85bb7717edccfa GIT binary patch literal 32768 zcmeI4dvp_38oi~z4V`7uv(jXK=+9GPz|xr1ELesUV0gf$D8JyZD~1HQ(r%0 z>KxFJP~yPgDOtxSn`lru^G zR}QiOQb?^-m#I~1wXC_nYE7>WjUGI9@DqbS$cn~1?2Achrua$rwhIM|+jbQ#X{&y+ zv`ri!p2-Sj%o{X!P>J`5Ga#4y^qw+bghz;!WI->vd^(S;1!MM2a40$tr67sd`Gk<5>&2e_WqZUP{;J(# zw7+_n7~v{%C?2P4w=;*T?QZ{2yd<~Z9Xja)j|A#%)Z(axsAWl&)I#b2X_1+ro;=ew zeOe573CRsf>`z=qg_v)^|quv(Dl; zO$__#klm!7WHjZRip9CvSDf;8HS0b#OC6wQN&yP`qMGSB54TnP(5v zrmqj5d>3pldU9e`bQ;_Em~4vw$9+G^&UXH>`I+-F=VmS%B!1y4bQJq5w-sj{^Dp}# zQ?2w@(S4b}n(kE<^o^o?*PuMJPubkiwQT^r=~vx3zRM?ad{?M-oM@#li>oK3WZ$(D zv{*V%&}vlL_bVSZWcTed(Sf()@(Ja%BZ#7*;|d#j%Y*IWp0eP6affSzvsJrQ2Hx^p z&Dw7@x%gI-OKvq;daKDJ|GBA*`G6w;M*`jtI12CqzyiRZ13m~i2Jj)khXD%#ivS-1 zd=&68z+V794mb|*Nx<=dPXSHZf>z=?p904;!%0jB^?1)K&r9dHKVvw$-JX8}G3 zXak%LSPWPKI2Ujp;C#RZfX@RiqFn~K7;p(-DPS33Ip9*jO2B1+Re;riF97}u@YjGZ z0lo~l9Pl@QD*%5B_zK`kz*T^&0oMS&3iuk}8-Qy8YXE--_$J^wz~2LI0Ne=p7U0`} z?*MKB`~%>-fLj2!0@eb)2lzhVHo)zGe+2vx@FT#F0Y3rU0aypP3vf5!9>99QPXRvz z+zaRcbOJU2x&Ze9x&ikC9sra94+0`!6QBZk2=FlA5x_41zXWUs{0i`EKo8(Cz;6J* z1w0Pe0(cTo1@r=*0z3_P2Cxw|g&n1$rg&VBpGYWpOhG2GW(Zp$cs}C*x=*rfWds_@Urwv%HW_IMi)@#+|H9cl-F?Ro;^JG%g_jh+11`X?dEv0+X@QrcfT0JSVZA*70 zZdX}{Z-^nd4ju10PJbWo**XnnlKb?B1pn9UCWQj{G67=ll90FO&~z_{saH9;#LEer4muUR#Pv z*XsKWZ~kq!@tZ=U58fVKYuArCG|#(b++#0a?mRj+-$w$+odVlfsz_LJ8}Glb-SyS1#Vrxs}c4Ovkm@H z!Ih%sl$Fo@;T@O`RkS7MP`}(5qFze$CDVb&m*SBw`%*p1aTK0OhqZ=C z`i;7l86{q32Y_b{@pM#}DJ`W2|Gq_uEt1YxMCT<6LjH`LJ zKyQd8hxuT(&RJ-+nn7UpLIufeq5>*ng;)bUk4-V#DkxTkrOd!&v&B-z^edQ}$wc~A z_F=tbh|Obh+2?w10v5$2uv{rTeQ3-qyW&v26o1wniv$8WsIXe0%xYyHdTy~;$gkaI zwOVaZvn|-nL{CgX35M7W|qES1_Qguj%-TEmP%z;1vIt=k2W?of^}g< z1sSq@=`x4okwRErX!~hcrjRVYnfW1z>|$}~3dggW%>t0Gqro=vXEn3b**r_3iwni; z7dGn$`K)?_9_(S+4)J-m#+&Z}An34|u8U@aME=8gseWLjEvz0X9G7Hk1<5I@i-maso^$YjQYWVZ0= zJu(7Dl35t`zuA}>d%*;tjIGr$fUf715545@`Y{TEiP?q26PFS}h^4eh&vJ2n*V3gi zfYN@D{Yi4SY4DVp3&i4*`7!LT6iwAh%T&E4MgQ%{>v|8BYE&j1qK%v-1aIz~up55k z1ULasfD_;ZH~~(86W|0m0ZxDu-~>1UPJk2O1ULasfD_;ZH~~(86W|0m0ZxDu-~>1U lPJk2O1ULasfD_;ZH~~(86W|0m0ZxDu-~>1UPT)T!@K+QR9VGw& literal 0 HcmV?d00001 diff --git a/playing-coffee/roms/cpu_instrs/individual/11-op a,(hl).gb b/playing-coffee/roms/cpu_instrs/individual/11-op a,(hl).gb new file mode 100644 index 0000000000000000000000000000000000000000..0634b7fdd19bd62c16860ab1bad05760d6ac5965 GIT binary patch literal 32768 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¥c;}{QEQ%JA?wec_EeE@9-?l!#sbNoBH@9> zf%fHJ9dyZ68)}QNUb*gBScs7szoj~A-j6=T73ahaKNwF9(6`L!7U6Q|!is}A+-8(n z>emQILXNpQTXXkDTD!tB&Yez^PRYzbzIpBsPnA!g+l{6Gkxo9gl#!KdVgsSUfPplx zzK!gz&)7oXex_w-Z?=>J2w2g`CJSaiy)r{VQyskhp05Wu%{qLDVv)P8Li?KRd9T%g zufc`i9mSRA-fO==h5pt0{r@`ua{Gk%cN^h@%~%@spOKmGe;k<^XBeCxtOb}{!EG&}JnGTuRY)adhMdlj!-U^k?pUMrP?-uG@T0c-~^_DiH}+`UjV%}+A@{Y zbX^QS52qtGqWp4$T+`@1z!&FTMQ zum9^aeuqQ147QwVgnjY9sJQi@0IqE!qKDtK#t!vdr~2!%&d)4{MoegNz(q}S)(i3e zEBfSi z{#B6xOo%!c0I0d$$NL~S;%k_TgJ_M@!M!+Odxmr#4!+fD15`ulug-KSHtkqC0 zI&(L?PHxYijs<6_XK*=ND|w50x2>I|FTd??-rT}CS}ys&kU4H94bjH2t!1wzzS!J% zEyqTEsa~~Ecj+*_;5ou_;EvxkVt)l%uc{8TIaF&vgQ81^?7O0%}66qvvHb8;nC0XB)IliqPHK&(VSXO|)bqpNpkAIiEfKnKu z+3HF>_a`jjjRpF@*Jv^Xh4(0dKeF(kttE4$|8g3oRynV%%rqZNt(;UnIW1RW!dg4} znSwVfrCGMTNYqy(9g+T<5qJ;7fX84U?=v{hC$TZi1bir#$R%rB)!?jdQS8G0o;N*I zsGBQw31?-D+f=v8=yAn@iM)>7;Z5-LX`e-NL;bL{-OB2)$^LBh9Hqs`>e6V%1bgO% z>N|7Tf;;XaaZ2*SbNA>?4pXU^r_IfyRL!mKKx|+)cby(AI@kl-Nj*WXA$P4r;Zz45 zrr;hTm595)O8Ax56MXy5b(s?J@E|SyCli;$+x@;RZzQeW;gp_oUv^pAgP`8H?z@l_g&GLI7j=oR1JL+;(*(NBf<^v6VF)K~A zBns+ySO;$~OP6lOIVZrJX_oLEPlK3eL4NzrK2&A~-m^TN5U>UK+0-bjwJA#%?4r2F zX9{;_*NG>HabLH?@+bYd?pUMB5e@XU-@v7!PQ7b6z84SB6nHA>(j4*O;;n`+ zEA=5cR}|2f*845vT|6vPH+nH1>)I4DkT}6~?cS%3vvV%q2A#ITW|qlR0@(C!`1!?b zwq5-;Y(o;5`JvFr7$X&iW|-?jE}Y`|aQlM7SEi><1+9p`Chc;AL`RmCbRt&dGaF>H zW_w=JFQX5kvSmLEdXC$6l;zE%B(`PlL*>OQ%)EiwxdIXMLn%G(#f=URm$&wZ?#M|8 z^gZd9!4A4$VgLxl(10?sQ;2rkbc&mw>Od?61M$00IeR}vzLH--e+2%j)M^GkathFE z$6ND$r!2E0t89g3aa_hnRk*I)cw?u4bOk1GWCyTgaZ^^>!w-Fl4Qh4G_C<~T1&9sj z)Q^NYI2-Ty^Vm*E49Y_)c|hN^DomoD8vYc`t|#KGkpW&#U%nUtm+m|~C4xPfZt%-l zUJpM9SV{>nBOGc7MTozvB%ri36nRi(HZ0ew5t3d=OkY%X!;G?j189%{zuS%71p7$8 z6bL4)?4dxv=Etd;lmw)Q61>YEgwHR&H1_RNh$oIkt$hj>=Rp>meuX_di0LJHFO-iG zyBbFZ+QjK%sKK=p=`{#o!nfaO@|Br>jh})yTi&FnY9x&-5#Z>rE@R;@{>Al#w|dr^ z^GYxek*b`er;$~a#jdS-Rr(}v8}9{!Vwgj_B!ZtGX--G9-U@2^hlzpTVM+#_O=mog^^A>Hj6gGy-Sg^` z`Cbq38kndvLw)K8CuWr0Kd5NwLFKF10wa62uH^g$5f191Zv2A*Oh#ZZ|tv zE9+WR$p=%Q;^{MsS?PQ{9*h)|5%=2EUg)}M3g$g!!sMcPp&CW6P?Z=&oy#SY7^#)d zqrnAphRju_$CTGmro)um6VF|^{K?(-##q$(tfDNN)#-XT=PK)YGA2(Xa$qJLOOyP=;L3aQ zfcUQ;^ln%8j(hSQ-*Wc)igwX@7_px9#P`>GEpEt5=YH}p?+N)bi7kqQW<)HTR7`W8 zR8i8WcdU81VvT!|dFTzy31|FiL7+jO1z_9KMcVHNaybfG1rG-?LoOH&9|bW(0D`nj zLf}o&Ulri06|(i?rVt)2u$fR$J;dS{HRMeW{MfJZA2%iNLdfepNqb!~!?eH4ZI-ZQ zrdXm-wq&hImJr+J@LFcen?iKn!#)H z()yf-g)gUI_o6+JR_x;;hf_b_>stA~fGzpv9T2aJoIdltvDcnzjnT}L4k?KSR;4{u z{q{(o#`QipC#zpos-4jQx{a;n1|CXdQw&Q6O1lRq0W`~=_qdbm?pWXInqT5?OY-8M zLnb$=rjRAMMu}dNr^kIhl0XZg%i}{$2APmj#O7G7_dVkZ1TKbFyVgF9B_@6y^(LV? zpF>-6A*j7PUtf=MX%SJyP5r4PkD|nDh))G`;p_6i_rUpo*n7v|+`nw!cddBEwr$(C zZQHhO+qSu4+qSu4J6ShPc!=J$Zg}eCHT*jPD0)eQ-^Y z|M*ye)o{#M=j>kXGihMka&_|RU$8jJsIwg;1h{|cr3InQ zOynSwi_+u*&TSMsX}hK17Rp1i%NhfY5o76)XJyE*7~$}-YLNEVB^fvvrDH77>H z;Khz2J*O7j_~lm;?)dhS)JBEt&*65NtW&lhY9OOc2ksl-&B|PhuPAO6KV52hHXNR& zelB)%>pct^YvA-GsRAXMYEzO?EBk>wntR4{vPWA1tRG;>R!$kD3orzC-@3~wu) zDw9x?F;yg7kzO8DPT@!ppT&EBDYl_-t5G!Ni{6l^l(6kqkp=%p`g+?izwCVD~b1j)8TBCp3vt4^lY;R5$5GEJEs7r*S3Z zCEV;$d5dYX?e5YH@s=l4ApTQiS^oauX9M|=cg)hHGQNsY7kVw2kHjz(O5-+^3|K7ZDyuC zoKEqM5>NI)?a6bI2%GvC`jCWfhRm|piBEqwKIgh;d(0O6t)`_Ahi03xTNIySPFYg& zs;2i!JrwF@9ysi=WGuV>o{M}z8e!`XCJLkB_Yq0c2muJlT<1fk9Yp*6D>@#3Vk5(eF$j@QjX( zj*k{PpfSVyD{dIJS^+2^>~&X|-)8)aa*w`@jr!^Re8cITug(g~cj7ruYe{hwS%jw}D82f!&v)F^@O5n`Gr)IUw;{Jv& z&ik0yzaL~!SC0vLH~7r+qXN><5q6%D_js?*O-X6ihOp2~oz(wkv9x@M*#jxHJeZEq zbc!CzU;29h?hjk6YP6oF)N)*qUrBrV>{%cej+DsAkLkE40|=ji-s}szCwRx)e^Tv~ zoJa%h=n~)n`rMkqJ@D6gfBiFkZOc4Ol*r{1N{he`e2-CJIFXp1<|*0N9B-0v2XkEq z)Wk2<9{XRaO@6B&cSj9V5@-m z?X8}R6%CZ-q4e}wAcP;~fmaKY(=$3BKzpHS(u*}sKL$87yx|lvmvef+Kh?pz<4+m0 zTZ1=X{dwsEYrmEnHOcmCYI_?C|z+4Qrm5hEOR$Ws#xhosz`<6*8rQBmkT$JcAv@^FsR%)#3PUr z2%#A1abkaVA>v*^OCZ;#$^)FaxI8H;ic>;pzW&A?wL&^lWZ$t2PJYa82vlT!Ir^?= zZ+t!98{D*++SsmW#=xd%$XSb&X>}=29q_h49zjhO8KeR)wrI30(C0u`MtQSnkTMv(tvGS%+@2*4l=0O;K7_ z)HaC$=P6hk=_#;0*3#9bVXR*c5wq|NOI4x*{H|^n1<$td+gb7NM|sxzeJGwF5|I$4 z*Q)*E^)Z5_eANvA)E`N1X`rvb;%rqmIi;Jtwh=`|*;K)hTI75&WKG#Lh&CbLZbsLc zB9vIvlHEcAe72^}KL-D1f|q~!ZOu&D4j_XT$o(;;oG#-+nJSUY`=H3CQd0W$sa{Rk zyl(vg_f1P%+f=%i=kp_vIyIDDKC#iFd?mZ6z8UI9Lh4`&x<>0wZXc9f+VROp6c7z5 zW-eTAn&I&b-rm&J+8Xsu5QFG5Vw-F+lY{4EL%97@nQZxHM$%+homqW4l*)EDii)tL zn`3wpHsO20dYm2#jN@#l-|PUR_~#fHdx3zHYuJ+_=e)C~!miu+rHB>>P3HQg?k)5Y zg3@3>QG3`sX8*=K`N_RUM>Vv_Ot^WfG1Hwe-u_M>j^X^E)bIF=EY<>j_hW2`Wc{5E zGGNZ*L?;-~M8D%0X7Y8aA-YU&gy#)+Rdd0@gb~@sWXS69?&*bUE$4QCMe!6?>^(PqJXQFr= z;F3Gd+mR(25eihc4`Yl1d8<^opSTMgh$N>%)!qHFQrVUA3|B)dY$O~G6>Pwje{%_Y zkHQ8pn+7)#gHjXAt}JHT5Ycu#KaO!PhX+lOA10JbepS+2+}%j&itS?rMiY_B0gwLG z`(*VXC`65*)LWNpv{nE`;jNFnt{I*QCK^GRKuovGI0$|&_Q0$Xq&@E9DS6X#J*oLb zlH1;u=%hH$W#q48{uFL_;hP&kujZxnxSwdQGzW7&NKMn)T)sH(^Y;ZgjYry@yf25O zhOge9e`R(0hwVY?-TW z8Za{3NCLVc+|v6@*>7tp9-gYQeN}Fk_Uxa(F6^_^7J9fb!$N(gd!UJrqIE?O?u2U- z!d?hgRtJ&^Rt5*WqjXge-cWlVL5?7`c5Tn~FJYb8UFd9n;@(jo!#FsnaJaNW9J`NZ zVObOe7XIMPZ49~0IeEOPw^YcT<3rK+HR}M_iIy*CM8o6%IWkW#`#H(n?-|en?wI}{ z(39^qksS*He<#9E*{|Pn&Y2!;G&$}6J* z;aw$*BITXhwmu!zTW>l{Trd@%JL>t0LK)-)MS@#+@jQMPp#-ha@{RqWFrktM=#Mq=b$5*f%|9PorW@FLt1Yn ze`G$8u#;eI9#qL}Yb5-s(stMlwrPVFa;s3GYgBeZ`k@pr!={A)Itp)Vd%F?RmETh1 zTpKB-ViMRDdw|ybRxOznf(H}g;7m+jN^`L1G48-^LSYPhAAQ2bh?EB&vRp0y2WP&< z=ys73MIm>isuY2PX<4SG&aa=~>k<+%s0fOnq}Q3e+1qH^AF6tcwP^c(QVY=fxfPkl zFDns}Ag|eE%1DNfl?VT@0o12VekFVfV(ph8{wv_&A8vquOo;z;2V^N**{spRerD^~ z@h2^X&jhD5V<;+UfS(dFN?8?oMMfHkGw>D>Q^xCe)+d1|d+bQ%bhMGa>v{Sx=~C$&tZHg<8mu>?Rh$h(HfE zrNHMFCQa39m|3x$Oc#nL>dC~KQiV&474p4w8jLj#_#MAvV299zeM<-l%qy+Z_V|tp zI`f{UKY?^^TYE+1Dr6LO=ibiQxF1BMmPRK-ci$sW(@d2I6ZhQgC`d|jB>`rsX|)Hm zVb!xXd-RdB{jOKC`IrQ!(x%R<`|ZeY)sg{bUBsd|!Nix;5=1)MZ_{{(B&6>6JA34Z z@lIAh>-9IRp>gA#&VJX!uh*6*d+L;uMk7>)Zw^pTW^a1+?oXa~Xx$w%StGHrNT3Tu zhRga-X6;UCm)O%g361g5KzhRq^Q|coZKJ)&srK4_EB43lesli1=6TYBk>8xLwpi;y z@T7DzRb%(~)DS|lFbEnhF1Ze7_;JS4HZ(kGHFz9d~ z))~W-+6N7%n36dtPZkz6vWL8tKmoa&zLeBkOAM~Wqw=&scYg5f!wMRpCGh0Uwj%dVyGTfo{2VwRV%m>!vY#|-xz?hWk4 z8PV>|c{r#Pjy@+b4G+j0LJ>k{IcNkW=yTCi{3WhZKQDFm+&wazv|UD}6ykOUj7A|= zDAP)5L}Cpk!(o686O)a%?r80q4G-sf+C?1{ml;*GAsF?ww!p`g?nv5;bm7E=8Qxz= zh>aQ4l4>ZtO2WhM#?wgefhlW>c-K4YNBs|i+L+|CXLH;UEvj^Qg*MBIl=^V;#NhlR zridNsvDCc+RI46U6+6$$W@881XZMbxIK)~V2?}In+mFkg)NTbARf~!np!kf*dD9?5 zsq>2`jcEYd4aFD9`%1eUXzD#x8r5ef>5C+CHO6knGJX|z&sWY#Y)AW1g1^^P6=NRh z;kNj%k!)RpIe;nwLdsN^EJe00J7JI>d%!U{^s%lE5nc+_NjFyaVMv3B7GXulV;A^~nnkt_>ol9vPyR%yPZ~uqr{qvdnU-7;FXwUzJdigK--hOGYB{W|%!*f6W zXOZMDtNsz++et~oW{(}#`)hodyl_)VOIp`MVfBuerja7a3YZJ#_(yA)K!?Toh_kLn z7LJc`JZk)m6h)}Xki*MSh92Z{km3(p(5GbxRTSx=SaBlv&R+G(D6fp04$yl-{KKGI z2qR9xq>-en85|)wAnC4%)22L3{@=v~m|$5GBi&4A7S%pf=;i7Mx`FPHq)omrdqyve zS=5)>xjt}tn#Jf!iC4Eq%w_(-`|4mysA_8u%XSY2Ii-$h(+O36^^-wXCpnfus5rcRIsif6L(Y|cE%%4#g5q>a$Tq_l!Mxi5|vn!fd;u~T)Fft)xnjmRtD~XUK72;Z`BZogv zBIxbGx=0g7G4h8R7P0v<+Kh|KS624#JVhlVZ@QO=m=w%B;~c)`f+H2(HZ8U`c}h)^ zYTgJJk7)quQR6{pp2|_FL4AhUPoi{HzmDRCf9^BS-2zhy$ifgLP@sw_zo;oUJg#RR zHlNjeJUrUXp{&^?8f6IkDUf?w|K!RzP#z$K5EG1l zrhms3;3iRdu&RQa6hfrK(|<^ajwLRpac zeJV)FCSVK_OHV$^&DF%naB>lTA7fn-QDxY9_=Gn2kctp`3TQ&enlgGP@aRCv+*)GH z*gh#~bXQa_qp_z-$=LxC+$13#g^>ubQZxvO9U0J7UE*FvBaf?>aO2x{wpr3@lxVds zy^*a~|ITA{Z@)oJrk%RM!}~!{|FzjZtSfe|yK7+g?j2BWxKWXX{q%wh9-Pj&y~f6dfm`*CTA%FR)TR z?l5V)XP24Hry1#}1&WgsEf(qf=t-=0gux~n3D|`yr(kfqSBL18%5$Pi;$^9>)bx5jVU#?TOm9e*Z_5`*W)AQ#3?T z^>wu8`0|1LpPX#|E*||olsrlCPoyE(t~&~6&JBLDz=6861UJ@6!se0{vlwj4B|0cv z@-V}bnyc_(Ao-RobK|&`kH<2t$sVc%#$eH&Du`Y8GBe{~wEabTazT;~S(tTcmJ~eaU3z2)1Ii0s}-cdn6mm z!2W;?s%_a`a8oHLZ=&J{NM=H67rkwSHwW2?s>+G*oe0?3LTR0JrV@1hnoIcL#1w{3 zl5>dngZ%TxjcedNKMGLUgnFJ4PMRGS4~&KY&Mo@q(znq9Nn^WlL-E;hG0{S78-f%P zhso?`$rqWe*^89#uYOXjEx zYy76>94j6LVzsKR(Vb2$WPc)PXxFGvnF`<9e)8wej87EkuOLiuOo5bt900p6kYpvw z2bZ-~jukj8eWG2wb=`?ixJYa70|Wd8etQ&jp8i=cFYK3UV+z)X4l4jF`Cgz#ih zgMhg-hOaR==Sbio?JMTQ2QFsss|Bn##8oq&$}9&t>2v|&Evbdgl4LZaYdMZf#S8t3 zj?0AyDJ>mV;{?Y=77pc649q(AUSt^qDO1C!+*!^daWK^GW*g4Oq17_D`54HBjSicM zK13JiR#0zr;&g}*%m{A0UT{`fhfRdPE6gv9Ox13*lfx%a;VSWdCd?Y@x|i;}-*lHg zY4=OJ54ptV*EC^Id_N*vLM$En7>ht7%@Yp5DsZn|w%nW4cz|>*jN@_U;wxhU`_l9a z&BU@tuxK3844=#Fdd)7I@T1DJ#ww;Q)p!1CCidsu@ZSNO{~OK!zhLvemPh!XgD$WC zXMoKIoKI5!1Od%M{GZi1{}TNEXi&lPm)i-OE?3a;Ld495cW6W}#n>;xhjhRW z7Ue0ApBO6^k92WtrI;AJkOW$iPE2LZkLU-9mg4U06c2$MQYGi9CiL;qq~xM;hDzi)$_( zDt0Tdo~tBoFB5m{cO4#`j~gnV{)>cJTGL9keCed)Q%ciF*IS<~X1Mc=e`K?Xq)Nn{ zMxI^Lo%0vm@)R~9H&lK+^B2~9N+##oDfjzr5-ky4zpuEEI52)d#vsu^<^hl67uH+fC#;^8s=Q`kAw2P)Yhv zhg>@R6FD!hC%vcQ{m z(Q1x08k-B301Mf`rPGl3da3Ez_LWRasSd%Rj>B}S7=j^}NbRw-v#o&{VBrB_TCt_qME~-fmMtuiZ5T0TAP_(zmpR^wFq$80{(Sb?xra2qz)O6_OrPs z^(B=QtYpmHMq!SFce*<*7*t`qV>Dx}EBWApTJUo93ebeP@%Qj5HqSE*iFguab)rxr z+~f3M&hEfCG)({WvG6#Tvsafu6^0MchG*-~uE+N`r-Ap3qElX>31t4 zw}r);Lj&$mcTkUwgQPY-b`%v=kQ6Z2FB>-s*WS@38~VGLUD<-!vOQ+B_?Ni0v8Eg+ z8&pyL^`gvgxV!kAt{isJ$q_yRDAXu9*8Kq$U+xetn_!iSnQ_lc#6R7(OemK+kNG-) zm6BR7%iEm7C_QE)oDR32^eN6zvN_W3(UX$Q2^v)s-lbW{zPFNI{is<_s`lc+0x=Ix z1Ei-{yf39MYAlF4gkepX`xDgs=L^3iojuUHYN{AS)5OC8dv-61fjO8ccmI?w(1&7P zljjkmO7}BXYiIr>?%BEq*Qhu{c)zB`ysXHz5=>j`^|3V<%@eT=teE^R>~DOqwAmLo z$VG$6l??wi)g^ZoY~99CCY)H4jHG$#EOY-Lu(Pm&yG{o2H{gBDtXh0?p}PfNoB}6TwCvZoeM_hsDW0sUH%>6TkcDScpi5< zrM?%@{3aIOGM@*q7Ta{ws6GK+)*qWXYZ-EP89qlP=5E1rY5Byxwl@VbBPstYVmNP0 z_~z3z=25N&#bf2%IyPX1lMzc=Zd3W5z6#~!F^7H=n=VuyFe(rECL$D5aN!&nHdZN8 z>f|BgNMd)bQ`6IfSj-~krCe)Yp$#^n_b1T=-pgc+YtX2lW^#$O_@D{c6-(RQ+lxmN7zX z{~hcp0u}3ZOq%iehWr`k?@#Fe9kThqJCpohvibkN$mX|CCUAmZhnm@cLpJ|6&&z*F zHV+s3|18-&|JeQiRkFGF-;vE-{z^7~{X4R`&3{EU@A@}na~*dSKvRDcR101d`dj7> z93;-vM5}^&F}8HL0JHSLA7o+PTH=vDN%r+o2PN_PVa>l>6^V0x1Y|&iH08pEJGt~8 zK|Hhcru86>AANZ!?HQ_u6k=^~4X4ocLrYh28}o`CQ4q6EzylSyNO+>`J7fk+s1Qnq z(#ryA$ast;E-pzdK!RKViY)+`L}astMx-)a1gXHwKs}r;ND4??OgM<>8iz z%QGV6hFtT~y##`SA%rDmbJdjgsR2JJHc|G39EZMWwX+(KhT{@On*|Dp<;!v9srKYh zTob8<_!7XiCoC>r4mn1}J6+^pyZUE576tl-UVEex-=SJnl2-nh8to~k7Q{DDg?QjV`t2%ly+CP}sflaKI}ocOlR3xDz#Y zbX<%*kA$sE(KjNsvXuH5-cVy$TDi2fw$2R<{VdVctKo3AT=k3;3sxaCHTZ^u zU}5orjF4(~(%mkW4MAkyX>vbG`bR`ODF+*2U2{7ALkHk4bl$Psr1q7a6a6o?G<&(0 z{{WjuO&AXmeu2&9W05ES25b)fSFkx*&_9CB%N-Tf#lOJji|&5|HkVBNGuV9YkHF?u zlFq4LU~?0dKY`6xBRa8VSNeW!4MNxI46lc;nH3c`$^u7Nm>4aW7{Sx{p0~EG_ct5m z%B|bRVGO=m7z16Q!%L8&lTQlyc@A(4C|@QrO<9Y%Lif;-qO=X7L`$_2%)@rohwh1$ zuR2t!XW8*c0&Lo7$Jp3lGPYLX{s+yJ!Itwa}99b&KCdZC!V9^Zd`j2nmDE| zaa;8EV_@*f8Bk|#jk!t2QuNMlrLt=`$pLUOzDbFF+CrSVpu2UZmP+oCZ2#5jj8oW4 z)faY*+)92wV##jgA8}F39lVjlZ)^A-E503dfPy#NPF;(+WlXN0)BB(%RV)!aLWO(-b zV8`pE)NW~r)I}^_;(3yo@{C8^lbL&rOPk51k?_7wPTnuh{Pmh*jb4Z=GEpR3P?DW& z^ba9vm*1LlrPuNwDabmg2As^dYADYCH$|M|9JrxFIf&FuI06X8Hu!4*)d z8RJgDkdF6)a8;C$@}FKNd(X)@)Qwfqj!u>3?NS|zq)VpdvQ7w<vK$#Hmmnb~Soq%}}DX43S_ zVWrha`-$YiM4ig-4R;N>B+Bxg(TXQfvp^q4z*RF3?MH6SBhA_bsgl83cMTJZV#P4y z&t)NP`*F#cs5QXheKDj9w-(-S)KGxkyV{k6bnwb!T#GJt zBpMb;VQNmDcEl&0C$sKlw2R1Y7i)P6gR}Tvia)H^Hr7LKwVL^l2F^2&pzj^+`FblN zw#l_8l`>{Z7vn}zsqabE!yrc@B_i|eex}yQ;=Bfpsx;`FJ&roBasP0v&KyOBp8Q3q z)0AI`6r_CslDNf?gkxjf=hO1b@_0+v7GZp)uPY?ojP;sU8WXeGR(OpqG$bt`uv86M z3_N1P+>I_woIvxGIyM?ND>vyTVkcX^~)5FNJj@Ml$2AsiS$8Kga zz{RXz{Vin)6~x#f%_7cQ!aU4J>qLW>u37n|xAtXS%@Z zc|`tYGQSxoaXiN?`jsCV^ROU6Rc2rTeM^jie0mn>!ZVFF!!TcesDQ)2Y__xAJD@b`y!%;(QXy02$G>Fb&QpLEdtrPA~V9QLGyzu~aMSJ8$xVu13RCPrP>_Ae-} z5y9K*E6}q+*JtHl)@hL*Oy{M|DA71+>3h zYFj+qibyThHzF1>NI;!WW_0^e@Q53Y!(a{Qb629lJ$6VdcG`5M~e#MYS|dds1?}+brHmSYd>fW8;Xo<|3O` zPvC~b)Ei-;!Mu=siunfSl>>2%6n_8C8mR3De*YsiGPP?JjtXIz^(S2g@)R0VQJiKZ z^AUuBp2#NBaV&87*s!xNZhIXU$1%d9+30du%Q<-`aNA7?B#|#6G&ZI8^)rh&Ospq# z?@92gls$}BG!vTye-!ap0l1U^t z_rr&mAt@)PNRVUJ86+Ac7%I4`phnOUG8YLPyDGa#s-7F8r@tM2eZ#t1(v7y5>HZ0X z{6Rmb7sE}u6LR+tLHNIeZT~kx`2S+te==A#OF`%k+g2o_80AnIGnh7@7`I;AM?Ql| zuZ)68`KYOMP;*MumwvoHZXg7J)m4(vew3W)ZzZeG{YhzTf?D zBFq6HA{3~LiSP8cr4iymUDmTL;(*>)t5}b?t8aV(7p>U=qEjnS)D}cZu9v!73TR;a zQ2G9kW*r{y2B)ukxVm-plSr}x%W9neGqLUQVb9lBU&if*!2es3_>Zvd3cd2{w6L9L z6pzTLm@qRM^z&(m{A`7J0w+}rXn0n&rBto)nBY3wNkjp7!G24iZjEa#R~Pf<;M+gv zWHpL9=U8;51bl#WY+>?wZ^oID0IP{IhA2U|z8Nh>fre=i#m>%(^-`f=TM3E>KwrBQ zG9j0}I92bWqhOEtL@heSv%k)Rk#y z%Ras1*;^ckpS!#UMxjenFZ#19BpCMX99SYza#IN1F^2Nmpf$BdIkEaZ6!IkTX!@6l ziJDGdg8z?z>%}MvUzS_=?sZOzD8P z@ra6r1p}F@*{x($cpY(dS7!g&FMj$c)iS{P&Fu5Az}bust@Cf^NSO34qni!TA6vY zdif;vo-4|Y*|*tj%tuH(KfFBsbFs~Xmls(;t!L&)4l8$U?p!7Vcl)F{!&1pDStj-( zswj}og*&02?+{qj`E~5i=nR^YxJeBS6)#2_P;KTzUF`IgHCCqT)>KrWmYlRi=eW@c z;QNg#iEnUv0RB%i_o2fz6JNDp2|%DPT>7^^-xwC9In&U6&CA(-T_OD|c<8^0qkkqb z{pSUem#US`9_ydF0`=-dTetu~J0G2eiV{en+LP4V@+%#;zZ&IxTEehap&LIQSe&=c zduC~*hWFiaOoloIB(je)@p>hM(Bi-vfWqR*(h;UJ2nm77*WNX4aB>2!?K(YQUtl?N z(~dXufW=4Qgs#A=ZxF4Jr$sO7_*G#aGx+UZJ0FeZ`&7P-!iI(Elp`*Bp%pC8U zKRh}+mD7^3=lBlGCm?3AFr_Tj9|kSqB1QW&b;uLt{p*H8I%aoeM8moH` zlBecv3!GcWqaw*KccH-xL50|#vUPX3-1;`wa)h1#gnaE<_--Y%b-dyy@EOs`iD zqy<))Fz5pzFv0Tw`Rp#h4;d$oPBUeU~9F{|?HRjmS`$n!wX;xj~h zXu##r7E-xSs6Di?Qizmqy4s6&bAAo&nSs+uR08Be^70!%esv3$>t)`2S2MO#_K)qQ zp4$eDh~HPWT`eId?7u=VOVEdF4+Pi|eQJ|1GQx(#)6$OuPX{~dgb$Tbb9`2eVIc*O zOj`zV*#g;*T&IVWdYd?+7{j8sfk+kLEki<7sBxVi8m<>n;4$@4q>^Rw+ocauR z=bnIKbkUaYJ?HK>n5waQ=)m?AZ3;PY1|XfQ@6|?%x^pZWi~?XEpk3dIhuRn|O~`@W zB|JeF>?Y}>Fpqp$PhOo*k5u-ZA!w7i=Yyb+0mbUHbxPB!>LU$QN9O9+{4+-xn|wzx zPlI803P1$rPYZVdz(6yT1YP?3<|`j(xU5d{6FmYI#wTXe48>4UnRpn}$8}CbPcC}t z>vrUWlClUSA~1fF`$!jK{<7kX(pfYHORlKTcC1@sbZ{$iU_@n1P;L_FX3Orq0iTZ~ z1a!eD&AJpX*K?rwVb}(sMgi-kAF#IC1$%F1yT@Efm?Ds;l$KY(Cz$_c)S1Tz6sQKg zdvo(KYC&hb)dx|D<sFR#2YlgnIl zZ6Rn)Qq_>SvP@53{8eVBy1TZ0pV|zGG^D)?~F0HB-bil!Ifqczq=XI zvo_W^S|Gt$ml(Z#4s$wyYJJ8|(p{(WKMpx`^Bu-{!!U3>xbH0oytt2zX%>+@Pk=%q z`@wZf265{%n9dr#?-U(oL(Om~zENfxT`?t}h+tHHuh)A#RWil`g4w+_`gpxXWaN;3 zLoHREOqQv4PHR*yZk3=1xi>WfQCcWa`pujEbl7`IDHc<1;*|sBArEcKc?G;+|4yGz z51=4hup(p#_35_3?7`c~Ga(~y)#8wGp9lfawX%VNylgvU{ZT@Tf97E(#3eTD<1u6_9d`uA$=E^Hs<{?%QZ3-!O%(BWw0;P_=4 z_cv&ss;@5nb!OPlY8^YUU?xAL{^$_>+PM6xyE%Zf*w(WWCRoxN#JVDFUU9oMn70cK zd7H#abTV3>+R64Wp79scc%vst7e=jG0`B`bYoe%618Au@o)DR=$5y5RC4FN#?id>Y zZ|Q?2-GU*d6_bQbb?xjd`f1E;CFk^dj|gQG6TsQjc?L?+2m|r=q%kD2oG~`M-^My} z0_vmki12(V;r{bI6l;tjorW}wVAS^DIWvt!1f-&I;qbSp+SRz?sdKWq=8;j$96{)d z1h-5btv9 z&P#k$Z}}%@P89F&ZOK#pHbi~LMh)W3_6Bx<~S{nN>xpzoq)5k2EOr>XcvsPkSDX zq|w0021gqx(ubu}ab!ldM@j3Mt-f*>2lZYZQ))PNOvv+T7ux8ed zhzbcTb5Pm~b=ukwHwt_238d+QJQIrkL@|XRW@4l<^InD~h9qLgSYI9VnWobTC5_vGnw<-IptmsGu7ev=!fU zBCrw>I(8M7%?AFU@xvXq+Tj3NDs^6M`$MgB&o$R>{D`zt1_4>jq#%72oU>bBt_ab% zh@ttM)j>MeI4Vty7GpAArk%j>GY7TZ*LKjT5-nU+z*@ za@K-2-&}~Q(MZ1I>Zya*iwN#=}I&pM;Sw^1n&$_R*?|O9wqI#Y_>s_ zO@0DCa$g*^wdzOR-@+qLLA@1dV^DbTkTylseHCgx%j`bGv%zSlcXZ~3F0kecSeHt5 zEFKW2E?0_RAum6U__uqOiec8VoFK{c8DmJm(4^!?7=FImS_F?jK2ilH#lGj#f0x1W zKf$Y{5QM-nT}%qgHm_~}Jez;pbmHyWIB8pppLJvtenbo`z-0TyoOQ1VVf7%vO1CO2 zPu#K~OHhbH5+^p0-!6njAyDCGalFe&YOIs+px959sTP*daD9SHYw~GX0WnPO-n=+g z2OgukN@fL8EpX`WEfcqqzge*|bE&{U)qdIFQtB2Wk5iG^7Uy8J^?Bq{{7cTc3)42n z(jRd?-5KNnBi2K0u1Z{AggmmS|0b|G$!SUS(;|9J z*RaC)gRiF2lT^w$v178rUc7}~T%e03#2L{)S(4x+SlLTZpNzmr47R?k6REP9OT!Vvui8a+(1l)_zV z)m-gQ>X9oCH;}4p9O#4?N6uf0IE_5sI2^jK!{I1bCPBTDOVqa(7y9yqeEG6d(x+)2 zeh5qk*U^EdsWp6H!omBF6^+nuaQEy}6t%kJ(Fu)-!o&K?pU-P1g>^~EA+vlCmr?ad zLXCs&z3Dv?44kBgfhj3$vhdfo&_9oYc*4iQ!+oWEfBO1YJl{V`^8TkhUlPVl(U&jz zO8Lq!>Hi0x@*ml#E9?D9xIQ$$zruhP@UlocZ(gj!B1+5=g3Va+W?WS8MKlfy;WNe8 z7ks{Ua*Wks5cWs*gblR2xLj>vITWx9pm-O0ASe{pc!M?NNz_QGP3U|i3aM#AHZLw& znb{}^yeUsU?2mIB*%hi*LeONm+)o+I*Ve zru(!#-#y-yAi;hTm>!gNzYQdn0gX`O2%Vlciydfj%x#5$uCWZwq9+z-qRsCI5RUjN zsa1vuPLZg;El{F9ojEX`ojT7Ddvu$qgssu)u7kI(Vh&VSF}T~!4YDpkakl44Ia6cR z>#wvN#mxZQxfC)XoDTCf!o-ada@qpmTf<6fv(}EC zaF70R)2jNhtR)klNCGOdjjVCbeA;_Vr##o~Re)0Lw4x`qx@uR55bZa9pDb%B+zTj* z-c{(ZEzKRT84pLWv@b@?j19Tk6?wTv0e>OQu}f@Hp+9ClJ2t|J?W_X{yaYWYbnaLi z@aArY`QZqs`iQOIGb0c+gg8}1cS!Ydcke+Ym9bmLDmRd*nkh)%i+?pLZt7M@;_4(7 zZ%6%#J<`&5;Or$UR=0&KukPIS$yxL$ZDk-#Y7G1V8`nsF^0B_y)Y_KV8HhaO`kfQN zfCGjCw{zoV@b^cUV4=ijUzr@qV>L1B8hh7)%cch6KO3A-D7BlG#+ z@JufZ2k;MWv<-1bUDzcIelu#Mj(dyC!r&{QuGywaW(dy!xFaGu9t8gq@KE!yp-CAh zl0g?P#)!MFR`w%%F{$+{lWtwpfF*6{P@%3^;(M2cx54i*YhT3`$kqtSDjMQ+_sC9J zcWLiXul;TaGS1&L+YtZ39WQ4Wc(6X%ap#M6XDxzZ7$LQf^&VTBP-rdB!N`A;E^HY2 zjef1nc_bmjC_Rm}6iLmg8!fB1^tvsGvDrTi;Z{EKEF@ zJlEJ7M{6=KF8GC#q%k{0@4_$DkVaIn-4Q}kS*PopTE`NM{Dc&K3g+@7bk}Zd=ltRH zgic?&CqA%EG?OTa4ROir)oXtd6g*VM-k&6wfZOaZ_Q@mnAIGPo-$81A)7)(GfZM27 ziJD97U$$#|J9NW~?q>SU-31Q8THwz{ zoNC_}pL?CQ_*{~xT0YFzvvWA{V0)HwOM^c5Vm0nPxTMC4TPKXMYVl!x^tJ~Z@Aned zY&k>^PWEk1Jv9#|9?^}BM7G&i7;@Yls4aTO26sY}J9Har)@AvOnBsrE-|sBC;0$F- zTS9#d!D17tnLtL&s<)Xvgjn{w;U+O47f&V@suywsay!wjTLtonY zuL$XX6uJL^kUrCE9f*qvJkzOTw_mQc;O$$%E=^3#E`@oql*@?;KLAp@trN>w7G?>? zdAZ9MEiPleKhTRQoL;mfKdlO!D*w!UX1SuDfO+`~#xhwKYd#YV`i-y%UZ*WCAQCcA z(GYK{If_FhQ~T1v-7$)C2q zTVl>GAsohovcW7quD;Plo|Dt~u<^+*(DNcQO#}mOvItCEj0b_?5|yS#CncttgTz%5 zGfe|dI{?LocKesbwd1$-&n=}%r_#%mHkz8l)?eM%-6$pptS?!At#bbJEF{ieSKHkcwRY$g>=Y`IheW;L7B zBs7P3^SoR+bURGHblrI!KNEi2wmqNRwaEGPx(HRvZI5n@2aSgC!x%aqOlu^^=eRx~&uEQx=z~7sY-Tm3E{pmB_OHGTAH7dn*nxes6s&imP z`=+}ReEr))?_ZdO{;hH7fBw8pYw2_;%;;nz0$v^T$p|x5}|Xhd6KJ8ASe&#}1KFKlkdse|}oU>SnwJYNyJk{|LGY6{&iy zii%QY*R-Z7IgdR{QQe@?8-5+A8^__aBGTV8tH|sd)FmF^|$0&fjX;3s7hJ0Cb743BUx zTk7w7jLqGmjCF?^=Igf07F^Wb8fQ*HDuz7g0o$Zj2p$U_FhYy%(Aa**iPD@vBrj@+ zo}Kxp@Gy(QH#54h_bh9UZ6M;|%JS@OEZwaRB<}V3xB75C%`X&5dYN}zpJs=-SX97T zT1mTY4&Q71ere4AM0JCMd{qAEva+t9t*)+MS?EnwrF?ye5ZIoQa-|Z%m+J%GXK^P} zQt{Zd2R-Up?6Ou_y{#&BkFn`L=REt=#kAY^JiKXh(1PY>qOG-1S!Ql&@i<0{v|HBj zv|XnAK|5mH;Fo1(coXoUIZg@;;+|a8cq=i}P($J+{+oJW|3+6cAN2TvP?s{w${mt- znvubMXNEU~N}dd=I|_8Ute8(Vte{_S9UK37_$Umavs zt|&)=88F*cY`;7iX3cnvWh=bQ_)pjYsQ-t(w+xP~J<>L%R!eFzGpohS3@wHhGcz+Y zGcz+YGcz+YGc)5S&)8$X-ifij5xWty>l3B=Q58B7RrQ>_^W<}9UMq-0B&5ie{(~@F zqsis}F}PD&Vz+C`JNwjYVjuLCQJk;yxvb)JnPXrPJjI}_9^FcOR9kq;*O*Pu=bOQ= z=q-*k`T$#6`C!2P{ef_sX2_Q&?Y%uG^%$@n5ziUy$qR@xvn%XVs$0GCbg+QNo#SLk zpga5A>adTyFtwJm#NS5qz`N!!1?GX;$?fm~Zd!-g^y!qJvp5X_#rX@^^x#6DJkGS9 zQSl3UTpU564HjAJv1ECA>wryVPT7nagQ5k#Z_1|x;o8?Vbf0%B>Hrp_-?UcIVR#0T z8C$b}Kc=$l^f{*zKw%!G6>z;9IvOHyWzDJqfSnb5ztz1or)hmZB=%0FKCG^P38zIK z)F1A3Jgk3G1cr4qcV^GWMswMIiq@U{xZ8`AJdo(yvp-klbt`~>a1Ly{RCoTlc+QR7 z|D40ZM*S&MHL&KYq#^ zqJziHun_Lv!%! z?h_03ul&k?{FV2iKnkh1p)XooPHoBTNuqRd+IAmah9=Fs{dzYo`mPh(ziAxFpx#O1 zQifLC{An?&-{pH^W?#Xksc!Xt6Yt`J=VY`O9eo+`rcF}K&~mq*U(Q~aK@3iggO#M= zZ2Hz5?|+%DCVeT(jS3DDapB?csCQnoZfA%tUAYwGT;|(KF_JFWXZx<8cuf17p99jn z^weB&X`#>glrOWt@3L&0{$w+yLaDmXK=p1_XA0R;wSwAV#c@HaQTf3fPO0G(MQJ_L zfbo;H;Ve_{@=$!{GV1Cd%XL_$h{}dhWES zp@1!ZhNgi?XBZHM4CrLXZBzOQeB>$;lyA3K948jFapU0tM3F>meG4}W%i7VE0lKxA zRJYT~j&Z60P*HFoC5mo&sKDtoaU>SI9jpmvS|Q3JTbmit$FBqQRg%qf?ig;nL-95C zngXZ5ar$IeD?(&IPiq%)8XxenblU;S?d1t&SJc{dDJQ=6V~?Bpt#=S7%#w5x4C!xicwC|Ia+kZZ^RBbyw>yam zH0IXlZK#cI*a+-p5C0*Weh7kL32xRp)yHnfKGZ)w)=e7lGg4xLIq&xPFQ&SwikdTx zPjDCXJ)8WGWVX}iB^E1fg$I3$XOsUVnx};Y67*hTwF`ESz_6*`gSbL%5cg3>tm87X z#IpINnX)0X&IF$6MH!*11v!pjJJroBL4HxXTT8!nJ?kEmPi%>iXM60l87#tZ)R-<( zA(RsDzJknwqb8q4{4#fGFXb&?Xn^bH2*BvH`T7%dUM?n=LcWby4>62WhzhRZ7s{T` z{}Axz9Y-oec76;^0zW_s25rCg8obtx(eJxk*zs14F#BXsaQc~Bc466G^wL1yp{W9N z=^}Yt;0yZ7M+|jK3aj#^1H!;dHMauiEGu3Ty=qrHz;2rHyK9 zojBy2O}+?kg(O$c@QR7fd9)Gi5fW1yPFoKeI(|qy9tXXj~)iv+1Wd`=Mibu%; z6G4I~D$Peuyp4PLW+?#;XhlJz%Sb}I^*z&O!1DYVd#v@q{9!2!(>l4$O>}nl*Hz!> z{H_WrkrSy}Kk7(LMg$1btVT6wTaL-aGI3ufqo=i(RG{bq;{N&~7>T;+r$>i2@%w(e zr2|ncLWeyBbZiJj`6q)>PB&9Z!EUAVBh{RJ5Sa)A%h#)(Jv234doBh!RGLXzM9MPV z*Ls>vhB?%-)MPS~Coa+x%=--Dz0Uz|dZY1sMRyR{n^p6_T&9(3&arXMM&r%7lc--j z$trENjJg;gf9>9MTdOZlTi_X@3RZZ1+~-uJo|Z$aE#4l%hgl^9E?0ReOA$0dFN*D` zh{@~l>gU_n%M^QA_b}Yrbl?pKeB^N!XAYf1+^aqPhajG(gB3Quqb*Ya)!EnA`#gk| zL8?cL!mil!c&_&9uWE}KRPtxTo&X=Ajmv)=YZmJtuedOmTGinspxlBg8>=7m@^uzr z5S_TINXOcPZq@AcKez#rv!WkNt`Hl;hbG28M^-EXZv{{QG@L)f8lfmd%M@XpTKgsIgaVU{)S@G2aZc=iqvzlz{pt^mC||UUsU`>@gxMUdB;qFb za+-}^jBRk#J2~pR=99$CbTYvAZ2GOy;KA5)SR$QCyk~ws3{iN@Aq&4r6?_NmJXyPN z_%?~~Kdazh-@V@4|2sV-g1xO+|Fa%){%d;p_tU-pRuB^`%@7XJx^Pyn384lEyb3T_ ztcc7JzoBU;C)YO@7V+WQlWos3$(y>)Zy6~p4iE5S?x-Q)%b#Is+Ac4ufqRs{Qd8yQs-Br@ z>|F74yoaH|>$9Nq<>OC+n9#efCD;nWG#E$SXd!2zXHK(AqsMpkC5XK=kpJ{!j5DBo zjM^We(Sc~oW~&lnD^2riE>d8qjJwQjn_RqlS+24{@FXG;LHz*Hc*1kVMo%g=|9D31 zSj>3g8hnxLdifx``=>8Ax?5?bE^YJEc)vM2g1V%>X(m=iQ9Hd`3)AcFaLSF>jj^Wf zBK6~k&ZW|Gh`eJ#2@kU(RI^R$$q>(c1&G?9soen%vp`ortek%P<=tzI)8tz!abO?L zxcBV{_en(2*U}_(p_n!eb`&2(Xf0ngy{gx^qL^l&92^}%vgD<5E!@|)l=0Ie^9-T7Z0D%t6I_!TLN4pAA+m&0>a1{ zJgDH(7GUp3P@whh{=&_6G$_J18U)l9NwDmPEUvOkncEhYXp2epIFaor|AuDDrog7h z3aiII7|)f=YN&q%X89o9M{(JJXhJO5ikv z(rUn{u_R|s>-21WwnkDz5| z;B@T=8|hBaxlmXFhqXzEg(RgD;x*2g%g-$`Y0zze`9icmy91@(akqX$z9Ge`w7AF8T?OGRj=p!{^W zl-mum>GkM^PjG+Hsf15yYm#EEIh@MibOk{V6~(yl_jct~_JkQJq}6m;2i+gOU*sV% zyo`{j1DCOI2GeQ>DbZX8ng?kpP>=pKXI5>7)Bg}#Lm#%1ww%zvdn_(&H3w&SSnUFu zq1dW9u#(>2sk}m2wxiN?jQEYc15$tUB2fW`Nkf5S6wY(F(z|sg-jgoUlUwN4ngs#) zn*GcZ%Uq|@qTFIIRyw@-WZWc@ya)KX(E6!;j;TYj+1^vB`FM-gS@5LCmoA4Z5~`!a zKVF|v3TwdE|4TxxW{*AUaE*bBbypL(&sY2t%4pQNboeyf^BMe1J+eGh*vu}ep7_@D zvs-MDD?^UsjUfZ5LO(J>P&O~18CK$7PoVNUWu2TWJuI%W$DEq=g*lYEyrgsDdG*T& zDIK(+OW-#TL{)Jh$s6a+lhl`6IMEC#+Lc`LF4S^|bKTv)His-7T^r6*_;Hv=6))BT z=>X~SW{ou^nP;B1no9EX(jZ+_B#in|a2;?HFy}jAqJ{f1QNp)dln@7EmWT%d?&Rq6 zUqPT4mhhveq|hBJ)Wm(W5P5w0zQ%eN+HnHYl_x^Y9s2?b@<#}>UVfV|MvrSZFn z&e||+W*G;6pIAK3)`-U!xq;}=?34s>78#08aCN&%S7>Nxs5L=y)_gF$qyA(7|isTX-CdkoZ(@zANePAb`4Pycb3z|9#S187dkXksKODSgBsfme6p^WpF` z>d*zNPYsNBda8URS`hkqv%5%B-}hoFDl^@osWFCAG=sjM`{E(J2sNi0tmbn9oh8$6 zTmHxH6@KqQoD5s)w zaN9716B$>?aGv^?saig8fTu@J({r7KM&)J_g%Aw|n_FflcC#t~+~_P}{1W%m+EF!} z`%bZDI0thK>$FH>iz0XteSTJSMwR^CYd z@rLrZzABM1#8(v-01$xU9}DPT%k6)_CZ>9ZA?~9MZl*7%t6n7RLkD4Pd9ik#S_hSN zUWJ?r$MJ^kM-T~?6|c$Ylh$C8AU;I2?kkF#$Et`UlzJkXuIRi(_I&{2gDp2}OAhQ4 z&YU=i3mDGlHuaBE*&#|So!?GiuX)eqsm$_sW}e-Swb5Tz9y)KPw;S5P=x$n#smABx z2;>S0)}lLeu6BPjW4K)ucYiyiczTGAKp2edyg%~>BF8!Tk-=v3c5ax-UiC@je(i|T zxas=1)2fQn%xOk=?bN6II=uK9nc#+pJYTI>)!=N_{&4&>y+UjqOGw`_^<0eSFn9Z+ zZE((hc651TEpzQ~uG=VeFiY?rJ`$g^Z)Yoh0!MgS>#j;RJL)jQbf&Rdmo}IB4rSoH zeYUXtIPOtBooOK=DJ+?WUSb{z<5pK>yS3j?;f?j18OCVQ1cxD^kkes3cd>jFi#|j_ zQe(vKa6jy1o>G2hyp33wHUES!Qg|&koAhX9s(OX<;At}t221|nHSfTEr8%enH+&}5 z?J`}b$U&t@LT~3!O*6j2{y5z$+8S+E>-x=OIa`TDm$l`xj(xjQOE%6ET4JO^lk|X> zlGv78?EAV{l`199ew^sEyYe@xW+xr7;ue9E(i4c}jIdZ4Ac+vFx$ig|p9AQuO5=+| z?szGX$~j}#Tu5x4<&+Zr66A}QVwoFhj|MI`Ty#>|&v&=uHDvM15!@uo42vEgD8|D# z_os>+V>WE`%&wsA$yTwK4+W3&v^yI<^;qOo8`t^^&}7N+!S3R@MTD&04D!^@5h1G* zc&vk@s08f<2bkJVgsgm{io)1a1IS^Db<_SE?n=#s)KS^100^>)S-^B9GfRfDtoFzOQ0_rcv#x31oIx#EYArjhGTt?Ndb zrjt{la?XXEAM~{iGUg$fDf2T`_AkmOR52A0A}^OkACvs?w^P$_M=C+dVrai}NSH50~lvYib9n zX5j&uK1%MrQH@;?S=S?nuzkJf5Wea_e}(k5oC#}Sw)==bU^loF(sqWWhY z^DciJCL>F3y5Qekzvas=^~Ef=ii=!(k{T*YdZ3wKJd8yuq>8DqHHA4CyBh2_Q~7F2 zwH>5~C_OLcO~B92ZLe2QM)uc1kuO*(P1>Bq!Kr%fvl&AKLBy{v&L(6*Ke#*tR<_dU zJSTBdDXA;Rkt18zIm}RyUDIgd-(J3!=fAmU9+_f~+E0I9OK)_j7t9T6^=A3B6-NR4 ztSk9l$D+Cq&0)vBpL|n{=~*m7N(S|2JdvSU_b?S} zcN$#IfObt`p0akyAU48~SW+k5AG07gZupeGv$5Q0XEq|wwln(9pglA9yTsGVreIQ% z=}4uwFO2R(Fw;RJlYe19EHu7Y@WA?YBImxKsI8&e`e3X@BzYL2(7b?l;vho(rCADC zOut%bjB-$v_GQYR6JDW39yz=?TX<7KfxEFo-~5gV`N#J)mu?pmMI60Tm7}Gah-#xI z%!n2&qM}~#Y$-pxof&Ewf*u0GUsw}{+eU)}Mhs(e(;DJZ%4@<^{J)?`>^P-xA_$Gj z`14fZ_N@qJC{Kv_>!pHDg!+Xr?Fpyw2lOwRE@=DuNbne?&@#B}s8kdN@LY}GgxK)e zWQ%?I8Agh1ud`31h5cT_2Xm&4C?s};T7E|NP0{w;3Fr9+=t(y!$N8&+1|;KWIm{Vg z)f37BgDo&ijojVeS04@V=kU!%g7;iF<#0=Hpr)QC=w7(rv{uQMmxZae(`hhZDxlrR zlR`;(PFE$hG*J~uT|c=8}?FXwNlL{KQhOqUm{~ei&Q&CGvSg_1yfy!BvQG z({&JK^l`%BaR1Dz!*OB!(F1pwb2;1X@=U73QDM)uGsW(B-Kw zGo}ucYqn`AKw{nP(&py&w%rgtbNK5&NpIRM>8QR=r;%~_o&{HS0E7M|6l40&8`K1W zx9zo@&NygAdIBAfCb=<}?JUOAYOl_gHvAorCmTGL4lJ9k%+92nsTWT*rl9d=gqyGN z1bhX*+q530m~#U5gCD)r!>@I2{^j_auBk(h8&o&#jzMmh4Fr?u2Ohb} zWmw6V>F;Z9m$5{j_A$heA26|vgaR|?b9(xlR@Jgk-)7URQ!m@A8;v}S*mMRaiK!1< zABltxTs0}0pEn@Z2=nle+D9sC(mc``pGHz2nAEN82*a}F#U#P3C^2)>wn6Tb}J5>};Na_kV&B1s*z(DdfuD?wK^q-=Zf{B8%;E}GP9 zlNzW=(-&Cjpnz$!>>2=JVB5hj0SjI$lE*atoMJZ9(lMv(xb>aEr z$o|3;f~L*jKR?C)bI+R8A5eeH-5uQr0bIYR4h9GS0HpsocmHpUhWSeGp{pZE@2nu7 zFuu-F$a=&uqNLR3#}%_}0S21b!wE8?V7kV7l8Q4;DbbGeogIf?W&C}&INo+Git({` zj>k3j*Ie`TW~kidk2e(C!1yn>(JL!b3mbu zjdjF>kBfuE)O{Zh*6LRshli?b+T@j&0G-1J3=HS1<;}*%Pp>CBxeHKi>v=W|ro>hY zzWq~ccR0$J-A4XY%OPZK>KjWqCga)oW1a*eAc+RFC=gZpBC+ILtHJ^NkDZ+XH$E67M8R4j_||>i*hU~VYBt0(WsAd%88-iW0T0?rp!IsS zvANmV{T<5fjpc)Hvz79BM6%k>Rm9BlnYlpvmnw^dd}8pjjqTBQi~o8p|*r%!7U6)bFNm?HvZHPN1&1GxZGm% zB1>XRMTZC%Gzh04x9vwU`MnA!Rs;EczQOoP4fF~CA5!-Fn!-ExDXecF%A=ll_i)9= zxwLzQVoJz5zD4js zNZuqQfX@!{bdxsRV#xDq>pjFFmc%96yPj(%Yi@86=pgVp!J|SSq@z&xv#)MAp5;Vl z>?R9AFq4WwVJDS>Nvz#IbgEgiv_PDR8Y2!nzrl#AB6r1o0xA?VDfKNlxf~WKfG{lhQPY?B)b(M`+%|Gg2UI4V2h*{NAO88rGIZ@ehy32 zRb_PlOxiMc%KPLQGpq)#nkC?zGTT~lC`Rk1P6uVFQ*h(RQDNeJSIrS?ph1ZUaM&_l zWH=p}2N#w@#`KZdUCFM-4-~xDxZiUC=ORu17Q8;jd0=9*hr~Na81SPH@@vD}3A$W_ zOqrw=+%W1_9F~mNR@%YZhjkHmDsLJdO@>eyXAiO}HVvR~7lK!pP82~OP!N0KB_kOe zw>R3=OtL4m)i}Njs3KQicD`U$2z(hq5{NAaoJTD{A^S58f5`Cui-E$AYxrcQJ|C_%@B9rvzJRqjU&e^_ zXSbWCOsjx2aGQWpWG@O3CF^qx080#1vMeC{Pewv?w!^&gTES^1m96vj2gW8-mKY>k zRTxZb=!9L>@%u6?@TRE87!+Q);rxA%V_H8Ewpu%fNetSr;#kkoS2=%R zh~DW{;9GGF{h(InqbLB_vvbWR6t03&3!;mcI*<@F)jt|4^=e?Fb!&zlEj+6}=ZFkB zd-N>e8}Xn^(|ElVy$+yBU~2IR-zHTJ7Ikb;dyRBFp@hxw;y*q{Wc}qh^Tq!F=N9tEkC}QXZO`K&gCyB@;+P`UGc3Ap4@R*4BT}P zea$Go_&v;+*Nj*_GF)9zG9Z=*;sEG%Gw>FnImwD-8m%r45bbeqgr|~Z(rTs5SGo8! zQ)wNGu05Dsz+j3q#>qqEd33~sm{LQ9R-7+SSjMV^Fb4W4u|N>UE;8G53Y)kc!Fqv{ znk%!5y7lvv!hlkFJWO)2~n&oZW|)|5Yb^$ooQgMXwt7LoPz z9KzA0=bjOlj#%BXojj3x;8Dme)q zAu8SZEO8WZ?p{VJt&&3@I_2ILlhw{`Yj^v?jmS9uM7LNER7i}j(XyzR7`WSCjzm4k zx)e-VIvlz4SX6)hOu3|~$e_;rs*2rx7~Zmr!QD8py%dL6w54(5m{0$LVhm1UfBbYH^!+Y)KS+mXL{^;l|8P<dQH{d-LYyXVu_+#WDbVrzX`0&|gxS|Es%{=pUyt|1$yaa%0vZ z>kIg!_yzp=$8G)8e?HmYxk~@q)*qhm)s4Xd8PNHv2;1$Y3Rh?q!?d2FV-!;`1lY}F z#=g~oRztanjmT;^+_>-YE?tu@T0>X%%U9UtDI||X@b{CTKO*iZ->_!Zh<6?-k`Fjb zR2Z)fc!E_{Ub$K7fW*YchhrUv;0ca%$bqvi7qe@26Hl=6?d-t-H)+ za3l$tv-crm*QbNt7M%w!rGu(MRUan%o-;I57aQ#(sj@H4MRJ;(n;i@~yjG8oQ(Get z2avcOQN;ut3gWUjY|WFuWq;b}4(0YQ57exc^>%p7L7#GW0`m8UnV6aC%5poL7L8MB zMXanvQexJpLL}unoDN=K*7pOCNw=EP)+YAH89wq@;Syf1sEDHWaw(5Av3ohFNTqcd zKtw0M=Ua3+sLn%B^hWylFO2M!eHNwYm!Y((N0w|NosC%ee4=n+pT>-`5EJIA%l1E% z#f6&-4?#wQGPgOLyN2TwvCyu6RzS+h;ZKlB_;#&S{vN7^^KaLNhu4h@sPr(Lv%0v+ zvc))mQO#E$n|(Nzx7pCIOr1#1`_lt0?RC^16U6ql=_OJLP^% zH9Rc0)GA6GhxuLH!r`DSNbFA!$&q@cF6VT zd6uuj)kdLCe1g0vnbrGSPe9?wk&yI5v~Ss+_+WlK*-s>$b#%T{GB`QZ?67-!|whKyZhe>yZgtzrvIgV ze{Y8Q>-zsU>h9mDyMLqZ{+FQcDF11}`*Uyke|1j%B_01y5oO4{f^hlDpyR)WWBv0{ zUXLFrBP=1TcB_)QNND=wsc?&MRWwob z9QRnQL7mSongC4cdH@f7+Xfbs$IcNxteGqUd-KR;V9u892XLZ4E#4k-W^75;PT3X+ zbEPI$AbnVd|NW01TZqo)>dSV8((y09qIQ|7FTsYftk+!Gkdkzg7KTML#Oe5oLAYLJ zcU{F$neMZ#m$=bPFPpS(y0tc53or9>u0$2>)2l~F+^YFF<=GQXK<`Q~eC^IjqSb-ArlHs(IBbH+(AJMg|cSqfhjKeK%wN&#OvT*7$ znWm=M7A?mZ_p1w zK}Bgw?WZM+&i9cO+NKATlYFGAkVBFk_~V!4$^-H?yk|pHS4w_33UhF4=(~Tf(&Z;_ z-X=SwvDBv3&hmTOpbu*;ZGG;n`+lw8GUhx%g8_>ECG z3P}4}_x$RLV#h(`b;S*_!U*6^|LR_FGICq-gX~C-W=csxHKSMG^YT5?L}F)JoGz$z z_bNy*)E{P1<#<<$H}~}PA!io8GD>RKSkZm@k;MelhCD>D36mPZp!rj4we~*F+4l4W ztL09YQ)j^tzwcl>42v4vDNjeodNM*UZ*MYz;M7IjxB* zy0p|WPScZX-KpmMTeQW-PxVed^v7QIr~sd1g1HbRq4`3;3STLWC>5M@3QI4{82g-T z5bejoY{sRlfZIkt!q2}OWee}hwq^Xa^Yi{SsNnzRh5bifoVncQ7cUOd<54sF^ja7V zOg)82(sxP?XJ$R-A~~0bplraPK{@gLKDOF;8D>kdfuc5`&e91uKfoOPb&`D` z2U)e4sNp$|Gik`k;KPr}tYL~2me zZ-#!1X;N8aP$oYT6mH#USul+ z8!y3ASQ<#iv~=d?{vW#+mcGboO?9>XEAbhW-6~c43?y$d;E}B;Q z(ESOiPwG7yOK|_#A+5wiiDW10u($Z4I~}DOwXYpz*`rl$6{D{WJIfUzG6RQ1tZkLb zay~O@eQ-FAF>;ZhO_LhEh7hwb${sl)G)H7Bjm$(Rux8c}u8%S9cubB8cF=Z77twmY{rrz2XRgm607OP4XT23Tn7 zS;AT$U|HIW#_EVVqJrtHiKkYqkyOwxtSKHRL|V7!8^f4G>n~J}`L1LGjV-J6iWAJe zA6O_t88}U9(#?x;Zvh3|aU;0JQtL@4-QZeZ9O8Yh>)6i4cMP`@2LP(MspwD4GBr}En9bPEvg7nkg2~QWA4`6_;SlpW%9eVl8do#H*ETxJOxh_q`L}DjHOct~PEPID1~I zm3|G*8&26fY(Zt~!ANaQvs}V8pi%AYDfn4m!{f;@FD~bTuGX?cOEoDG>guJ^1=ayR zf~5cH>Dc-y3*w4<~PaiKNghAm&9 z<;VIEunZ|HlrvRFsqK-K#!08Txd0K5Bj|8_lcm94JM)*f^JLXxVbV-#ydus|C^a|0 z8!6Vu{cL$@R2m6uaB%mX1^TJ{vGhb;norX0e;4Tun0NMA;LGF>i1e@2^ZuNT{N=~~ z>mALXIByuNf9AXaRP%%FyD*%~`vFk0Rq*cEwQ~t z>j}Wx9Px`>Pb!|245)aAZIiJp#HSX%WbFqSq)i8bjufF@%}gksjhcRz>U)pPaS4f> zu@ktf&QPEg`>O7R8n1N7 z+EyW%6RF}WUy^9KR`Ez?3#UM_u;;t`?0lQ`T+woV99pUVFQ>l5WB> zmRPJi5Q~+wZka51$beo^Ox3(*Sa=dM|>v~hfv&Y-Q)`#+K1 z4q&R&Aj=xQRRdbfcouV;%4ZjVmJ;=_wc&?5;ruc(@5nH+|At-^yOoNcm7t+_>duB{SMU+z`)FX^jUaRSKSp&{|n=UTErV? zcUL!(%QFNTG(vxeyGg2F#dJ8_{!yMPOiP9XufN+T${P;Rti2LDgZ3jU4f+qPNg%ZdGcCJRG2e=l64 z9%g^EmX77y8)t4Ez{#S>h?wTzPEIpI%7!+-G5T6NQWg8xfK*+ZrAE86b{|EK@@gkN z#Vy@{c8{7Q0#}0iRjS8fmVxhIO00!ONW)!*DcV$V#d#r-#B6rk+9{r~wDLC6`PqrH zop&AU-6@A(?S2%@LHvN<)hmyVQv+p=}hqzbLW>{n4DMz7+No5ic3`)14h}c|R-yi(_rr zXYvRX{7RA>={W7v92BU9M}DmIwi$(ZE1gUpS@wMZ8nbF486>P!7d39W8W?D+IcGvdE%d(o0W@10X9=rNT0R>NqJhA z)yVx~=PF2TLMiv6GC+-AhoT*;_={JOmMOpSGQ|>P@nv=S(J-drL8)kknQLDamR(%- zE}-Bfc>B!$=-v06I zufJ5?{-RL-hP?g%3-YGg+x7k@a_!a^mhnFz-qatcL8kmVw95e1hlW`7@q=NdhEHu1wmZ`3)PkL3ynnHffDcr5;U#H}VV4jPsmSRza0onjW?X zm#r^gQ6l^|{GLnglri19>YOSf7Gj{%7TDl0ZOT0u`XAP0y4{X7es~nuxo~ z=FrfJJ2Du7#XqHVDBzpq9$C<@0*at*EO_RfWYVV=wuzQqK_!D{4CcHL?|YZ5l~Q4f zZE2^W6vw}OjTc-$k2rja6Xg6uqjU4FZE$|L&z5dKr{XV+x0tbr0kJQ}+f9_h#NRO9 znEs3LRvh^Ej5kqxITgt-#v7sQ-!R_7;{TKJw)WpK-p(Z)a=sXEph|yYy!nH*uKoaj zWm)f90(l5^R#jjZpPxf1(eghpJ7a4Q+X0T^^mJ@pZa`)*!Jx9?bM_pE+*Rk(0TbZM z2P4%3xW>+>a6CdcOeEq!-ja)hSigoSELsCI`Jt)If2}othgqs%xCRsZ%Q0hdcXd_p z3*1&=Vs6eVQ2^ln4#BQ;GUgbpxn^0f{giXMeQs%c6lq^NK_w&;LlKpIuA)$#Oty0j z$gCt=8Px@91))G7ucFj>l54fv{_WIVVhgYgfJJc&>3fre_dP%-Ty|q*6xiL}6EWr` z_eR>ErdVD!Y1h(4KZW#Rmai=aEn6i{(KZGHt{%BFVeOMO z2zA_1y3Na?eqcEC7usZ53a2SFWbe)mh=T!WmZ~qP+ z-Qh;ILVFhdk(&D!)3hwG6s`se!`}uFgRTDYcayl$m`eSQzHHejUzip8e^sFV9`P3Q z`>Q~R=>PZ@(t6bbjK@uHjDV>m{bR$v%{GnE%y~V;@$h%|q<-)A6Q&uUR>N0E#llr7g%iZR&^X>I zfotjUXb7KjZ72gn%#{bmnOf3%ImmD`qB;!M^KLAUM(wFkF>(Gh?)g)(dB~@crd_L% z7*%RUXn=es1Q!%pQvR@!Gm`!!cPMt9VoF6oh!_(lp_K)&*$$}T%!o&>_DPFLo%U>r znFsH~y|K)BugJbKs(4gUY4Qn$#o|T;lA+MgY@SF(3 zZ{v~{vmDg#opI@9dZ)tZ;bbKN(Ua4m4K+A+KcHWhWOpCRI_-~aZLxTQ**J$wk@k+> z=F?su-sS@i3-I&F-iW?ix(scDQm2NRts<&jeS7C*&(&OcbP= zu*%HPQu6s`c7a$W4Kq~HQM*YyBh}I8Qs$oT8RrC1$Z|G8F^{*m9hc)<8RQm4ej#+Z z<}qVi(X%r>mUEllPd={7-OygWnFm7D_P!eh%3H{TlXf?4o&S5))L5k2ieQ_1nb;SDEm-36uV=#3oFQ_b%ly=OGEf*(5-D$_F^v{E`U0R`bGrH-YVKOj6zn(78Z9q4 z0NG)AqKX1IFE~{>9R^z>LgIcde9y(GpuQnT%{r$;*M#br1$U zl~XFp;ztND7F!%ycKW%^HAnL*hrQ{VuaMpE%}*xdu(OYbmdoN)i-xA>I!CNxoz(zA zAT?J(2v!&+9^h{U#X`49xj~DismR1?s15wuRBbl1soX7bUUq*+<%MN-)uOI_Mavu6 zhhokF)O6gB^8RE6!t4a%8!NOmE&0>l@lM3MDed!*>+5d`X#c#$@6YS&-~6+G^UwZw z^3V8diLiTaF3T4jC#f^5!YEH-O~*C;cql61*VgE7|?x zZ)Ci54J5#9K>X&mPGoaL={9y@68Z z7nxPCjRP_e^kv+ool;DpR^Hg}eciS5i$x6$8)kj9217D*Oe!*}=rlML<842TvVi3*1Ml#8o4$#OwE?a|yXfi3dD7S%{^4UhJU&Q=4*v3)Q z3?L`<5ZM(O`J}P{^ZeS1Ft9E&O|#TI7E?Mu5@L*({@=9BKQempF36 z53Vl#S%cKTcS{9GAb!Qirp}v5SMdxd42S;YJ6V|!4}IOen-CLuHrzHyTd|(0MoS(p z`Xs%Ef210lvHWA;{_dvD?ESU*eEc;f|6dN=Hue@@Fa-U-AtAUch*++&!FHS~LaO1w z^GomN2>9+@Z8?bm=>ftYQb^(JS4Rtb=j}9gen1uX$Hgvy@};;x8we^^MadHalPsfB zC4u0R6bYhgRO2@Jyb59nqe&(FOyz9><(o@LwpFhUJLEXcr}ia}JM>A-EA)C-cbBT7 zL)Wj*7AbF{DbI=(;Lalz$ALiwllWfaLz0xMKv^%c#Qs=MCzF7RN!vx^mdrk@0J4u% znolRv!mR#lYF5i)oH?Z!H)!Si_VK>9t!t;K1rD+!_2@jj!O(8=FQz;d&~!lFVjUG# zMW1#ISQ+^|Kjs`3k>3j;_e|209TJHs)|mXLc#|-K#_!CjapVcfw<%_5X=00~kB_sR zELrug0?DDizT`?9n{MiBYCsAR7eP8*q`d=smE~1 zG2NdHT2T2)Q`PwTmT29gD*aEdhAs%-6^R+GK?ab!_Y$IU<{1N17p>=id{ zV1Q-Ks)%c)6ehiy!T+my1&0!l&GlUGsBTh zl&7L&bj@M#4i{(IiOu1-|1LqoelBhu4QTiNQGO-)1~$owY@SKldw3>GJ4pjJE76Bv5N`Tw zXJ_B})+v*tJVP;6VML+V4$J}9-Ndg{P<6f{gMW2LI+&8xj@?JR`(8L1Y&}c7$O>r5 znkVgGNirIb0T|IHu)RxPHj266r{rwEM%?z@tJDx5a;8K7B!_6QlRw_9jv0?|A& zTqv!GoHTm{+L`D##%y$lHAJr-3&c+v!elN7ljy^e-SE|EqW+eUGP5eVSaXw1V(fhU zb=m(MHk%8}xP}Wrga?^68vMyq0f>ZFTHe0`F$4ZJ}$g@=+mk@dc{tstw z0USq@Wedxe#b}|$7Be%mWHB={GrI*ATg=SN%*<>tGcz-HKh5s!{yQ_@#C!WkbVo&3 zL{~*;R_D3r=Dp`I+Rs`D9n;X}(&k?dV<|VY6>2 zjEUg~?KJ37FCft28>ZUgnXvgStFv_mjO1@_m~z1I0)oz1x1VZQur@>agfUCmERo^F z9rDI&1R1{t&>PO6Z!bZ6rg&07;g|a)4YxXG*%d16d|h-|*C+n!q60WQ9X~k+E2a32 z>=CpJndz*bfgA4+`lqsl-*T+2gCBKseIJTC|LfK5uMDgd{?>Wo{aE!qbh}bin^?lv z;K#H<+kGp-62d?b?>eM#O|k~}024{e@q>MrS3J#X?JhOf*3Fjsp11@Fe{5%fRZpkt zsJa4J8HalQ;t4g2>JI~mRFV;n3z5L=X_gs3kWGXJ4IqMfl)7;!TZ1%ZFguSGo()qX zR=Zcceh^16hvUUb#U`*c`OBQ9h2n&2et$i6-pLWUZmr1mZcBxuk>iWzlPFBL%941-r~pXb;or_;fB`B7H|21-C#dxF>Oo%=Klf0uc|@)I*lJK|T99MSrj-i`%4PRq(a?Mq$35t#hs%ERy$k@)IMscfY`QQ5E>= zX(7i?-7~X_nJ%AyrZ-~oOKFf8mxL2~ofH*2Vl=4VbNC!zSYlV04X%d%7|MV0EiBs6 zy%sYh`jG&oan&{nj7v+S=)l)FGHkzxDo-HW$XH`Ciq6W{mnsLlgn&WnO{B$23R#`r zYY?}|wXve5y7`P0>yGDnMP%gCbxHv_)=lp?m&M#U)KGf$u4k@$zHlF1YkxoSp zf>Q0#1HM6${r7s~yAy|O7C|@ZVX<9kjffZx`{>#0v}u<%Pw_7X3W0AwFwaAGW5^;k z@~=GH+=z}1Q5Y_PyL~Dwnnoshz^zxY_|B;;ZH&01t&k{i1LBtSXg86iA4;+gUOg0t z7uNN_Fz%T2otP->xo;Q5j{=3TnAPacT~bM2kZK}dC+#1xH8FPMTb3C%TTv-TNijgl zrbzwCW{099EYvjDHT{5QquL&*G)I6WLd1z%(pYU6FYDS86!o{8D0K)#V=rKiExDQJkUj{FpU{J)F|wwLS=IMEu_Qe+59RvgwLGa>5HaVV5CA^$qU^_VdXk2flWhX zd8oLSj)>|iRJrG(0|H5?_v+Tc&Cc{SvIEaMT4|SA+$Hd0HN?FHy(Y{l6?qgy5AP+F zUL{6DoaPzznrMy~&n3ThV1Xaa5sQoo`6qg^T8U56hkf$B&fDScX$rnK-0`&*w@U7wd-=KOT!OD*-pzMiY|EeWhW z%=@>>Me~&3tX}KrDr{&jnh!j?hdYgQJ{$k!sr5fqE^z)7mXe5 z^AF$UzX{L%yN=KQy2Rg){~$S+j^wO7pE0z#M?gs8`*n^{-h(j37`|UxKhS*E4Eobs zAAi`E#?SZ=68|0p$T%>;xNpFP#vwZ1hHv&(8jtFjfQe8mY7-a6%k`8wLft@!wQKb? z7#sC862vQT8SMf%I!W5C*G=PnQ627q6ZFWI^nGQVP$m(!#Ppo&o*qNWF z#yDYg?vAni;X4_KKDwxihz1)dlp{~^R|hp2xCkM&5f`O?dh-3>diBUTQYdetx+Q&j zB!O?H7Ij@yWgoF55T`&2h?7qTl6e*-d^&)n+&|nY*!Y#iw%_+ic+RbYE!Tu5SvBJl z_^H~e>5&FKaG^vg*lQF0xl`y!g9KKoNEI46!T3(@S9~S-X~>K#DBm}?4$@)x)E9Su zUsGzr_>spHE4~BY-=#O@%=uc%l~ZtzKFJgsToh!CMIgJYlPH$@BoTJTd*}MJA>n$d zW!QastVJC$c}{UAd&!tQAgHE5A$+uKERiyvM*&-i_rA?i?8kGQ>%+A7YC1e^JS>yS z!O~{g7!4`5bLgb`(}cb%IZSg~ov?>O6nC^@wB|05w&$36HiWRp&9!$GJZuP;*+jI` z>kvxlgc=y_({6kZ7u(b2?uUF8+>YPMO+b|X9C&J8$7Z}8T=?;aP@jiz9QV5y4!32SaZI6|Gx zdEko|Omn-vCSJwv1ISaVUu5GrIqg4qDg4ko$w^wbwO?Shwk%#%Kef01dR{qrb$(wt z__L<66AL%hHV{zHmbG%5(-^J@c0=_cbH zxmAOYJ+Dd*&i$557H9FM)V&98zlfi0)~C^3gY6ReCsIhu+30;Sg%I0zCu{-6tOoD7 z{$NodKO&TlH4H-+faZ@nJ1KMMrd*S5x+VGsD=t85%j>AC;o_DPDPV^yenpZUv#}KF4-M35?o4dhyN!a1MR#ao~4EO9(K<3l@oR&vx*LT*>;(tDEVlts7Zc(Eu& zz~A)lOFj`|di^MxV-dlOpH`Fe*bHy+rEblU(5v(t9Vw&d#Ll5^su|GVQnElekTkeo z(4FcqwPanIJ^7f7xxQAdpu%(=iVhrKrErsNOt)cRuE>`0zBDRLwoERWPhLx=XpU{x zVSe_rsh07qowD40N8zNh+_>Q^xiXzH;9yGUpnLhcr#v+Nf^l8-M!DvM(WDH&nUSvP z;%dbIV2$SI)S6A;AAw5d(HU>!j%F7NxGzoafY&)cKa`^{Ht$+q74NH_pUlgNO_d~} zOW%_MH|(RdRafVf^~moeKOfU#pIoVE&UBePXISvORu1l$FLo`TI3d{FR@m?xW5H$0 z!(71}^h75UVRZ}O9Z@JjHTKpZM4p?JAH^6oa&F-rxeK@3>YsLnae}WRhNXb7`p_1} zF2Y$@d>x^q^1Z38ybpZzY}vZR^bC4ne8%JBYFN4=x%5>E%V~@9HR8b~eCWe^ZIh6b zExHXmo(w&9(4|m2f0;+J(@q=6Oj(3O#Yak2>V|`mD7mX6(u7^VnRA|D)(I4X@Po`+ zNW;8>Lf^R(6E^%L@=RlQ{bHvCF^!t2V=I>Gl!Yj<`(|i(143)P(CoqSo)Wwb#!Kc@DK0&u%c}! zrxFqYY-HUN)(3Sa>&);!?IBQwGXCy+&JXG{1Q=5MH)_5EJjai~z&#VVE|WVoi1w;v z#r()RylC_XbB)gFqJzu(gfDKPM&N?ev7+o6bKR!}O`PnHmS3kAKP+&c0@QiDgeRX{ znRC9xCT|{(C9^Q=v{Rf1*7ooRRx_%%gW|imws;4^bwov*)(qE9U&S`+$-hUybqo=A zp^=gGkZ|B4;=6vqyMlTo)iMz)lc?sFR@Hi>GZ;OiM5lXY4?H`wwoT%Ocq`n4#i82L zt`S5&OURMrj;h8Znlo-#*{b8cF*2@$p~PB7Hb{`SaI>vlrG`MgWp5Bp_L`=vH%qH! z-tvBkmlWsDpSQId*WZ>*t4LUGVrDKwKRZ8*$|wjwgH5dLp4~Sq>sG8ut!>!bMygCH zN<@suoY6b1m+*rjGH+Ui_?`aVk}5!|YK5qyDGwo$Y}wJDf`S9Tue!98wcBDi!WJBM8G! zht2A#mPy#}$>Uz=8A5fIO%*?+2+jb&9Z;mMRZ+gNJ#E6eQM$aELq_fXETbv60+w*7 zzh4Et81~(>mkMkmwWC`~8w;^%#oZH`5U%d!7sILhRAf1%BcV0Yp??zEg#4$9@~Ruy zsSBw;IT?>x+UA}IX0VH!ka3hawF6w6W|L;()L`EFOLm2nM>02ZsQ-l2FIs5Nn|@W? zaC&9K&P?(yyNrCB40{QUFJeR zZHzU}nE^YUUs^t%qHP6@8F=)j@1wDjA!}m%G2ka(rA%L6m+dXE7Z=3=dozj(N9PFk zle=3IJu|D~Y3Q@z@vrP`TsY}g1ERN7 zwqhqWO{wxGH7Ar&OljH2tQTq(Loymzns?1;&n0a!8mQM*ho#rb_XRxyG^ET~`K(%G zAB1!@*h5kp*2H&AX#sn~<)ogd+*T6C#b2JnCp0gJVd@g)?}k6UGe4IX2p4uK{mzXS z@Q54NeEH$NFe5IO(E$IIs@WoX>XM3PcfTN#)uk-0kz?^HL@M|X86CAr1-Zh4+tFjI zd&^V*K0S0Zqs4}?1-W)=qxnj}jHRgs6hWeK;Za0_|eK{KvL*A|PFr|{?5 z_js16vyswD#|YY>BE?{I>XZv;CeK`xwXN$7ZJtEnO#n? zGY3qhl8!Bsg6T&3%I$D*2p((E$1X#Y;RGicZrjVBaD~k;;Ky{r3O92PTaBBCn)mPv zzD9B9;_nd)k-JXsTwR6uUMS|Ds#mqdJYqkr;DnuTArEK2WTmHP* z#Wn-Awokhx&VG!pXBwP#!DxYGmR{^gz2BRdW?t-2bU_vFgjHw0VOSEJyODYz86slJ zE19(yyUz(=S<^A`%oqLlnTJTKj0mjm|i5%@`!@ZP2PE(i(>MBkzaL;~yG7k%cJ+XV@d#(=;> z2SN16ag6)E;wKrN8=W~4&dcYXKk<=gx9H8l!FI{Oh2TGjIG!Fcbo)ISfYlkI3B?hc z#u)rc{rJQHr<3}PQ3@$-9E5=raO zWYDPta(gUEhJKMJU<;y;A!(-uCVwJ`pGD(I~^9-q1IY;NX)KQFs+RGJOjl)-HXB$s5$)4h%rIY=4@Tg>X==&{$u%9=_?D z5rGx1sLVuPal#@Gnj+NWF{UU>NlT8>m4hnWK4aP-JxfMu*|5Q_;&;if z@q?oB(fC@an%rOmY7s}O9W;B53|^<%9YII4C-$P5uH=HYoDUFzMrRNZoHu^GZH_p0 zQ~jCt=rODLQ{dZ}XovxN@*LfI^6t{Y7q8BH7s^LI6nE^f1+b^!RJ=KVn}e9wZWCR3 zj+iGuzYyZb*Fw+)m9Q% z9S+hd0qXuaIS9H(khSq|z@1nxGN}+&SO{dfs9b0UaE1{I=()z}HLFl$POL|AT)6f- zK5}mC^);`?gtwK&v?=_Z#TJ(lIU2J7)xLqcTnpa1P@X^|6d?YRYml(SlMm@Gh_PQe zvG(ojqL_Gw>@a)>L8ad6*52h!+nGUZl!uc~lC)lQ%O!0|NaySrwb;#zQU7$RYl=wP z$zpT5%QdjH-cKbQKbX|3zn}9O>9!+o-r9`gvm%eR-#afGG#F${`^mEw683c!*p3em z)^!(#)Eg`@aFM8g6QUoq(m)2J3A}(o1!Xc^UvBqzi4g-mxUPNla;hlqs)aTc)yvA} z^8PmXA)uBq|jI3TN$lMY8vWElV>!%gg|2?s)5x;KBKiiQhFzYA&Uu)rO};M zx3_i{!WlO}KRa+sCdd%RN6#5=&CGul63g8GQ?adY#G|+@0+X+1p^}Jmaro}b^svT% z1PbpZjVXF*oheybg+9^89w~^U<(pE>6TNI(0_?xMB#`O3b?seMpd1B7TgDp7`*+2f4 zeH62M^&{8*=q!TakcczdZf^9WJ?6o%|UJ01Y!~ckx{d&~%J>7mf6*Y4*EoN`pQ8c~xl9yN<$&9=hD?R64)D zNyH4WRAePMS3ELFEce|og|8}_E&Dx1!lFy5=zU_^0z@m+2F)vE-q6d$Ctmdav#rBh zhPmk7Q1(^N*<0%-GkLP zetMh1$Epb`b+cXP`r(ti|N!Pbg>hti*Ou2r!iL6Y2eCNY9JB~wiQ2oR2Gag!XjCnpj zc=Ta!QYx2J>MiBo)(L1zMyF}6Jl%leI;$_3f4m(PVQPG6on`B^b_R-&$roYE*b2*) zp0frFQ@71KJKVaqq@zz?7p0}erf%T0k?!l*;e>L_;Oq+Lt4Wncxxi&*i%iGC6k4(d zYA!FRBVbCGnGKxJvuZgt8W*(}&Y!ex?{t~7j+%k0lnJg#j>ykdq~;mPz;W3+YmKb&$U#nybH_y-GFi-3=Y9tLU&%{Sa81uL@n1SX{*czHu*f;iMomoIjmoU zcDL83a9b87r3!c$IS58|LZk@SzH-@gMxmVX>aH!#^k!^*iTvj$c~W}8L3w9e2=BzzeTXv7Q*7#MdaWVs<44fj_NE|a}@LGY82rvB_a@&oN2CWaBM+lvkm2SZi=wE=QqfZ%(2JSQ> z=KLD#heOT&gqM4h?iyGbc(m?2yHA395P!N`ygnX^{U>yE@Zm>B9b{){@8Flaq+LH0 z20O==2pL`QKcGC5O##8f2~i0Z>w8>Q?X!~b>^MQ>`iW0u$&ib$T$`W3XBLy-UiR|8 zA4>Ayc5yCprl^k*Vho_ltHioNiU9^@o6Z|Ac zs_pEHV>P%r_8qx1A-;P=iOQU?q@)6>{7vKbkMRW}#Yl6TudmSQf3ie)#bo2n%vHiA zX0I8)D=ON3I_ZY?Un>8`Ej01fZD8tBV%JBUZ31aX4j zv0$4&I!gnl__WdveOHBSV2D-bkJQc-{&&}tFzx}1RP@0co}hQ*D}wM+UJfHmin|%G z<0SSbXL)nTWMGWVtw}vBJVFrDZM!PP*m)P}I}d*QM*Vld31SB(+*vIAGwz%nW8lQu z?w0%{23jJrjHQ#OiF``l$lOpt`jHF4a5 zE_Mu^XA*eeTRV;|+KFcbT)$c1ltms0g`h<17#5u6*;EC8{&eN|e)NqoxwW4r&{=04 zMC1MD>_NVE-^yD08AEI?6w-o^ib^cAoQvx5r#8gkCos=m#AJzCOaE_upDh#t z(Z#jhX)t46;UB8wiO-Y6oU^(UUv5dR}>)8=uwgOJTJZN#3e< zkBP@=hUIQHxH-{~7h}e`hEuCNwL$3QvKf7~vOw3hOMxjy(A0YW5nJl%uxkw1IUi$i zKxfdtj2{x2<;#zZ^YUHPks$unu6nNBSQ*l{#23#p^CcGaV9*<_v;-bGG#DS!y(d-) zei?j7(#g9LLs@DN0i0#u^K-iFE>-q|ISY=`HCi#)t|E%^z1UR>*%t%IUyP70DXv#{ zae;?Qq|}Q~VLOvYfl5h4m7F-U-qwb` zNxRjHtlkX^@Xy_Sp|1wfDjY~JBa%vZ{F~%v5Fa|`95PW&U6dPzeO4MooUAPE8$Tp} zR~p3OFs=6}O*0mOF7+gL?;3b~(u+qw(i`pTdFTZ{5vRc$H$FWaOavO0QB7Pe;ACQkou z%alC*lg-IT2* z@(Zfr07FGx{VuJ5Xy7-im_meef(a=n(xZa_5-Z#jT-g~L2AkRF2PjIEJv|nSZh~^> z4Ec+R2S-T)OA>566s*SX7}ErbAUqpk#J@9a`ct{(vX^gl5S$KP!-pfbIKL^fKYb@j9W0N~p*s{iruKAhF%pDuUcTGJ9H z(VqU7-vpKX@26Iv_s{>v(D_-;DL;@e|c4~%9y71Ng*Cy|7_5;HuyLzTlo@t#uspLtV3p0#Hhudu#hGyka8p*?-;d&OocQ?~1nmkgA6Z(5UNVnxXsZPDoA5{Fu-`Iy z)|gtEgWFsF&|`z_3&4B^j(DZ2=B>R4wsgoY89lNnHP3F^{>XW4HLBVpY?%7-5v#0G zmYnfXRgqG8YwT>pmwX9p z3xzq&Y}bnJwf%&49h@n0M!Nz@lT{vGq_%sLqk zweuQ93u|6J;Eg*DeL)SOMvgSBe0RO!QkqsMo0J#;~Og#LruM3_`yW!U3 zT;z9kH8|aEnB{@jPnR3UL>J(ynbbT~L2(AOI|J>c4` zrUNn{X~wrMQxYv6ggVYs)Q-_#^SC8SByqDksZ*Vrjh-`!rnSLsw``UnjM&2=!|@aS zrzzG)$)uXDHtDWg2M9)P^->JC-3A+acB3NY3ZR4ui&dGmF}7-_c_g&c@qmAF)i8~n zo(X6>GpnvQX*G(Hw2RABA30fwKCfqCF9byj!@n|z;0Ht_c>vb&Z4L6<{B4_NtTG3{ zk3({5oF(&wjf{wmrrIF)^mC{P%L}*;s{>Cgg5Nt%wDhZ#-{ELf5NH?_Z8Y>-s5Gjn zFBWj?pX;4=wg{OJ9yA!b^4K&QpKUy-6)Bz`d{BnlvEhCapkO{+X?llooC0plZtVBG z9$+i}HGSkHYk#Rz;hZ)uY-6&kWK^y0)#_|F-boTJ{`048SBKL^i-+DL<*-9T zyW_hNAMRdYEiR8Gv zfMQ2s)sA0WU#w$)P}t8``9z@wKKUC!a|jr-dMT=y5W)CJKxyzyh2SUl3Qh&L8OsD~ zx5gsCYN*DSS1$%?5PfUzRC@gHwb5zdranB=E|0|FlTx&XHkuz_^OxPBm6EJH9Qug zPn)(TVJ+!2)(r$a_td=(bnBGvNPA2;&`0 z1D9t>eA*X!_+)~sUD6;3(s)4Q9(!tzc{>|$Nd7sE{w+TP7$K?nEs+#)GnhVr-wMoA zzu!!$bXD)BE95Vh@qb;|KhEe010nvg13{Y(N8ILvKK~6x|6OzIe-$46Pf%u}H8OB< zGI9Dx40VvkS;QO4V&v%mMozNqXu`rNJc=8NSv0R0-y%msNHSmBi%}6rCd0j8uR%S@ zpzd!GZuNaU6Wmi&{#!tY0M2AwNRZ z}S`44RXA2EOZ^(YQ^r5d>ymzrq# zzdc84KG#)?7210IXafW1SoXGR; z@|5@986nn>W#Nu5CJsD51BAr>>C|0txYJ!u8m%3zEw6JDuHWIEE){Tn8g9)Gj)G2! zKsz%@0XH6!E04qDCp4CKSJG&NF(z+Y9FcW}TlQ9TXK|~ff806dHgUO6Z55`eX)6QJ zJ_2_(cX8&p*{h<~-_PTQ#vB!=*oRLuK6+3Va#Sy`QIRfnv|Pr-qEcT0Powxymp{!3 zEUjPBUT@GgIWQV(Zedy@E>klM7q0+NMZlv}HkK?ty?wpqbMniT4M6a>q|KV6tmXrU zXS-es$EwqUly(ErC3!=U{-;A)s~-UvN;PxlmNcxk>Ms%DX6;MIfSJemOSkcPW*cUt zKXaE4cJJQq$8kn|Qg$x9T`a(iI;k7so1yW6LEi;}riY@#o#?!+^hok02t2xy_Mfhy zPO0-&J~brVTC(-piI)O|!7PQA<$6YDES(y(7}E=$TTE424iB`nw(16yMhH?CrydqP zT+|(QqWL%rzgf(V4Wbc-3i#DuwyUJFG=C1U=cmzRR-57*9X7n}B>L4=5VcSqFVY<) z(zVdwky)-4Rcdz?w`82d!}dB~(4BiM93WBjOx~&(F=pMx+ogD=^36G2aHc z|3(*eSGOaBSEu&bZUsO>0pwy>NLR#X#~7KnDt@lLnpwbgEly*e=(I6gKF~&`6y+s# zr1gA&xOg;Xs3Sy!Z};fHgGP(OHzS6@$LpCf@C|2T%r+{!v4~FE#e>1h;rW*9CgV0& z${x?zRI65qlM0wFLLbw$6*)x>!%zl)^muR5cKWOo!n{q*1ie>)6zx^p(Vjb z@|H+#2Gz0|GAGuuX!pM_k24P;0Z{R|hHj;H}|f$lS`m6DR!R{IH5&yn&xUtc6H1&YZj z9ueX15CjfGqy4$+0;WdKnkC5V4y9iV7T6@{O{vK&F!@nubb1YWTT~;Ffkar5^ceW0s;}h2VC3wMK zx(8t?<)ZMJv-^VS#54|3)J=KO4kTkl+#jNL{TTZ$T~w97BIpp{oz7MG&PG{yqImAH z>r&!3h+$NX2DJ4c_@kZ)Q&(`6+s>&M-66{sUw@P2sfU12AmzK1CZSG;OBkA|;XN*` zDh#3g&~C})%A-hHH3d^li>HU`ZwM@y+irL84|`4hsLrfNV<0HE{f(;QS zL4k)1#H0DL4Rxyw!$E|i;2*0ekx7mKg}w4JSM@b%jMVu8^2*9S4#>vQbdQ<~8Tv`( zOSn)sig(xNA@gUee2VGF)0hf60rcE)~ID zL!^)lIL*^Oc=XrZ=l3o|RKuB+647;dK+PPWZ;?FShFV;!i-$T1e47kHlO%ygR#Exf z^Ne)xHx%E_E-me0*i$14MbY7<4xo~RRzy*0E+P4j{p~v<;*+l&|C3NjI)wxs`a#?X ziNd5v9$}>#=ckycq1}g+txpC}171hmRZ3L6ScLTZO5mSD1j?U#QTmm@J&!#z>2u~V zhnu9D&xy|4e~LrEz;R1AHbTTbzR{x6k4knu4xl#kEllv0jQG`2y&AOwY&LowSHs`d2Xjw(iT$ zKUH6b^CJQ9D;$MjS%9jnDGQMLwAq23l9u6Rc6PIG6eaq`UOCap{m{EF^*A#r^1IJI zj)?=PKe^=71@`4HfmskZi(5>FijZctFV|=r%^!(vypsoJac{@k`uX27Vm;&VOWA$R zOLyu;-KUav~)iOrI3L!L!ODL0}g7d~=>*fM8Z5 z>!159{r+As6VJ@jr0~#*?Of++_X`~ z)1}?o@^lWx&Aa_FB)GOKX{p5Z1gpk#X?aH;xG@qzDq&&dO580uSYxppaBqK-F=(EdbIsjl6zVOi$u>#* zShSk9)gJ$F2Ks{+9qOHW8hYPdrFeKGy?J}pRCZk(8|l5QknB;o7wJk@$B}6(dhDN} zAF@!B01tlHKIrCd=e9!!o-r*gB0Sjm^IBdZ95c+T-}{cWA4}JE4Ce2P6oflzN^;Bn zsGGn##v>=waSO_~io}9gK9)F+ifp;WnF)?u2Fe^m0vNZYH-}`uJ-0+a*hy(P>L@bG zrbbdecLk+$(et~71X5|HJO9>Opdv^yBA~--u-R8Pf8ExicPEK$;89S_Q~xgXln1SFyknvEJW5%z`kh{FI85l; zeovhDJ?5f%ZFX~W6=|JX2=QbbMQE%pj}31!v*8$^vB>c*(`cRUYPP-`Z#*_+!6P+T z5njgv8^LEquv(J@@5={y1Gw?G2*YDTNM+Gk<#Q0%L;TqvyqHqIbg_q0bI2ysVbELn z+JW_**t=JI1k7VlFMxSH6|au42O^EmIIBs+PVpy;zyx3v3TJ;m1O3Ji!pn254u;Dk zoe##}6IdK=Rzf{50ka3zHe2dVdaNKLXEL6sH$N|!J^~B0trd*pOS6O!#qr{I>3-5s zlvXLyyj_q|#EO_4Fra*UhuuhU_-y1VyiMuh)%hK&Z{({HFx)Lz~HkD&^rZXLw5~xyn#MMkl%kdOMxNosE~5^|rF1vo_0<34zGK zyM~6zzx%J0||vVe!>QTmDbOOs;^m-0D+tGr^A6XHEQk*gB|>W3+1;J${uN^vc{1<5*R?cH{_SFxcj z5GGX)@~k12Scadv&?%u!S-lNS&h1)ldxQ*APrz<#ppB?WBJnpLWESwq)9>+rRiOMJ z?ub6o$mt+`T@{tzI0#m$7?DEWtc9g7qZ*{jD#X#T=Djk+gwx%~*Y@2=o-f^)lKV#_ zoO;O;WN{+g1*cyt7dZ(31M*9*bPa<4C*;ox5q>~^N-t0UtkC}l`7+Hm#1-v7<*-6v zxh2^e0^*w9*wM(8P4=1ga**}vKbU*_ELBx-XuG}@*HVL|^tM;9T9O-&>WtOY2!=wJ z@`{AlR69demq`JAz}gnVFDD5(k%1g^?!r)aT0{lP^qfzO zoob@slDF~PM~0xM?QJK1ivl07*gb!f>+#2JdQWy7FoJKu?ReOQ!#5|Qzkv6@iURbY zfiV9_+~XJBAIblY_&ooM^fwEwlaYb#N0XPyN4LV8d#&{>%&7Mu8~J=4=Q^{}@)|-ev9->8-7r7zh9Q^5$ApWd%~khuu`?-` z^@9D58OXwd#XjRTW(N3 z^NX#x8o=J0iS_MBsMym>f>aHk^DCoMxJ~Xxx{I%yr)BpfkoZ{16q!&4Z6{+e*lo%F zgy=XOlgD9aU(a)C-Iu&HK@!)J1IFYm53Pe6bKhp4u&S3Y8_KNo)fvxd0D@zwu8I`P z%tF)N(&`OSFqcb|+kPMZYfTX(&Ev*_>^8gIO@T<(yIXDnQqogr`&A#hCAEzeCt=OE zw9nPvjAl4~n{4{Iv0g`%X%f#iHh_*RskD z?eG-jf^7W~*NAG0)p-KXRmLl2%ZpwP-S&cxC;LvEjE|Y<(LMG!Zlj zWQ5DmAEu6bE3C6dGA9`gtD`5_y}e{)2gC`Iu>tNh-u;LE`KzpHKc9}B5z%(V zfAgj|%=l-sweb&m8!9h(D-rw>XU|nBrIUiU?`ShL8a;wGPhO<0!6aGC5Z^ek%gry+ zS+m5b;=p$s+GY(ILUUk=`6ed-V}VB6>IL;mMPMXzYgot>+;r`q+l z%gavihT3$QT@T~P93tOCb2%CnRkl9Zcj!;0&oStPH2Xdxt&eWvgZp+UU7MFr5UmC} zZoi~OvZJJtiNviGmcJ?sR?BkG^r&<^XvC82JiG|n{}$PQQqYJ*@vXvCu6{;HDE98T z4D|4EQ&u2Mq$+4%if@EC z{A!4%5+{AWKau3iBEL~kiE#2sL3P=2;I$=CdEjo`SqpBx)w`b(l(4dxp7J+F(exs`8yWWPawid9Ej6boaEQ3q&u8-B`L*2 zaMBNXpT)6V2=`Fv!R}mqc8qatmMBN2 z!3Aw^fBqUr!sij!^#xmUG$N2KoD%+|_*TvZQ9P;CDc~|82Zqn5N6vD>P6+M2gu(Ag z@b<^21MJFoTTd>>(tC81{I5{=uW-RL=WB3UTAzrS%|QkP`g~9$lgxR(*S93H7G&oJ;aIkh zM=m3PaI24{o_?IF#{9LO-r0ObaMd32hL>%Jem{z#-Z znbPUvo`pf#YU?)h-QI>_b_(&M5+d{oui5j?TvdQPKmFCPedN~P$~dPanRK*P`{f#& zeiM+p#6C0pF!Vit+3f*6jQ%BXM}ti6q+T4)CfJi6)qIF5Q=Ds&nN!GxB?wJtFPUhi zJ`_&>j=_=Qj}tNhs)vz5XGFyUPL#XFssi`ZEv=)O>T5JF-Z(eaeJfwMD^$*;a>!gZ zkeY;?O8UKvUEIR*)yp|D)w<(+3p5PVoO6cWJIGOo;=OC=8JQfd-KEd)P@~}C5eIhv z4`=TfWm&sqjaF9LHY?3a+qUhjv~An2v~AnAZQC}#e7oO1KDSTb|9h;l$BJh?Gh)V! z8T{A0v_HabriPA^SXLG>l>>JGzp!|`Mq3|ylQZM%)j4_Xjka;MoTFJjk%$U|z93wG zc-=J~|JX=Ur{#;#PSH4iH@8gn^mBR`6Z-b9m2NRw)OO)|%cz^-HC}LO-5S)_$-)=mrd4i6UXZSveCCgMcLY$L}B$a)|#A+-UF*$%)QCws&5cyjzc=CG6k_4uihbp zBd?y<+*8Hs*p%gSmqmY7?O9m@<9VJ;jWn3xYhIO3Cc;LId=H}=Uz0ATC=GiI@`pYq zEH|;c4(4ko)tV01c*ZNa8-FRKWyK5r#an^9O6KHp;8r8F37x_k}=)ki=!j*!t#)tym)eH)%X`5`$e6>j}*pVWom_`tCclv7%4HAg%!jxIaWJ29RPd9gDsh(c;f64r z3~iRP?QRuN3*C;@Lw2o&~4^+^=^A#MgM@~ z7P6#b2|1j4B=|qIBZ%?sr9L6g{+SBgN`Te+pvyN(AWXF zAAK=;eq6Y7>VAkP+l5oC;$y4@lSXMHc$wq6td1i^RWS0K0IiNHr3#t}Xj|Dh=h$)} zFFyjGiwPZhhJ0&R$3H;CR@D3Mg>2@lpA6m2bqF^~g8>N$Lg0_IplxNeli^lmG8&G| zpV7lQG#$rhv=i5B>l)9J^Yhkd#AglY%o5J(O^om87jy=>=l~wbXgVwDv>29gZC|LN zd+SdTrElV+7oS^}tHQXM7J}=Wh!*v_vLP{e>5%ax0-pnTpbcBvIrMmaGga;L-4}F~ zp|?SKZ3k9qI+Qg!TV*w9v!kS48+k~Aj!e+@!i001cr*k2;=X#>uqoy2H(JA2Oy_G4 zr4Nq5zb*DIiCZ&rsZn%wuE9Jit$DO*u@udIDA7)WTc&79^vXTGBDa+4gBq1poJ94p zlPl8h>9O`!yj8q*KRH+91UKJQX*BU6<<>baj4<;8D?nvbx2@tDBP||7%_)s-(1C`*!ZNd4FhVBfBDf-|Z6(ijj0O3>csf=)o zuRPQXBHS#7Y+gE@eYe?fuxc?2z(L&$+^r$YMV;jI8yMTw0iwOx)B7f#gZV7y0U8Ov8MyG<&)gFe!QGZQNU0!VjU zIT0@dYhV1(wZq?48*lUHpap8k^+~&Bto( zt~{<{KW9O@Km)y40!p5NKhypVdi+y!_U3Z`AKdgN?CGEW@PhoKm(BKnD2bW=2Z$}L z{v{S0p?Bd9R;RK>t}-C6wUludnqBenL(j8Cxm5N9D`lJ5ixi2tXMOSDq%EvJup?Sm zm`TWtcOlms4H4l&V8Q&C9spM4Om3ddf6Ko}Mbrr~n*gYf#TeGhw%JmO5~Ln-7GnE~ zy7S_7-_Eu_ZrrZj9(rUv-lK1QV%d?tJy0pOF94ix%+f&f9(gxRcu==PWj|IBB_t+n zq&K@d?5&1Ojhl&Ij=!Cq#P^=z;_F*GX9HhOez-1rb$t6bSMt~~bEZu2&qigTe+k8J)Or>BN}yaG9u8HWyc<5GjE}?b{nB} z$6>tgaVr$ap3_H{iP;(z%^8qK!_zZ6nyMekl%Il19PYHEnG>!VY@4hIJCLk)q>JDV z)l5MyPmX@)2#~LFDk@f8UfE$@6HS}HqQI9)7T*AdM`1Cub(9C+Z?H4n)xWdgttPk( zYupJkKMHPJOy09r{TydKlkPdRd9JLOf|K1eF*Qy2y@R=2r~bz6G#=~CD3&~}X`i40??sDu(&9%ZAZKFjq+Az8MVZs;>b8(#jU5-FKemuEoW)+I$e_5W*7y7Z z=|90XGfJs0i$Ha$`Yrm4mgAiuV?8oAIXe0(!jn{qgXYoIP6@B+ZxXQ=E*6&i>_> zjPqPu$KQsDB?-0ZL2nXeDX~oOl0W2l*GIgCJ$?$^9N;q5r8*TzXS6KfF1LT`I4<*D!xPTC?arw`H|AhI{x@wehf*dLz7WKFMDU(IC8uGO_fg zTyQ7|W`yzNE$oFEiOqt+;;ITFoNMnb8Hck+a`5Ev`*Hty(N1?;_$8D3eyzA~D5UQw z#Hf=S0ZeB&3Ipagl>Y>W!y1ZSQW#4@n#xTwLry5GEQ>NCf~DGlvz! z+WA|iSG{@q(T$>pv6}1>rv%=5hXjKX8Z*VT2$QpW7dQP+c#&KWh;eT9R8n>Kbq4Wg zU!zNGtDEuQ&36WtrkLAj=&D4ggL;mPR;&G98szUEc4=Nnrdwl<%0}iAS0&>MC6&u< zwRj%%P^j)ztuzE>6Gu}El-0~T`QM3&&6C+b#Q8g5eOtK8yf7Vxx*@7hEd+Nr3l1n)cT$FzD zY-p8^#(aB3N?W;n!IhGTOZ8!)*s{Hjl!b_oSUl6kRO;Hh5?t+kh2;90E7$qbhgHz3 zQFbV}<`}NS4dk}B!*b5C>Ty>U9%3D+P*AVE>WX}i=9y(sUO2(fDl!sV%Z%lj&nuF9 z`(clJ92rp(#Y=-`f6ljlw219njW$tD`;BoUxYUvd8;~hP2)XU_kxMM}JJkms2$pwK z3QKN@O|*Y!UM{89Q5I-^7VdrbMN@&^PfgM104(LWxIg4!451GtO?_i;-AO}`mRe4c zzC#G{zTl;pH7GnfZ%{fB@k#D8b!)Tz`MkZii-7w2g-7xIacEkG=ic! zVltY{$UByRyIZJPxG&+^)CuHM*-diVlqg|Y8PoUvRm#9N%$dO?d-N;t$_s0cOHQ(d zb8F~}ZOPIRgG5~_VfV=4g#-OF0a}#V%cx_Giw>)hfH|&Skc677;Ou&8*OwnSC#pm( z_h^sw;g=1lr=Zg&f^)rVZKS1aBkR)&3U}CEU+S>pg7(n#@aF$ghXwwj4y!Kj__l@w z-PLRYYuSHI#hTxAO_LQZ$Ss2c3i~K)kLO?0QV-K8msSlxW}~zY!f-Faa&++wzI_16 zZBpu`7mL(q5Yqd&E$xTP{EeGtXO(QeT>9yj@IOV=9qlXTzbBcwc)V10{(m4U^Zy{K zfuW&+p8elfw|@$)cmGRhU58+$_(d^GN{T4Vn)I(m78ZpS6_f*gEa!dn8Zaoch5yc1 z+?6}D)>Z0$GJ3B>M+hUfn9VZ@7gZ;V51lk}W6iI`M}Q8(OD*57Qnf$84UgBUQH9R;U}E2627XrjNrnUIuYvuClx{A5^u zeY~b|SHinZE%bj0#5bMc^=X>eO{Wp_-78V?jH!~ypyn6jG;&yQXR=zuRajmm^7X1& zSorne^;VvA?sgx=Rir);45;)KCm=;!uFcs9zeiJ%KQDW=ER%k}B^oMfKs9270W{@X z?9*{uJUmk5DU`HiKyD3p&ln0^Wg3(KOP@F4a}*Y`&p^K2modgE!tOmUKy9XPH;1=9 zs|`SbKAjS?T>GIfA}ghP8$diLHZQ@*t(vNHH=?NHpRyC|)f`5!>Sm@Qo=x5y`;81% z$Rzf+FHb30#O!&EOSI&F*hQI(skA4NOCcAAnW*no#_=D&@V~=8dKG>$bC%yuKU0re zZK5yVsOavm`241*gyC$D{kSMbHb}hDl5RI^y9D*n%LhiZoqKC;Cw|=9N+83m-AosM z1HQHLb$$MAc*(kNIDgPd|Ex|-o&4sZT1554c$)ybrgYo0mjM0(L>2bCjxeJ@d@JAj0E`ku>3nt8ow$aJG49)So+f$N#l?o$NOGA3>+ndOeEq1qbOO`7 zXvigLA!8*r(4%_jj}vy9J_L%mi℞Uq9I0$W&P?N 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 + 4.0.0 + sixtenhugosson + playing-coffee + 0.0.1-SNAPSHOT + playing-coffee + A GameBoy emulator written in Java. + + + junit + junit + 4.13.1 + + + \ No newline at end of file diff --git a/playing-coffee - Copy/roms/cgb_sound/cgb_sound.gb b/playing-coffee - Copy/roms/cgb_sound/cgb_sound.gb new file mode 100644 index 0000000000000000000000000000000000000000..dc50471b00315edade129fc438517975a7036465 GIT binary patch literal 65536 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 diff --git a/playing-coffee - Copy/roms/cgb_sound/rom_singles/01-registers.gb b/playing-coffee - Copy/roms/cgb_sound/rom_singles/01-registers.gb new file mode 100644 index 0000000000000000000000000000000000000000..be180baf816f1cdbf2a7ac3dc36e72adf8636f43 GIT binary patch literal 32768 zcmeI4?{5?59l#$uiAjUwBs68$G?2#xJU1ib=tQto_gP>FG;JjmX)W4|?YzbpGI@5I zWW<1DoTQuDsV~-d&6w6~>n8QhI`(3eA+0fIJ3YdKhE}L6mF?Ws(Pq)irn?qGu=jn= zNu;v<1E}98cfRNQJl~(6=Xvhlg^2B zFI>O=-t_tN1J8zCH!fX&fBMz)=f1-3AvO!L9sUUE{MEueb0{JKB!C2v01`j~NB{{S z0VIF~kN^@u0!RP}AOR$R1dsp{Kmter2_OL^fCP{L5>2+;iF>2|J zXi`7>_R82hV(bZ8($o0?*5pcT&D71?w$a-WPsKszk9qAqmJ3h0_M)5w1Z`~Dcm(R& z3>iHF+0@ddp57i(3vuWtyQ8_JZh={@7B7kfB|BYm^)`C*E1F<*Q~GP3znGFb?FSF< zl)Dce-nEkk!u@nCa4bjKkQj*L<8^@D(wg5?98 z1^QPUK@P%Rdp%{dYwqSs!=ItVn(gCd5B-l`9fH1hUBe%S#SFyW26vm#=%sl*@l{fJ zt0I0*RbGW&g^;%h8LF(ddylfVoW52kFL%h~vh6Q#6Kk{my}Hjnv4n~^5o^nCi$jRQa7Vg-aIbl%oEae^Q82i8I!J>5yxl z8``13$Vd=Q4I|o_^im@0_trh({fE>f=JrLx!PS|GY-DLNI}ktXeE=*00{nt{xkByC z2Bu~)vBNA*z~)DBVl+HFgf^kU*X-B6tP6KyO`o}=}}sW)kjHlgubDec`<7u35x zdWL?gf4q|z1(LPJ*SQl}{_nKy6mcgi(19(s4(=wnD{ysT zsY|%p9~cY;`@sj$Ta{=hxcr{A+UuRQjBIJ7%XRd&rSbJJoX9Yotl;o)WLTVTfWsOJ zgs|GeRF=&Yp3kzs6>Kx?_XYb53lwT*7%9}wutb5(=y`K#3P3-f9q8;Kh33?m3~ahzXWV4cMfgAA`4d+Ko~*Sn zorM5D%d%UA(^>xXob+usdBssXww*3-t)sWZhw7-K8AuMYQ*e|2f7j9L%x3g40x>|R zPj5Lr=TOp)`nCsO-?9b#Bzu+aEUpk%5)I$Y~x?jmU zS|6*U%WN8IgHOncGH6_T!Pr~UuL|Xe$Mfi;Ez{XeZCg%4j9a-YYp)AQG$N5uC}h1s zLgf%#gh)*BJ7N-1Lz1jyeoWeX+IJM&=;1~I8ZOd|9)6uL;?qKRV6W16&H*v ziUNM4AyrjFQHZa^6b16Ko(f@#5{8fjX2DD)ev!W9Cl1lT4s3?N7D^Q@PBM$-Yne=j zsK?@QFckSh^IP#i!IF=KdMp`2iuiuv2Ox?j;y|$)zv>|{O67~Bm(GU7J1ORwe^@TrCe^rhtmOpSPWWfVuvGHIX z1pybxB8>R2NWx4kFaU^&y=nodjzd1M$kzUGNs=t=tnk2PNRph87U(HAe^K4K&?f|5 zFyh52mG!lwV2$Gs5WB literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/cgb_sound/rom_singles/02-len ctr.gb b/playing-coffee - Copy/roms/cgb_sound/rom_singles/02-len ctr.gb new file mode 100644 index 0000000000000000000000000000000000000000..eb5ca82b76bba016c588efbb905a21fce786102f GIT binary patch literal 32768 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 literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/cgb_sound/rom_singles/06-overflow on trigger.gb b/playing-coffee - Copy/roms/cgb_sound/rom_singles/06-overflow on trigger.gb new file mode 100644 index 0000000000000000000000000000000000000000..b2d5c75a2fbb2e3f5530bb065ebf257325a390db GIT binary patch literal 32768 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 literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/cgb_sound/rom_singles/09-wave read while on.gb b/playing-coffee - Copy/roms/cgb_sound/rom_singles/09-wave read while on.gb new file mode 100644 index 0000000000000000000000000000000000000000..1e4a78cdc9ed1408864a96ff3a47e359edec58b4 GIT binary patch literal 32768 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 literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/cgb_sound/rom_singles/10-wave trigger while on.gb b/playing-coffee - Copy/roms/cgb_sound/rom_singles/10-wave trigger while on.gb new file mode 100644 index 0000000000000000000000000000000000000000..56f0e901ea1f0ecb0b984536a14eb8870babf5e5 GIT binary patch literal 32768 zcmeI4UvL{o8NgSvt+=)FxZRM`#?{ydNw9%9OXd10m<{)Gq9;1^4$6Frm-4J6^p)Nl%wXV9b`%k4FZ<4Syc zay(4nk(UhL`c`}2?)Ue%d;4fY{?D5>9K8IFs$Oa%tqsS?Eb)>y((La(^KIgL>r#Aq zY3a?mg@w_t#eFv~Exk4OlZA_qv-^lGg6xJrLV9<+dfys~NB{{S0VIF~kN^@u0!RP} zAOR$R1dsp{Kmter2_OL^fCP{L58pc78R(&T~Ce0lk!37@{QE-F@Hs;?{bE6h22ucXxzH5In0BM9c^!)>gV#aX5Y}AVvMS_x>uKNbt!lg}3Fj|xT3?=Mk#O5z#DtTA+ z)6{nTG3D34$6wL3k8An4L&4u<_zxDHki-fktX=F|5VdOUvRRu*T8P}2t&>>eXS0IE zEq*j>5P#3(2cK1^&dj~KaA|3!rk%@OZ<05{_pj=-*<$X*r6>Fc9Q=o zb4sim|9MCai9k_lYucKzW(`}vRtnD6p6>sA{}=jy8EP&>3Ofs3-EzlQ6D#i=I)9S#vDyu2tNdt00DkMy;`Gg z<^ofDHND?norXRGeRjVneXF0%R*cp30^Pnk{X5#WI&+aWug<?;kuLH2C=(Z`JueO(g!9 z+&?uuIYOVGI5s*uGC`+~9*d38v3N@h)b7vPtbrFZ%sE$FoFx9Gj8i|vw)t0bd`_?M zPZ`<)*vzBZO4VgjpK$y1Ft|AaK7iysiS~laHD~8ry^W0Yw9$HuCEhwxg&R2hXeRYJxFGJ zgrA1-JH8r>tg}34LVzEd>~{HOldtL0mxJV-r*Uc@-PqGaZ;MOxsHX$IpkwSM=;Xim zCc4C2)(|5Q19XNA|AD+m(LK#w55LmW1Aa11aAVngJ;%;kgP`#gd)qps?Z`V*ervE| z{7Q1O^l}Yp=rb@iNt7`Ka8wyq9I zG$N5$Eau!Mv1$w+LL{a{JxPhEF-cbP-z0&7Kx9n)1o3*kj-D?7b^#w5kH8oiCq#|b zqADy&QVJZ_n=C8vlG>CAQ553#c){TyiK_8QRgDr!*h?vp#X^dtypq=g^WH!-mV#I* zCFv1zR8f*bKPA*;8T3{0h>>}`5fN8Bo1;ub6j>6vLU?%KU0BxRK)n$E>v4q<2^^$U zl_XVFJTR{)3iwULR8@^7Aik1R6v)dN6~YuH4j~Dwf|*L9B7G@JJfeXe*o=cMlqy<^ zXjCHL>w0ws{Halq zx>y$})VV;s-HzFAGAOF|xJkm16Qm;&%Wv%iA=O1i-V~!A2L2tqP(Kl*6;Tg!;)1NE zKn}=Bz%FcHo_T-?;DY!Pi9iI{N1zXF9N@)7{Silug8oeb0LHq$@F(i8%SpxY2M&cS zcz`Sp9_*ta-~(BN5&yAAn28Mr07-FH9RT%t$OksrIX^y0l7*cU9=ME4k{8kfJ!S33 z^`i^#gun$OE>Wp!Y<&vOJpLd7B!C2v01`j~NB{{S0VIF~kN^@u0!RP}AOR$R1dsp{ zKmter2_OL^fCP{L5^Jn>^yX_^~8iXeQh0CP|ujNHb|{>we`&#QXk* z=<3SKdvj;bj6ECmUcIpL{@gFlEIhz&Ben>#6aEp>`^-bPt)YknkN^@u0!RP}AOR$R z1dsp{Kmter2_OL^fCP{L5rZ|GFA9(8NEk10eo5Nu@Df)jIXsP9Jk;ib+ ztMQC>`dn@9T|V~+Eon-AjJ0@EyVUw+$8LHv=C8QP^3i~E(01W3yUqtQfS`kInD;_m zU69eekd2G34-O3y7sR30ACBiTnhj>7T0Acjl$>O2&i8l5;29iuTxqLa_Xeh@iKpN));OwcF3 zJ+P1DxW81DPs)u`?ZjUK2PZ6XHaTKq?Oe4WSVyw7iXbA6Gw2M26#7Hit@~7OUe`{p<5y8^LRB3v68o)Cg(I|B&UcDp%@*8{I*2(ebBq#NMjERd?ySEmXLl zt$ni+dMbOIuWSGLNofLsEl8O&=B%04Eah_Em##e4|3v>&{lE1$E{B%yS?=f#wmuVI z`}pAW+FQdfuBE?oY>n=wANngj6MJ6RGoHJYDQQ-@Y3Y1ce?x&GqqV-t?sIyt{E;Qp z>2-47X?hb5|2JekChOC(enQrF%9^FWEN|Mtu%uWS{d;mov*JSDnig`_i^7U^OnA>q z2$!wn!X-D>nS4~sDC8zccF!^9f(CoH&S3rdBbv*DB7yI}2}mFE4%TeN--K#nc(Py*TqWZ7$9((8gl=Em|j^&FX_o z@{XlGsCQfZ6#Z2DWPq3jVmOiwzLcRqs5nj#U#bEF_-q>BZGpF9e?p(Tp7f72{b@!2 zy`ulcE^1()e`0iOl8#8P6l3vhc z6ZUbZTq-*Z>g6aN8#xjgJq$ho;-*ADxRmMtYj*Mm@c0YkWNlk)tCKIyx~C zn*fLvV8ogVgfLejZLs;mO9uOE!74TC%4qtN>N+u2TbHgYaQ6PG_{gl6=>4jX z{McK8p0yX|3<&TugWV{cH1w0I@U)M-?siS>p&PsE=?#9{Kj&_RkLL(G0WbOQZ#`XM z4s)0hhygmoTAQi5MaA9NarbX}dcaSn9>N;yeRqglZM&| z6)@_1-Eg<>tEU@m4r+sYxTXvmS6(sqm$b`VIp+7@e}CJYabHKzF^F*^cWLX^5O^a3 zi9{myMG`4T;2}g{V%VJ!h!hcmqWVM9IoKJBNnarzkH^;Yc7k1}myCy?4~-Kdg)3nR zmINUQ4y!E~6k(98NrZ?Zak@R=aEOGZ=ut@u6M@?cNs#$Mk|aHX#|`tI&Tu3Nv65oK z&E>EtCb)i*s|ACgFY$->%upkT{KBZIaKA$j~T_X7}l<8h$b@uYBAAmHmtc@6wYVV*i) z=P6XVK)f#7tcwis>fKHfx8*o#4Mo(geITT~C87wVzjy zE{q9*3x?mHLRs7T6zqBYK>|ns2_OL^fCP{L5;uTH}RQy{;p(g!>3v)e6CuNfd^V5a%_%G|014 zJ4>9_^_*ltK|)$35DQ}?2nh+KQu+sUr4x!d`@Bnf6@|6)U;(F_t6_CfoaweU&1}B! z;)b;#{@%p*<=)-UArubbK-;w1D4P;$Sl#CMxX&`mYZ704B3#EofcDoU^83J-wSnZ zg^ccnY-;IJS9cewg*f!R2V-+7%>=W&Qampbl&o~g+1=nNR5ih9QTU51q3A!Df}4!)nIS( z68O-$yV+XN`xMikr{>|OB9TC7kUlXo$ldsR{Z9k~)IUi5p%FUlAA)gjZ{$vIPTz-I zwA&sHN1pTtssGTSKop`M4ut5T!~PKM?T>P2y>}Y(m%>wnJP5g+hr|AOq#qI(3`7U3 z>I;zhcX;L-|^74|?R4E%$DhnG4O4${3;Tn*a_X_}dX zm#eE`JL~_qm*`+15(rlv5%_-iCc4X!V#{n;JnOktZUz8H%LMMn%CeBc+d|Q<@PI3dV>qXB-t~jbp;=MqIdT92YJbnHZ>z zq=c-IdFTtmqLJD6h;UthTQO!H)K8^L*BZ<9UaPuB6_wMfkr5n51}4@yd(XZ4TH0B! zzm`@-NCvXD@2Wj1E|2!aKpfHL3PwjqXEB*OUL4KEi(|QxX6{34h0N)HQ#8o6p!?E~ z`J+*D*At~qT}tMB9@}2en?fUB+ZPV;9cr$O=E6&3xxU0%&swkq2yh4Wa)nx%1x$^F zf!wg!45^a^x0Y`WFMTfa4}2UT5F^&4hUyYJu0 zPlNh;D0NmbSdD&4Vdgf{`Nq+|nlg)mP=>xq)emM%`tr20IBh^trLrYQoxJ@0{(d+R zzz2Z4Ezu5e`3LhwuXEHg(%wLqZS*ylKK|c=apDGQ{Y5EUUC7#V# zw%)3Vo$=Tg_LZNdMoQEBvP&d)ttnlXh5fdqy2zN5=%3BH$oHHTc(Rt_L=FP{ILB@j zpU>&1vclI~tV-@n85Cir0b+0)R;fA2Or%PjdGMj!_0^k~hCS-X_6 z*KOYL%l3Bgld{2&jiLWmWhe99pz$z!D}O+_lr^WC^W7!wv|we)Ye`6je_Ea`1;aIw z>nwkZt}T_Rr(S;{Ym|Ps^V6YW_U<;-4mLTeU`HHHlL>@{cz>wz) z&1c2~1yddhc9}ASP;rW_+p-k`er9tbH*%|mZ@k0HCla_cr%!~`NoB+91j01LV1M|!dOaK?e z7l;=kzAuk7YqR6e6N}SR^}lec**AeaSDRS?ac7NWk3)dkQV4ki!ZKhT^JJr2Mj+mg|fE# zDVXzkLjp(u2_OL^fCP{L5 disabled clocks as well + ld a,1 + call end + + call begin + wchn 4,$40 ; enable length + wchn 1,-2 ; length = 2 + wchn 4,$40 ; enabled -> enabled doesn't clock + wchn 4,$00 ; enabled -> disabled doesn't clock + ld a,2 + call end +.else + set_test 4,"Anything besides enabling shouldnt't clock" + call begin + wchn 4,$40 ; enable length + wchn 1,-2 ; length = 2 + wchn 4,$40 ; enabled -> enabled doesn't clock + wchn 4,$00 ; enabled -> disabled doesn't clock + wchn 4,$00 ; disabled -> disabled doesn't clock + ld a,2 + call end +.endif + + set_test 5,"If clock makes length zero, should disable chan" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + lda chan_mask + ld b,a + lda NR52 ; channel now disabled + and b + jp nz,test_failed + + set_test 6,"If length already reached zero, shouldn't clock" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + wchn 4,0 + wchn 4,$40 ; no clock; length still 0 + wchn 4,0 + wchn 4,$40 ; no clock; length still 0 + lda chan_maxlen; end triggers channel, which loads it with max length + call end + + set_test 7,"Trigger should un-freeze length that reached zero" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + wchn 4,$00 ; disable + wchn 4,$80 ; trigger unfreezes length, so it takes on maximum value + delay_clocks 8192 + wchn 4,$40 ; enable + delay_apu 2 ; clock length by 2 + lda chan_maxlen + sub 2 + call end_nodelay + + set_test 8,"Trigger that un-freezes enabled length should clock it" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + wchn 4,$00 ; disable + wchn 4,$C0 ; trigger unfreezes length, and since enabled, clocks it + lda chan_maxlen + dec a + call end_nodelay + + call begin + wchn 1,-1 ; length = 1 + wchn 4,$40 ; enable, causing clock to zero + wchn 4,$C0 ; trigger unfreezes length, and since enabled, clocks it + lda chan_maxlen + dec a + call end_nodelay + + set_test 9,"Triggering that clocks length of 1 ","should clock twice and shouldn't freeze" + call begin + wchn 1,-1 ; length = 1 + wchn 4,$C0 ; trigger and enable + ; First length counter is enabled, which clocks it to 0 and freezes it + ; Trigger unfreezes length counter, which clocks it AGAIN + ; The result is the same as the previous test, which enables separately + lda chan_maxlen + dec a + call end_nodelay + + set_test 10,"Trigger shouldn't otherwise affect length" + call begin + wchn 1,0 ; length = max + delay_clocks 8192 + wchn 4,$80 ; trigger + lda chan_maxlen + call end_nodelay + +.ifndef CGB_02 + call begin + wchn 1,0 ; length = max + wchn 4,$80 ; trigger + lda chan_maxlen + call end + + call begin + wchn 1,-2 ; length = 2 + wchn 4,$80 ; trigger + ld a,2 + call end +.endif + + set_test 11,"Disabled DAC shouldn't stop other trigger effects" + call begin + wchn 0,$00 ; disable wave DAC + wchn 2,$07 ; disable square/noise DAC + wchn 1,-1 + wchn 4,$C0 ; clocks length, which becomes max + wchn 0,$80 ; enable wave DAC + wchn 2,$08 ; enable square/noise DAC + wchn 4,$80 ; trigger + lda chan_maxlen + dec a + call end + + set_test 12,"Other trigger effects should still occur when disabled" + call sync_apu + wchn 0,0 + wchn 4,0 + wchn 1,-1 + wchn 4,$40 ; len = 0 + wchn 4,0 + wchn 4,$40 ; len = 0 + wchn 4,$80 ; len = max + wchn 4,$40 ; len = max-1 + wchn 4,0 + wchn 4,$40 ; len = max-2 + wchn 0,$80 ; enable now + wchn 4,$C0 + lda chan_maxlen + sub 3 + call delay_apu_cycles + lda chan_mask + ld b,a + lda NR52 + and b + jp z,test_failed + delay_apu 1 + lda NR52 + and b + jp nz,test_failed + + ret diff --git a/playing-coffee - Copy/roms/cgb_sound/source/04-sweep.s b/playing-coffee - Copy/roms/cgb_sound/source/04-sweep.s new file mode 100644 index 0000000..17ac4e4 --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/04-sweep.s @@ -0,0 +1,120 @@ +; Calc = calculation of new frequency and check for > $7FF +; Update = modification of frequency with new calculated value +.include "shell.inc" +.include "apu.s" + +begin: + call sync_sweep + wreg NR14,$40 + wreg NR11,-$21 + wreg NR12,$08 + ret + +should_be_almost_off: + lda NR52 + and $01 + jp z,test_failed + delay_apu 1 +should_be_off: + lda NR52 + and $01 + jp nz,test_failed + ret + +main: + set_test 2,"If shift>0, calculates on trigger" + call begin + wreg NR10,$01 + wreg NR13,$FF + wreg NR14,$C7 + call should_be_off + call begin + wreg NR10,$11 + wreg NR13,$FF + wreg NR14,$C7 + call should_be_off + + set_test 3,"If shift=0, doesn't calculate on trigger" + call begin + wreg NR10,$10 + wreg NR13,$FF + wreg NR14,$C7 + delay_apu 1 + call should_be_almost_off + + set_test 4,"If period=0, doesn't calculate" + call begin + wreg NR10,$00 + wreg NR13,$FF + wreg NR14,$C7 + delay_apu $20 + call should_be_almost_off + + set_test 5,"After updating frequency, calculates a second time" + call begin + wreg NR10,$11 + wreg NR13,$00 + wreg NR14,$C5 + delay_apu 1 + call should_be_almost_off + + set_test 6,"If calculation>$7FF, disables channel" + call begin + wreg NR10,$02 + wreg NR13,$67 + wreg NR14,$C6 + call should_be_off + + set_test 7,"If calculation<=$7FF, doesn't disable channel" + call begin + wreg NR10,$01 + wreg NR13,$55 + wreg NR14,$C5 + delay_apu $20 + call should_be_almost_off + + set_test 8,"If shift=0 and period>0, trigger enables" + call begin + wreg NR10,$10 + wreg NR13,$FF + wreg NR14,$C3 + delay_apu 2 + wreg NR10,$11 + delay_apu 1 + call should_be_almost_off + + set_test 9,"If shift>0 and period=0, trigger enables" + call begin + wreg NR10,$01 + wreg NR13,$FF + wreg NR14,$C3 + delay_apu 15 + wreg NR10,$11 + call should_be_almost_off + + set_test 10,"If shift=0 and period=0, trigger disables" + call begin + wreg NR10,$08 + wreg NR13,$FF + wreg NR14,$C3 + wreg NR10,$11 + delay_apu $20 + call should_be_almost_off + + set_test 11,"If shift=0, doesn't update" + call begin + wreg NR10,$10 + wreg NR13,$FF + wreg NR14,$C3 + delay_apu $20 + call should_be_almost_off + + set_test 12,"If period=0, doesn't update" + call begin + wreg NR10,$01 + wreg NR13,$00 + wreg NR14,$C5 + delay_apu $20 + call should_be_almost_off + + jp tests_passed diff --git a/playing-coffee - Copy/roms/cgb_sound/source/05-sweep details.s b/playing-coffee - Copy/roms/cgb_sound/source/05-sweep details.s new file mode 100644 index 0000000..1519b68 --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/05-sweep details.s @@ -0,0 +1,121 @@ +; Calc = calculation of new frequency and check for > $7FF +; Update = modification of frequency with new calculated value +.include "shell.inc" +.include "apu.s" + +begin: + call sync_sweep + wreg NR14,$40 + wreg NR11,-$20 + wreg NR12,$08 + ret + +should_be_almost_off: + lda NR52 + and $01 + jp z,test_failed + delay_apu 1 +should_be_off: + lda NR52 + and $01 + jp nz,test_failed + ret + +main: + set_test 2,"Timer treats period 0 as 8" + call begin + wreg NR10,$11 + wreg NR13,$00 + wreg NR14,$C2 + delay_apu 1 + wreg NR10,$01 ; sweep enabled + delay_apu 3 + wreg NR10,$11 ; non-zero period so calc will occur when timer reloads + delay_apu $11 + call should_be_almost_off + + set_test 3,"Makes private copy of frequency on trigger" + call begin + wreg NR10,$12 + wreg NR13,$04 + wreg NR14,$80 + wreg NR13,$00 + delay_apu $39 + call should_be_almost_off + + set_test 4,"Exiting negate mode after calculation disables channel" + call begin + wreg NR10,$09 ; since shift > 0, calculates sweep value at init + wreg NR13,$00 + wreg NR14,$C0 + delay_apu 2 + wreg NR10,$10 ; neg->pos, so disables channel + call should_be_off + + set_test 5,"Ending negate after it maybe changed freq disables chan" + call begin + wreg NR10,$10 ; enable sweep + wreg NR13,$00 + wreg NR14,$C0 + delay_apu 2 + wreg NR10,$18 ; negate mode + delay_apu 2 + wreg NR10,$10 ; neg->pos, so disables channel + call should_be_off + + set_test 6,"Ending negate mode any other way doesn't disable channel" + call begin + wreg NR10,$1F ; use negate mode once + wreg NR14,$C0 + delay_apu 2 + wreg NR10,$18 ; since period > 0, doesn't calculate at init + wreg NR13,$00 + wreg NR14,$C0 + delay_apu 1 ; no sweep clock here + wreg NR10,$10 ; pos mode before neg mode ever used + delay_apu 1 ; sweep clock occurs here + wreg NR10,$0F ; now let neg mode be seen once, but period = 0 so no calculation is made + delay_apu 2 ; sweep clock occurs here + wreg NR10,$10 ; doesn't affect channel + delay_apu 2 ; sweep clock occurs here + wreg NR10,$1F ; let neg mode get used + delay_apu 18 + wreg NR10,$79 ; period and shift can be changed without channel disabling + delay_apu 5 + call should_be_almost_off + + set_test 7,"Subtract mode uses two's complement" + call begin + delay 2048 ; avoids extra length clocking on CGB-02 + wreg NR10,$1C + wreg NR13,$B0 + wreg NR14,$85 + delay_apu 2 + wreg NR10,$01 + wreg NR14,$C5 + delay_apu $1F + call should_be_almost_off + + set_test 8,"Subtract mode uses two's complement (upper bound)" + call begin + wreg NR10,$1C + wreg NR13,$B1 + wreg NR14,$85 + delay_apu 2 + wreg NR10,$01 + wreg NR14,$C5 + call should_be_off + + set_test 9,"Update channel frequency only when period is reloaded" + call begin + wreg NR10,$74 + wreg NR13,$06 + wreg NR14,$85 + delay_apu 14 ; just reloaded + wreg NR13,$06 + delay_apu 13 ; if 14, fails + wreg NR10,$11 + wreg NR14,$85 ; just before next reload, so freq is still $506 + call should_be_almost_off + + jp tests_passed diff --git a/playing-coffee - Copy/roms/cgb_sound/source/06-overflow on trigger.s b/playing-coffee - Copy/roms/cgb_sound/source/06-overflow on trigger.s new file mode 100644 index 0000000..2ebc236 --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/06-overflow on trigger.s @@ -0,0 +1,64 @@ +; Finds highest and lowest frequencies that don't overflow +; immediately on trigger, for NR10 values of $00-$07 + +.include "shell.inc" +.include "apu.s" + +main: + + ; DMG-06: + ; 0555 0666 071C 0787 07C1 07E0 07F0 + + wreg NR12,8 + ld d,$01 +shift_loop: + ld a,d + sta NR10 + ld bc,$87FF +- ld a,c + sta NR13 + ld a,b + sta NR14 + delay_clocks 40 + lda NR52 + and 1 + jr nz,+ + dec bc + bit 6,b + jr z,- ++ res 7,b + call print_bc + inc d + bit 3,d + jr z,shift_loop + call print_newline + check_crc $F604603B + + ; DMG-05, DMG-06, DMG-09, CGB-04, CGB-05: + ; 0556 0667 071D 0788 07C2 07E1 07F1 + + wreg NR12,8 + ld d,$01 +shift_loop2: + ld a,d + sta NR10 + ld bc,$8000 +- ld a,c + sta NR13 + ld a,b + sta NR14 + delay_clocks 40 + lda NR52 + and 1 + jr z,+ + inc bc + bit 6,b + jr z,- ++ res 7,b + call print_bc + inc d + bit 3,d + jr z,shift_loop2 + check_crc $5A1697EE + + jp tests_passed diff --git a/playing-coffee - Copy/roms/cgb_sound/source/07-len sweep period sync.s b/playing-coffee - Copy/roms/cgb_sound/source/07-len sweep period sync.s new file mode 100644 index 0000000..6b3ed7e --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/07-len sweep period sync.s @@ -0,0 +1,106 @@ +; Tests length and sweep periods, and synchronization between the two +.include "shell.inc" +.include "apu.s" + +test_timing: + ; Time how long until next length clock +- inc de + ld a,(NR52) + and $01 + jr nz,- + + ;call print_de + + ; Error if not in 0...4 range + ld a,d + cp 0 + jp nz,test_failed + ld a,e + cp 5 + jp nc,test_failed + ret + +main: + + set_test 2,"Length period is wrong" + call sync_apu + wreg NR14,$40 ; avoids extra length clock + wreg NR11,$3F ; length = $01 + wreg NR12,$08 ; silent without disabling channel + wreg NR14,$C0 ; start length + ld de,-$170 + call test_timing + + set_test 3,"Sweep period is wrong" + call sync_sweep + wreg NR10,$10 ; sweep period = 1 + wreg NR12,$08 ; silent without disabling channel + wreg NR13,$FF ; max freq + wreg NR14,$87 ; start + ld de,-$2E4 + call test_timing + + set_test 4,"Sweep clock is synchronized with length" + call sync_sweep + wreg NR14,$40 ; avoids extra length clock + wreg NR11,$3F ; length = $01 + wreg NR12,$08 ; silent without disabling channel + wreg NR14,$C0 ; start length + ld de,-$170 + call test_timing + + set_test 5,"Powering up APU MODs next frame time with 8192" + call sync_apu + ld de,-$16F + call test_power + + call sync_apu + ld de,-$B5 + call test_power_off + + call sync_apu + delay_clocks 8192 + ld de,-$B5 + call test_power + + call sync_apu + delay_clocks 8192 + ld de,-$B5 + call test_power_off + + call sync_apu + ld de,-$B5 + wreg NR52,$00 ; power off + delay_clocks 8192 + call test_power + + set_test 6,"Powering up APU resets 128 Hz sweep divider" + call sync_sweep + ld de,-$229 + call test_power2 + + call sync_sweep + delay_apu 1 + ld de,-$229 + call test_power2 + + jp tests_passed + +test_power_off: + wreg NR52,$00 ; power off +test_power: + wreg NR52,$80 ; power on + wreg NR14,$40 + wreg NR11,-1 ; length = 1 + wreg NR12,8 + wreg NR14,$C0 + jp test_timing + +test_power2: + wreg NR52,$00 ; power off + wreg NR52,$80 ; power on + wreg NR10,$11 + wreg NR12,8 + wreg NR13,$00 + wreg NR14,$84 + jp test_timing diff --git a/playing-coffee - Copy/roms/cgb_sound/source/08-len ctr during power.s b/playing-coffee - Copy/roms/cgb_sound/source/08-len ctr during power.s new file mode 100644 index 0000000..1ea218a --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/08-len ctr during power.s @@ -0,0 +1,84 @@ +; On CGB, length counters are reset when powered up. +; On DMG, they are unaffected, and not clocked. + +;.define REQUIRE_DMG 1 +;.define REQUIRE_CGB 1 +.include "shell.inc" +.include "apu.s" + +enable_len_ctrs: + wreg NR22,8 + wreg NR24,$C0 + wreg NR12,8 + wreg NR14,$C0 + wreg NR30,$80 + wreg NR34,$C0 + wreg NR42,8 + wreg NR44,$C0 + ret + +main: + call sync_apu + + ld a,0 + call fill_apu_regs + + ; Load length counters + wreg NR41,-$33 + wreg NR31,-$44 + wreg NR11,-$11 + wreg NR21,-$22 + + delay_clocks 8192 + call enable_len_ctrs + + ; Power down. Comment out to see what would + ; happen if length counters did run. + wreg NR52,$00 + + ; Try to enable length counters + call enable_len_ctrs + + ; Give plenty of time for them to be clocked + delay_msec 250 + + ; Power back on and wait a bit longer + wreg NR52,$80 + ;call enable_len_ctrs ; can't do this here + delay_clocks 2048 + + ; Get values from length counters + wreg NR22,8 + wreg NR24,$C0 + ld a,$02 + call get_len_a + push af + + wreg NR12,8 + wreg NR14,$C0 + ld a,$01 + call get_len_a + push af + + wreg NR30,$80 + wreg NR34,$C0 + ld a,$04 + call get_len_a + push af + + wreg NR42,8 + wreg NR44,$C0 + ld a,$08 + call get_len_a + + ; Print them + call print_a + pop af + call print_a + pop af + call print_a + pop af + call print_a + + check_crc_dmg_cgb $32F0CFBB,$3CF589B4 + jp tests_passed diff --git a/playing-coffee - Copy/roms/cgb_sound/source/09-wave read while on.s b/playing-coffee - Copy/roms/cgb_sound/source/09-wave read while on.s new file mode 100644 index 0000000..2d06a77 --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/09-wave read while on.s @@ -0,0 +1,41 @@ +; Reads from wave RAM while playing, each time 2 +; clocks later. + +;.define REQUIRE_DMG 1 +;.define REQUIRE_CGB 1 +.include "shell.inc" +.include "apu.s" + +main: + wreg NR51,0 ; mute sound + loop_n_times test,69 + check_crc_dmg_cgb $118A3620,$270DA9A3 + jp tests_passed + +test: + add $99 + ld b,a + + ; Reload wave and have its first + ; sample read occur 2 clocks earlier + ; each loop iteration + ld hl,wave + call load_wave + wreg NR30,$80 ; enable + wreg NR32,$00 ; silent + ld a,b + sta NR33 ; period + wreg NR34,$87 ; start + + ; Read from wave + wreg NR33,-2 ; period = 4 + delay_clocks 176 + lda WAVE + + call print_a + + ret + +wave: + .byte $00,$11,$22,$33,$44,$55,$66,$77 + .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF diff --git a/playing-coffee - Copy/roms/cgb_sound/source/10-wave trigger while on.s b/playing-coffee - Copy/roms/cgb_sound/source/10-wave trigger while on.s new file mode 100644 index 0000000..d7ec063 --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/10-wave trigger while on.s @@ -0,0 +1,49 @@ +; Retriggers wave without stopping first + +;.define REQUIRE_DMG 1 +;.define REQUIRE_CGB 1 +.include "shell.inc" +.include "apu.s" + +main: + wreg NR51,0 ; mute sound + loop_n_times test,69 + check_crc_dmg_cgb $533D6D4D,$8130733A + jp tests_passed + +test: + add $99 + ld b,a + + ; Reload wave and have its first + ; sample read occur 2 clocks earlier + ; each loop iteration + ld hl,wave + call load_wave + wreg NR30,$80 ; enable + wreg NR32,$00 ; silent + ld a,b + sta NR33 ; period + wreg NR34,$87 ; start + + ; Retrigger wave + wreg NR33,-2 ; period = 4 + delay_clocks 168 + wreg NR34,$87 ; restart + delay_clocks 40 + + ; Print wave RAM + wreg NR30,0 + ld c,$30 +- ld a,($FF00+c) + call print_a + inc c + bit 6,c + jr z,- + call print_newline + + ret + +wave: + .byte $00,$11,$22,$33,$44,$55,$66,$77 + .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF diff --git a/playing-coffee - Copy/roms/cgb_sound/source/11-regs after power.s b/playing-coffee - Copy/roms/cgb_sound/source/11-regs after power.s new file mode 100644 index 0000000..6379ad7 --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/11-regs after power.s @@ -0,0 +1,51 @@ +; After powering sound off then on, NR12, NR14, and NR44 +; are clear. +.define REQUIRE_CGB 1 +.include "shell.inc" +.include "apu.s" + +main: + call sync_apu + + ld a,$FF + call fill_apu_regs + + ; Power down for a moment + wreg NR52,$00 + wreg NR41,-$12 + wreg NR12,$F0 + delay_msec 100 + wreg NR52,$80 + + set_test 2,"Powering off should clear NR12" + call sync_apu + wreg NR14,$80 + lda NR52 + and $01 + jp nz,test_failed + + set_test 3,"Powering off should clear NR13" + call sync_apu + wreg NR10,$11 + wreg NR12,$08 + wreg NR14,$80 + delay_apu 20 + lda NR52 + and $01 + jp z,test_failed + + set_test 4,"Powering off should clear NR41" + call sync_apu + delay_clocks 8192 ; avoids extra length clocking + wreg NR42,$08 + wreg NR44,$C0 + delay_apu 63 + lda NR52 + and $08 + jp z,test_failed + delay_apu 1 + lda NR52 + and $08 + jp nz,test_failed + + jp tests_passed diff --git a/playing-coffee - Copy/roms/cgb_sound/source/12-wave.s b/playing-coffee - Copy/roms/cgb_sound/source/12-wave.s new file mode 100644 index 0000000..ba3185d --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/12-wave.s @@ -0,0 +1,112 @@ +; Tests wave channel timer reload and phase rest on trigger, +; and access to wave RAM while playing. +.define REQUIRE_CGB 1 +.include "shell.inc" +.include "apu.s" + +main: + ld hl,wave + call load_wave + wreg NR32,0 + + set_test 2,"Timer period or phase resetting is wrong" + wreg NR30,$80 + wreg NR33,$00 + wreg NR34,$80 + delay_clocks 1024 + wreg NR34,$80 + ld c,$31 + ld de,-$FE + call test_wave + + set_test 3,"Current byte readable at any wave addr" + wreg NR30,$80 + wreg NR33,$00 + wreg NR34,$80 + ld c,$3C + ld de,-$FE + call test_wave + + set_test 5,"Normal access when chan disabled" + wreg NR30,$80 + wreg NR33,$00 + wreg NR34,$80 + wreg NR30,$00 ; disable chan + wreg NR30,$80 ; DAC on + ld c,$31 + ld de,0 + call test_wave + + set_test 6,"Write test" + wreg NR30,$80 + wreg NR33,$F0 + wreg NR34,$87 + delay_clocks 256 + wreg $FF30,$BC + wreg NR30,0 + ld a,($FF34) + cp $BC + jp nz,test_failed + + set_test 7,"Timer period change" + wreg NR30,$80 + wreg NR33,$00 + wreg NR34,$87 + wreg NR33,$F0 + ld c,$30 + ld de,-$E + call test_wave + + set_test 8,"Frequency 0 is valid" + wreg NR30,$80 + wreg NR33,$00 + wreg NR34,$80 + ld c,$30 + ld de,-$FE + call test_wave + + set_test 9,"Maintains phase properly when vol = 0" + wreg NR30,$80 + wreg NR32,0 + wreg NR33,$00 + wreg NR34,$87 + ld c,$30 + ld de,-$1E + call test_wave + + set_test 10,"Maintains phase properly when stereo = 0" + wreg NR51,$00 + wreg NR30,$80 + wreg NR33,$00 + wreg NR34,$87 + ld c,$30 + ld de,-$1E + call test_wave + + jp tests_passed + +test_wave: +- inc de ; 8 + ld a,($FF00+c) ; 8 + or a ; 4 + jr z,- ; 12 + + ;call print_a + ;call print_de + + cp $11 + jp nz,test_failed + + + ; Error if not in 0...4 range + ld a,d + cp 0 + jp nz,test_failed + ld a,e + cp 5 + jp nc,test_failed + ret + +wave: + .byte $00,$11,$22,$33,$44,$55,$66,$77 + .byte $88,$99,$AA,$BB,$CC,$DD,$EE,$FF diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/build_gbs.s b/playing-coffee - Copy/roms/cgb_sound/source/common/build_gbs.s new file mode 100644 index 0000000..877754f --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/common/build_gbs.s @@ -0,0 +1,139 @@ +; Build as GBS music file + +.memoryMap + defaultSlot 0 + slot 0 $2000 size $2000 + slot 1 $C000 size $2000 +.endMe + +.romBankSize $2000 +.romBanks 2 + +.define RST_OFFSET $70 + +.ifndef GBS_TMA + .define GBS_TMA 0 +.endif + +.ifndef GBS_TAC + .define GBS_TAC 0 +.endif + +;;;; GBS music file header + +.ifndef CUSTOM_HEADER + .byte "GBS" + .byte 1,1,1 ; vers, song count, first song + .word load_addr, reset, gbs_play_, std_stack + .byte GBS_TMA,GBS_TAC ; timer +.endif + .org $10 + .ds $60,0 +load_addr: + .org RST_OFFSET+$70 ; space for RST vectors + .ds $148-RST_OFFSET-$70,0 + .org $150 ; wla insists on generating GB header + +gbs_play_: + jp gbs_play ; GBS spec disallows having gbs_play in RAM + +;;;; Shell + +.include "shell.s" + +.define gbs_idle nv_ram +.redefine nv_ram nv_ram+2 + +init_runtime: + ; Identify as DMG hardware + ld a,$01 + ld (gb_id),a + + ; Save return address + pop hl + ld a,l + ld (gbs_idle),a + ld a,h + ld (gbs_idle+1),a + + ; Delay 1/4 second to give time + ; for GBS player to interrupt with + ; play, if it's going to do so + delay_msec 250 + +.ifndef CUSTOM_PLAY +gbs_play: +.endif + ; Get return address + ld a,(gbs_idle) + ld l,a + ld a,(gbs_idle+1) + ld h,a + + ; If zero, then play interrupted init + ; call, or another play call, and we + ; can't run the program properly. + or l + jp z,internal_error + + setw gbs_idle,0 + jp hl + + +; 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 diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/build_rom.s b/playing-coffee - Copy/roms/cgb_sound/source/common/build_rom.s new file mode 100644 index 0000000..f369b92 --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/common/build_rom.s @@ -0,0 +1,70 @@ +; Build as GB ROM + +.memoryMap + defaultSlot 0 + slot 0 $0000 size $4000 + slot 1 $C000 size $4000 +.endMe + +.romBankSize $4000 +.romBanks 2 + +.cartridgeType 2 ; MBC1+RAM +.ramsize 02 ; 8K +.computeChecksum +.computeComplementCheck + +;;;; GB ROM header + + ; Reserve space for RST handlers + .org $70 + + ; Keep unused space filled, otherwise + ; wla moves code here + .ds $90,0 + + ; 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 + + ; Keep unused space filled, otherwise + ; wla moves code here + .org $150 + .ds $2150-$150,0 + +;;;; Shell + +.define NEED_CONSOLE 1 +.include "shell.s" + +init_runtime: + ret + +play_byte: + ret + +.ends diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/console.bin b/playing-coffee - Copy/roms/cgb_sound/source/common/console.bin new file mode 100644 index 0000000000000000000000000000000000000000..b02f2d37ee94c6570a3622734f2d704dfca44b0c GIT binary patch literal 768 zcmX|9F=`|+5G))_B$f+182koBX5hr&r*zbT5qlFY8w?8X8GehSdBVQJIPe4Zu1Z?m z2wEj|cXgKp3^N%Fwbp)6@0AO%)yuRSqFVOjFXoqd`4hj9QtEN1nR)XG+d{u=K$L$a zcxBdDa9GaHO?x1~9ZyqY!B>?@1~S_Jk8EuOnO>t1U_)GPUYcV}PSJ688I zDZyK&1&Z3Yf=R|a=4{O6sIrFR>wY_q1954MiRzbDIv)c%zSNhFNh$uKIuubR9$M#% zQVgthyMM7>6t%8xc-*ZoU#;c~Eo^b8>ieF1Te%=ANe|UbtGOeuN<>ma4(yn>d z&PyDPc+NXzR}RnDiJDL@@*5U1@Exf>ZKHTq^DiA1#(zox`Vp^snt$Nkdp#BumLA0V yczj1Ga3U7XIy~nG6}avCt6hL`&VKmF{r{ZIvhq4Tb=k}k+hUx5zmKoWJ@6mAT&*eq literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/console.s b/playing-coffee - Copy/roms/cgb_sound/source/common/console.s new file mode 100644 index 0000000..403fa5c --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/common/console.s @@ -0,0 +1,285 @@ +; 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 + setb console_pos,console_width + setb console_mode,0 + setb console_scroll,-8 + 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 + wreg VBK,1 + ld a,0 + call @fill_nametable + + wreg VBK,0 + 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 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 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 diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/delay.s b/playing-coffee - Copy/roms/cgb_sound/source/common/delay.s new file mode 100644 index 0000000..cbcdcf3 --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/common/delay.s @@ -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 diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/gb.inc b/playing-coffee - Copy/roms/cgb_sound/source/common/gb.inc new file mode 100644 index 0000000..2d0118d --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/common/gb.inc @@ -0,0 +1,81 @@ +; Game Boy hardware addresses + +; $0000-$3FFF Fixed ROM bank +; $4000-$7FFF Switchable bank +; $8000-$9FFF VRAM +; $A000-$BFFF optional cartridge RAM +; $C000-$DFFF RAM +; $E000-$FDFF RAM mirror +; $FE00-$FE9F OAM +; $FEA0-$FEFF unused +; $FF00-$FF7F registers +; $FF80-$FFFE RAM +; $FFFF register + +; Memory +.define VRAM $8000 ; video memory +.define TILES $8000 ; tile images +.define BGMAP0 $9800 ; first 32x32 tilemap +.define BGMAP1 $9C00 ; second 32x32 tilemap +.define BRAM $A000 ; cart memory +.define WRAM $C000 ; internal memory +.define OAM $FE00 ; sprite memory +.define HRAM $FF80 ; fast memory for LDH + +; Registers + +.define RAMEN $0000 ; cartridge WRAM control +.define BANK $2000 ; bank select +.define P1 $FF00 ; controller + +; Game link I/O +.define SB $FF01 ; serial buffer +.define SC $FF02 ; serial control + +; 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 diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/macros.inc b/playing-coffee - Copy/roms/cgb_sound/source/common/macros.inc new file mode 100644 index 0000000..9d7cf44 --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/common/macros.inc @@ -0,0 +1,91 @@ +; General macros + +; Reads A from addr, from $FF00 to $FFFF +; Preserved: F, BC, DE, HL +; Time: 3 cycles +.macro lda ; addr + ldh a,(\1 - $FF00) +.endm + +; Writes A to addr, from $FF00 to $FFFF +; Preserved: AF, BC, DE, HL +; Time: 3 cycles +.macro sta ; addr + ldh (\1 - $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 + +; Writes byte to addr +; Preserved: F, BC, DE, HL +; Time: 6 cycles +.macro setb ; addr, data + ld a,\2 + ld (\1),a +.endm + +; Writes word to addr +; Preserved: F, BC, DE, HL +; Time: 12 cycles +.macro setw ; addr, data + ld a,<\2 + ld (\1),a + ld a,>\2 + ld (\1+1),a +.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 diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/numbers.s b/playing-coffee - Copy/roms/cgb_sound/source/common/numbers.s new file mode 100644 index 0000000..6d6faf8 --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/common/numbers.s @@ -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 diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/printing.s b/playing-coffee - Copy/roms/cgb_sound/source/common/printing.s new file mode 100644 index 0000000..bb9389b --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/common/printing.s @@ -0,0 +1,77 @@ +; 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 +;print_char_nocrc (defined by user) + + +; 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 diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/shell.s b/playing-coffee - Copy/roms/cgb_sound/source/common/shell.s new file mode 100644 index 0000000..3e588c7 --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/common/shell.s @@ -0,0 +1,261 @@ +; Common routines and runtime + +.define RUNTIME_INCLUDED 1 + +; A few bytes of RAM that aren't cleared +.define nv_ram_base $D800 +.define nv_ram nv_ram_base + +; Address of next normal variable +.define bss_base nv_ram_base+$80 +.define bss bss_base + +; Address of next direct-page ($FFxx) variable +.define dp_base $FF80 +.define dp dp_base + +; Top of stack +.define std_stack $DFFF+1 + +; Final exit result byte is written here +.define final_result $A000 + +; Text output is written here as zero-terminated string +.define text_out_base $A004 + +; 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 nv_ram +.redefine nv_ram nv_ram+1 + +; Copies C*$100 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 a,(hl+) + ld (de),a + inc e + jr nz,- + inc d + dec c + jr nz,- + + ld a,b + jp $C000 + + +.ifndef RST_OFFSET + .define RST_OFFSET 0 +.endif + +.ifndef CUSTOM_RESET + reset: + di + + ; Run code from $C000, as is done on devcart. This + ; ensures minimal difference in how it behaves. + ld hl,$4000 + ld c,$14 + jp copy_to_wram_then_run + + .bank 1 slot 1 + .org 0 + jp std_reset +.endif + +; returnOrg puts this code AFTER user code. +.section "runtime" returnOrg + + ; Catch user code running off end + jp internal_error + +; 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 + + ; Clear memory except very top of stack + ld bc,std_stack-bss_base - 2 + ld hl,bss_base + call clear_mem + ld bc,$FFFF-dp_base + ld hl,dp_base + call clear_mem + + ; Init hardware + wreg TAC,$00 + wreg IF,$00 + wreg IE,$00 + + wreg NR52,0 ; sound off + wreg NR52,$80 ; sound on + wreg NR51,$FF ; mono + wreg NR50,$77 ; volume + + call init_runtime + call init_text_out + call console_init + call init_testing + + .ifdef TEST_NAME + print_str TEST_NAME,newline,newline + .endif + + 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 + + call console_show + pop af + call play_byte + jp post_exit + ++ push af + call print_newline + 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 + + +; Clears BC bytes starting at HL +clear_mem: + ; If C>0, increment B + dec bc + inc c + inc b + + ld a,0 +- ld (hl+),a + dec c + jr nz,- + dec b + jr nz,- + ret + + +; Reports internal error and exits with code 255 +internal_error: + print_str "Internal error" + ld a,255 + jp exit + + +; build_devcart and build_multi customize this +.ifndef CUSTOM_PRINT + .define text_out_addr bss+0 + .redefine bss bss+2 + + ; Initializes text output to cartridge RAM + init_text_out: + ; Enable cartridge RAM and set text output pointer + setb RAMEN,$0A + setw text_out_addr,text_out_base + setb text_out_base-3,$DE + setb text_out_base-2,$B0 + setb text_out_base-1,$61 + setb text_out_base,0 + setb final_result,$80 + ret + + + ; Appends character to text output string + ; Preserved: AF, BC, DE, HL + write_text_out: + push hl + push af + ld a,(text_out_addr) + ld l,a + ld a,(text_out_addr+1) + ld h,a + inc hl + ld (hl),0 + ld a,l + ld (text_out_addr),a + ld a,h + ld (text_out_addr+1),a + dec hl + pop af + ld (hl),a + pop hl + ret + + print_char_nocrc: + call write_text_out + jp console_print +.endif + + +; only build_rom uses console +.ifdef NEED_CONSOLE + .include "console.s" +.else + console_init: + console_print: + console_flush: + console_normal: + console_inverse: + console_show: + console_set_mode: + ret +.endif + + +; build_devcart and build_multi need to customize this +.ifndef CUSTOM_EXIT + post_exit: + ld (final_result),a + forever: + wreg NR52,0 ; sound off +- jr - +.endif + + +.macro def_rst ARGS addr + .bank 0 slot 0 + .org addr+RST_OFFSET +.endm diff --git a/playing-coffee - Copy/roms/cgb_sound/source/common/testing.s b/playing-coffee - Copy/roms/cgb_sound/source/common/testing.s new file mode 100644 index 0000000..5dd63ce --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/common/testing.s @@ -0,0 +1,195 @@ +; Diagnostic and testing utilities + +.define test_code bss+0 +.define test_name bss+1 +.redefine bss bss+3 + + +; Sets test code and optional error text. +; Takes multiple strings due to wla's idiotic +; default limit of 63 chars per string. +; Preserved: AF, BC, DE, HL +.macro set_test ; code[,text[,text2[,text3]]] + push hl + call set_test_ + jr @set_test\@ + .byte \1 + .if NARGS > 1 + .byte \2 + .endif + .if NARGS > 2 + .byte \3 + .endif + .if NARGS > 3 + .byte \4 + .endif + .byte 0 +@set_test\@: + pop hl +.endm + +set_test_: + pop hl + push hl + push af + inc hl + inc hl + ldi a,(hl) + ld (test_code),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,(test_code) + 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,(test_code) + cp $FF ; if a = $FF then a = 1 + jr nz,+ + ld a,1 ++ 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 print_newline + call print_crc + .else + ld bc,(crc >> 16) ~ $FFFF + ld de,(crc & $FFFF) ~ $FFFF + call check_crc_ + .endif +.endm + +; Checks CRC, differing based on DMG or CGB build +.macro check_crc_dmg_cgb ARGS dmg, cgb + .ifdef REQUIRE_DMG + check_crc dmg + .else + .ifdef REQUIRE_CGB + check_crc cgb + .else + .printt "CGB or DMG must be specified" + .fail + .endif + .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 diff --git a/playing-coffee - Copy/roms/cgb_sound/source/linkfile b/playing-coffee - Copy/roms/cgb_sound/source/linkfile new file mode 100644 index 0000000..02a5a2e --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/linkfile @@ -0,0 +1,2 @@ +[objects] +test.o diff --git a/playing-coffee - Copy/roms/cgb_sound/source/readme.txt b/playing-coffee - Copy/roms/cgb_sound/source/readme.txt new file mode 100644 index 0000000..a0269c1 --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/readme.txt @@ -0,0 +1,82 @@ +Game Boy Tests Source Code +-------------------------- + +Building with wla-dx +-------------------- +To assemble a test ROM with wla-dx, use the following commands: + + wla -o source_filename_here.s test.o + wlalink linkfile test.gb + +To assemble as a GBS music file: + + wla -o source_filename_here.s test.o -DBUILD_GBS + wlalink linkfile test.gbs + +Note that some tests might only work when built as a ROM or GBS file, +but not both. + +Some tests might include a ROM/GBS that has all the tests combined. +Building such a multi-test is complex and the necessary files aren't +included. + + +Framework +--------- +Each test is in a single source file, and makes use of several library +source files from common/. This framework provides common services and +reduces code to only that which performs the actual test. Virtually all +tests include "shell.inc" at the beginning, which sets things up and +includes all the appropriate library files. + +The reset handler does minimal GB hardware initialization, clears RAM, +sets up the text console, then runs main. Main can exit by returning or +jumping to "exit" with an error code in A. Exit reports the code then +goes into an infinite loop. If the code is 0, it doesn't do anything, +otherwise it reports the code. Code 1 is reported as "Failed", and the +rest as "Error ". + +The default is to build a ROM. Defining BUILD_GBS will build as an GBS. +The other build types aren't supported due to their complexity. I load +the code into RAM at $C000 since my devcart requires it, and I don't +want the normal ROM to differ in any way from what I've tested. This +also allows easy self-modifying code. + +Several routines are available to print values and text to the console. +Most update a running CRC-32 checksum which can be checked with +check_crc, allowing ALL the output to be checked very easily. If the +checksum doesn't match, it is printed, so you can run the code on a GB +and paste the correct checksum into your code. + + +Macros +------ +Some macros are used to make common operations more convenient. The left +is equivalent to the right: + + Macro Equivalent + ------------------------------------- + lda addr ldh a,(addr-$FF00) + + sta addr ldh (addr-$FF00),a + + wreg addr,data ld a,data + ldh (addr-$FF00),a + + setb ld a,data + ld (addr),a + + setw setb addr+0,data + + for_loop routine,begin,end,step + calls routine with A set to successive values + + loop_n_times routine,count + calls routine with A from 0 to count-1 + + print_str "str" prints string + + +-- +Shay Green diff --git a/playing-coffee - Copy/roms/cgb_sound/source/shell.inc b/playing-coffee - Copy/roms/cgb_sound/source/shell.inc new file mode 100644 index 0000000..0d43faa --- /dev/null +++ b/playing-coffee - Copy/roms/cgb_sound/source/shell.inc @@ -0,0 +1,27 @@ +; Included at beginning of all programs +; that use standard shell + +; Get include files from common/ +.incdir "common" + +; Sub-test in a multi-test ROM +.ifdef BUILD_MULTI + .include "build_multi.s" +.else + +; GBS music file +.ifdef BUILD_GBS + .include "build_gbs.s" +.endif + +; Devcart +.ifdef BUILD_DEVCART + .include "build_devcart.s" +.endif + +; GB ROM (default) +.ifndef RUNTIME_INCLUDED + .include "build_rom.s" +.endif + +.endif ; .ifdef BUILD_MULTI diff --git a/playing-coffee - Copy/roms/cpu_instrs.gb b/playing-coffee - Copy/roms/cpu_instrs.gb new file mode 100644 index 0000000000000000000000000000000000000000..7b06221b23dcd84644e7910bb8ba91a957c9ba04 GIT binary patch literal 65536 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 MfFZyT_;UpQ3qRkrD*ylh literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/cpu_instrs/cpu_instrs.gb b/playing-coffee - Copy/roms/cpu_instrs/cpu_instrs.gb new file mode 100644 index 0000000000000000000000000000000000000000..7b06221b23dcd84644e7910bb8ba91a957c9ba04 GIT binary patch literal 65536 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 MfFZyT_;UpQ3qRkrD*ylh literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/01-special.gb b/playing-coffee - Copy/roms/cpu_instrs/individual/01-special.gb new file mode 100644 index 0000000000000000000000000000000000000000..ad3e9984f967b77b7ffdf768842ce3c04517d059 GIT binary patch literal 32768 zcmeI)+iw(A7y$5Zx80$Z?a~4dM`&TnrIv<M@$w=uCK3`2FB)GYyojYQGo9TnBOD?LkTIcgH`+#5Y&z&DBJTQ~SxVyN5AgC$ z=FIFj=klHNn@isc@IT+|W#Mz*8^+l>SX?y#V-STpsA*XJ+UpRze71LXdiska6BGOP z^v157oxXhJ{fY6rv_FZBU>pjr8QZ`6Q{6v4+|~eQtiEk&qx|dgI{D|M4Q4ZR*bx55 z&zvSm0wh2JBtQZrKmsH{0wh2JBtQZrKmsH{0wh2JBtQZrKmsH{0wh2JBtQZrKmsH{ z0wh2JBtQZrKmsH{0wh2JBtQZrKmsH{0wh2JBtQZrKmsH{0wh2J|1AMqp1c%MZmd?| zT}pr&#L%`X4^^m7ti5y zCSPXpCX27N_$kxz`37sD5B~JBzQxO?<@iZ9;}5Zde}GN<2iX^Xie2=F*?B*k#AZVl zoAR>{Ji@N}+0O0k3g2b=XYS>9S>EiDQf)k(Ta;7mkn3kz)X(CD#j(BP7oM_Wwfre7 zr{H98CDNwq!^(}(t|W#-PGQDhySA-Z?G6_sxIhWlGQ*#*f!Q%Qf?3pUPEsm&skr9=F1Tw-yRWvJlB@(WNuEg@IFD_~FOp-tK+7 zU+NjGD10`ls%j}bId0i!C)tmcR#n)lzi*&tp%n`)Gz#i#s~_!@cRaZ9p+}+6Ty1Oc z?H{UZB0IWv$Xm8TAp}dao_~I5c76gAR7Yhm=pT_fw`_qz1HeV^4j-Kg!+2OOdk1s% zPs`th^miIwev$uT*>76e(5hdnk|$cF`OeZ?vTr%OK64%1+4{_J8z0DHb~7A`0sjWi zZ^X)Y)|MAnB6YFR)vgtrT)s2Io@|D9rRx0~)I1g%H=eOAhGQp*We23>8 z$9HdJb_6FQ-oDnR8NYd@+g4g6-{zI%)>^(b<9na9-`%^vP#K7iWpKiH#8I*{*PGm2 z4X#0xn^(Bf@;f8)4Q(Z^u7K|yX2oXM;?isR2R!GZXx!J*vSCBRO1EjnnuD0*TH*Yy z4-69v)`;Tp19W)FTnCQ>a7>kNDq@ye<;q_>vnn+`= zw4Rbgtm=A7v`>p>ih}J8v5T`NMdKo`*l(T{5Lpx^=8EZY$KRr7B@eC_^FQ0GYrt@T zv|&KXFvO16bzR4ClL^Bx5=qRjr*s|X6`Xa@biEf-0@h+T15`16rh+6k?1z2&u`e!F zY-z~Nl`iFSIWPv(Y3wNGi<=tc!v;Y-n7Aj1F{Q|_ig6H8Y$6YCK|VuO8Q@r#N^>}# zp^B-CdNGBH3+7uDbXx@-V)arOl0lq=`gkID>plRKOkKGds*;A|2fVm`5YoC>j~3*` z*hpjCsHBix^ug>A`q7 zpooPB#^S-l`zQor7>hLGzru)a;szr?N<6CpK;=5l2XAul{KObjM86DisA72iicAJRtiVPC5ef0$kIf%WY58UCBiFHDI~ zGb`PCbAn;$KmsH{0wh2JBtQZrKmsH{0wh2JBtQZrKmsH{0wh2JBtQZrKmsH{0wh2J tBtQZrKmsH{0wh2JBtQZrKmsH{0wh2JBtQZrKmsH{0wh2JB=Bz&_#K5wPf-8> literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/02-interrupts.gb b/playing-coffee - Copy/roms/cpu_instrs/individual/02-interrupts.gb new file mode 100644 index 0000000000000000000000000000000000000000..20895940ad31ca0229cb4ff8bb7c7ef79ca8db10 GIT binary patch literal 32768 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 literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/04-op r,imm.gb b/playing-coffee - Copy/roms/cpu_instrs/individual/04-op r,imm.gb new file mode 100644 index 0000000000000000000000000000000000000000..58ca7b8ac3c2b16efc9c8e401e790671c00022a2 GIT binary patch literal 32768 zcmeI4ZEO?g9l-x~k|Pbqu|w1yAuneVtPr4hD}zWgk1{}@y`p91iI-s1C}m)=&Ci}PS^)m|6_7tDiNZ`%vs1o!oe z{kL!2xc1Vyb332yci*{q~E)78){-D{jD zeGra=>Vuv*I6wB8Q-VZ*2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F z0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F z0z`la5P|t3>m;#d?jCJ;y z(NpT*EU`-esoHf)x?9lnDOH~z(_3PCdrUv8YL>nv_Rt1z3kfTxm(`eNg~hCOK+ISN z#T(Wk@tPG8-?WCrD^?1k^xKxD-l*yzZjSq=?kNHK+9yU_V%t^wK0^d!2$)W zC96Ld6P;zq=)J0jxn}io^-Is}-MeEuWEjMs8!uKH!OogBoj1jLKDU0Lf5*d_8e^a+ z7(1hG%Jj_o)`E(%V#KuN$4O6#bXy?OM>2rSBUr#FwTmp7AScq2w?mH!p0%GrK}Nw%0)h zkvx>7cI2|&euXTWYc*Ih*JfzAs*0^xdYiG+J6sl^>G-cVwl^OteOR31&o=GqtPeOXaO2HrJ}~XrpNYEHs`Z3v(ffb)Do!P z+_fG^n0+B z5|)BxFz2h%{Rg@U+jqG#k@o{&g|{*V?UW?oZv3NIRXH*&=H{Lau8qe>PJ04Q^gX@R zU)#nvetKcR^RN4UTbS?sYkNEPbnNcfzf5`~-m4DehkrHTzm^|4-eC^sNAWt6AH(Zt z6i*cWujBfb^ZKjfm2Cr%@!d;w<)$aQa(A&8C$o51+?!-uax;^-Snf~aYSfn|^^@bB z3o0fK$P}k1^_yxbEeXZD{4O(8S}L85l#9$_8(ElkdIBLL52QCyMV)>fbq^iM0=vqSBu;_VBs}j(Shq;o*Gw z@Zp+M-x&S=YewCKwf@_yyY6kMDgJiFvDoHKU4Obf)p$0ycGVk4JAV=A{mHY*l}~-S z_D9V>aDQ%k@6{uz>&dF$uk$4wt@oEBRy6qNu722&r(+5wdJ4g-;aBZTj|*pCX83~lE@ZY zjASj=z$9OjAvAufMw(#@vAPWI=!!CGOuS&OEogXTwip9`|B@x%g+@!$qC*&CI&br zlwdii;FKsv(P5Li#x2(j$dNKZ2`(*|q zI*2L?L==Vp@VP9@=r~cUaTKDX_?o>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0{;a9e+RrWAo2hJ literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/05-op rp.gb b/playing-coffee - Copy/roms/cpu_instrs/individual/05-op rp.gb new file mode 100644 index 0000000000000000000000000000000000000000..1c19d922f728bd11dcd8b289813fd0409f438157 GIT binary patch literal 32768 zcmeI4Uu;v?8Nfd~$t48iI3#12{2@0ucp<>J9fC!gIm##kRUOS<)=E`rZ#B5I!%6~Z zV2I;7$;7&8539O|@lu8+wVJfSkhbwpo33MQb7w9pT|pPENv{WHnnfeK)m?)L_P%o+ z*w$`&m^7){z9XM|&UeoD=X}3=&bfIC!2fwkG`p{UpsH7v!jhVUFaaJ|3iVB$uYM1_ z?_C+ZGc|Sd_{EERzd7i=du8gq<0mej-^e~eEQYen8B;yq_{7|2Ufj_Hn%CF6tX;a_ zvQ+vwoB*v0`V-*(%&(jlNCb!g5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la z5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la z5CI}U1c(3;_}?OskS1@`$#b1DTy?#D8T7N4ZT7xVqZPMEe|3b4u}ov{R!54u9Wi{` zD3|n4bI?U#6)R?qS>x7(ZYQtjx+W^y*FUxX>Gh`q^_jlRicD*#?0X?R^TCcIGv@}5 z&P+TrG9#^)ZU-u>4y@b1Za8}_UerhPZZndvF~wS@i*A<4yG-3KH(YvFGh8Mvfl2di zgVSb>wDuQUtTEowde2Jtin=kQ8O?E{BX0QP#sy8cjWzLyHrOpD?YL3V;<_CcbM_H2 zYabP->=E&%9TBhFqvAC?8Ai1uaWQQtw>~L;XeYPr6z>|(YIf$!#>RN@PGh;D&z)M7 zk`pe|PKq8oi33Z#yU)M7EADMDcEwXNE(S9ZHq^c*&y5X)Q9Pn&bGF~#o3AxT^L3b@ zfVt$1eR0uUfvmAp({ZgiV^aJ2iw6(x*$r6+iG7o$S~J*Fx25Nn*vRL$4Gr#jI8$d1 zl>~Ed^lZ;!v_uH_MD?=kDmSzXu?==%4C^@7i48pQNA(uIzC!Gxw5%|8L0VcEKQGl6 zCeBGUi2=A{nHwHAD)fk-`bM}qFcZ%`zOHMB6$uxLq_cp~U0<@m`aAY6_fMY8{+@er{j?R>Vg;<#RyQkR&|_B1r|{{?0ihnvd>FQg&7wOc(IAmY21y*@|1CA zvS&rr#0A;X?6h%9D`zC3bdUG4qvhq&g-AIi{W@_Xp0gKPL3VVZ+3-S>M;4kqy3pjv zLX+42b5nf@KpVhXfEs{i06)NDfE5610K8zVO?iF%Vev-+A9~lW)bswQ{W)y$5MEC{ z<=^h-1I1`IflwQhcn~39mp5Dpj!)d=(Tc^Wwjo)lP2y z>F+*wZtaQJQ+ zn+6Z#QR7y|?KCbJpXeo-D|MKuddz`IU9G0j@})XyjHQFkg)StwDu>hz_Ev3K3Q1KO9#S1^tFYs;Y*<7+;Af3a-m(6<~@oh#>)I z(M*LPU%nUwffuwxn_;xYRC$R)YNmW6l}dqnC>ljWzFsUrCmt#|@}baXN5+smevtb? zh`jJPSe$rjFen20x>24%e`=5~ozL?nRJmZhm5$j;=;zrBZU{Sa7<_%9)T8$RP}UTA zUJMEh{X2Luf8eAQo)2^4qO3+yZkHp7o!j6z6Oakw!uTTeVFY}SNFU2E!VB^I`y4Td z`gb`1Y^(a*AJ4xkM-;~&I^;6vfimBC_#Qa{FUmX&|F=urjK5$65aD~(0Z<*s_25f( z_K#N-Wp3w$M=ryn=)q;7p7P-v)vb$t0OM+k|Ft1+T0b-_9T;xle}34Oi;Y=so7MmQ zTb4MMHl9x#8#8!}17i8nxpAD(g9s1-B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U z1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CJ0a HUn1}~7IqB+ literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/06-ld r,r.gb b/playing-coffee - Copy/roms/cpu_instrs/individual/06-ld r,r.gb new file mode 100644 index 0000000000000000000000000000000000000000..d497bfd1275361bc847fa94dc87b43729f180b5d GIT binary patch literal 32768 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 literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb b/playing-coffee - Copy/roms/cpu_instrs/individual/07-jr,jp,call,ret,rst.gb new file mode 100644 index 0000000000000000000000000000000000000000..5c8d20bb3a1a0458f29afe40b8d3ca125b13a391 GIT binary patch literal 32768 zcmeI4Z){W76~K?3#H7JEf3-bALS7(vAxefRTZy#StF%K!)0(C02P#!X7!@8ZwUQ7J zIK=jxWa_$U8m%_9>pqlK>)0P{FeYsjvJZ9~+j%pOkq+1ck>t4$P%WC-pf?f&_Rf6{ ztm~$Hm^2Ah=jq*h-?{hvx##!pz0aR4DPUgG|EVo_e~9Zywe`%$vV3|OEG^m#!(fMH zP+HmW+IPWm`E>WS$;pdvkBsbkvDpE*sC2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko z0U|&IhyW2F0z`la5CI}U1c(3;AOb{y2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko z0U|&IhyW2F0z`la5CJ0a*&+}TM=zAP?l!pKjOFY&C@01ZHmw$gGLMV@FonEY6=LTK zJsHLDsTCnVmlpmn;FB~I0a#uS>Vx`_KCBqg^D~XZ`KQ{x(YB@SsIxTDo>-o!ZE#g= z^Ui*}xqtTku7k6~&kf9qYs9P0{HngzJ*~aTiEu{or>vSURip{UOcAUslCo%ukt-W} zKBQVSoB|UU+A_0Es`1cAhESybBGht1oX;rgY)GvRtMy@Zbyz(WQVex%_`VH3W}-$| z&4=EBKfCbo6OZ@BIbc6m|kSCTWv z>eVf&V$Gi_!31rX%Z&O;Sg__Hsdk1Goa>A_8hU2?-n~0_K$1b^mCknMDrtPi4d?x^_=AlH#E}0bw+v+%Mg~~bv*G0uk(1#T%yy~wxOiK+wLU+c0*!&b1WP%VJQgPlV#(`k z*a5w%@lZnjVMq;x)$WAa6*5?n+7M#qx>5N`QGYXL=7OFKb^mB4awK8!8NVCKJ8ei%3$6GwMT!qd3D`D z?%Sf_MAw!Cw%DDAvv5$72syrM-4D?E4Qlgaa*<&5c{L!-~5(Q-7fidltK>J_G+ zyk5R5$`SNMor)O!a&*yfEiv*W|R}64R_;Tg`&cdVK4{x ztZ{8TK2pjT;aJymTb-p1eB&opxGMgA-*5Bsoqus}Q(x1brUR|w2a(QDPdfP99_PjM z;L#>Mm>$Ab%7I&tYFLiec z7t6gVT#f3pG4=Rp^YVg;1(MmhDfLPymk@2)dH$62=bjZ$`EoJw50S&+8Dp^-B>jtx zdKVk)Uu^K;VuJ&V4Nm;;rix&Za7{N`B$q{Mw@9P@Si~Ruvp@EMKX%C<8}r9b`(vs< zcFrHW?2m;c7?(hi;H(6i1Q#WU1N;TxZvfK(*8%e<*9WI7?@XPK%uPwRO0ARYd#BYyV_EAYuZzq&VD|XU+Go}SW(|`) zS*-%_yt(_W)Rt|JHpeHvCYRg}mY@2;6Cb|!leV_+_x-$j|1WpsUiuS%e&)@l zFDzD~mX&vkyvLhu=}o!UMI)@JSK{W`GBy;)Z_K`UBMdn}9l`c{jy$%kCba@j&s3ep z=2G>Vx^`T6p%H#;E5-$Xx2Z&&whBCVUS{9I0{^{~h?C5c>cYbpgP^~zyi(ac zZgWR%rM1<+TD1y2g-b;II;!j9>O)i*~v$IY%vy(6O>!_R5(S`a70Kd8<1 zGe+YIttGcad?b?-o6FSY<3{F>ZC~y_fSa3JsawWz!dRo1=uEa=i1(J^^Hp;Bv=ag}TS;+I!@$fZc94^&E9*SLcAg#qfCJGq)_n!F)0P?rt{&0UZQn8GN$LZ|rxw z-RRfrk!9KA#rSTY+l})wTQ@MbyBk9Sj-r_i5}&>xfsJ2ihc>-vi>dM}0I}KJg;*>G z^8P>o4f%ZWlFWFhV9NVFkC`%th_mC(Vnw^_k!%3i2w_rxB&2Y_72?OG5e8$8;11Q(Ie2AUf zU_Y}V6U2q_1!%_z_#TlyUcCs>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&Ih`?u;z&`;#5pP@o literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/08-misc instrs.gb b/playing-coffee - Copy/roms/cpu_instrs/individual/08-misc instrs.gb new file mode 100644 index 0000000000000000000000000000000000000000..4da139be35c0f82db12136c2b65b2571122a026b GIT binary patch literal 32768 zcmeI4Uu+b|8Ng@H_Hr@D*=M`WVr;y|=432fjs{X!Jw{H7f~wSYRjD9F&@2ipg%dXINMc{<|Q~$l+srDqezLWHc%c~{`Fzk=R13KEP^(mMZ)%8z*IdE>khPyeZXzM z*#p{=$`5^tns2o`GvCbjXTIOg%-+)x@)f`0)voiO$?~}+WN}F!873aGgt&bTul|B~ zub%6^F*$kV=-IQoUg-ATJU4mu=(}f6KS3WM76X}c`edp0k+CnmxXDKpZ)Iy$op`5a ziTHUqPLxK{5ht!M{mgNKNB{{S0VIF~kN^@u0!RP}AOR$R1dsp{Kmter2_OL^fCP{L z5Ghhl-ykD0) zby^=+{%#2+`bSFZNpU`>>9dMnt?Kov-lXbh6wT6CsSj+RnM+uzZY!#0g@qZbUr1XA zg-Pp>aK(xUm#hKdqLm1PTE8lcTZyNi6>eFHjh(_x{W---eM^5r&E2Rdl()N*i;{sj zXIKfrV8Ej?p3HMM3+je$%VB*;T9 zGy03F;Ic_t?^HCH>x@3C{BV0;-_9K*O-cO4(R`^9Y$@B&a!sgUV;g(AcRm;?GkWs8 zu`7DAUE)Jg%Ki6)rwWl-0pu}7RPX#q$Z%Tht(W9!q zIi+_g7A?^m6zZ%Sovjr8_hU{j;K|bWkLKd9q%1b$_my0MGej>-?3wMmLpyhrlQbZC zAc;-DWux;7X)3eapvg>wp~0%kFNf3{j9tE%&6C<=f7sMicc}1l(Ha1y2^!)A@+C$A+yz1ACO z{nV4%+cK|e$4&nR({HXcn`sV$zHHWf0iTxW;@x0Y31NM74%|s8U%*A*1N~Q+KF4-w zH6smLRTlj|YnF+IswKPuCr}=mT|CM9-*2g~Xa&loXE)i+Y)7gK5L@k=2ZL*Bc*W*38^gYi1sv{L~B_7I&uTQ0DFwES7syup0GsN&UoV%hI9= zN7DJZDgByKNQr!Yp0&~gg>~YYNFgcyG5)SPV=Xj;^uR)+;e`hK7aBac(BPqk1~2~a zrdoD{m?PvCA)gYGAF(qdwtK|pnmAt**Vx3ZZQ?9K5`wqJJ;x ztz?DCiYycwEo6?^^eT9vXEJZtv^o=;rdu+Hr|GXV(P=uJ8JOm)65PmU-=BZgn4QYG z$>iGTbbM~g?V4QMJFQnF@~%n!^|5@3Qy#Hdd&DWpAR{QKuYmN;27#=O%Z} zYn}Gj-hSz$^*bM}KhFQ;sSDwL^|*X=-IDa%M|&>xY>PL1@Y-nZH@DUJ)v(fbjeC8p zV&hvY_I!JL;&(s3Grjsp32ynFlJJQZKD)7SM6^^*PmMcg$k?87_?B!PZzMy2;RC2& z^V(3;n#?jdB{Ovfoy*i4x^F_*+DLxMm%_rn+gv8ju~Xxxd?lPOyXj$g$bWyz#7W9! zy5Nw7AmFdd)_j{McqzfVYpZ{|Vg-0o%S8AJ>W`1p5wipQe3xD~H^tXaI9)!oBc~mQ z&t~jK3?{=qoGtY;R^u|GwXjHhELRX);Ilbl<^I&RzWV@dVrHe&379aH;3YAWuQ!rz z$bpKDwFY1F%`)*WT?W~u;ixDogU01o&8Kr3oW=D6ZcC=_yjE`$mpxCs%c6yzfUkwZd2O1@0$o~&!{k-tMc9*?8vtpmF{FX?TEy1kbW zIcNuE7!rgiI4oX)fCP=`eTfiBA}-zo4m(It?%prUK_W1FAqp}Zh?1yB@bJ*@sSAdp z5GyK0cqRuWDZ=!lOf3)qeVN^?ro5+}#bx*XIzU*IfWYPo;o*ihvn zRdz*5a<*_enM@LSe>4h)Y`$;>op_+&$ooT2Ix>W0@q^3{Kx7w-1D6v|4h97RzAhJL z!JiytQ)lCB3PmmuZ@FW(oOH14c^3&ga+p-MhmsH910e-P3fvciJO%#_UdW#?(h|#u zI&ndkqafD>B7mLQKtJVy3E+bG0%?Z`@E(CaT)hA<#PV-<#31P3WB{Np>N9^V|Dqg` z9Dm@D$&3fcY~jIsWCXk*voP!rotPPW!2lq_)~W-b*bno8m+Y(`uOI}Nof96o^a_Fp zrUiP^t#^t`7utmAE#vHWOrX8(rQLlmh`YP{ezvc#oc+A9OpXqkE{nSV^{tGX&4kQ@LIC1xrOriQJM0eU4D2= zFwKvo|N5DW2?_#&fFK|U2m*qDARq_`0)l`bAP5Kof`A|(2nYg#fFK|U2m*qDARq_` z0)l`bAP5Kof`A|(2nYg#fFK|U2m*qDARq_`0)l`bAP5Kof`A|(2nYg#fFK|U2m*qD zARq_`0)l`b@V`aCFSi{{wOuH-q1*IPC)9nNhVnZt*&CfI|0yBFwOp@qc<4%0HQZXZ zw}DIQKXXteC~>_}Z_=Cf7S#wG=`CrA&#L@Z<-E$B#2qy*I;pCu{qNq}m;t!jGsu4>+xWKEKf;0t1 zzSyQG+K|@a{evMTYumimeeyt5)y{ggkv^@+r8x___Vm!ry4G) z*H|HijK`$o#!Bg!;g*gV4N{L0aFN;ypVVOlX51`&ZUkn|kxpw1yau~YyT%vo%ZjB} zC$$f2xA{#$BOqlM0lF~4a^L>Lclj*o+FibO8%>5XF;7f+&UT@x#zo=^HPmaAmRiFp z!G>@uC1|ExdbJ0AQc@ftZH`x^x%O&p-dpdfufP93gcSH6Y#U4o+AC70R-BTu_}I+4 zCHH?hk{YZVGzS-X_EiigOM*aWQjeLsxuFqhoM=Rvs5Vn=naC5rR8P_Ei!`>%xsj#= za%QA?znm6nc~4IE*Lby{&%fVirEEvIcFTWO&rOQ|E{vc8eHhg&s&T62N+Z>{K2CX5 zFDseVt-avYJU(qww^rjdlw_^gt0dNq##f5=#=%4`Nfa{w|Jv5Q&6_> z#Ae;Ixc2`0(h(vgUrKT*ahaL8h01TjV}eRYxHzcNsu~E&K1E`-Lw4mrSB z)|DTNH0>yY>QH~D&v z%grFvaJf;}wxvZ zOTf#(2H+>aE5IgTGq45tDex+=75Evj4R{^c4(tGa4!i-p3G4z=LBD6g6+jU%4yXjC z0x6)swqO)c2$TU606Q=Y7!70r#Q>wj1eOGr43+|x3N{QZ4Qx1AI@k!X46sbFEU;{_ z9I#xlkzk|1t^gYiCV}OF<%1P~6@nFk6@$rOC17L0O2Nv&#(`Z4Rsl91%nEiD*aWaj zu!&$cut{K5VAp_M3-(p8>%gkPt_S-X*c323*i^6^V1c&2!xEvBTGH7utCvoJ-hg$s zwZ=N%y6^};g`$31z*&Kiue2f7BkQ3=b6ABbE4%pd#z_29$36G`)B_!F^nCxJ=5J=1 zt%1RH8(#5OZFa%8X|cDbZOh!u8#;$Ktb4xOX`A=rIn$Oq)_v!ec^5W6HKTu}wa=YA z?IuUy^?e_{_1Wgf3Tn=eedikU)V&?{tw&0IA1q&^Z&=@IS$?dqdD}^+=BoMqGlwgc z)py?BF(5D8|N19gcX$4F^^af8T;*7E;HoR9uWs3$K7HfS^=tF?jch;iVN3V-H{sf0 zcSl0`5B%uBliN1WGXHG(K+D&2KPyP?Jij8m0kzvR?`HF-7u=k&CoOpD#$llsuB|Us zrHR!8JyU+Z;KTO* z${ibDKC$(9$({|9H~qS^>g$#Zy>tDyE#G=(yS~x2t?0c!x{p3GM7-u;g{s6%33HzkAD`{Da=zC->a+t>@C$?i@X7?yCihmj#pmFn8^$ zD<|LE_hfF*=T+>W(ozRtR!?TgB-SI&2ze*e&-ypmamwWDnA;gcKBRb9QKzxC}u zeeF=%eQVkl-tOGB=E^~GDhEi@}12~rKY|a^K!deN6xZ;u;Bb{0n?cC z$*$cM=E%(0v$Ek+wb!~5pT~nYbv5(?ax6`?MuatuiWHKpTK?VNmH07X`J` zUDBN;SZ_|DclL#{RJosj5FapS)2DN>@;Fue>zXPbS4`m=+CNYb@>dgAb0#sfBVbO; zAGv+(Sn}jcm1%>geXc6c>C?#1jmjtbY=1UOT*}d>Mb%xjBW&zzq{;9-VMS4|QBn}J z#)iqmqcOQ6UHiDph`w9-)g_P8_la9+d5k7ZXYvy09V`mAr%?`yT#_F&kE%+QFDM0+ zU5LJdL&~IabhSPss?s*2C|FlsK7M>oL9i%)>`IE!AL_ZZ?Ui_egxcEL#7?+2R!avU zDGs~YEx}nU*&OXZK;h)V>N@8XWMt4gii)L>>BQow1C+nqEZNgi@IRBSc} zbvzd(z~MlWIfEQdgWb905vS7*iQ7valKFrKo(w6&OxH6C?X@0?<#D*pT(&zLZm#d) zYBn3`JNdy|#hg*i|B@P7=Iq*20a66s+pz98Y zgZ#Q`ola-1i{d-n4hPLE(K?_w97`x9Xq3#Hu=D9lc9?k~JF;0ywv;L_9<-m09c^!K zhw~AShYb0Asn`?oNFgCVQad>zQ%D})&ixQXUU(cTiFi)CT>|+!8aqq=oOV8SKF+5w z#D(IGNtlhnG@iXV39f|fLUwg+`=$E;#JmpMMbU0n$bSMabx{sWIg=8Lv|Er9f@f%D4+3q{}=q9B}PcoFWzwQA2tzFs+UE1PqdXCGb*rf+2 z;YFMT0YN|z5CjAPK|l}?1Ox#=KoAfF1OY)n5D)|e0YN|z5CjAPK|l}?1Ox#=KoAfF r1OY)n5D)|e0YN|z5CjAPK|l}?1Ox#=KoAfF1OY)n5D*0ZO#*)bMsZX? literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/10-bit ops.gb b/playing-coffee - Copy/roms/cpu_instrs/individual/10-bit ops.gb new file mode 100644 index 0000000000000000000000000000000000000000..8988458ea0eff185d7f3bd78ff85bb7717edccfa GIT binary patch literal 32768 zcmeI4dvp_38oi~z4V`7uv(jXK=+9GPz|xr1ELesUV0gf$D8JyZD~1HQ(r%0 z>KxFJP~yPgDOtxSn`lru^G zR}QiOQb?^-m#I~1wXC_nYE7>WjUGI9@DqbS$cn~1?2Achrua$rwhIM|+jbQ#X{&y+ zv`ri!p2-Sj%o{X!P>J`5Ga#4y^qw+bghz;!WI->vd^(S;1!MM2a40$tr67sd`Gk<5>&2e_WqZUP{;J(# zw7+_n7~v{%C?2P4w=;*T?QZ{2yd<~Z9Xja)j|A#%)Z(axsAWl&)I#b2X_1+ro;=ew zeOe573CRsf>`z=qg_v)^|quv(Dl; zO$__#klm!7WHjZRip9CvSDf;8HS0b#OC6wQN&yP`qMGSB54TnP(5v zrmqj5d>3pldU9e`bQ;_Em~4vw$9+G^&UXH>`I+-F=VmS%B!1y4bQJq5w-sj{^Dp}# zQ?2w@(S4b}n(kE<^o^o?*PuMJPubkiwQT^r=~vx3zRM?ad{?M-oM@#li>oK3WZ$(D zv{*V%&}vlL_bVSZWcTed(Sf()@(Ja%BZ#7*;|d#j%Y*IWp0eP6affSzvsJrQ2Hx^p z&Dw7@x%gI-OKvq;daKDJ|GBA*`G6w;M*`jtI12CqzyiRZ13m~i2Jj)khXD%#ivS-1 zd=&68z+V794mb|*Nx<=dPXSHZf>z=?p904;!%0jB^?1)K&r9dHKVvw$-JX8}G3 zXak%LSPWPKI2Ujp;C#RZfX@RiqFn~K7;p(-DPS33Ip9*jO2B1+Re;riF97}u@YjGZ z0lo~l9Pl@QD*%5B_zK`kz*T^&0oMS&3iuk}8-Qy8YXE--_$J^wz~2LI0Ne=p7U0`} z?*MKB`~%>-fLj2!0@eb)2lzhVHo)zGe+2vx@FT#F0Y3rU0aypP3vf5!9>99QPXRvz z+zaRcbOJU2x&Ze9x&ikC9sra94+0`!6QBZk2=FlA5x_41zXWUs{0i`EKo8(Cz;6J* z1w0Pe0(cTo1@r=*0z3_P2Cxw|g&n1$rg&VBpGYWpOhG2GW(Zp$cs}C*x=*rfWds_@Urwv%HW_IMi)@#+|H9cl-F?Ro;^JG%g_jh+11`X?dEv0+X@QrcfT0JSVZA*70 zZdX}{Z-^nd4ju10PJbWo**XnnlKb?B1pn9UCWQj{G67=ll90FO&~z_{saH9;#LEer4muUR#Pv z*XsKWZ~kq!@tZ=U58fVKYuArCG|#(b++#0a?mRj+-$w$+odVlfsz_LJ8}Glb-SyS1#Vrxs}c4Ovkm@H z!Ih%sl$Fo@;T@O`RkS7MP`}(5qFze$CDVb&m*SBw`%*p1aTK0OhqZ=C z`i;7l86{q32Y_b{@pM#}DJ`W2|Gq_uEt1YxMCT<6LjH`LJ zKyQd8hxuT(&RJ-+nn7UpLIufeq5>*ng;)bUk4-V#DkxTkrOd!&v&B-z^edQ}$wc~A z_F=tbh|Obh+2?w10v5$2uv{rTeQ3-qyW&v26o1wniv$8WsIXe0%xYyHdTy~;$gkaI zwOVaZvn|-nL{CgX35M7W|qES1_Qguj%-TEmP%z;1vIt=k2W?of^}g< z1sSq@=`x4okwRErX!~hcrjRVYnfW1z>|$}~3dggW%>t0Gqro=vXEn3b**r_3iwni; z7dGn$`K)?_9_(S+4)J-m#+&Z}An34|u8U@aME=8gseWLjEvz0X9G7Hk1<5I@i-maso^$YjQYWVZ0= zJu(7Dl35t`zuA}>d%*;tjIGr$fUf715545@`Y{TEiP?q26PFS}h^4eh&vJ2n*V3gi zfYN@D{Yi4SY4DVp3&i4*`7!LT6iwAh%T&E4MgQ%{>v|8BYE&j1qK%v-1aIz~up55k z1ULasfD_;ZH~~(86W|0m0ZxDu-~>1UPJk2O1ULasfD_;ZH~~(86W|0m0ZxDu-~>1U lPJk2O1ULasfD_;ZH~~(86W|0m0ZxDu-~>1UPT)T!@K+QR9VGw& literal 0 HcmV?d00001 diff --git a/playing-coffee - Copy/roms/cpu_instrs/individual/11-op a,(hl).gb b/playing-coffee - Copy/roms/cpu_instrs/individual/11-op a,(hl).gb new file mode 100644 index 0000000000000000000000000000000000000000..0634b7fdd19bd62c16860ab1bad05760d6ac5965 GIT binary patch literal 32768 zcmeI4Yj6|S6@ZUqdl5zm2|RQ$eyp%jga?B1qE0Ke4rQP%C2prp$k5PirbHw}wN021 z5oE=WlsuZrW12}zen4p&Ql_a1&4lNKkt`&@XEER?9sk3_LUcxyCCXtHchV)E>=wy zJ`Keo>V}p$SikZ!rwI}PB0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y z2oM1xKm>>Y5g-CYfCvx)B0vO)01+SpM1Tko0U|&IhyW2F0z`la5CI}U1c(3;AOb{y z2oM1xKm`7`2*ic{6BWLRdLJCOygdN&{sBWgrItnWi-q&1P*kT!#dk_QdD#f7&S)o- zw?=C^m%MbvocGmuJ<|b3XOiWeDmmm)`v%X zS8W*;<_c###W@em-!Q);Jrv8!oms0E&X#F>xhV5iF`l((vQel!x;m;_G@ODcTy87P zHmx3uerWJz>MPNv{lbO3td2(288NjsrZ&XXLs8jK=fys=!P|Voh^fVBOg2J%#@NiK zjfeS@#uk3q2=nh6o&1oI2%*~M7=O%2ELp*SY$TShKq{0z0NS)*gIkP0UfLu8AdmI2p`@TUh>_Z=$<3gyLp7oiQ33nzH3uXSM