├── .gitignore ├── test ├── suite │ ├── blargg │ │ ├── oam_bug │ │ │ ├── 2-causes.gb │ │ │ ├── 1-lcd_sync.gb │ │ │ ├── 3-non_causes.gb │ │ │ ├── 5-timing_bug.gb │ │ │ ├── 8-instr_effect.gb │ │ │ ├── 4-scanline_timing.gb │ │ │ ├── 6-timing_no_bug.gb │ │ │ └── 7-timing_effect.gb │ │ ├── cpu_instrs │ │ │ ├── 05-op rp.gb │ │ │ ├── 06-ld r,r.gb │ │ │ ├── 09-op r,r.gb │ │ │ ├── 01-special.gb │ │ │ ├── 03-op sp,hl.gb │ │ │ ├── 04-op r,imm.gb │ │ │ ├── 10-bit ops.gb │ │ │ ├── 02-interrupts.gb │ │ │ ├── 08-misc instrs.gb │ │ │ ├── 11-op a,(hl).gb │ │ │ └── 07-jr,jp,call,ret,rst.gb │ │ ├── dmg_sound-2 │ │ │ ├── 04-sweep.gb │ │ │ ├── 02-len ctr.gb │ │ │ ├── 03-trigger.gb │ │ │ ├── 01-registers.gb │ │ │ ├── 05-sweep details.gb │ │ │ ├── 11-regs after power.gb │ │ │ ├── 06-overflow on trigger.gb │ │ │ ├── 08-len ctr during power.gb │ │ │ ├── 09-wave read while on.gb │ │ │ ├── 12-wave write while on.gb │ │ │ ├── 07-len sweep period sync.gb │ │ │ └── 10-wave trigger while on.gb │ │ ├── dmg_sound │ │ │ ├── 02-len ctr.gb │ │ │ ├── 03-trigger.gb │ │ │ ├── 04-sweep.gb │ │ │ ├── 01-registers.gb │ │ │ ├── 05-sweep details.gb │ │ │ ├── 11-regs after power.gb │ │ │ ├── 06-overflow on trigger.gb │ │ │ ├── 09-wave read while on.gb │ │ │ ├── 12-wave write while on.gb │ │ │ ├── 07-len sweep period sync.gb │ │ │ ├── 08-len ctr during power.gb │ │ │ └── 10-wave trigger while on.gb │ │ ├── oam_bug-2 │ │ │ ├── 1-lcd_sync.gb │ │ │ ├── 2-causes.gb │ │ │ ├── 3-non_causes.gb │ │ │ ├── 5-timing_bug.gb │ │ │ ├── 6-timing_no_bug.gb │ │ │ ├── 7-timing_effect.gb │ │ │ ├── 8-instr_effect.gb │ │ │ └── 4-scanline_timing.gb │ │ ├── instr_timing │ │ │ └── instr_timing.gb │ │ ├── mem_timing │ │ │ ├── 01-read_timing.gb │ │ │ ├── 02-write_timing.gb │ │ │ └── 03-modify_timing.gb │ │ ├── mem_timing-2 │ │ │ ├── 01-read_timing.gb │ │ │ ├── 02-write_timing.gb │ │ │ └── 03-modify_timing.gb │ │ ├── interrupt_time │ │ │ └── interrupt_time.gb │ │ └── README.md │ └── mooneye │ │ ├── acceptance │ │ ├── div_timing.gb │ │ ├── ei_timing.gb │ │ ├── jp_timing.gb │ │ ├── pop_timing.gb │ │ ├── ret_timing.gb │ │ ├── rst_timing.gb │ │ ├── boot_hwio-S.gb │ │ ├── boot_regs-mgb.gb │ │ ├── call_timing.gb │ │ ├── call_timing2.gb │ │ ├── di_timing-GS.gb │ │ ├── halt_ime0_ei.gb │ │ ├── intr_timing.gb │ │ ├── jp_cc_timing.gb │ │ ├── oam_dma_start.gb │ │ ├── push_timing.gb │ │ ├── rapid_di_ei.gb │ │ ├── ret_cc_timing.gb │ │ ├── reti_timing.gb │ │ ├── add_sp_e_timing.gb │ │ ├── boot_hwio-dmg0.gb │ │ ├── boot_regs-dmg0.gb │ │ ├── call_cc_timing.gb │ │ ├── call_cc_timing2.gb │ │ ├── if_ie_registers.gb │ │ ├── oam_dma_restart.gb │ │ ├── oam_dma_timing.gb │ │ ├── boot_regs-dmgABCX.gb │ │ ├── halt_ime1_timing.gb │ │ ├── ld_hl_sp_e_timing.gb │ │ ├── reti_intr_timing.gb │ │ ├── boot_hwio-dmgABCXmgb.gb │ │ ├── gpu │ │ │ ├── intr_2_0_timing.gb │ │ │ ├── intr_1_2_timing-GS.gb │ │ │ ├── intr_2_mode0_timing.gb │ │ │ ├── intr_2_mode3_timing.gb │ │ │ ├── stat_irq_blocking.gb │ │ │ ├── vblank_stat_intr-GS.gb │ │ │ ├── intr_2_oam_ok_timing.gb │ │ │ ├── hblank_ly_scx_timing-GS.gb │ │ │ └── intr_2_mode0_timing_sprites.gb │ │ ├── halt_ime1_timing2-GS.gb │ │ └── halt_ime0_nointr_timing.gb │ │ └── README.md ├── expected │ ├── blargg │ │ ├── dmg_sound │ │ │ ├── 04-sweep.png │ │ │ ├── 01-registers.png │ │ │ └── 06-overflow on trigger.png │ │ ├── oam_bug │ │ │ ├── 1-lcd_sync.png │ │ │ ├── 3-non_causes.png │ │ │ └── 6-timing_no_bug.png │ │ ├── cpu_instrs │ │ │ ├── 01-special.png │ │ │ ├── 05-op rp.png │ │ │ ├── 06-ld r,r.png │ │ │ ├── 09-op r,r.png │ │ │ ├── 10-bit ops.png │ │ │ ├── 03-op sp,hl.png │ │ │ ├── 04-op r,imm.png │ │ │ ├── 11-op a,(hl).png │ │ │ ├── 02-interrupts.png │ │ │ ├── 08-misc instrs.png │ │ │ └── 07-jr,jp,call,ret,rst.png │ │ ├── dmg_sound-2 │ │ │ ├── 04-sweep.png │ │ │ ├── 01-registers.png │ │ │ └── 06-overflow on trigger.png │ │ ├── oam_bug-2 │ │ │ ├── 1-lcd_sync.png │ │ │ ├── 3-non_causes.png │ │ │ └── 6-timing_no_bug.png │ │ ├── mem_timing-2 │ │ │ ├── mem_timing.png │ │ │ ├── 01-read_timing.png │ │ │ ├── 02-write_timing.png │ │ │ └── 03-modify_timing.png │ │ ├── instr_timing │ │ │ └── instr_timing.png │ │ └── mem_timing │ │ │ ├── 01-read_timing.png │ │ │ ├── 02-write_timing.png │ │ │ └── 03-modify_timing.png │ └── mooneye │ │ └── acceptance │ │ ├── ei_timing.png │ │ ├── jp_timing.png │ │ ├── call_timing.png │ │ ├── div_timing.png │ │ ├── intr_timing.png │ │ ├── pop_timing.png │ │ ├── push_timing.png │ │ ├── rapid_di_ei.png │ │ ├── ret_timing.png │ │ ├── reti_timing.png │ │ ├── rst_timing.png │ │ ├── call_cc_timing.png │ │ ├── call_timing2.png │ │ ├── di_timing-GS.png │ │ ├── halt_ime0_ei.png │ │ ├── jp_cc_timing.png │ │ ├── oam_dma_start.png │ │ ├── oam_dma_timing.png │ │ ├── ret_cc_timing.png │ │ ├── add_sp_e_timing.png │ │ ├── call_cc_timing2.png │ │ ├── halt_ime1_timing.png │ │ ├── if_ie_registers.png │ │ ├── oam_dma_restart.png │ │ ├── reti_intr_timing.png │ │ ├── gpu │ │ ├── intr_2_0_timing.png │ │ ├── stat_irq_blocking.png │ │ ├── intr_1_2_timing-GS.png │ │ ├── intr_2_mode0_timing.png │ │ ├── intr_2_mode3_timing.png │ │ ├── intr_2_oam_ok_timing.png │ │ ├── vblank_stat_intr-GS.png │ │ ├── hblank_ly_scx_timing-GS.png │ │ └── intr_2_mode0_timing_sprites.png │ │ ├── ld_hl_sp_e_timing.png │ │ ├── halt_ime1_timing2-GS.png │ │ └── halt_ime0_nointr_timing.png ├── generate_expected.py └── run.py ├── gb ├── bits.as ├── linkCable.as ├── mbc2.as ├── mbc3.as ├── mbc5.as ├── mbc1.as ├── timer.as ├── joypad.as ├── machine.as ├── mmu.as ├── index.as ├── channelWave.as ├── channelNoise.as ├── om.as ├── channelSquare.as ├── apu.as ├── cartridge.as └── cpu.as ├── Makefile ├── ch8 ├── cpu.as ├── mmu.as ├── index.as ├── machine.as └── op.as ├── LICENSE └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | bin/ 2 | build/ 3 | *.sav 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /test/suite/blargg/oam_bug/2-causes.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/oam_bug/2-causes.gb -------------------------------------------------------------------------------- /test/suite/blargg/cpu_instrs/05-op rp.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/cpu_instrs/05-op rp.gb -------------------------------------------------------------------------------- /test/suite/blargg/cpu_instrs/06-ld r,r.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/cpu_instrs/06-ld r,r.gb -------------------------------------------------------------------------------- /test/suite/blargg/cpu_instrs/09-op r,r.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/cpu_instrs/09-op r,r.gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound-2/04-sweep.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound-2/04-sweep.gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound/02-len ctr.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound/02-len ctr.gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound/03-trigger.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound/03-trigger.gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound/04-sweep.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound/04-sweep.gb -------------------------------------------------------------------------------- /test/suite/blargg/oam_bug-2/1-lcd_sync.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/oam_bug-2/1-lcd_sync.gb -------------------------------------------------------------------------------- /test/suite/blargg/oam_bug-2/2-causes.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/oam_bug-2/2-causes.gb -------------------------------------------------------------------------------- /test/suite/blargg/oam_bug/1-lcd_sync.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/oam_bug/1-lcd_sync.gb -------------------------------------------------------------------------------- /test/suite/blargg/oam_bug/3-non_causes.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/oam_bug/3-non_causes.gb -------------------------------------------------------------------------------- /test/suite/blargg/oam_bug/5-timing_bug.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/oam_bug/5-timing_bug.gb -------------------------------------------------------------------------------- /test/expected/blargg/dmg_sound/04-sweep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/dmg_sound/04-sweep.png -------------------------------------------------------------------------------- /test/expected/blargg/oam_bug/1-lcd_sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/oam_bug/1-lcd_sync.png -------------------------------------------------------------------------------- /test/suite/blargg/cpu_instrs/01-special.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/cpu_instrs/01-special.gb -------------------------------------------------------------------------------- /test/suite/blargg/cpu_instrs/03-op sp,hl.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/cpu_instrs/03-op sp,hl.gb -------------------------------------------------------------------------------- /test/suite/blargg/cpu_instrs/04-op r,imm.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/cpu_instrs/04-op r,imm.gb -------------------------------------------------------------------------------- /test/suite/blargg/cpu_instrs/10-bit ops.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/cpu_instrs/10-bit ops.gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound-2/02-len ctr.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound-2/02-len ctr.gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound-2/03-trigger.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound-2/03-trigger.gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound/01-registers.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound/01-registers.gb -------------------------------------------------------------------------------- /test/suite/blargg/oam_bug-2/3-non_causes.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/oam_bug-2/3-non_causes.gb -------------------------------------------------------------------------------- /test/suite/blargg/oam_bug-2/5-timing_bug.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/oam_bug-2/5-timing_bug.gb -------------------------------------------------------------------------------- /test/suite/blargg/oam_bug/8-instr_effect.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/oam_bug/8-instr_effect.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/div_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/div_timing.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/ei_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/ei_timing.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/jp_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/jp_timing.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/pop_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/pop_timing.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/ret_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/ret_timing.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/rst_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/rst_timing.gb -------------------------------------------------------------------------------- /test/expected/blargg/cpu_instrs/01-special.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/cpu_instrs/01-special.png -------------------------------------------------------------------------------- /test/expected/blargg/cpu_instrs/05-op rp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/cpu_instrs/05-op rp.png -------------------------------------------------------------------------------- /test/expected/blargg/cpu_instrs/06-ld r,r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/cpu_instrs/06-ld r,r.png -------------------------------------------------------------------------------- /test/expected/blargg/cpu_instrs/09-op r,r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/cpu_instrs/09-op r,r.png -------------------------------------------------------------------------------- /test/expected/blargg/cpu_instrs/10-bit ops.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/cpu_instrs/10-bit ops.png -------------------------------------------------------------------------------- /test/expected/blargg/dmg_sound-2/04-sweep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/dmg_sound-2/04-sweep.png -------------------------------------------------------------------------------- /test/expected/blargg/oam_bug-2/1-lcd_sync.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/oam_bug-2/1-lcd_sync.png -------------------------------------------------------------------------------- /test/expected/blargg/oam_bug/3-non_causes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/oam_bug/3-non_causes.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/ei_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/ei_timing.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/jp_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/jp_timing.png -------------------------------------------------------------------------------- /test/suite/blargg/cpu_instrs/02-interrupts.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/cpu_instrs/02-interrupts.gb -------------------------------------------------------------------------------- /test/suite/blargg/cpu_instrs/08-misc instrs.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/cpu_instrs/08-misc instrs.gb -------------------------------------------------------------------------------- /test/suite/blargg/cpu_instrs/11-op a,(hl).gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/cpu_instrs/11-op a,(hl).gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound-2/01-registers.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound-2/01-registers.gb -------------------------------------------------------------------------------- /test/suite/blargg/instr_timing/instr_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/instr_timing/instr_timing.gb -------------------------------------------------------------------------------- /test/suite/blargg/mem_timing/01-read_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/mem_timing/01-read_timing.gb -------------------------------------------------------------------------------- /test/suite/blargg/oam_bug-2/6-timing_no_bug.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/oam_bug-2/6-timing_no_bug.gb -------------------------------------------------------------------------------- /test/suite/blargg/oam_bug-2/7-timing_effect.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/oam_bug-2/7-timing_effect.gb -------------------------------------------------------------------------------- /test/suite/blargg/oam_bug-2/8-instr_effect.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/oam_bug-2/8-instr_effect.gb -------------------------------------------------------------------------------- /test/suite/blargg/oam_bug/4-scanline_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/oam_bug/4-scanline_timing.gb -------------------------------------------------------------------------------- /test/suite/blargg/oam_bug/6-timing_no_bug.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/oam_bug/6-timing_no_bug.gb -------------------------------------------------------------------------------- /test/suite/blargg/oam_bug/7-timing_effect.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/oam_bug/7-timing_effect.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/boot_hwio-S.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/boot_hwio-S.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/boot_regs-mgb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/boot_regs-mgb.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/call_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/call_timing.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/call_timing2.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/call_timing2.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/di_timing-GS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/di_timing-GS.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/halt_ime0_ei.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/halt_ime0_ei.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/intr_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/intr_timing.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/jp_cc_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/jp_cc_timing.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/oam_dma_start.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/oam_dma_start.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/push_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/push_timing.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/rapid_di_ei.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/rapid_di_ei.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/ret_cc_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/ret_cc_timing.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/reti_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/reti_timing.gb -------------------------------------------------------------------------------- /test/expected/blargg/cpu_instrs/03-op sp,hl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/cpu_instrs/03-op sp,hl.png -------------------------------------------------------------------------------- /test/expected/blargg/cpu_instrs/04-op r,imm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/cpu_instrs/04-op r,imm.png -------------------------------------------------------------------------------- /test/expected/blargg/cpu_instrs/11-op a,(hl).png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/cpu_instrs/11-op a,(hl).png -------------------------------------------------------------------------------- /test/expected/blargg/dmg_sound/01-registers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/dmg_sound/01-registers.png -------------------------------------------------------------------------------- /test/expected/blargg/mem_timing-2/mem_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/mem_timing-2/mem_timing.png -------------------------------------------------------------------------------- /test/expected/blargg/oam_bug-2/3-non_causes.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/oam_bug-2/3-non_causes.png -------------------------------------------------------------------------------- /test/expected/blargg/oam_bug/6-timing_no_bug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/oam_bug/6-timing_no_bug.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/call_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/call_timing.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/div_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/div_timing.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/intr_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/intr_timing.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/pop_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/pop_timing.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/push_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/push_timing.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/rapid_di_ei.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/rapid_di_ei.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/ret_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/ret_timing.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/reti_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/reti_timing.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/rst_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/rst_timing.png -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound/05-sweep details.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound/05-sweep details.gb -------------------------------------------------------------------------------- /test/suite/blargg/mem_timing-2/01-read_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/mem_timing-2/01-read_timing.gb -------------------------------------------------------------------------------- /test/suite/blargg/mem_timing/02-write_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/mem_timing/02-write_timing.gb -------------------------------------------------------------------------------- /test/suite/blargg/mem_timing/03-modify_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/mem_timing/03-modify_timing.gb -------------------------------------------------------------------------------- /test/suite/blargg/oam_bug-2/4-scanline_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/oam_bug-2/4-scanline_timing.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/add_sp_e_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/add_sp_e_timing.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/boot_hwio-dmg0.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/boot_hwio-dmg0.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/boot_regs-dmg0.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/boot_regs-dmg0.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/call_cc_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/call_cc_timing.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/call_cc_timing2.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/call_cc_timing2.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/if_ie_registers.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/if_ie_registers.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/oam_dma_restart.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/oam_dma_restart.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/oam_dma_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/oam_dma_timing.gb -------------------------------------------------------------------------------- /test/expected/blargg/cpu_instrs/02-interrupts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/cpu_instrs/02-interrupts.png -------------------------------------------------------------------------------- /test/expected/blargg/cpu_instrs/08-misc instrs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/cpu_instrs/08-misc instrs.png -------------------------------------------------------------------------------- /test/expected/blargg/dmg_sound-2/01-registers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/dmg_sound-2/01-registers.png -------------------------------------------------------------------------------- /test/expected/blargg/instr_timing/instr_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/instr_timing/instr_timing.png -------------------------------------------------------------------------------- /test/expected/blargg/mem_timing/01-read_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/mem_timing/01-read_timing.png -------------------------------------------------------------------------------- /test/expected/blargg/mem_timing/02-write_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/mem_timing/02-write_timing.png -------------------------------------------------------------------------------- /test/expected/blargg/oam_bug-2/6-timing_no_bug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/oam_bug-2/6-timing_no_bug.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/call_cc_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/call_cc_timing.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/call_timing2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/call_timing2.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/di_timing-GS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/di_timing-GS.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/halt_ime0_ei.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/halt_ime0_ei.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/jp_cc_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/jp_cc_timing.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/oam_dma_start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/oam_dma_start.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/oam_dma_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/oam_dma_timing.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/ret_cc_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/ret_cc_timing.png -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound-2/05-sweep details.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound-2/05-sweep details.gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound/11-regs after power.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound/11-regs after power.gb -------------------------------------------------------------------------------- /test/suite/blargg/interrupt_time/interrupt_time.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/interrupt_time/interrupt_time.gb -------------------------------------------------------------------------------- /test/suite/blargg/mem_timing-2/02-write_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/mem_timing-2/02-write_timing.gb -------------------------------------------------------------------------------- /test/suite/blargg/mem_timing-2/03-modify_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/mem_timing-2/03-modify_timing.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/boot_regs-dmgABCX.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/boot_regs-dmgABCX.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/halt_ime1_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/halt_ime1_timing.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/ld_hl_sp_e_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/ld_hl_sp_e_timing.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/reti_intr_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/reti_intr_timing.gb -------------------------------------------------------------------------------- /test/expected/blargg/mem_timing-2/01-read_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/mem_timing-2/01-read_timing.png -------------------------------------------------------------------------------- /test/expected/blargg/mem_timing-2/02-write_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/mem_timing-2/02-write_timing.png -------------------------------------------------------------------------------- /test/expected/blargg/mem_timing/03-modify_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/mem_timing/03-modify_timing.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/add_sp_e_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/add_sp_e_timing.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/call_cc_timing2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/call_cc_timing2.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/halt_ime1_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/halt_ime1_timing.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/if_ie_registers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/if_ie_registers.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/oam_dma_restart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/oam_dma_restart.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/reti_intr_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/reti_intr_timing.png -------------------------------------------------------------------------------- /test/suite/blargg/cpu_instrs/07-jr,jp,call,ret,rst.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/cpu_instrs/07-jr,jp,call,ret,rst.gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound-2/11-regs after power.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound-2/11-regs after power.gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound/06-overflow on trigger.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound/06-overflow on trigger.gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound/09-wave read while on.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound/09-wave read while on.gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound/12-wave write while on.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound/12-wave write while on.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/boot_hwio-dmgABCXmgb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/boot_hwio-dmgABCXmgb.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/gpu/intr_2_0_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/gpu/intr_2_0_timing.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/halt_ime1_timing2-GS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/halt_ime1_timing2-GS.gb -------------------------------------------------------------------------------- /test/expected/blargg/mem_timing-2/03-modify_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/mem_timing-2/03-modify_timing.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/gpu/intr_2_0_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/gpu/intr_2_0_timing.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/ld_hl_sp_e_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/ld_hl_sp_e_timing.png -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound-2/06-overflow on trigger.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound-2/06-overflow on trigger.gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound-2/08-len ctr during power.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound-2/08-len ctr during power.gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound-2/09-wave read while on.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound-2/09-wave read while on.gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound-2/12-wave write while on.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound-2/12-wave write while on.gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound/07-len sweep period sync.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound/07-len sweep period sync.gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound/08-len ctr during power.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound/08-len ctr during power.gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound/10-wave trigger while on.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound/10-wave trigger while on.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/gpu/intr_1_2_timing-GS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/gpu/intr_1_2_timing-GS.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/gpu/intr_2_mode0_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/gpu/intr_2_mode0_timing.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/gpu/intr_2_mode3_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/gpu/intr_2_mode3_timing.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/gpu/stat_irq_blocking.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/gpu/stat_irq_blocking.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/gpu/vblank_stat_intr-GS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/gpu/vblank_stat_intr-GS.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/halt_ime0_nointr_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/halt_ime0_nointr_timing.gb -------------------------------------------------------------------------------- /test/expected/blargg/cpu_instrs/07-jr,jp,call,ret,rst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/cpu_instrs/07-jr,jp,call,ret,rst.png -------------------------------------------------------------------------------- /test/expected/blargg/dmg_sound/06-overflow on trigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/dmg_sound/06-overflow on trigger.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/gpu/stat_irq_blocking.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/gpu/stat_irq_blocking.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/halt_ime1_timing2-GS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/halt_ime1_timing2-GS.png -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound-2/07-len sweep period sync.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound-2/07-len sweep period sync.gb -------------------------------------------------------------------------------- /test/suite/blargg/dmg_sound-2/10-wave trigger while on.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/blargg/dmg_sound-2/10-wave trigger while on.gb -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/gpu/intr_2_oam_ok_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/gpu/intr_2_oam_ok_timing.gb -------------------------------------------------------------------------------- /test/expected/blargg/dmg_sound-2/06-overflow on trigger.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/blargg/dmg_sound-2/06-overflow on trigger.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/gpu/intr_1_2_timing-GS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/gpu/intr_1_2_timing-GS.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/gpu/intr_2_mode0_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/gpu/intr_2_mode0_timing.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/gpu/intr_2_mode3_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/gpu/intr_2_mode3_timing.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/gpu/intr_2_oam_ok_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/gpu/intr_2_oam_ok_timing.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/gpu/vblank_stat_intr-GS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/gpu/vblank_stat_intr-GS.png -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/halt_ime0_nointr_timing.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/halt_ime0_nointr_timing.png -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/gpu/hblank_ly_scx_timing-GS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/gpu/hblank_ly_scx_timing-GS.gb -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/gpu/hblank_ly_scx_timing-GS.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/gpu/hblank_ly_scx_timing-GS.png -------------------------------------------------------------------------------- /test/suite/mooneye/acceptance/gpu/intr_2_mode0_timing_sprites.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/suite/mooneye/acceptance/gpu/intr_2_mode0_timing_sprites.gb -------------------------------------------------------------------------------- /test/expected/mooneye/acceptance/gpu/intr_2_mode0_timing_sprites.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/arrow-lang/wadatsumi/HEAD/test/expected/mooneye/acceptance/gpu/intr_2_mode0_timing_sprites.png -------------------------------------------------------------------------------- /test/suite/mooneye/README.md: -------------------------------------------------------------------------------- 1 | Compiled from https://github.com/Gekkio/mooneye-gb 2 | 3 | Refer to https://raw.githubusercontent.com/Gekkio/mooneye-gb/master/LICENSE for updated license information (as of 2016-12-08 these tests are under the GPLv3 license). 4 | -------------------------------------------------------------------------------- /test/suite/blargg/README.md: -------------------------------------------------------------------------------- 1 | Blargg's Gameboy hardware test ROMs. Originally hosted at http://blargg.parodius.com/gb-tests/ 2 | before parodious.com went down. 3 | 4 | New official location: http://blargg.8bitalley.com/parodius/gb-tests/ 5 | New (extra) official location: http://slack.net/~ant/old/gb-tests/ 6 | -------------------------------------------------------------------------------- /gb/bits.as: -------------------------------------------------------------------------------- 1 | 2 | // Returns a value with the th bit set or cleared (based on ) 3 | // and all other bits (of an 8-bit number) cleared. 4 | def Bit(value: bool, n: uint8): uint8 { 5 | return (1 if value else 0) << n; 6 | } 7 | 8 | // Returns the boolean value of the th bit from 9 | def Test(value: uint8, n: uint8): bool { 10 | return (value & (1 << n)) != 0; 11 | } 12 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | all: bin/wadatsumi 2 | 3 | clean: 4 | @ rm -rf build bin 5 | 6 | bin/wadatsumi: build/wadatsumi.o 7 | @ mkdir -p bin 8 | @ gcc -o $@ $^ -lSDL2 -lc 9 | 10 | build/wadatsumi.o: build/wadatsumi.ll 11 | @ llc-3.8 -filetype=obj -o $@ $^ 12 | 13 | build/wadatsumi.ll: gb/index.as 14 | @ mkdir -p build 15 | @ # TODO: Add `-o` to arrow so we can avoid this hack 16 | @ (arrow --compile $^ > $@ || (rm $@; exit 1)) && opt-3.8 -O3 -S -o $@ $@ 17 | -------------------------------------------------------------------------------- /ch8/cpu.as: -------------------------------------------------------------------------------- 1 | import "std"; 2 | import "libc"; 3 | 4 | import "./op"; 5 | import "./machine"; 6 | import "./mmu"; 7 | 8 | // Reset 9 | def reset(c: *machine.Context) { 10 | // TODO: Zero out other registers 11 | c.PC = 0x200; 12 | } 13 | 14 | // Execute Next Operation 15 | def execute(c: *machine.Context) { 16 | // Read next opcode and increment PC 17 | let opcode = mmu.at(c, c.PC); 18 | 19 | // Increment PC 20 | c.PC += 2; 21 | 22 | // Decode and execute opcode 23 | op.execute(c, opcode); 24 | } 25 | -------------------------------------------------------------------------------- /ch8/mmu.as: -------------------------------------------------------------------------------- 1 | import "libc"; 2 | 3 | import "./machine"; 4 | 5 | def at(c: *machine.Context, address: uint16): *uint8 { 6 | if address < 0x50 { 7 | return (c.font + address); 8 | } 9 | 10 | if address < 0x200 { 11 | // Possible access into interpreter memory 12 | // Most accesses are illegal 13 | libc.printf("error: illegal access to interpreter memory at $%04X\n", 14 | address); 15 | 16 | libc.exit(1); 17 | } 18 | 19 | return (c.ram + ((address - 0x200) & 0xFFF)); 20 | } 21 | 22 | def read(c: *machine.Context, address: uint16): uint8 { 23 | return *at(c, address); 24 | } 25 | 26 | def write(c: *machine.Context, address: uint16, value: uint8) { 27 | *at(c, address) = value; 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Arrow 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /test/generate_expected.py: -------------------------------------------------------------------------------- 1 | from os import path, makedirs 2 | from sys import argv 3 | from subprocess import check_call 4 | from tempfile import NamedTemporaryFile 5 | 6 | def run(bin_path, test_filename, expected_filename): 7 | with NamedTemporaryFile(suffix=".bmp", delete=False) as test_out: 8 | check_call([ 9 | bin_path, 10 | "--test", 11 | "--test-output", test_out.name, 12 | test_filename, 13 | ]) 14 | 15 | try: 16 | makedirs(path.dirname(expected_filename)) 17 | 18 | except: 19 | # Ignore error (dirs already there) 20 | pass 21 | 22 | check_call([ 23 | "convert", 24 | test_out.name, 25 | expected_filename 26 | ]) 27 | 28 | def main(): 29 | base_dir = path.dirname(__file__) 30 | bin_path = path.join(base_dir, "../bin/wadatsumi") 31 | 32 | for test in argv[1:]: 33 | expected_filename = test.replace("test/suite", "test/expected").replace( 34 | ".gb", ".png") 35 | 36 | run(bin_path, test, expected_filename) 37 | 38 | if __name__ == "__main__": 39 | main() 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Wadatsumi 2 | > Gameboy (DMG) emulator written in Arrow. 3 | 4 | ### Tasks 5 | 6 | - BIOS 7 | 8 | - Sprite / Mode-3 Timing 9 | 10 | - Reduce code duplication among memory controllers 11 | 12 | - Prevent opposite directions on the D-Pad from being pressed simultaneously ( 13 | doing so locks up some games like "Pocket Bomberman") 14 | 15 | - [CGB] H-Blank DMA 16 | 17 | - MBC3 Timer 18 | 19 | - MBC5 Rumble 20 | 21 | - Command Line Arguments 22 | - `-m gb` and `-m cgb` to select mode 23 | - `--scale` / `-s` to specify the scale factor 24 | - `--audio-sample-rate` to specify the audio sample rate 25 | 26 | - When the LCD is enabled we should still emit "refresh" events to the 27 | front-end to keep up with V-Sync 28 | 29 | ### Bugs 30 | 31 | - In "Wario Land (SML 3)"; smashing a block from below while crouching 32 | produces a jarring sound. My guess is the noise channel is running too fast 33 | in this instance. 34 | 35 | - In "Dragon Warrior I and II"; saving to the journal seems to indeed write to 36 | the journal but only the existence of said journal. No progress beyond name 37 | selection is remembered. "Pokemon Pinball" also doesn't appear to save 38 | correctly. Both games are for the Color Gameboy but are supposed to be 39 | GB-compatible. 40 | 41 | - In "Pokemon Trading Card Game"; I get an immediate white screen and then 42 | it seems to freeze 43 | -------------------------------------------------------------------------------- /gb/linkCable.as: -------------------------------------------------------------------------------- 1 | import "./bits"; 2 | import "./mmu"; 3 | 4 | // TODO(wadatsumi): Just the barest bare bones here 5 | 6 | struct LinkCable { 7 | // FF01 – SB - Serial Transfer Data/Buffer (R/W) 8 | SB: uint8; 9 | 10 | // FF02 – SC – Serial Transfer Control (R/W) 11 | // Bit 7 - Transfer Start Flag (0=No Transfer, 1=Start) 12 | // Bit 0 - Shift Clock (0=External Clock, 1=Internal Clock) 13 | ShiftClock: bool; 14 | } 15 | 16 | implement LinkCable { 17 | def New(): Self { 18 | let component: LinkCable; 19 | return component; 20 | } 21 | 22 | def Reset(self) { 23 | self.SB = 0x0; 24 | self.ShiftClock = false; 25 | } 26 | 27 | def Read(self, address: uint16, ptr: *uint8): bool { 28 | *ptr = if address == 0xFF01 { 29 | self.SB; 30 | } else if address == 0xFF02 { 31 | ( 32 | bits.Bit(false, 7) | 33 | bits.Bit(true, 6) | 34 | bits.Bit(true, 5) | 35 | bits.Bit(true, 4) | 36 | bits.Bit(true, 3) | 37 | bits.Bit(true, 2) | 38 | bits.Bit(true, 1) | 39 | bits.Bit(self.ShiftClock, 0) 40 | ); 41 | } else { 42 | return false; 43 | }; 44 | 45 | return true; 46 | } 47 | 48 | def Write(self, address: uint16, value: uint8): bool { 49 | if address == 0xFF01 { 50 | self.SB = value; 51 | } else if address == 0xFF02 { 52 | self.ShiftClock = bits.Test(value, 0); 53 | } else { 54 | return false; 55 | } 56 | 57 | return true; 58 | } 59 | 60 | def AsMemoryController(self, this: *LinkCable): mmu.MemoryController { 61 | let mc: mmu.MemoryController; 62 | mc.Read = MCRead; 63 | mc.Write = MCWrite; 64 | mc.Data = this as *uint8; 65 | mc.Release = MCRelease; 66 | 67 | return mc; 68 | } 69 | } 70 | 71 | def MCRelease(this: *mmu.MemoryController) { 72 | // Do nothing 73 | } 74 | 75 | def MCRead(this: *mmu.MemoryController, address: uint16, value: *uint8): bool { 76 | return (this.Data as *LinkCable).Read(address, value); 77 | } 78 | 79 | def MCWrite(this: *mmu.MemoryController, address: uint16, value: uint8): bool { 80 | return (this.Data as *LinkCable).Write(address, value); 81 | } 82 | -------------------------------------------------------------------------------- /gb/mbc2.as: -------------------------------------------------------------------------------- 1 | import "std"; 2 | import "libc"; 3 | 4 | import "./mmu"; 5 | import "./cartridge"; 6 | 7 | struct MBC2 { 8 | Cartridge: *cartridge.Cartridge; 9 | 10 | // Second ROM bank is specified here. 11 | // In range 1-16 12 | ROMBank: uint8; 13 | 14 | // RAM Enable 15 | // There is only 1 ram bank in MBC2 16 | RAMEnable: bool; 17 | } 18 | 19 | implement MBC2 { 20 | def Read(self, address: uint16, value: *uint8): bool { 21 | if address <= 0x3FFF { 22 | // ROM Bank $0 23 | *value = *(self.Cartridge.ROM + address); 24 | } else if address <= 0x7FFF { 25 | // ROM Bank $1 - $7F 26 | *value = *(self.Cartridge.ROM + (uint64(self.ROMBank) * 0x4000) + (uint64(address) - 0x4000)); 27 | } else if address >= 0xA000 and address <= 0xBFFF { 28 | // RAM Bank $0 - $3 29 | let ramSize = self.Cartridge.ExternalRAMSize; 30 | let offset = uint64(address - 0xA000); 31 | if self.RAMEnable and offset < ramSize { 32 | *value = *(self.Cartridge.ExternalRAM + offset); 33 | } else { 34 | // External RAM is not enabled 35 | *value = 0xFF; 36 | } 37 | } else { 38 | return false; 39 | } 40 | 41 | return true; 42 | } 43 | 44 | def Write(self, address: uint16, value: uint8): bool { 45 | if address <= 0x1FFF { 46 | // RAM Enable 47 | // The least significant bit of the upper address byte must be '0' to 48 | // enable/disable cart RAM. 49 | if (address & 0x0100) == 0 { 50 | self.RAMEnable = (value & 0x0A) != 0; 51 | } 52 | } else if address <= 0x3FFF { 53 | // ROM Bank Number 54 | // The least significant bit of the upper address byte must be '1' to 55 | // select a ROM bank. 56 | if (address & 0x0100) != 0 { 57 | // Selecting $0 will bump you to $1 58 | self.ROMBank = (value & 0b1111); 59 | if self.ROMBank == 0 { self.ROMBank += 1; } 60 | } 61 | } else if address >= 0xA000 and address <= 0xBFFF { 62 | // External RAM 63 | let ramSize = self.Cartridge.ExternalRAMSize; 64 | let offset = uint64(address - 0xA000); 65 | if self.RAMEnable and offset < ramSize { 66 | *(self.Cartridge.ExternalRAM + offset) = value; 67 | } 68 | } else { 69 | // Unhandled 70 | return false; 71 | } 72 | 73 | return true; 74 | } 75 | } 76 | 77 | def New(cartridge_: *cartridge.Cartridge): mmu.MemoryController { 78 | let mc: mmu.MemoryController; 79 | mc.Read = MCRead; 80 | mc.Write = MCWrite; 81 | mc.Data = libc.malloc(std.size_of()) as *uint8; 82 | mc.Release = MCRelease; 83 | 84 | let self_ = mc.Data as *MBC2; 85 | self_.Cartridge = cartridge_; 86 | self_.ROMBank = 0x01; 87 | self_.RAMEnable = false; 88 | 89 | return mc; 90 | } 91 | 92 | def MCRelease(this: *mmu.MemoryController) { 93 | let self_ = this.Data as *MBC2; 94 | libc.free(this.Data); 95 | } 96 | 97 | def MCRead(this: *mmu.MemoryController, address: uint16, value: *uint8): bool { 98 | return (this.Data as *MBC2).Read(address, value); 99 | } 100 | 101 | def MCWrite(this: *mmu.MemoryController, address: uint16, value: uint8): bool { 102 | return (this.Data as *MBC2).Write(address, value); 103 | } 104 | -------------------------------------------------------------------------------- /gb/mbc3.as: -------------------------------------------------------------------------------- 1 | import "std"; 2 | import "libc"; 3 | 4 | import "./mmu"; 5 | import "./cartridge"; 6 | 7 | // TODO(wadatsumi): RTC 8 | 9 | struct MBC3 { 10 | Cartridge: *cartridge.Cartridge; 11 | 12 | // Second ROM bank is specified here. 13 | // In range 1-127 14 | ROMBank: uint8; 15 | 16 | // In range 0-3 for RAM and 08h-0Ch for RTC 17 | RAMBankOrTimerRegister: uint8; 18 | RAMAndTimerEnable: bool; 19 | } 20 | 21 | implement MBC3 { 22 | def Read(self, address: uint16, value: *uint8): bool { 23 | if address <= 0x3FFF { 24 | // ROM Bank $0 25 | *value = *(self.Cartridge.ROM + address); 26 | } else if address <= 0x7FFF { 27 | // ROM Bank $1 - $7F 28 | *value = *(self.Cartridge.ROM + (uint64(self.ROMBank) * 0x4000) + (uint64(address) - 0x4000)); 29 | } else if address >= 0xA000 and address <= 0xBFFF { 30 | // RAM Bank $0 - $3 31 | let ramSize = self.Cartridge.ExternalRAMSize; 32 | let offset = uint64(address - 0xA000); 33 | if self.RAMAndTimerEnable and offset < ramSize { 34 | *value = *((self.Cartridge.ExternalRAM + (uint64(self.RAMBankOrTimerRegister) * 0x2000)) + offset); 35 | } else { 36 | // External RAM is not enabled 37 | *value = 0xFF; 38 | } 39 | } else { 40 | return false; 41 | } 42 | 43 | return true; 44 | } 45 | 46 | def Write(self, address: uint16, value: uint8): bool { 47 | if address <= 0x1FFF { 48 | // RAM Enable 49 | self.RAMAndTimerEnable = (value & 0x0A) != 0; 50 | } else if address <= 0x3FFF { 51 | // ROM Bank Number 52 | // Selecting $0 will bump you to $1 53 | self.ROMBank = value; 54 | if self.ROMBank == 0 { self.ROMBank += 1; } 55 | } else if address <= 0x5FFF { 56 | // RAM Bank Number OR RTC Select 57 | self.RAMBankOrTimerRegister = value; 58 | } else if address <= 0x7FFF { 59 | // TODO: Latch Clock Data 60 | } else if address >= 0xA000 and address <= 0xBFFF { 61 | // External RAM 62 | let ramSize = self.Cartridge.ExternalRAMSize; 63 | let offset = uint64(address - 0xA000); 64 | if self.RAMAndTimerEnable and offset < ramSize { 65 | *((self.Cartridge.ExternalRAM + (uint64(self.RAMBankOrTimerRegister) * 0x2000)) + offset) = value; 66 | } 67 | } else { 68 | // Unhandled 69 | return false; 70 | } 71 | 72 | return true; 73 | } 74 | } 75 | 76 | def New(cartridge_: *cartridge.Cartridge): mmu.MemoryController { 77 | let mc: mmu.MemoryController; 78 | mc.Read = MCRead; 79 | mc.Write = MCWrite; 80 | mc.Data = libc.malloc(std.size_of()) as *uint8; 81 | mc.Release = MCRelease; 82 | 83 | let self_ = mc.Data as *MBC3; 84 | self_.Cartridge = cartridge_; 85 | self_.ROMBank = 0x01; 86 | self_.RAMBankOrTimerRegister = 0x00; 87 | self_.RAMAndTimerEnable = false; 88 | 89 | return mc; 90 | } 91 | 92 | def MCRelease(this: *mmu.MemoryController) { 93 | let self_ = this.Data as *MBC3; 94 | libc.free(this.Data); 95 | } 96 | 97 | def MCRead(this: *mmu.MemoryController, address: uint16, value: *uint8): bool { 98 | return (this.Data as *MBC3).Read(address, value); 99 | } 100 | 101 | def MCWrite(this: *mmu.MemoryController, address: uint16, value: uint8): bool { 102 | return (this.Data as *MBC3).Write(address, value); 103 | } 104 | -------------------------------------------------------------------------------- /gb/mbc5.as: -------------------------------------------------------------------------------- 1 | import "std"; 2 | import "libc"; 3 | 4 | import "./mmu"; 5 | import "./cartridge"; 6 | 7 | // TODO(wadatsumi): RTC 8 | // TODO(wadatsumi): Rumble 9 | 10 | struct MBC3 { 11 | Cartridge: *cartridge.Cartridge; 12 | 13 | // Second ROM bank is specified here. 14 | // In range $0 - $1E0 15 | ROMBank: uint16; 16 | 17 | // Ext. RAM BANK 18 | // In range $0 - $F 19 | RAMBank: uint8; 20 | 21 | // Ext. RAM Enable 22 | RAMEnable: bool; 23 | } 24 | 25 | implement MBC3 { 26 | def Read(self, address: uint16, value: *uint8): bool { 27 | if address <= 0x3FFF { 28 | // ROM Bank $0 29 | *value = *(self.Cartridge.ROM + address); 30 | } else if address <= 0x7FFF { 31 | // ROM Bank $0 - $1E0 32 | *value = *(self.Cartridge.ROM + (uint64(self.ROMBank) * 0x4000) + (uint64(address) - 0x4000)); 33 | } else if address >= 0xA000 and address <= 0xBFFF { 34 | // RAM Bank $0 - $F 35 | let ramSize = self.Cartridge.ExternalRAMSize; 36 | let offset = uint64(address - 0xA000); 37 | if self.RAMEnable and offset < ramSize { 38 | *value = *((self.Cartridge.ExternalRAM + (uint64(self.RAMBank) * 0x2000)) + offset); 39 | } else { 40 | // External RAM is not enabled 41 | *value = 0xFF; 42 | } 43 | } else { 44 | return false; 45 | } 46 | 47 | return true; 48 | } 49 | 50 | def Write(self, address: uint16, value: uint8): bool { 51 | if address <= 0x1FFF { 52 | // RAM Enable 53 | self.RAMEnable = (value & 0x0A) != 0; 54 | } else if address <= 0x2FFF { 55 | // ROM Bank Number — lower 8-bits 56 | self.ROMBank &= ~0xFF; 57 | self.ROMBank |= uint16(value); 58 | } else if address <= 0x3FFF { 59 | // ROM Bank Number — upper 8-bits 60 | self.ROMBank &= ~0xFF00; 61 | self.ROMBank |= (uint16(value) << 8); 62 | } else if address <= 0x5FFF { 63 | // RAM Bank Number 64 | self.RAMBank = value & 0xF; 65 | 66 | // Rumble ON/OFF 67 | // In cartridges with rumble, writing a '1' to bit 4 will enable the 68 | // electric motor, writing a '0' will disable it 69 | // TODO: Do something with this 70 | // let rumbleOn = value & 0b10000; 71 | } else if address >= 0xA000 and address <= 0xBFFF { 72 | // External RAM 73 | let ramSize = self.Cartridge.ExternalRAMSize; 74 | let offset = uint64(address - 0xA000); 75 | if self.RAMEnable and offset < ramSize { 76 | *((self.Cartridge.ExternalRAM + (uint64(self.RAMBank) * 0x2000)) + offset) = value; 77 | } 78 | } else { 79 | // Unhandled 80 | return false; 81 | } 82 | 83 | return true; 84 | } 85 | } 86 | 87 | def New(cartridge_: *cartridge.Cartridge): mmu.MemoryController { 88 | let mc: mmu.MemoryController; 89 | mc.Read = MCRead; 90 | mc.Write = MCWrite; 91 | mc.Data = libc.malloc(std.size_of()) as *uint8; 92 | mc.Release = MCRelease; 93 | 94 | let self_ = mc.Data as *MBC3; 95 | self_.Cartridge = cartridge_; 96 | self_.ROMBank = 0x01; 97 | self_.RAMBank = 0x00; 98 | self_.RAMEnable = false; 99 | 100 | return mc; 101 | } 102 | 103 | def MCRelease(this: *mmu.MemoryController) { 104 | let self_ = this.Data as *MBC3; 105 | libc.free(this.Data); 106 | } 107 | 108 | def MCRead(this: *mmu.MemoryController, address: uint16, value: *uint8): bool { 109 | return (this.Data as *MBC3).Read(address, value); 110 | } 111 | 112 | def MCWrite(this: *mmu.MemoryController, address: uint16, value: uint8): bool { 113 | return (this.Data as *MBC3).Write(address, value); 114 | } 115 | -------------------------------------------------------------------------------- /gb/mbc1.as: -------------------------------------------------------------------------------- 1 | import "std"; 2 | import "libc"; 3 | 4 | import "./mmu"; 5 | import "./cartridge"; 6 | 7 | struct MBC1 { 8 | Cartridge: *cartridge.Cartridge; 9 | 10 | // Second ROM bank is specified here. 11 | // In range 1-127 12 | // Banks $20, $40, and $60 are seen as $21, $41, and $61. 13 | ROMBank: uint8; 14 | 15 | // In range 0-3 16 | RAMBank: uint8; 17 | RAMEnable: bool; 18 | 19 | // Mode (ROM / RAM) 20 | Mode: uint8; 21 | } 22 | 23 | implement MBC1 { 24 | def Read(self, address: uint16, value: *uint8): bool { 25 | if address <= 0x3FFF { 26 | // ROM Bank $0 27 | *value = *(self.Cartridge.ROM + address); 28 | } else if address <= 0x7FFF { 29 | // ROM Bank $1 - $7F 30 | *value = *(self.Cartridge.ROM + (uint64(self.ROMBank) * 0x4000) + (uint64(address) - 0x4000)); 31 | } else if address >= 0xA000 and address <= 0xBFFF { 32 | // RAM Bank $0 - $3 33 | let ramSize = self.Cartridge.ExternalRAMSize; 34 | let offset = uint64(address - 0xA000); 35 | if self.RAMEnable and offset < ramSize { 36 | *value = *((self.Cartridge.ExternalRAM + (uint64(self.RAMBank) * 0x2000)) + offset); 37 | } else { 38 | // External RAM is not enabled 39 | *value = 0xFF; 40 | } 41 | } else { 42 | return false; 43 | } 44 | 45 | return true; 46 | } 47 | 48 | def Write(self, address: uint16, value: uint8): bool { 49 | if address <= 0x1FFF { 50 | // RAM Enable 51 | self.RAMEnable = (value & 0x0A) != 0; 52 | } else if address <= 0x3FFF { 53 | // ROM Bank Number (lower 5 bits) 54 | self.ROMBank &= ~0x1F; 55 | self.ROMBank |= (value & 0x1F); 56 | 57 | // Selecting an invalid bank will bump you up a bank 58 | let n = (self.ROMBank & 0x1F); 59 | if n == 0x20 or n == 0x40 or n == 0x60 or n == 0x00 { 60 | self.ROMBank += 1; 61 | } 62 | } else if address <= 0x5FFF { 63 | // RAM Bank Number OR Upper 2 bits of ROM Bank Number 64 | if self.Mode == 0x00 { 65 | self.RAMBank = value & 0x3; 66 | } else if self.Mode == 0x01 { 67 | self.ROMBank &= ~0x60; 68 | self.ROMBank |= (value & 0x3) << 5; 69 | } 70 | } else if address <= 0x7FFF { 71 | // ROM/RAM Mode Select 72 | let mode = value & 0x1; 73 | if mode != self.Mode { 74 | if mode == 0x00 { 75 | let tmp = (self.ROMBank & 0x60) >> 5; 76 | self.ROMBank &= ~0x60; 77 | self.RAMBank = tmp; 78 | } else if mode == 0x01 { 79 | let tmp = self.RAMBank; 80 | self.RAMBank = 0x00; 81 | self.ROMBank &= ~0x60; 82 | self.ROMBank |= (tmp & 0x3) << 5; 83 | } 84 | } 85 | } else if address >= 0xA000 and address <= 0xBFFF { 86 | // External RAM 87 | let ramSize = self.Cartridge.ExternalRAMSize; 88 | let offset = uint64(address - 0xA000); 89 | if self.RAMEnable and offset < ramSize { 90 | *((self.Cartridge.ExternalRAM + (uint64(self.RAMBank) * 0x2000)) + offset) = value; 91 | } 92 | } else { 93 | // Unhandled 94 | return false; 95 | } 96 | 97 | return true; 98 | } 99 | } 100 | 101 | def New(cartridge_: *cartridge.Cartridge): mmu.MemoryController { 102 | let mc: mmu.MemoryController; 103 | mc.Read = MCRead; 104 | mc.Write = MCWrite; 105 | mc.Data = libc.malloc(std.size_of()) as *uint8; 106 | mc.Release = MCRelease; 107 | 108 | let self_ = mc.Data as *MBC1; 109 | self_.Cartridge = cartridge_; 110 | self_.ROMBank = 0x01; 111 | self_.RAMBank = 0x00; 112 | self_.RAMEnable = false; 113 | self_.Mode = 0x00; 114 | 115 | return mc; 116 | } 117 | 118 | def MCRelease(this: *mmu.MemoryController) { 119 | let self_ = this.Data as *MBC1; 120 | libc.free(this.Data); 121 | } 122 | 123 | def MCRead(this: *mmu.MemoryController, address: uint16, value: *uint8): bool { 124 | return (this.Data as *MBC1).Read(address, value); 125 | } 126 | 127 | def MCWrite(this: *mmu.MemoryController, address: uint16, value: uint8): bool { 128 | return (this.Data as *MBC1).Write(address, value); 129 | } 130 | -------------------------------------------------------------------------------- /ch8/index.as: -------------------------------------------------------------------------------- 1 | import "libc"; 2 | 3 | // import "./mmu"; 4 | import "./cpu"; 5 | import "./machine"; 6 | 7 | #include "SDL2/SDL.h" 8 | 9 | // BUG: CInclude does not get macros (yet) 10 | let SDL_INIT_VIDEO: uint32 = 0x00000020; 11 | 12 | let SCALE: uint64 = 10; 13 | 14 | let _window: *SDL_Window; 15 | let _renderer: *SDL_Renderer; 16 | let _tex: *SDL_Texture; 17 | let _framebuffer: *uint32; 18 | let _evt: *uint8; 19 | let _running = true; 20 | 21 | def initialize() { 22 | // BUG: CInclude does not support unions (yet) 23 | _evt = libc.malloc(1000); 24 | 25 | // Initialize Framebuffer () 26 | _framebuffer = libc.malloc(32 * 64 * 4) as *uint32; 27 | libc.memset(_framebuffer as *uint8, 0, 32 * 64 * 4); 28 | 29 | // Initialize SDL 30 | SDL_Init(SDL_INIT_VIDEO); 31 | 32 | // Create Window 33 | // TODO: Allow arbitrary scaling 34 | _window = SDL_CreateWindow( 35 | "Wadatsumi", 0x1FFF0000, 0x1FFF0000, int32(64 * SCALE), int32(32 * SCALE), SDL_WINDOW_SHOWN); 36 | 37 | // Create _renderer 38 | _renderer = SDL_CreateRenderer(_window, -1, 39 | SDL_RENDERER_ACCELERATED); 40 | 41 | // White initial screen 42 | SDL_SetRenderDrawColor(_renderer, 0, 0, 0, 255); 43 | SDL_RenderClear(_renderer); 44 | SDL_RenderPresent(_renderer); 45 | 46 | // Create texture 47 | _tex = SDL_CreateTexture(_renderer, 48 | SDL_PIXELFORMAT_ARGB8888, 49 | // BUG: CInclude has this mismatched for some reason 50 | int32(SDL_TEXTUREACCESS_STREAMING), 51 | 64, 32); 52 | } 53 | 54 | def release() { 55 | libc.free(_evt); 56 | libc.free(_framebuffer as *uint8); 57 | 58 | SDL_DestroyRenderer(_renderer); 59 | SDL_DestroyWindow(_window); 60 | SDL_DestroyTexture(_tex); 61 | 62 | SDL_Quit(); 63 | } 64 | 65 | def refresh(c: *machine.Context) { 66 | // Render 67 | SDL_SetRenderDrawColor(_renderer, 0, 0, 0, 255); 68 | SDL_RenderClear(_renderer); 69 | 70 | let y = 0; 71 | while y < 32 { 72 | let x = 0; 73 | 74 | while x < 64 { 75 | let pixel: uint8 = *(c.framebuffer + (y * 64 + x)); 76 | 77 | // AARRGGBB 78 | let color: uint32 = if pixel == 0 { 79 | 0xFF000000; 80 | } else if pixel == 1 { 81 | 0xFFFFFFFF; 82 | } else { 83 | 0; 84 | }; 85 | 86 | *(_framebuffer + (y * 64 + x)) = color; 87 | 88 | x += 1; 89 | } 90 | 91 | y += 1; 92 | } 93 | 94 | SDL_UpdateTexture(_tex, 0 as *SDL_Rect, _framebuffer as *uint8, int32(64 * 4)); 95 | SDL_RenderCopy(_renderer, _tex, 0 as *SDL_Rect, 0 as *SDL_Rect); 96 | 97 | SDL_RenderPresent(_renderer); 98 | } 99 | 100 | def poll(c: *machine.Context) { 101 | // Handle Events 102 | if SDL_PollEvent(_evt as *SDL_Event) != 0 { 103 | let kind = *(_evt as *uint32); 104 | if kind == 0x100 { 105 | // Quit 106 | _running = false; 107 | } else if kind == 0x300 or kind == 0x301 { 108 | // Key Press OR Release 109 | let which = (*(_evt as *SDL_KeyboardEvent)).keysym.scancode; 110 | 111 | // TODO: Input map / configuration abstraction of some kind 112 | 113 | if (kind == 0x300) { 114 | machine.input_press(c, uint32(which)); 115 | } else { 116 | machine.input_release(c, uint32(which)); 117 | } 118 | } 119 | } 120 | } 121 | 122 | def main(argc: int32, argv: *str) { 123 | initialize(); 124 | 125 | // Initialize — Create new context 126 | let c = machine.new_context(); 127 | 128 | // Reset — CPU 129 | cpu.reset(&c); 130 | 131 | // Open ROM 132 | // [...] 133 | let stream = libc.fopen(*(argv + 1), "rb"); 134 | 135 | // Get size of ROM 136 | libc.fseek(stream, 0, 2); 137 | let stream_size = libc.ftell(stream); 138 | libc.fseek(stream, 0, 0); 139 | 140 | // Write ROM into RAM 141 | libc.fread(c.ram, libc.size_t(if stream_size > 0xE00 { 0xE00; } else { stream_size; }), 1, stream); 142 | libc.fclose(stream); 143 | 144 | // Machine -> On Refresh 145 | machine.set_on_refresh(&c, refresh); 146 | 147 | // Run 148 | while _running { 149 | // Run — Machine 150 | machine.run(&c); 151 | 152 | // Poll — Events 153 | poll(&c); 154 | } 155 | 156 | // Finalize — Dispose context 157 | machine.dispose_context(&c); 158 | 159 | release(); 160 | } 161 | -------------------------------------------------------------------------------- /test/run.py: -------------------------------------------------------------------------------- 1 | from __future__ import print_function 2 | import sys 3 | from os import path 4 | import os 5 | import re 6 | import subprocess 7 | from subprocess import check_call, check_output 8 | from glob import glob 9 | from tempfile import NamedTemporaryFile 10 | 11 | def get_name(filename): 12 | suite_name = path.dirname(filename).replace("test/suite/", "") 13 | test_name = path.basename(filename).replace(".gb", "").replace(".cgb", "") 14 | 15 | return (suite_name, test_name) 16 | 17 | def cpad(string, width=80, sep="-"): 18 | l = (78 - len(string)) 19 | pad = l / 2 20 | lpad = pad 21 | rpad = pad 22 | rem = l % 2 23 | if rem > 0: 24 | rpad += 1 25 | 26 | return "%s %s %s" % (sep * lpad, string, sep * rpad) 27 | 28 | _passed = 0 29 | _failed = 0 30 | _xfailed = 0 31 | _xpassed = 0 32 | 33 | def print_result(test_name, result): 34 | global _passed 35 | global _xfailed 36 | global _failed 37 | 38 | prefix = "\x1b[30;1m" 39 | 40 | if result == "PASS": 41 | prefix = "\x1b[32m" 42 | _passed += 1 43 | 44 | elif result == "XFAIL": 45 | _xfailed += 1 46 | 47 | else: 48 | prefix = "\x1b[31m" 49 | _failed += 1 50 | 51 | print("{:<72s}{}{:>7s}{}".format(test_name, prefix, result, "\x1b[0m")) 52 | 53 | def print_report(): 54 | message = [] 55 | if _passed: 56 | message.append("{} passed".format(_passed)) 57 | 58 | if _failed: 59 | message.append("{} failed".format(_failed)) 60 | 61 | if _xfailed: 62 | message.append("{} xfailed".format(_xfailed)) 63 | 64 | if _xpassed: 65 | message.append("{} xpassed".format(_xpassed)) 66 | 67 | message = ', '.join(message) 68 | 69 | if message: 70 | print() 71 | 72 | if not _failed: 73 | print("\x1b[1;32m%s\x1b[0m" % cpad(message, sep="=")) 74 | 75 | else: 76 | print("\x1b[1;31m%s\x1b[0m" % cpad(message, sep="=")) 77 | 78 | def run(bin_path, test_filename, expected_filename): 79 | with NamedTemporaryFile(suffix=".bmp", delete=False) as test_out: 80 | check_call([ 81 | bin_path, 82 | "--test", 83 | "--test-output", test_out.name, 84 | test_filename, 85 | ]) 86 | 87 | try: 88 | r = check_output([ 89 | "compare", "-metric", "rmse", 90 | test_out.name, 91 | expected_filename, 92 | "null:" 93 | ], stderr=subprocess.STDOUT) 94 | 95 | is_pass = r == "0 (0)" 96 | return "PASS" if is_pass else "FAIL" 97 | 98 | except Exception as ex: 99 | if path.exists(expected_filename): 100 | return "FAIL" 101 | 102 | # Any error here is a missing expected result 103 | return "XFAIL" 104 | 105 | def find_tests(dirname): 106 | test_files = [] 107 | for root, dirs, files in os.walk(dirname): 108 | for filename in files: 109 | if filename.endswith(".gb"): 110 | test_files.append(path.join(root, filename)) 111 | 112 | return test_files 113 | 114 | def main(): 115 | base_dir = path.dirname(__file__) 116 | bin_path = path.join(base_dir, "../bin/wadatsumi") 117 | 118 | pattern = sys.argv[1] if len(sys.argv) >= 2 else None 119 | 120 | tests = find_tests(path.join(base_dir, "suite/")) 121 | current_suite = None 122 | for test in tests: 123 | if pattern is not None: 124 | if not re.search(pattern, test, flags=re.I): 125 | continue 126 | 127 | suite_name, test_name = get_name(test) 128 | if current_suite != suite_name: 129 | if current_suite is not None: 130 | print() 131 | 132 | current_suite = suite_name 133 | print(cpad("%s" % suite_name)) 134 | 135 | expected_filename = test.replace("test/suite", "test/expected").replace( 136 | ".gb", ".png") 137 | 138 | is_pass = run(bin_path, test, expected_filename) 139 | print_result(test_name, is_pass) 140 | 141 | print_report() 142 | 143 | if __name__ == "__main__": 144 | main() 145 | -------------------------------------------------------------------------------- /gb/timer.as: -------------------------------------------------------------------------------- 1 | import "libc"; 2 | import "./cpu"; 3 | import "./mmu"; 4 | 5 | struct Timer { 6 | CPU: *cpu.CPU; 7 | 8 | /// Divider Register (R/W) — $FF04 9 | /// This register is incremented at rate of 16384Hz (~16779Hz on SGB). 10 | /// In CGB Double Speed Mode it is incremented twice as fast, ie. at 32768Hz. 11 | /// Writing any value to this register resets it to 00h. 12 | DIV: uint16; 13 | 14 | /// Timer Counter (R/W) — $FF05 15 | /// This timer is incremented by a clock frequency specified by the TAC 16 | /// register ($FF07). When the value overflows (gets bigger than FFh) 17 | /// then it will be reset to the value specified in TMA (FF06), and an 18 | /// interrupt will be requested. 19 | TIMA: uint8; 20 | 21 | /// Timer Modulo (R/W) — $FF06 22 | /// When the TIMA overflows, this data will be loaded. 23 | TMA: uint8; 24 | 25 | /// Timer Control (R/W) — $FF07 26 | /// Bit 2 - Timer Stop (0=Stop, 1=Start) 27 | /// Bits 1-0 - Input Clock Select 28 | /// 00: 4096 Hz (~4194 Hz SGB) 29 | /// 01: 262144 Hz (~268400 Hz SGB) 30 | /// 10: 65536 Hz (~67110 Hz SGB) 31 | /// 11: 16384 Hz (~16780 Hz SGB) 32 | TAC: uint8; 33 | } 34 | 35 | implement Timer { 36 | def New(cpu_: *cpu.CPU): Self { 37 | let t: Timer; 38 | t.CPU = cpu_; 39 | 40 | return t; 41 | } 42 | 43 | def Reset(self) { 44 | self.DIV = 0xABCC; 45 | self.TIMA = 0; 46 | self.TMA = 0; 47 | self.TAC = 0; 48 | } 49 | 50 | def Tick(self) { 51 | // If we have the TAC enable bit set, then we need to check for a 1 - 0 52 | // conversion on a specific bit. This figures out which bit. 53 | // 1 -> b03 54 | // 2 -> b05 55 | // 3 -> b07 56 | // 0 -> b09 57 | let freq = self.TAC & 0b11; 58 | let b = 0x200 if freq == 0 else (0x1 << ((freq << 1) + 1)); 59 | 60 | // DIV increments on each T-cycle; this `tick` routine is called 61 | // on each M-cycle (which is exactly 4 T-cycles). 62 | let n = 0; 63 | while n < 4 { 64 | // Remember the value of our watched bit on DIV 65 | let oldBit = self.DIV & b; 66 | 67 | // Increment DIV 68 | self.DIV += 1; 69 | 70 | if (self.TAC & 0b100) != 0 and oldBit > 0 and (self.DIV & b) == 0 { 71 | // Check for 8-bit overflow 72 | if (uint16(self.TIMA) + 1) & 0xFF == 0 { 73 | // Set the overflow sentinel 74 | self.TIMA = self.TMA; 75 | 76 | // Flag the interrupt 77 | self.CPU.IF |= 0b100; 78 | } else { 79 | // Increment TIMA 80 | self.TIMA += 1; 81 | } 82 | } 83 | 84 | n += 1; 85 | } 86 | } 87 | 88 | def Read(self, address: uint16, value: *uint8): bool { 89 | *value = if address == 0xFF04 { 90 | uint8(self.DIV >> 8); 91 | } else if address == 0xFF05 { 92 | self.TIMA; 93 | } else if address == 0xFF06 { 94 | self.TMA; 95 | } else if address == 0xFF07 { 96 | (self.TAC | 0b1111_1000); 97 | } else { 98 | return false; 99 | }; 100 | 101 | return true; 102 | } 103 | 104 | def Write(self, address: uint16, value: uint8): bool { 105 | if address == 0xFF04 { 106 | self.DIV = 0; 107 | } else if address == 0xFF05 { 108 | self.TIMA = value; 109 | } else if address == 0xFF06 { 110 | self.TMA = value; 111 | } else if address == 0xFF07 { 112 | self.TAC = (value & 0b111); 113 | } else { 114 | return false; 115 | } 116 | 117 | return true; 118 | } 119 | 120 | def AsMemoryController(self, this: *Timer): mmu.MemoryController { 121 | let mc: mmu.MemoryController; 122 | mc.Read = MCRead; 123 | mc.Write = MCWrite; 124 | mc.Data = this as *uint8; 125 | mc.Release = MCRelease; 126 | 127 | return mc; 128 | } 129 | } 130 | 131 | def MCRelease(this: *mmu.MemoryController) { 132 | // Do nothing 133 | } 134 | 135 | def MCRead(this: *mmu.MemoryController, address: uint16, value: *uint8): bool { 136 | return (this.Data as *Timer).Read(address, value); 137 | } 138 | 139 | def MCWrite(this: *mmu.MemoryController, address: uint16, value: uint8): bool { 140 | return (this.Data as *Timer).Write(address, value); 141 | } 142 | -------------------------------------------------------------------------------- /gb/joypad.as: -------------------------------------------------------------------------------- 1 | import "./cpu"; 2 | import "./mmu"; 3 | import "./machine"; 4 | import "./bits"; 5 | 6 | struct Joypad { 7 | CPU: *cpu.CPU; 8 | 9 | // Select 10 | Select_Button: bool; 11 | Select_Direction: bool; 12 | 13 | // State 14 | State_Start: bool; 15 | State_A: bool; 16 | State_B: bool; 17 | State_Select: bool; 18 | State_Up: bool; 19 | State_Down: bool; 20 | State_Left: bool; 21 | State_Right: bool; 22 | } 23 | 24 | implement Joypad { 25 | def New(cpu_: *cpu.CPU): Self { 26 | let j: Joypad; 27 | j.CPU = cpu_; 28 | 29 | return j; 30 | } 31 | 32 | def Reset(self, mode: machine.MachineMode) { 33 | // Input starts off disabled on CGB (but enabled on GB) 34 | self.Select_Button = (mode == machine.MODE_GB); 35 | self.Select_Direction = (mode == machine.MODE_GB); 36 | 37 | self.State_Start = false; 38 | self.State_A = false; 39 | self.State_B = false; 40 | self.State_Select = false; 41 | self.State_Up = false; 42 | self.State_Down = false; 43 | self.State_Left = false; 44 | self.State_Right = false; 45 | } 46 | 47 | def OnKey(self, which: uint32, isPressed: bool) { 48 | if which == 40 { 49 | // START => ENTER (US Keyboard) 50 | self.State_Start = isPressed; 51 | } else if which == 29 { 52 | // A => Z (US Keyboard) 53 | self.State_A = isPressed; 54 | } else if which == 27 { 55 | // B => X (US Keyboard) 56 | self.State_B = isPressed; 57 | } else if which == 225 { 58 | // SELECT => LEFT SHIFT (US Keyboard) 59 | self.State_Select = isPressed; 60 | } else if which == 82 { 61 | // UP => UP ARROW (US Keyboard) 62 | self.State_Up = isPressed; 63 | } else if which == 81 { 64 | // DOWN => DOWN ARROW (US Keyboard) 65 | self.State_Down = isPressed; 66 | } else if which == 80 { 67 | // LEFT => LEFT ARROW (US Keyboard) 68 | self.State_Left = isPressed; 69 | } else if which == 79 { 70 | // RIGHT => RIGHT ARROW (US Keyboard) 71 | self.State_Right = isPressed; 72 | } 73 | } 74 | 75 | def OnKeyPress(self, which: uint32) { 76 | self.OnKey(which, true); 77 | } 78 | 79 | def OnKeyRelease(self, which: uint32) { 80 | self.OnKey(which, false); 81 | } 82 | 83 | def Read(self, address: uint16, ptr: *uint8): bool { 84 | *ptr = if address == 0xFF00 { 85 | // P1 – Joypad (R/W) 86 | // Bit 7 - Not used 87 | // Bit 6 - Not used 88 | // Bit 5 - P15 Select Button Keys (0=Select) 89 | // Bit 4 - P14 Select Direction Keys (0=Select) 90 | // Bit 3 - P13 Input Down or Start (0=Pressed) (Read Only) 91 | // Bit 2 - P12 Input Up or Select (0=Pressed) (Read Only) 92 | // Bit 1 - P11 Input Left or Button B (0=Pressed) (Read Only) 93 | // Bit 0 - P10 Input Right or Button A (0=Pressed) (Read Only) 94 | ( 95 | bits.Bit(true, 7) | 96 | bits.Bit(true, 6) | 97 | bits.Bit(not self.Select_Button, 5) | 98 | bits.Bit(not self.Select_Direction, 4) | 99 | bits.Bit(not ((self.Select_Button and self.State_Start) or (self.Select_Direction and self.State_Down)), 3) | 100 | bits.Bit(not ((self.Select_Button and self.State_Select) or (self.Select_Direction and self.State_Up)), 2) | 101 | bits.Bit(not ((self.Select_Button and self.State_B) or (self.Select_Direction and self.State_Left)), 1) | 102 | bits.Bit(not ((self.Select_Button and self.State_A) or (self.Select_Direction and self.State_Right)), 0) 103 | ); 104 | } else { 105 | return false; 106 | }; 107 | 108 | return true; 109 | } 110 | 111 | def Write(self, address: uint16, value: uint8): bool { 112 | if address == 0xFF00 { 113 | self.Select_Button = not bits.Test(value, 5); 114 | self.Select_Direction = not bits.Test(value, 4); 115 | } else { 116 | return false; 117 | } 118 | 119 | return true; 120 | } 121 | 122 | def AsMemoryController(self, this: *Joypad): mmu.MemoryController { 123 | let mc: mmu.MemoryController; 124 | mc.Read = MCRead; 125 | mc.Write = MCWrite; 126 | mc.Data = this as *uint8; 127 | mc.Release = MCRelease; 128 | 129 | return mc; 130 | } 131 | } 132 | 133 | def MCRelease(this: *mmu.MemoryController) { 134 | // Do nothing 135 | } 136 | 137 | def MCRead(this: *mmu.MemoryController, address: uint16, value: *uint8): bool { 138 | return (this.Data as *Joypad).Read(address, value); 139 | } 140 | 141 | def MCWrite(this: *mmu.MemoryController, address: uint16, value: uint8): bool { 142 | return (this.Data as *Joypad).Write(address, value); 143 | } 144 | -------------------------------------------------------------------------------- /gb/machine.as: -------------------------------------------------------------------------------- 1 | import "libc"; 2 | 3 | import "./cpu"; 4 | import "./gpu"; 5 | import "./apu"; 6 | import "./mmu"; 7 | import "./cartridge"; 8 | import "./joypad"; 9 | import "./timer"; 10 | import "./linkCable"; 11 | 12 | import "./mbc1"; 13 | import "./mbc2"; 14 | import "./mbc3"; 15 | import "./mbc5"; 16 | 17 | type MachineMode = uint8; 18 | 19 | let MODE_AUTO: MachineMode = 0; 20 | let MODE_GB: MachineMode = 1; 21 | let MODE_CGB: MachineMode = 2; 22 | 23 | struct Machine { 24 | // Test Mode (for automated test runner) 25 | Test: bool; 26 | 27 | // Mode (GB / CGB) 28 | Mode: MachineMode; 29 | 30 | // Component: CPU (Central Processing) 31 | CPU: cpu.CPU; 32 | 33 | // Component: GPU (Graphics Processing) 34 | GPU: gpu.GPU; 35 | 36 | // Component: APU (Audio Processing) 37 | APU: apu.APU; 38 | 39 | // Component: MMU (Memory Management) 40 | MMU: mmu.MMU; 41 | 42 | // Component: Joypad (Controller) 43 | Joypad: joypad.Joypad; 44 | 45 | // Component: Timer (DIV and TIMA) 46 | Timer: timer.Timer; 47 | 48 | // Component: Cartridge 49 | Cartridge: cartridge.Cartridge; 50 | 51 | // Component: Link Cable (Serial Data Transfer) 52 | LinkCable: linkCable.LinkCable; 53 | } 54 | 55 | implement Machine { 56 | def New(mode: MachineMode, inTest: bool): Self { 57 | let m: Machine; 58 | m.Mode = mode; 59 | m.Test = inTest; 60 | 61 | return m; 62 | } 63 | 64 | // HACK: Taking the address of a reference (`self`) dies 65 | def Acquire(self, this: *Machine) { 66 | self.Cartridge = cartridge.Cartridge.New(); 67 | 68 | self.MMU = mmu.MMU.New(this, &self.Cartridge); 69 | 70 | self.CPU = cpu.CPU.New(this, &self.MMU); 71 | self.CPU.Acquire(); 72 | 73 | self.GPU = gpu.GPU.New(this, &self.CPU); 74 | 75 | self.APU = apu.APU.New(); 76 | self.APU.Acquire(&self.APU); 77 | 78 | self.Timer = timer.Timer.New(&self.CPU); 79 | 80 | self.Joypad = joypad.Joypad.New(&self.CPU); 81 | 82 | self.LinkCable = linkCable.LinkCable.New(); 83 | 84 | // BUG(Arrow) -- records need to be assigned before use 85 | let mc = self.CPU.AsMemoryController(&self.CPU); 86 | self.MMU.Controllers.Push(mc); 87 | 88 | mc = self.GPU.AsMemoryController(&self.GPU); 89 | self.MMU.Controllers.Push(mc); 90 | 91 | mc = self.APU.AsMemoryController(&self.APU); 92 | self.MMU.Controllers.Push(mc); 93 | 94 | mc = self.Timer.AsMemoryController(&self.Timer); 95 | self.MMU.Controllers.Push(mc); 96 | 97 | mc = self.Joypad.AsMemoryController(&self.Joypad); 98 | self.MMU.Controllers.Push(mc); 99 | 100 | mc = self.LinkCable.AsMemoryController(&self.LinkCable); 101 | self.MMU.Controllers.Push(mc); 102 | } 103 | 104 | def Release(self) { 105 | self.CPU.Release(); 106 | self.GPU.Release(); 107 | self.APU.Release(); 108 | self.MMU.Release(); 109 | self.Cartridge.Release(); 110 | } 111 | 112 | def Open(self, filename: str) { 113 | self.Cartridge.Open(filename); 114 | // self.Cartridge.Trace(); 115 | 116 | // If our mode is AUTO then figure out the right mode now 117 | if self.Mode == MODE_AUTO { 118 | if self.Cartridge.CGB == 0x80 or self.Cartridge.CGB == 0xC0 { 119 | self.Mode = MODE_CGB; 120 | } else { 121 | self.Mode = MODE_GB; 122 | } 123 | } 124 | 125 | if self.Cartridge.MC != 0 { 126 | // Push cartridge memory controller 127 | let mc: mmu.MemoryController; 128 | if self.Cartridge.MC == cartridge.MBC1 { 129 | mc = mbc1.New(&self.Cartridge); 130 | } else if self.Cartridge.MC == cartridge.MBC2 { 131 | mc = mbc2.New(&self.Cartridge); 132 | } else if self.Cartridge.MC == cartridge.MBC3 { 133 | mc = mbc3.New(&self.Cartridge); 134 | } else if self.Cartridge.MC == cartridge.MBC5 { 135 | mc = mbc5.New(&self.Cartridge); 136 | } else { 137 | libc.printf("error: unsupported cartridge type: %02X\n", 138 | self.Cartridge.Type); 139 | 140 | libc.exit(-1); 141 | } 142 | 143 | // if self.Cartridge.HasTimer { 144 | // libc.printf("error: unsupported cartridge type: %02X\n", 145 | // self.Cartridge.Type); 146 | // 147 | // libc.exit(-1); 148 | // } 149 | 150 | self.MMU.Controllers.Push(mc); 151 | } 152 | } 153 | 154 | def Reset(self) { 155 | self.Timer.Reset(); 156 | self.CPU.Reset(); 157 | self.GPU.Reset(); 158 | self.Joypad.Reset(self.Mode); 159 | self.LinkCable.Reset(); 160 | self.APU.Reset(); 161 | self.MMU.Reset(); 162 | } 163 | 164 | def Run(self) { 165 | self.CPU.Run(&self.CPU, 100); 166 | } 167 | 168 | def Tick(self) { 169 | self.Timer.Tick(); 170 | self.GPU.Tick(); 171 | self.APU.Tick(); 172 | // self.LinkCable.Tick(); 173 | } 174 | 175 | def SetOnRefresh(self, fn: (*gpu.Frame) -> ()) { 176 | self.GPU.SetOnRefresh(fn); 177 | } 178 | 179 | def OnKeyPress(self, which: uint32) { 180 | self.Joypad.OnKeyPress(which); 181 | } 182 | 183 | def OnKeyRelease(self, which: uint32) { 184 | self.Joypad.OnKeyRelease(which); 185 | } 186 | } 187 | -------------------------------------------------------------------------------- /gb/mmu.as: -------------------------------------------------------------------------------- 1 | import "libc"; 2 | import "vec"; 3 | 4 | import "./cartridge"; 5 | import "./machine"; 6 | 7 | struct MemoryController { 8 | Read: (*MemoryController, uint16, *uint8) -> bool; 9 | Write: (*MemoryController, uint16, uint8) -> bool; 10 | 11 | // Release (if `Data` is heap) 12 | Release: (*MemoryController) -> (); 13 | 14 | // 'self' instance that would otherwise be bound to those functions 15 | Data: *uint8; 16 | } 17 | 18 | struct MMU { 19 | Machine: *machine.Machine; 20 | 21 | /// Controllers (array) 22 | Controllers: vec.Vector; 23 | 24 | /// Cartridge (ROM) 25 | Cartridge: *cartridge.Cartridge; 26 | 27 | /// Work RAM (WRAM): C000-DFFF and E000-FDFF (8 KiB - 32 KiB) 28 | /// Bank 0 is always available in memory at C000-CFFF, 29 | /// Bank 1-7 can be selected into the address space at D000-DFFF. 30 | WRAM: *uint8; 31 | 32 | /// FF70 - SVBK - CGB Mode Only - WRAM Bank (0-7) 33 | SVBK: uint8; 34 | 35 | /// High RAM (HRAM): FF80-FFFE (127 B) 36 | HRAM: *uint8; 37 | 38 | /// Serial Transfer Data/Buffer (SB) 39 | // TODO: Move to a linkCable.as module 40 | SB: uint8; 41 | } 42 | 43 | implement MMU { 44 | def New(machine_: *machine.Machine, cart: *cartridge.Cartridge): Self { 45 | let m: Self; 46 | m.Machine = machine_; 47 | m.Cartridge = cart; 48 | m.WRAM = libc.malloc(0x8000); 49 | m.HRAM = libc.malloc(127); 50 | m.Controllers = vec.Vector.New(); 51 | 52 | return m; 53 | } 54 | 55 | def Release(self) { 56 | // Release memory controllers 57 | let i = 0; 58 | while i < self.Controllers.size { 59 | // BUG(arrow): records must be assigned to variables before accessed right now 60 | let mc = self.Controllers.Get(i); 61 | mc.Release(&mc); 62 | i += 1; 63 | } 64 | 65 | // Free (_)RAM 66 | libc.free(self.WRAM); 67 | libc.free(self.HRAM); 68 | } 69 | 70 | def Reset(self) { 71 | libc.memset(self.WRAM, 0, 0x8000); 72 | libc.memset(self.HRAM, 0, 127); 73 | 74 | // SVBK cannot be 0; so its first value is 1 75 | self.SVBK = 1; 76 | 77 | // Reset a ton of cross-component registers to DMG initial values 78 | self.Write(0xFF05, 0x00); 79 | self.Write(0xFF06, 0x00); 80 | self.Write(0xFF07, 0x00); 81 | self.Write(0xFF10, 0x80); 82 | self.Write(0xFF11, 0xBF); 83 | self.Write(0xFF12, 0xF3); 84 | self.Write(0xFF16, 0x3F); 85 | self.Write(0xFF17, 0x00); 86 | self.Write(0xFF1A, 0x7F); 87 | self.Write(0xFF1B, 0xFF); 88 | self.Write(0xFF1C, 0x9F); 89 | self.Write(0xFF20, 0xFF); 90 | self.Write(0xFF21, 0x00); 91 | self.Write(0xFF22, 0x00); 92 | self.Write(0xFF24, 0x77); 93 | self.Write(0xFF25, 0xF3); 94 | self.Write(0xFF26, 0xF1); 95 | self.Write(0xFF40, 0x91); 96 | self.Write(0xFF42, 0x00); 97 | self.Write(0xFF43, 0x00); 98 | self.Write(0xFF45, 0x00); 99 | self.Write(0xFF4A, 0x00); 100 | self.Write(0xFF4B, 0x00); 101 | self.Write(0xFFFF, 0x00); 102 | } 103 | 104 | def Read(self, address: uint16): uint8 { 105 | let value = 0xFF; 106 | 107 | // Check controllers 108 | let i = 0; 109 | while i < self.Controllers.size { 110 | // BUG(arrow): records must be assigned to variables before accessed right now 111 | let mc = self.Controllers.Get(i); 112 | if mc.Read(&mc, address, &value) { 113 | return value; 114 | } 115 | 116 | i += 1; 117 | } 118 | 119 | if address < 0x8000 { 120 | value = *(self.Cartridge.ROM + address); 121 | } else if address >= 0xC000 and address <= 0xFDFF { 122 | let offset = (address & 0x1FFF); 123 | if address > 0xCFFF { 124 | // Bank <1-7> 125 | offset += (uint16(self.SVBK - 1) * 0x1000); 126 | } 127 | 128 | value = *(self.WRAM + offset); 129 | } else if address == 0xFF70 and self.Machine.Mode == machine.MODE_CGB { 130 | value = (self.SVBK | 0b1111_1000); 131 | } else if address >= 0xFF80 and address <= 0xFFFE { 132 | value = *(self.HRAM + ((address & 0xFF) - 0x80)); 133 | } else { 134 | // libc.printf("warn: read from unhandled memory: %04X\n", address); 135 | } 136 | 137 | return value; 138 | } 139 | 140 | def Write(self, address: uint16, value: uint8) { 141 | // Check controllers 142 | let i = 0; 143 | while i < self.Controllers.size { 144 | // BUG(arrow): records must be assigned to variables before accessed right now 145 | let mc = self.Controllers.Get(i); 146 | if mc.Write(&mc, address, value) { 147 | return; 148 | } 149 | 150 | i += 1; 151 | } 152 | 153 | if address >= 0xC000 and address <= 0xFDFF { 154 | let offset = (address & 0x1FFF); 155 | if address > 0xCFFF { 156 | // Bank <1-7> 157 | offset += (uint16(self.SVBK - 1) * 0x1000); 158 | } 159 | 160 | *(self.WRAM + offset) = value; 161 | } else if address == 0xFF70 and self.Machine.Mode == machine.MODE_CGB { 162 | self.SVBK = value & 0b111; 163 | if self.SVBK == 0 { self.SVBK += 1; } 164 | } else if address >= 0xFF80 and address <= 0xFFFE { 165 | *(self.HRAM + ((address & 0xFF) - 0x80)) = value; 166 | } else { 167 | // libc.printf("warn: write to unhandled memory: %04X\n", address); 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /gb/index.as: -------------------------------------------------------------------------------- 1 | import "std"; 2 | import "libc"; 3 | import "time"; 4 | 5 | import "./machine"; 6 | import "./gpu"; 7 | 8 | // TODO(wadatusmi): This code is 90~% similar to ch8/index.as 9 | 10 | // TODO(arrow): Is this the best way to include C stuff? 11 | #include "SDL2/SDL.h" 12 | 13 | // BUG(arrow): This is not included right for some reason 14 | struct SDL_AudioSpec { 15 | freq: libc.c_int; 16 | format: SDL_AudioFormat; 17 | channels: uint8; 18 | silence: uint8; 19 | samples: uint16; 20 | padding: uint16; 21 | size: uint32; 22 | callback: (*uint8, *uint8, libc.c_int) -> (); 23 | userdata: *uint8; 24 | } 25 | 26 | extern "C" def SDL_OpenAudio(desired: *SDL_AudioSpec, obtained: *SDL_AudioSpec): libc.c_int; 27 | 28 | // BUG(arrow): CInclude does not get macros 29 | let SDL_INIT_VIDEO: uint32 = 0x00000020; 30 | let SDL_INIT_AUDIO: uint32 = 0x00000010; 31 | 32 | let SCALE: uint64 = 3; 33 | let WIDTH: uint64 = 160; 34 | let HEIGHT: uint64 = 144; 35 | 36 | let _window: *SDL_Window; 37 | let _renderer: *SDL_Renderer; 38 | let _tex: *SDL_Texture; 39 | let _evt: *uint8; 40 | let _running = true; 41 | 42 | def acquire() { 43 | // BUG: CInclude does not support unions (yet) 44 | _evt = libc.malloc(1000); 45 | 46 | // Initialize SDL 47 | SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO); 48 | 49 | // Create Window 50 | // TODO: Allow arbitrary scaling 51 | _window = SDL_CreateWindow( 52 | "Wadatsumi", 0x1FFF0000, 0x1FFF0000, int32(WIDTH * SCALE), int32(HEIGHT * SCALE), SDL_WINDOW_SHOWN); 53 | 54 | // Create _renderer 55 | _renderer = SDL_CreateRenderer(_window, -1, 56 | SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); 57 | 58 | // White initial screen 59 | SDL_SetRenderDrawColor(_renderer, 0, 0, 0, 255); 60 | SDL_RenderClear(_renderer); 61 | SDL_RenderPresent(_renderer); 62 | 63 | // Create texture 64 | _tex = SDL_CreateTexture(_renderer, 65 | SDL_PIXELFORMAT_ARGB8888, 66 | // BUG: CInclude has this mismatched for some reason 67 | int32(SDL_TEXTUREACCESS_STREAMING), 68 | int32(WIDTH), int32(HEIGHT)); 69 | 70 | // Initial clear (screen) 71 | SDL_SetRenderDrawColor(_renderer, 255, 255, 255, 255); 72 | SDL_RenderClear(_renderer); 73 | SDL_RenderPresent(_renderer); 74 | 75 | // Open Audio 76 | let asp: SDL_AudioSpec; 77 | libc.memset(&asp as *uint8, 0, std.size_of()); 78 | asp.freq = 96000; 79 | asp.format = 0x8010; // Signed 16-bit samples (LE) 80 | asp.channels = 2; 81 | asp.samples = 1024; 82 | 83 | SDL_OpenAudio(&asp, 0 as *SDL_AudioSpec); 84 | SDL_PauseAudio(0); 85 | } 86 | 87 | def release() { 88 | libc.free(_evt); 89 | 90 | SDL_PauseAudio(1); 91 | SDL_CloseAudio(); 92 | 93 | SDL_DestroyRenderer(_renderer); 94 | SDL_DestroyWindow(_window); 95 | SDL_DestroyTexture(_tex); 96 | 97 | SDL_Quit(); 98 | } 99 | 100 | def render(frame: *gpu.Frame) { 101 | SDL_SetRenderDrawColor(_renderer, 255, 255, 255, 255); 102 | SDL_RenderClear(_renderer); 103 | 104 | SDL_UpdateTexture(_tex, 0 as *SDL_Rect, frame.Data, int32(frame.Pitch)); 105 | SDL_RenderCopy(_renderer, _tex, 0 as *SDL_Rect, 0 as *SDL_Rect); 106 | 107 | SDL_RenderPresent(_renderer); 108 | } 109 | 110 | def main(argc: int32, argv: *str) { 111 | 112 | // Parse command line arguments (a bit) 113 | // TODO: Build a real arg parse for arrow 114 | 115 | let is_test: bool = false; 116 | let test_output_filename: str; 117 | let input_filename: str; 118 | let selected_mode = machine.MODE_AUTO; 119 | 120 | let i = 1; 121 | while i < argc { 122 | let arg = *(argv + i); 123 | 124 | if *(arg + 0) == 0x2D and *(arg + 1) == 0x2D { 125 | // Option 126 | if libc.strcmp("test", arg + 2) == 0 { 127 | // --test 128 | // Mark test mode 129 | is_test = true; 130 | } else if libc.strcmp("test-output", arg + 2) == 0 { 131 | // --test-output 132 | i += 1; 133 | test_output_filename = *(argv + i); 134 | } else if libc.strcmp("mode", arg + 2) == 0 { 135 | // --mode 136 | i += 1; 137 | let mode_str = *(argv + i); 138 | if libc.strcmp("gb", mode_str) == 0 { 139 | selected_mode = machine.MODE_GB; 140 | } else if libc.strcmp("cgb", mode_str) == 0 { 141 | selected_mode = machine.MODE_CGB; 142 | } 143 | } 144 | 145 | } else { 146 | // Filename (and we're out) 147 | input_filename = arg; 148 | break; 149 | } 150 | 151 | i += 1; 152 | } 153 | 154 | 155 | acquire(); 156 | 157 | // let s = shell.Shell.New(); 158 | let m = machine.Machine.New(selected_mode, is_test); 159 | 160 | // HACK: Taking the address of a reference (`self`) dies 161 | m.Acquire(&m); 162 | 163 | // Specify the external rendering method 164 | // m.SetOnRefresh(s.Render); 165 | // m.SetOnRefresh(render, &s); 166 | m.SetOnRefresh(render); 167 | 168 | m.Open(input_filename); 169 | m.Reset(); 170 | 171 | let n0 = time.Monotonic(); 172 | let inStop = false; 173 | 174 | while _running { 175 | // Run a chunk of instructions 176 | m.Run(); 177 | 178 | // Check for window events 179 | // s.Run(); 180 | if SDL_PollEvent(_evt as *SDL_Event) != 0 { 181 | let kind = *(_evt as *uint32); 182 | if kind == 0x100 { 183 | // Quit 184 | _running = false; 185 | } else if kind == 0x300 or kind == 0x301 { 186 | // Key Press OR Release 187 | let which = (*(_evt as *SDL_KeyboardEvent)).keysym.scancode; 188 | 189 | // TODO: Input map / configuration abstraction of some kind 190 | 191 | if (kind == 0x300) { 192 | m.OnKeyPress(uint32(which)); 193 | } else { 194 | m.OnKeyRelease(uint32(which)); 195 | } 196 | } 197 | } 198 | 199 | if is_test { 200 | if m.CPU.STOP == 1 and not inStop { 201 | inStop = true; 202 | n0 = time.Monotonic(); 203 | } 204 | 205 | // TODO: extract this out nicer 206 | let n1 = time.Monotonic(); 207 | let elapsed = n1.Sub(n0); 208 | if elapsed.Seconds() > 20 or (inStop and elapsed.Seconds() > 1) { 209 | // Create surface from GPU frame buffer 210 | let surface = SDL_CreateRGBSurfaceFrom( 211 | m.GPU.FrameBuffer as *uint8, 212 | int32(WIDTH), 213 | int32(HEIGHT), 32, 214 | int32(WIDTH * 4), 215 | 0x00ff0000, 0x0000ff00, 0x000000ff, 0xff000000); 216 | 217 | let file = SDL_RWFromFile(test_output_filename, "wb"); 218 | SDL_SaveBMP_RW(surface, file, 1); 219 | 220 | break; 221 | } 222 | } 223 | } 224 | 225 | m.Release(); 226 | // s.Release(); 227 | release(); 228 | } 229 | -------------------------------------------------------------------------------- /gb/channelWave.as: -------------------------------------------------------------------------------- 1 | import "libc"; 2 | import "./apu"; 3 | import "./bits"; 4 | 5 | // Channel 3 — Wave 6 | struct ChannelWave { 7 | APU: *apu.APU; 8 | 9 | // Internal On/Off for this channel 10 | Enable: bool; 11 | 12 | // "DAC" Enable -- essentially an enable flag that is touchable 13 | DACEnable: bool; 14 | 15 | // Position in WAVE 16 | Position: uint8; 17 | 18 | // Sample Buffer 19 | Buffer: uint8; 20 | 21 | // Length — Same as channel 1/2 except that this is 8-bits and the 22 | // square channels have 6-bits 23 | Length: uint8; 24 | LengthEnable: bool; 25 | 26 | // Volume — 2-bit code that indicates volume data 27 | // 00 — silence 28 | // 01 — (100%) data 29 | // 10 — ( 50%) data >> 1 30 | // 11 — ( 25%) data >> 2 31 | Volume: uint8; 32 | 33 | // Timer — Same as channel 1/2 except this is set to (2048-)*2 34 | Timer: uint16; 35 | 36 | // Frequency — Same as channel 1/2 37 | Frequency: uint16; 38 | 39 | // Wave Pattern RAM (16 B) 40 | // Waveform storage for arbitrary sound data 41 | // Holds 32 4-bit samples that are played back upper 4 bits first 42 | RAM: *uint8; 43 | } 44 | 45 | implement ChannelWave { 46 | def New(apu_: *apu.APU): Self { 47 | let ch: ChannelWave; 48 | ch.APU = apu_; 49 | 50 | ch.RAM = libc.malloc(16); 51 | 52 | // On initial power, the WAVE ram has a particular pattern 53 | // according to model 54 | // Reset does NOT re-set this 55 | *(ch.RAM + 0x0) = 0x84; 56 | *(ch.RAM + 0x1) = 0x40; 57 | *(ch.RAM + 0x2) = 0x43; 58 | *(ch.RAM + 0x3) = 0xAA; 59 | *(ch.RAM + 0x4) = 0x2D; 60 | *(ch.RAM + 0x5) = 0x78; 61 | *(ch.RAM + 0x6) = 0x92; 62 | *(ch.RAM + 0x7) = 0x3C; 63 | *(ch.RAM + 0x8) = 0x60; 64 | *(ch.RAM + 0x9) = 0x59; 65 | *(ch.RAM + 0xA) = 0x59; 66 | *(ch.RAM + 0xB) = 0xB0; 67 | *(ch.RAM + 0xC) = 0x34; 68 | *(ch.RAM + 0xD) = 0xB8; 69 | *(ch.RAM + 0xE) = 0x2E; 70 | *(ch.RAM + 0xF) = 0xDA; 71 | 72 | return ch; 73 | } 74 | 75 | def Reset(self) { 76 | self.Position = 0; 77 | self.Enable = false; 78 | self.DACEnable = false; 79 | self.Length = 0; 80 | self.LengthEnable = false; 81 | self.Volume = 0; 82 | self.Timer = 0; 83 | self.Frequency = 0; 84 | self.Buffer = 0; 85 | } 86 | 87 | def Release(self) { 88 | libc.free(self.RAM); 89 | } 90 | 91 | def Trigger(self) { 92 | // Channel is enabled. 93 | self.Enable = true; 94 | 95 | // If length counter is zero, it is set to 64 (256 for wave channel). 96 | if self.Length == 0 { self.Length = 256; } 97 | 98 | // Frequency timer is reloaded with period. 99 | self.Timer = (2048 - self.Frequency) * 2; 100 | 101 | // Wave channel's position is set to 0 but sample buffer is NOT refilled. 102 | self.Position = 0; 103 | } 104 | 105 | def Tick(self) { 106 | if self.Timer > 0 { self.Timer -= 1; } 107 | if self.Timer == 0 { 108 | // Increment position in the duty pattern 109 | self.Position += 1; 110 | if self.Position == 32 { self.Position = 0; } 111 | 112 | // Fill the sample buffer 113 | // / 2 = wave index 114 | self.Buffer = *(self.RAM + (self.Position / 2)); 115 | // % 2 = 0 (hi) or 1 (lo) 116 | self.Buffer >>= (1 - (self.Position % 2)) * 4; 117 | self.Buffer &= 0x0F; 118 | 119 | // Reload timer 120 | self.Timer = (2048 - self.Frequency) * 2; 121 | } 122 | } 123 | 124 | def TickLength(self) { 125 | if self.Length > 0 { self.Length -= 1; } 126 | if self.Length == 0 and self.LengthEnable { 127 | self.Enable = false; 128 | } 129 | } 130 | 131 | def Sample(self): int16 { 132 | if not (self.Enable and self.DACEnable) { return 0; } 133 | 134 | // The DAC receives the current value from the upper/lower nibble of the 135 | // sample buffer, shifted right by the volume control. 136 | return int16(self.Buffer >> (self.Volume - 1 if self.Volume > 0 else 4)); 137 | } 138 | 139 | // FF1A - NR30 - Channel 3 Sound on/off (R/W) 140 | // Bit 7 - Sound Channel 3 Off (0=Stop, 1=Playback) (Read/Write) 141 | 142 | // FF1B - NR31 - Channel 3 Sound Length (W) 143 | // Bit 7-0 - Sound length (t1: 0 - 255) 144 | 145 | // FF1C - NR32 - Channel 3 Select output level (R/W) 146 | // Bit 6-5 - Select output level (Read/Write) 147 | 148 | // FF1D - NR33 - Channel 3 Frequency's lower data (W) 149 | 150 | // FF1E - NR34 - Channel 3 Frequency's higher data (R/W) 151 | // Bit 7 - Initial (1=Restart Sound) (Write Only) 152 | // Bit 6 - Counter/consecutive selection (Read/Write) 153 | // (1=Stop output when length in NR31 expires) 154 | // Bit 2-0 - Frequency's higher 3 bits (x) (Write Only) 155 | 156 | // FF30-FF3F - Wave Pattern RAM (16 bytes) 157 | // Contents - Waveform storage for arbitrary sound data 158 | 159 | def Read(self, address: uint16, ptr: *uint8): bool { 160 | // WAVE RAM is not affected by master on/off 161 | if address >= 0xFF30 and address <= 0xFF3F { 162 | *ptr = *(self.RAM + (address - 0xFF30)); 163 | 164 | return true; 165 | } 166 | 167 | // Check if we are at the right channel 168 | if (address < 0xFF1A or address > 0xFF1E) { return false; } 169 | 170 | *ptr = if address == 0xFF1A { 171 | (bits.Bit(self.DACEnable, 7) | 0b0111_1111); 172 | } else if address == 0xFF1B { 173 | // FIXME: I have no idea if this supposed to be readable or not 174 | uint8((int16(self.Length) - 256) * -1); 175 | } else if address == 0xFF1C { 176 | ((self.Volume << 5) | 0b1001_1111); 177 | } else if address == 0xFF1E { 178 | (bits.Bit(self.LengthEnable, 6) | 0b1011_1111); 179 | } else { 180 | return false; 181 | }; 182 | 183 | return true; 184 | } 185 | 186 | def Write(self, address: uint16, value: uint8): bool { 187 | // WAVE RAM is not affected by master on/off 188 | if address >= 0xFF30 and address <= 0xFF3F { 189 | *(self.RAM + (address - 0xFF30)) = value; 190 | 191 | return true; 192 | } 193 | 194 | // Check if we are at the right channel 195 | if (address < 0xFF1A or address > 0xFF1E) { return false; } 196 | 197 | // If master is disabled; ignore 198 | if not self.APU.Enable { return true; } 199 | 200 | if address == 0xFF1A { 201 | self.DACEnable = bits.Test(value, 7); 202 | } else if address == 0xFF1B { 203 | self.Length = 256 - value; 204 | } else if address == 0xFF1C { 205 | self.Volume = ((value & 0b0110_0000) >> 5); 206 | } else if address == 0xFF1D { 207 | self.Frequency = (self.Frequency & ~0xFF) | uint16(value); 208 | } else if address == 0xFF1E { 209 | self.Frequency = (self.Frequency & ~0xF00) | (uint16(value & 0b111) << 8); 210 | self.LengthEnable = bits.Test(value, 6); 211 | 212 | if bits.Test(value, 7) { 213 | self.Trigger(); 214 | } 215 | } else { 216 | return false; 217 | } 218 | 219 | return true; 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /gb/channelNoise.as: -------------------------------------------------------------------------------- 1 | import "libc"; 2 | import "./apu"; 3 | import "./bits"; 4 | 5 | // Channel 4 — Noise 6 | struct ChannelNoise { 7 | APU: *apu.APU; 8 | 9 | // On/Off 10 | Enable: bool; 11 | 12 | // Linear Feedback Shift Register — 15-bit 13 | LFSR: uint16; 14 | 15 | // Volume * — Same as channel 1/2 16 | Volume: uint8; 17 | VolumeInitial: uint8; 18 | VolumeEnvelopeTimer: uint8; 19 | VolumeEnvelopePeriod: uint8; 20 | VolumeEnvelopeDirection: bool; 21 | 22 | // Length — Same as channel 1/2 23 | Length: uint8; 24 | LengthEnable: bool; 25 | 26 | // Timer — Same operation as channel 1/2 but set to: 27 | // << 28 | Timer: uint16; 29 | 30 | // Shift Clock Frequency — 4 bits 31 | Frequency: uint8; 32 | 33 | // Counter Step/Width 34 | // false=15 bits, true=7 bits 35 | CounterWidth: bool; 36 | 37 | // Divisor Index 38 | DivisorIndex: uint8; 39 | } 40 | 41 | implement ChannelNoise { 42 | def New(apu_: *apu.APU): Self { 43 | let ch: ChannelNoise; 44 | ch.APU = apu_; 45 | 46 | return ch; 47 | } 48 | 49 | def Reset(self) { 50 | self.Enable = false; 51 | self.LFSR = 0; 52 | self.Timer = 0; 53 | self.Frequency = 0; 54 | self.Volume = 0; 55 | self.VolumeInitial = 0; 56 | self.VolumeEnvelopeTimer = 0; 57 | self.VolumeEnvelopePeriod = 0; 58 | self.VolumeEnvelopeDirection = false; 59 | self.Length = 0; 60 | self.LengthEnable = false; 61 | self.CounterWidth = false; 62 | self.DivisorIndex = 0; 63 | } 64 | 65 | def Trigger(self) { 66 | // Channel is enabled (see length counter). 67 | self.Enable = true; 68 | 69 | // If length counter is zero, it is set to 64 (256 for wave channel). 70 | if self.Length == 0 { self.Length = 64; } 71 | 72 | // Frequency timer is reloaded with period. 73 | self.Timer = (getDivisor(self.DivisorIndex) << self.Frequency); 74 | 75 | // Volume envelope timer is reloaded with period. 76 | self.VolumeEnvelopeTimer = self.VolumeEnvelopePeriod; 77 | 78 | // Channel volume is reloaded from NRx2. 79 | self.Volume = self.VolumeInitial; 80 | 81 | // Noise channel's LFSR bits are all set to 1. 82 | self.LFSR = 0x7FFF; 83 | } 84 | 85 | def Tick(self) { 86 | if self.Timer > 0 { self.Timer -= 1; } 87 | if self.Timer == 0 { 88 | // When clocked by the frequency timer, the low two bits (0 and 1) 89 | // are XORed 90 | let b = bits.Test(uint8(self.LFSR), 0) ^ bits.Test(uint8(self.LFSR), 1); 91 | // All bits are shifted right by one 92 | self.LFSR >>= 1; 93 | // And the result of the XOR is put into the now-empty high bit 94 | if b { self.LFSR |= 0x4000; } 95 | // If width mode is 1 (NR43), the XOR result is ALSO put into 96 | // bit 6 AFTER the shift, resulting in a 7-bit LFSR. 97 | if self.CounterWidth { 98 | self.LFSR = (self.LFSR & ~0x40) | uint16(bits.Bit(b, 6)); 99 | } 100 | 101 | // Reload timer 102 | self.Timer = (getDivisor(self.DivisorIndex) << self.Frequency); 103 | } 104 | } 105 | 106 | def TickLength(self) { 107 | if self.Length > 0 { self.Length -= 1; } 108 | if self.Length == 0 and self.LengthEnable { 109 | self.Enable = false; 110 | } 111 | } 112 | 113 | def TickVolumeEnvelope(self) { 114 | if self.VolumeEnvelopePeriod > 0 { 115 | if self.VolumeEnvelopeTimer > 0 { self.VolumeEnvelopeTimer -= 1; } 116 | if self.VolumeEnvelopeTimer == 0 { 117 | if self.VolumeEnvelopeDirection { 118 | if self.Volume < 0xF { 119 | self.Volume += 1; 120 | } 121 | } else { 122 | if self.Volume > 0 { 123 | self.Volume -= 1; 124 | } 125 | } 126 | 127 | self.VolumeEnvelopeTimer = self.VolumeEnvelopePeriod; 128 | } 129 | } 130 | } 131 | 132 | def Sample(self): int16 { 133 | // The waveform output is bit 0 of the LFSR, INVERTED 134 | let bit = not bits.Test(uint8(self.LFSR), 0); 135 | 136 | return int16(self.Volume if bit else 0); 137 | } 138 | 139 | // FF20 - NR41 - Channel 4 Sound Length (R) 140 | // Bit 5-0 - Sound length data (t1: 0-63) 141 | 142 | // FF21 - NR42 - Channel 4 Volume Envelope (R/W) 143 | // Bit 7-4 - Initial Volume of envelope (0-0Fh) (0=No Sound) 144 | // Bit 3 - Envelope Direction (0=Decrease, 1=Increase) 145 | // Bit 2-0 - Number of envelope sweep (n: 0-7) 146 | // (If zero, stop envelope operation.) 147 | 148 | // FF22 - NR43 - Channel 4 Polynomial Counter (R/W) 149 | // Bit 7-4 - Shift Clock Frequency (s) 150 | // Bit 3 - Counter Step/Width (0=15 bits, 1=7 bits) 151 | // Bit 2-0 - Dividing Ratio of Frequencies (r) 152 | 153 | // FF23 - NR44 - Channel 4 Counter/consecutive; Inital (R/W) 154 | // Bit 7 - Initial (1=Restart Sound) (Write Only) 155 | // Bit 6 - Counter/consecutive selection (Read/Write) 156 | // (1=Stop output when length in NR41 expires) 157 | 158 | def Read(self, address: uint16, ptr: *uint8): bool { 159 | // Check if we are at the right channel 160 | if (address < 0xFF20 or address > 0xFF23) { return false; } 161 | 162 | *ptr = if address == 0xFF21 { 163 | ( 164 | (self.VolumeInitial << 4) | 165 | bits.Bit(self.VolumeEnvelopeDirection, 3) | 166 | self.VolumeEnvelopePeriod 167 | ); 168 | } else if address == 0xFF22 { 169 | ( 170 | (self.Frequency << 4) | 171 | bits.Bit(self.CounterWidth, 3) | 172 | self.DivisorIndex 173 | ); 174 | } else if address == 0xFF23 { 175 | (bits.Bit(self.LengthEnable, 6) | 0xBF); 176 | } else { 177 | return false; 178 | }; 179 | 180 | return true; 181 | } 182 | 183 | def Write(self, address: uint16, value: uint8): bool { 184 | // Check if we are at the right channel 185 | if (address < 0xFF20 or address > 0xFF23) { return false; } 186 | 187 | // If master is disabled; ignore 188 | if not self.APU.Enable { return true; } 189 | 190 | if address == 0xFF20 { 191 | self.Length = 64 - (value & 0b1_1111); 192 | } else if address == 0xFF21 { 193 | self.VolumeInitial = (value & 0b1111_0000) >> 4; 194 | self.Volume = self.VolumeInitial; 195 | self.VolumeEnvelopeDirection = bits.Test(value, 3); 196 | self.VolumeEnvelopePeriod = (value & 0b111); 197 | self.VolumeEnvelopeTimer = self.VolumeEnvelopePeriod; 198 | } else if address == 0xFF22 { 199 | self.Frequency = (value >> 4); 200 | self.Timer = (getDivisor(self.DivisorIndex) << self.Frequency); 201 | self.CounterWidth = bits.Test(value, 3); 202 | self.DivisorIndex = (value & 0b111); 203 | } else if address == 0xFF23 { 204 | self.LengthEnable = bits.Test(value, 6); 205 | 206 | if bits.Test(value, 7) { 207 | self.Trigger(); 208 | } 209 | } else { 210 | return false; 211 | }; 212 | 213 | return true; 214 | } 215 | } 216 | 217 | def getDivisor(index: uint8): uint16 { 218 | return 219 | if index == 1 { 16; } 220 | else if index == 2 { 32; } 221 | else if index == 3 { 48; } 222 | else if index == 4 { 64; } 223 | else if index == 5 { 80; } 224 | else if index == 6 { 96; } 225 | else if index == 7 { 112; } 226 | else { 8; }; 227 | } 228 | -------------------------------------------------------------------------------- /ch8/machine.as: -------------------------------------------------------------------------------- 1 | import "std"; 2 | import "libc"; 3 | 4 | import "./cpu"; 5 | 6 | struct Context { 7 | // 16 General Registers 8 | // V: [0x10]uint8; 9 | V: *uint8; 10 | 11 | // Index Register (16-bit) 12 | I: uint16; 13 | 14 | // Stack Pointer 15 | SP: uint8; 16 | 17 | // Program Counter (12-bit) 18 | PC: uint16; 19 | 20 | // Timers — Automatically decremented at 60 Hz 21 | ST: uint8; 22 | DT: uint8; 23 | 24 | // Stack (16 16-bit Slots) — SP is used to index into it 25 | // stack: [0x10]uint16; 26 | stack: *uint16; 27 | 28 | // The CHIP-8 has 3.5 KiB of accessible RAM (no memory protections) 29 | // THe program ROM is loaded directly at $0 30 | ram: *uint8; 31 | 32 | // FONT sprites 33 | // font: [0x10][5]uint8; 34 | font: *uint8; 35 | 36 | // Screen Size 37 | width: uint8; 38 | height: uint8; 39 | 40 | // Frame Buffer 41 | framebuffer: *uint8; 42 | 43 | // Input State 44 | // Input to the Chip-8 is a hex keyboard so the input state is 16-bools 45 | // True is pressed; false is released 46 | input: *bool; 47 | 48 | // Refresh Handler 49 | on_refresh: (*Context) -> (); 50 | } 51 | 52 | def set_on_refresh(c: *Context, fn: (*Context) -> ()) { 53 | c.on_refresh = fn; 54 | } 55 | 56 | def refresh(c: *Context) { 57 | c.on_refresh(c); 58 | } 59 | 60 | def new_context(): Context { 61 | let c: Context; 62 | libc.memset(&c as *uint8, 0, std.size_of()); 63 | 64 | c.V = libc.malloc(0x10); 65 | c.input = libc.malloc(0x10) as *bool; 66 | c.stack = libc.malloc(0x10 * 2) as *uint16; 67 | c.ram = libc.malloc(0x1000); 68 | c.framebuffer = libc.malloc(64 * 32); 69 | c.font = libc.malloc(16 * 5); 70 | 71 | libc.memset(c.framebuffer, 0, 64 * 32); 72 | libc.memset(c.input as *uint8, 0, 0x10); 73 | 74 | // HACK: This should go elsewhere 75 | c.width = 64; 76 | c.height = 32; 77 | 78 | // Initialize FONT sprites 79 | // c.font[0x0] = [5]uint8{0xF0, 0x90, 0x90, 0x90, 0xF0}; 80 | 81 | *(c.font + 0x00) = 0xF0; // 0 82 | *(c.font + 0x01) = 0x90; 83 | *(c.font + 0x02) = 0x90; 84 | *(c.font + 0x03) = 0x90; 85 | *(c.font + 0x04) = 0xF0; 86 | 87 | *(c.font + 0x05) = 0x20; // 1 88 | *(c.font + 0x06) = 0x60; 89 | *(c.font + 0x07) = 0x20; 90 | *(c.font + 0x08) = 0x20; 91 | *(c.font + 0x09) = 0x70; 92 | 93 | *(c.font + 0x0A) = 0xF0; // 2 94 | *(c.font + 0x0B) = 0x10; 95 | *(c.font + 0x0C) = 0xF0; 96 | *(c.font + 0x0D) = 0x80; 97 | *(c.font + 0x0E) = 0xF0; 98 | 99 | *(c.font + 0x0F) = 0xF0; // 3 100 | *(c.font + 0x10) = 0x10; 101 | *(c.font + 0x11) = 0xF0; 102 | *(c.font + 0x12) = 0x10; 103 | *(c.font + 0x13) = 0xF0; 104 | 105 | *(c.font + 0x14) = 0x90; // 4 106 | *(c.font + 0x15) = 0x90; 107 | *(c.font + 0x16) = 0xF0; 108 | *(c.font + 0x17) = 0x10; 109 | *(c.font + 0x18) = 0x10; 110 | 111 | *(c.font + 0x19) = 0xF0; // 5 112 | *(c.font + 0x1A) = 0x80; 113 | *(c.font + 0x1B) = 0xF0; 114 | *(c.font + 0x1C) = 0x10; 115 | *(c.font + 0x1D) = 0xF0; 116 | 117 | *(c.font + 0x1E) = 0xF0; // 6 118 | *(c.font + 0x1F) = 0x80; 119 | *(c.font + 0x20) = 0xF0; 120 | *(c.font + 0x21) = 0x90; 121 | *(c.font + 0x22) = 0xF0; 122 | 123 | *(c.font + 0x23) = 0xF0; // 7 124 | *(c.font + 0x24) = 0x10; 125 | *(c.font + 0x25) = 0x20; 126 | *(c.font + 0x26) = 0x40; 127 | *(c.font + 0x27) = 0x40; 128 | 129 | *(c.font + 0x28) = 0xF0; // 8 130 | *(c.font + 0x29) = 0x90; 131 | *(c.font + 0x2A) = 0xF0; 132 | *(c.font + 0x2B) = 0x90; 133 | *(c.font + 0x2C) = 0xF0; 134 | 135 | *(c.font + 0x2D) = 0xF0; // 9 136 | *(c.font + 0x2E) = 0x90; 137 | *(c.font + 0x2F) = 0xF0; 138 | *(c.font + 0x30) = 0x10; 139 | *(c.font + 0x31) = 0xF0; 140 | 141 | *(c.font + 0x32) = 0xF0; // A 142 | *(c.font + 0x33) = 0x90; 143 | *(c.font + 0x34) = 0xF0; 144 | *(c.font + 0x35) = 0x90; 145 | *(c.font + 0x36) = 0x90; 146 | 147 | *(c.font + 0x37) = 0xE0; // B 148 | *(c.font + 0x38) = 0x90; 149 | *(c.font + 0x39) = 0xE0; 150 | *(c.font + 0x3A) = 0x90; 151 | *(c.font + 0x3B) = 0xE0; 152 | 153 | *(c.font + 0x3C) = 0xF0; // C 154 | *(c.font + 0x3D) = 0x80; 155 | *(c.font + 0x3E) = 0x80; 156 | *(c.font + 0x3F) = 0x80; 157 | *(c.font + 0x40) = 0xF0; 158 | 159 | *(c.font + 0x41) = 0xE0; // D 160 | *(c.font + 0x42) = 0x90; 161 | *(c.font + 0x43) = 0x90; 162 | *(c.font + 0x44) = 0x90; 163 | *(c.font + 0x45) = 0xE0; 164 | 165 | *(c.font + 0x46) = 0xF0; // E 166 | *(c.font + 0x47) = 0x80; 167 | *(c.font + 0x48) = 0xF0; 168 | *(c.font + 0x49) = 0x80; 169 | *(c.font + 0x4A) = 0xF0; 170 | 171 | *(c.font + 0x4B) = 0xF0; // F 172 | *(c.font + 0x4C) = 0x80; 173 | *(c.font + 0x4D) = 0xF0; 174 | *(c.font + 0x4E) = 0x80; 175 | *(c.font + 0x4F) = 0x80; 176 | 177 | return c; 178 | } 179 | 180 | def dispose_context(c: *Context) { 181 | libc.free(c.V); 182 | libc.free(c.stack as *uint8); 183 | libc.free(c.ram); 184 | libc.free(c.font); 185 | libc.free(c.framebuffer); 186 | libc.free(c.input as *uint8); 187 | } 188 | 189 | // Timer: Tick 190 | def timer_tick(c: *Context) { 191 | // Decrement delay timer 192 | if c.DT > 0 { c.DT -= 1; } 193 | 194 | // Decrement sound timer 195 | // TODO: Start/stop sound 196 | if c.ST > 0 { c.ST -= 1; } 197 | } 198 | 199 | 200 | // Run 201 | 202 | // Change to adjust execution rate 203 | let HZ = 540; 204 | // cycles should happen each minute 205 | let INSTR_RATE = ((1 / HZ) * 1000); 206 | let TIMER_RATE = HZ / 60; 207 | 208 | let mutable _clk: uint64 = 0; 209 | let mutable _elapsed: float64 = 0.0; 210 | let mutable _counter = 0; 211 | 212 | def run(c: *Context) { 213 | // Execute 1 Cycle — CPU 214 | if _elapsed >= INSTR_RATE { 215 | cpu.execute(c); 216 | _elapsed -= INSTR_RATE; 217 | _counter += 1; 218 | } 219 | 220 | // Tick machine at 60hz 221 | if _counter >= TIMER_RATE { 222 | timer_tick(c); 223 | _counter = 0; 224 | } 225 | 226 | // Increment elapsed µs count 227 | // FIXME: Platform-indepdent high-res timer (not possible 228 | // so we need to make a module in arrow's std) 229 | if _clk != 0 { _elapsed += float64(libc.clock() - _clk) / 1_000; } 230 | _clk = libc.clock(); 231 | } 232 | 233 | // Input Press/Release 234 | 235 | // {95} NUMPAD 7 -> 1 236 | // {96} NUMPAD 8 -> 2 237 | // {97} NUMPAD 9 -> 3 238 | // {92} NUMPAD 4 -> 4 239 | // {93} NUMPAD 5 -> 5 240 | // {94} NUMPAD 6 -> 6 241 | // {89} NUMPAD 1 -> 7 242 | // {90} NUMPAD 2 -> 8 243 | // {91} NUMPAD 3 -> 9 244 | // {98} NUMPAD 0 -> 0 245 | // {99} NUMPAD . -> A 246 | // {88} NUMPAD ENTER -> B 247 | // {87} NUMPAD + -> C 248 | // {86} NUMPAD - -> D 249 | // {85} NUMPAD * -> E 250 | // {84} NUMPAD / -> F 251 | 252 | def convert_scancode_to_key(which: uint32): int32 { 253 | return ( 254 | if which == 95 { 1; } 255 | else if which == 96 { 2; } 256 | else if which == 97 { 3; } 257 | else if which == 92 { 4; } 258 | else if which == 93 { 5; } 259 | else if which == 94 { 6; } 260 | else if which == 89 { 7; } 261 | else if which == 90 { 8; } 262 | else if which == 91 { 9; } 263 | else if which == 98 { 0; } 264 | else if which == 99 { 0xA; } 265 | else if which == 88 { 0xB; } 266 | else if which == 87 { 0xC; } 267 | else if which == 86 { 0xD; } 268 | else if which == 85 { 0xE; } 269 | else if which == 84 { 0xF; } 270 | else { -1; } 271 | ); 272 | } 273 | 274 | def input_press(c: *Context, which: uint32) { 275 | let key = convert_scancode_to_key(which); 276 | if key < 0 { return; } 277 | 278 | *(c.input + key) = true; 279 | } 280 | 281 | def input_release(c: *Context, which: uint32) { 282 | let key = convert_scancode_to_key(which); 283 | if key < 0 { return; } 284 | 285 | *(c.input + key) = false; 286 | } 287 | -------------------------------------------------------------------------------- /gb/om.as: -------------------------------------------------------------------------------- 1 | import "./cpu"; 2 | import "libc"; 3 | 4 | // Flags 5 | // ============================================================================= 6 | 7 | type Flag = uint8; 8 | 9 | let FLAG_Z: Flag = 0b1000_0000; 10 | let FLAG_N: Flag = 0b0100_0000; 11 | let FLAG_H: Flag = 0b0010_0000; 12 | let FLAG_C: Flag = 0b0001_0000; 13 | 14 | def flag_set(c: *cpu.CPU, flag: Flag, value: bool) { 15 | if value { *(c.F) |= uint8(flag); } 16 | else { *(c.F) &= ~uint8(flag); } 17 | } 18 | 19 | def flag_get(c: *cpu.CPU, flag: Flag): bool { 20 | return *(c.F) & uint8(flag) != 0; 21 | } 22 | 23 | def flag_geti(c: *cpu.CPU, flag: Flag): uint8 { 24 | return if flag_get(c, flag) { 1; } else { 0; }; 25 | } 26 | 27 | // Microcode 28 | // ============================================================================= 29 | 30 | // Read Next 8-bit 31 | def readNext8(c: *cpu.CPU): uint8 { 32 | let value = read8(c, c.PC); 33 | c.PC += 1; 34 | 35 | return value; 36 | } 37 | 38 | // Read 8-bit 39 | def read8(c: *cpu.CPU, address: uint16): uint8 { 40 | let value = 0xFF; 41 | 42 | c.Tick(); 43 | 44 | // IF during OAM DMA; only HIRAM is accessible 45 | if c.OAM_DMA_Timer == 0 or (address < 0xFE00 or address > 0xFE9F) { 46 | // if c.OAM_DMA_Timer == 0 or (address >= 0xFF80 and address <= 0xFFFE) { 47 | value = c.MMU.Read(address); 48 | } 49 | 50 | // libc.printf("\t[DMA: %3d] read8 $%04X -> $%02X\n", 51 | // c.OAM_DMA_Timer, address, value); 52 | 53 | return value; 54 | } 55 | 56 | // Read Next 16-bit 57 | def readNext16(c: *cpu.CPU): uint16 { 58 | let value = read16(c, c.PC); 59 | c.PC += 2; 60 | 61 | return value; 62 | } 63 | 64 | // Read 16-bit 65 | def read16(c: *cpu.CPU, address: uint16): uint16 { 66 | let l = read8(c, address + 0); 67 | let h = read8(c, address + 1); 68 | 69 | let value = uint16(l) | (uint16(h) << 8); 70 | 71 | // libc.printf("\t[DMA: %3d] read16 $%04X -> $%04X\n", 72 | // c.OAM_DMA_Timer, address, value); 73 | 74 | return value; 75 | } 76 | 77 | // Write 16-bit 78 | def write16(c: *cpu.CPU, address: uint16, value: uint16) { 79 | write8(c, address + 1, uint8(value >> 8)); 80 | write8(c, address + 0, uint8(value & 0xFF)); 81 | } 82 | 83 | // Write 8-bit 84 | def write8(c: *cpu.CPU, address: uint16, value: uint8) { 85 | c.Tick(); 86 | 87 | // IF during OAM DMA; writes are ignored unless the address 88 | // is 0xFF46 (OAM DMA restart) or in HIRAM 89 | let written: bool = false; 90 | // if c.OAM_DMA_Timer == 0 or address == 0xFF46 or (address >= 0xFF80 and address <= 0xFFFE) { 91 | if c.OAM_DMA_Timer == 0 or address == 0xFF46 or (address < 0xFE00 or address > 0xFE9F) { 92 | c.MMU.Write(address, value); 93 | written = true; 94 | } 95 | 96 | // libc.printf("\t[DMA: %3d] write8 $%04X <- $%02X (%d)\n", 97 | // c.OAM_DMA_Timer, address, value, written); 98 | } 99 | 100 | // Increment 8-bit 101 | def inc8(c: *cpu.CPU, value: uint8): uint8 { 102 | value += 1; 103 | 104 | flag_set(c, FLAG_Z, value == 0); 105 | flag_set(c, FLAG_N, false); 106 | flag_set(c, FLAG_H, value & 0x0F == 0x00); 107 | 108 | return value; 109 | } 110 | 111 | // Decrement 8-bit 112 | def dec8(c: *cpu.CPU, value: uint8): uint8 { 113 | value -= 1; 114 | 115 | flag_set(c, FLAG_Z, value == 0); 116 | flag_set(c, FLAG_N, true); 117 | flag_set(c, FLAG_H, value & 0x0F == 0x0F); 118 | 119 | return value; 120 | } 121 | 122 | // And 8-bit value 123 | def and8(c: *cpu.CPU, a: uint8, b: uint8): uint8 { 124 | let r = a & b; 125 | 126 | flag_set(c, FLAG_Z, r == 0); 127 | flag_set(c, FLAG_N, false); 128 | flag_set(c, FLAG_H, true); 129 | flag_set(c, FLAG_C, false); 130 | 131 | return r; 132 | } 133 | 134 | // Or 8-bit value 135 | def or8(c: *cpu.CPU, a: uint8, b: uint8): uint8 { 136 | let r = a | b; 137 | 138 | flag_set(c, FLAG_Z, r == 0); 139 | flag_set(c, FLAG_N, false); 140 | flag_set(c, FLAG_H, false); 141 | flag_set(c, FLAG_C, false); 142 | 143 | return r; 144 | } 145 | 146 | // Xor 8-bit value 147 | def xor8(c: *cpu.CPU, a: uint8, b: uint8): uint8 { 148 | let r = a ^ b; 149 | 150 | flag_set(c, FLAG_Z, r == 0); 151 | flag_set(c, FLAG_N, false); 152 | flag_set(c, FLAG_H, false); 153 | flag_set(c, FLAG_C, false); 154 | 155 | return r; 156 | } 157 | 158 | // Add 8-bit value 159 | def add8(c: *cpu.CPU, a: uint8, b: uint8): uint8 { 160 | let r = uint16(a) + uint16(b); 161 | 162 | flag_set(c, FLAG_H, ((a & 0x0F) + (b & 0x0F)) > 0x0F); 163 | flag_set(c, FLAG_Z, (r & 0xFF) == 0); 164 | flag_set(c, FLAG_C, r > 0xFF); 165 | flag_set(c, FLAG_N, false); 166 | 167 | return uint8(r & 0xFF); 168 | } 169 | 170 | // Add 8-bit value w/carry 171 | def adc8(c: *cpu.CPU, a: uint8, b: uint8): uint8 { 172 | let carry = uint16(flag_geti(c, FLAG_C)); 173 | let r = uint16(a) + uint16(b) + carry; 174 | 175 | flag_set(c, FLAG_H, ((a & 0x0F) + (b & 0x0F) + uint8(carry)) > 0x0F); 176 | flag_set(c, FLAG_Z, (r & 0xFF) == 0); 177 | flag_set(c, FLAG_C, r > 0xFF); 178 | flag_set(c, FLAG_N, false); 179 | 180 | return uint8(r & 0xFF); 181 | } 182 | 183 | // Subtract 8-bit value 184 | def sub8(c: *cpu.CPU, a: uint8, b: uint8): uint8 { 185 | let r = int16(a) - int16(b); 186 | 187 | flag_set(c, FLAG_C, r < 0); 188 | flag_set(c, FLAG_Z, (r & 0xFF) == 0); 189 | flag_set(c, FLAG_N, true); 190 | flag_set(c, FLAG_H, (((int16(a) & 0x0F) - (int16(b) & 0x0F)) < 0)); 191 | 192 | return uint8(r & 0xFF); 193 | } 194 | 195 | // Subtract 8-bit value w/carry 196 | def sbc8(c: *cpu.CPU, a: uint8, b: uint8): uint8 { 197 | let carry = int16(flag_geti(c, FLAG_C)); 198 | let r = int16(a) - int16(b) - carry; 199 | 200 | flag_set(c, FLAG_C, r < 0); 201 | flag_set(c, FLAG_Z, (r & 0xFF) == 0); 202 | flag_set(c, FLAG_N, true); 203 | flag_set(c, FLAG_H, (((int16(a) & 0x0F) - (int16(b) & 0x0F) - carry) < 0)); 204 | 205 | return uint8(r & 0xFF); 206 | } 207 | 208 | // Add 16-bit value 209 | def add16(c: *cpu.CPU, a: uint16, b: uint16): uint16 { 210 | let r = uint32(a) + uint32(b); 211 | 212 | flag_set(c, FLAG_H, ((a ^ b ^ uint16(r & 0xFFFF)) & 0x1000) != 0); 213 | flag_set(c, FLAG_C, r > 0xFFFF); 214 | flag_set(c, FLAG_N, false); 215 | 216 | return uint16(r & 0xFFFF); 217 | } 218 | 219 | // Push 16-bit value 220 | def push16(c: *cpu.CPU, r: *uint16) { 221 | c.SP -= 2; 222 | write16(c, c.SP, *r); 223 | } 224 | 225 | // Pop 16-bit value 226 | def pop16(c: *cpu.CPU, r: *uint16) { 227 | *r = read16(c, c.SP); 228 | c.SP += 2; 229 | } 230 | 231 | // Jump 232 | def jp(c: *cpu.CPU, address: uint16, tick: bool) { 233 | c.PC = address; 234 | if tick { c.Tick(); } 235 | } 236 | 237 | // Relative Jump 238 | // 7-bit relative jump address with a sign bit to indicate +/- 239 | def jr(c: *cpu.CPU, n: uint8) { 240 | jp(c, uint16(int16(c.PC) + int16(int8(n))), true); 241 | } 242 | 243 | // Call 244 | def call(c: *cpu.CPU, address: uint16) { 245 | c.Tick(); 246 | push16(c, &c.PC); 247 | jp(c, address, false); 248 | } 249 | 250 | // Return 251 | def ret(c: *cpu.CPU) { 252 | pop16(c, &c.PC); 253 | c.Tick(); 254 | } 255 | 256 | // Byte Swap 257 | def swap8(c: *cpu.CPU, n: uint8): uint8 { 258 | let r = (n >> 4) | ((n << 4) & 0xF0); 259 | 260 | flag_set(c, FLAG_Z, r == 0); 261 | flag_set(c, FLAG_N, false); 262 | flag_set(c, FLAG_H, false); 263 | flag_set(c, FLAG_C, false); 264 | 265 | return r; 266 | } 267 | 268 | // Shift Right 269 | def shr(c: *cpu.CPU, n: uint8, arithmetic: bool): uint8 { 270 | let r = if arithmetic { 271 | if (n & 0x80) != 0 { 272 | (n >> 1) | 0x80; 273 | } else { 274 | (n >> 1); 275 | } 276 | } else { 277 | (n >> 1); 278 | }; 279 | 280 | flag_set(c, FLAG_Z, r == 0); 281 | flag_set(c, FLAG_N, false); 282 | flag_set(c, FLAG_H, false); 283 | flag_set(c, FLAG_C, (n & 0x01) != 0); 284 | 285 | return r; 286 | } 287 | 288 | // Shift Left 289 | def shl(c: *cpu.CPU, n: uint8): uint8 { 290 | let r = (n << 1); 291 | 292 | flag_set(c, FLAG_Z, r == 0); 293 | flag_set(c, FLAG_N, false); 294 | flag_set(c, FLAG_H, false); 295 | flag_set(c, FLAG_C, (n & 0x80) != 0); 296 | 297 | return r; 298 | } 299 | 300 | // Rotate Left (opt. through carry) 301 | def rotl8(c: *cpu.CPU, n: uint8, carry: bool): uint8 { 302 | let r = if carry { 303 | (n << 1) | flag_geti(c, FLAG_C); 304 | } else { 305 | (n << 1) | (n >> 7); 306 | }; 307 | 308 | flag_set(c, FLAG_Z, r == 0); 309 | flag_set(c, FLAG_N, false); 310 | flag_set(c, FLAG_H, false); 311 | flag_set(c, FLAG_C, ((n & 0x80) != 0)); 312 | 313 | return r; 314 | } 315 | 316 | // Rotate Right (opt. through carry) 317 | def rotr8(c: *cpu.CPU, n: uint8, carry: bool): uint8 { 318 | let r = if carry { 319 | (n >> 1) | (flag_geti(c, FLAG_C) << 7); 320 | } else { 321 | (n >> 1) | (n << 7); 322 | }; 323 | 324 | flag_set(c, FLAG_Z, r == 0); 325 | flag_set(c, FLAG_N, false); 326 | flag_set(c, FLAG_H, false); 327 | flag_set(c, FLAG_C, ((n & 0x01) != 0)); 328 | 329 | return r; 330 | } 331 | 332 | // Bit Test 333 | def bit8(c: *cpu.CPU, n: uint8, b: uint8) { 334 | flag_set(c, FLAG_Z, (n & (1 << b)) == 0); 335 | flag_set(c, FLAG_N, false); 336 | flag_set(c, FLAG_H, true); 337 | } 338 | 339 | // Bit Set 340 | def set8(c: *cpu.CPU, n: uint8, b: uint8): uint8 { 341 | return n | (1 << b); 342 | } 343 | 344 | // Bit Reset 345 | def res8(c: *cpu.CPU, n: uint8, b: uint8): uint8 { 346 | return n & ~(1 << b); 347 | } 348 | -------------------------------------------------------------------------------- /gb/channelSquare.as: -------------------------------------------------------------------------------- 1 | import "./bits"; 2 | import "./apu"; 3 | import "libc"; 4 | 5 | // Channel 1/2 — Tone & Sweep 6 | struct ChannelSquare { 7 | APU: *apu.APU; 8 | 9 | // Channel Index — 1 or 2 10 | ChannelIndex: uint8; 11 | 12 | // Enable — On/Off 13 | Enable: bool; 14 | 15 | // Timer 16 | // Set to (2048 - )*4 17 | // Each CPU M-Cycle this is decremented 18 | // When 0, reset back and push a waveform 19 | Timer: uint16; 20 | 21 | // Frequency 22 | // 11-bit value in NRx3 and NRx4 23 | Frequency: uint16; 24 | 25 | // Sweep Timer 26 | SweepTimer: uint8; 27 | 28 | // Sweep Enable (internal) 29 | SweepEnable: bool; 30 | 31 | // Sweep Frequency — Shadow copy of frequency used in sweep 32 | SweepFrequency: uint16; 33 | 34 | // Sweep period 35 | SweepPeriod: uint8; 36 | 37 | // Sweep Increase/Decrease Direction 38 | // true = increase 39 | // false = decrease 40 | SweepDirection: bool; 41 | 42 | // Sweep Shift 43 | SweepShift: uint8; 44 | 45 | // Current volume of channel — same range as VolumeInitial 46 | Volume: uint8; 47 | 48 | // Initial volume on trigger — [$0, $F] (where $0 is silence) 49 | VolumeInitial: uint8; 50 | 51 | // Volume envelope timer 52 | // Set to 53 | // On the 7th step of the frame sequencer; it decrements 54 | // When 0, increase or decrease volume 55 | // according to unless reached min/max 56 | VolumeEnvelopeTimer: uint8; 57 | 58 | // Volume envelope period — explained above 59 | VolumeEnvelopePeriod: uint8; 60 | 61 | // Volume envelope direction 62 | // true = increase 63 | // false = decrease 64 | VolumeEnvelopeDirection: bool; 65 | 66 | // Length Counter 67 | // Writing to NRx1 loads this as 64- 68 | // Every other step of the frame sequencer; it decrements 69 | // When 0; channel is silenced 70 | Length: uint8; 71 | 72 | // Length Counter Enable 73 | // When false a length=0 does not silence the channel 74 | LengthEnable: bool; 75 | 76 | // Duty Pattern Position 77 | DutyPosition: uint8; 78 | 79 | // Duty Pattern Index 80 | // 0 — 00000001 (12.5%) 81 | // 1 — 10000001 (25.0%) 82 | // 2 — 10000111 (50.0%) 83 | // 3 — 01111110 (75.0%) 84 | DutyIndex: uint8; 85 | } 86 | 87 | implement ChannelSquare { 88 | def New(channelIndex: uint8, apu_: *apu.APU): Self { 89 | let ch: ChannelSquare; 90 | ch.ChannelIndex = channelIndex; 91 | ch.APU = apu_; 92 | 93 | return ch; 94 | } 95 | 96 | def Reset(self) { 97 | self.Enable = false; 98 | self.Timer = 0; 99 | self.Frequency = 0; 100 | self.SweepEnable = false; 101 | self.SweepPeriod = 0; 102 | self.SweepDirection = false; 103 | self.SweepShift = 0; 104 | self.SweepTimer = 0; 105 | self.Volume = 0; 106 | self.VolumeInitial = 0; 107 | self.VolumeEnvelopeTimer = 0; 108 | self.VolumeEnvelopePeriod = 0; 109 | self.VolumeEnvelopeDirection = false; 110 | self.Length = 0; 111 | self.LengthEnable = false; 112 | self.DutyPosition = 0; 113 | self.DutyIndex = 0; 114 | } 115 | 116 | // From writing b7 to NRx4 117 | def Trigger(self) { 118 | self.DutyPosition = 0; 119 | 120 | // Channel is enabled (see length counter). 121 | self.Enable = true; 122 | 123 | // If length counter is zero, it is set to 64 (256 for wave channel). 124 | if self.Length == 0 { self.Length = 64; } 125 | 126 | // Frequency timer is reloaded with period. 127 | self.Timer = (2048 - self.Frequency) * 4; 128 | 129 | // Volume envelope timer is reloaded with period. 130 | self.VolumeEnvelopeTimer = self.VolumeEnvelopePeriod; 131 | 132 | // Channel volume is reloaded from NRx2. 133 | self.Volume = self.VolumeInitial; 134 | 135 | // Square 1's sweep does several things. 136 | self.SweepFrequency = self.Frequency; 137 | self.SweepTimer = self.SweepPeriod; 138 | self.SweepEnable = self.SweepPeriod > 0 or self.SweepShift > 0; 139 | if self.SweepShift > 0 { self.CalculateSweep(); } 140 | } 141 | 142 | def CalculateSweep(self): uint16 { 143 | // Calculate new frequency using sweep 144 | let r: uint16 = 0; 145 | r = self.SweepFrequency >> self.SweepShift; 146 | r = self.SweepFrequency + (r * (-1 if self.SweepDirection else +1)); 147 | 148 | // Disable channel if overflow 149 | if r > 2047 { 150 | self.Enable = false; 151 | } 152 | 153 | return r; 154 | } 155 | 156 | def Tick(self) { 157 | if self.Timer > 0 { self.Timer -= 1; } 158 | if self.Timer == 0 { 159 | // Increment position in the duty pattern 160 | self.DutyPosition += 1; 161 | if self.DutyPosition == 8 { self.DutyPosition = 0; } 162 | 163 | // Reload timer 164 | self.Timer = (2048 - self.Frequency) * 4; 165 | } 166 | } 167 | 168 | def TickSweep(self) { 169 | if self.SweepTimer > 0 { self.SweepTimer -= 1; } 170 | if self.SweepPeriod > 0 { 171 | if self.SweepEnable and self.SweepTimer == 0 { 172 | let newFrequency = self.CalculateSweep(); 173 | if newFrequency <= 2047 and self.SweepShift > 0 { 174 | self.SweepFrequency = newFrequency; 175 | self.Frequency = newFrequency; 176 | 177 | self.CalculateSweep(); 178 | } 179 | 180 | self.SweepTimer = self.SweepPeriod; 181 | } 182 | } 183 | } 184 | 185 | def TickVolumeEnvelope(self) { 186 | if self.VolumeEnvelopePeriod > 0 { 187 | if self.VolumeEnvelopeTimer > 0 { self.VolumeEnvelopeTimer -= 1; } 188 | if self.VolumeEnvelopeTimer == 0 { 189 | if self.VolumeEnvelopeDirection { 190 | if self.Volume < 0xF { 191 | self.Volume += 1; 192 | } 193 | } else { 194 | if self.Volume > 0 { 195 | self.Volume -= 1; 196 | } 197 | } 198 | 199 | self.VolumeEnvelopeTimer = self.VolumeEnvelopePeriod; 200 | } 201 | } 202 | } 203 | 204 | def TickLength(self) { 205 | if self.Length > 0 { self.Length -= 1; } 206 | if self.Length == 0 and self.LengthEnable { 207 | self.Enable = false; 208 | } 209 | } 210 | 211 | def Sample(self): int16 { 212 | if not self.Enable { return 0; } 213 | 214 | let pattern = getDutyPattern(self.DutyIndex); 215 | let bit = bits.Test(pattern, (7 - self.DutyPosition)); 216 | 217 | return int16(self.Volume if bit else 0); 218 | } 219 | 220 | // Register Read & Write 221 | 222 | // FFx0 - NRx0 - Channel 1 Sweep register (R/W) 223 | // Bit 6-4 - Sweep Time 224 | // Bit 3 - Sweep Increase/Decrease 225 | // 0: Addition (frequency increases) 226 | // 1: Subtraction (frequency decreases) 227 | // Bit 2-0 - Number of sweep shift (n: 0-7) 228 | 229 | // FFx1 - NRx1 - Channel 1 Sound length/Wave pattern duty (R/W) 230 | // Bit 7-6 - Wave Pattern Duty (Read/Write) 231 | // Bit 5-0 - Sound length data (Write Only) (t1: 0-63) 232 | 233 | // FFx2 - NRx2 - Channel 1 Volume Envelope (R/W) 234 | // Bit 7-4 - Initial Volume of envelope (0-0Fh) (0=No Sound) 235 | // Bit 3 - Envelope Direction (0=Decrease, 1=Increase) 236 | // Bit 2-0 - Number of envelope sweep (n: 0-7) 237 | // (If zero, stop envelope operation.) 238 | 239 | // FFx3 - NRx3 - Channel 1 Frequency lo (Write Only) 240 | 241 | // FFx4 - NRx4 - Channel 1 Frequency hi (R/W) 242 | // Bit 7 - Initial (1=Restart Sound) (Write Only) 243 | // Bit 6 - Counter/consecutive selection (Read/Write) 244 | // (1=Stop output when length in NR11 expires) 245 | // Bit 2-0 - Frequency's higher 3 bits (x) (Write Only) 246 | 247 | def Read(self, address: uint16, ptr: *uint8): bool { 248 | // Check if we are at the right channel 249 | if ( 250 | (self.ChannelIndex == 1 and (address < 0xFF10 or address > 0xFF14)) or 251 | (self.ChannelIndex == 2 and (address < 0xFF16 or address > 0xFF19)) 252 | ) { 253 | return false; 254 | } 255 | 256 | let r = (address & 0xF) % 5; 257 | *ptr = if (r == 0 and self.ChannelIndex == 1) { 258 | ( 259 | bits.Bit(true, 7) | 260 | (self.SweepPeriod << 4) | 261 | bits.Bit(self.SweepDirection, 3) | 262 | self.SweepShift 263 | ); 264 | } else if r == 1 { 265 | ((self.DutyIndex << 6) | 0b11_1111); 266 | } else if r == 2 { 267 | ( 268 | (self.VolumeInitial << 4) | 269 | bits.Bit(self.VolumeEnvelopeDirection, 3) | 270 | self.VolumeEnvelopePeriod 271 | ); 272 | } else if r == 4 { 273 | (bits.Bit(self.LengthEnable, 6) | 0xBF); 274 | } else { 275 | return false; 276 | }; 277 | 278 | return true; 279 | } 280 | 281 | def Write(self, address: uint16, value: uint8): bool { 282 | // Check if we are at the right channel 283 | if ( 284 | (self.ChannelIndex == 1 and (address < 0xFF10 or address > 0xFF14)) or 285 | (self.ChannelIndex == 2 and (address < 0xFF16 or address > 0xFF19)) 286 | ) { 287 | return false; 288 | } 289 | 290 | // If master is disabled; ignore 291 | if not self.APU.Enable { return true; } 292 | 293 | let r = (address & 0xF) % 5; 294 | if (r == 0 and self.ChannelIndex == 1) { 295 | self.SweepPeriod = (value & 0b0111_0000) >> 4; 296 | self.SweepDirection = bits.Test(value, 3); 297 | self.SweepShift = (value & 0b111); 298 | } else if r == 1 { 299 | self.DutyIndex = (value & 0b1100_0000) >> 6; 300 | self.Length = 64 - (value & 0b1_1111); 301 | } else if r == 2 { 302 | self.VolumeInitial = (value & 0b1111_0000) >> 4; 303 | self.Volume = self.VolumeInitial; 304 | self.VolumeEnvelopeDirection = bits.Test(value, 3); 305 | self.VolumeEnvelopePeriod = (value & 0b111); 306 | self.VolumeEnvelopeTimer = self.VolumeEnvelopePeriod; 307 | } else if r == 3 { 308 | self.Frequency = (self.Frequency & ~0xFF) | uint16(value); 309 | } else if r == 4 { 310 | self.Frequency = (self.Frequency & ~0xF00) | (uint16(value & 0b111) << 8); 311 | self.LengthEnable = bits.Test(value, 6); 312 | 313 | if bits.Test(value, 7) { 314 | self.Trigger(); 315 | } 316 | } else { 317 | return false; 318 | } 319 | 320 | return true; 321 | } 322 | } 323 | 324 | def getDutyPattern(dutyIndex: uint8): uint8 { 325 | return if dutyIndex == 0b11 { 326 | 0b01111110; 327 | } else if dutyIndex == 0b10 { 328 | 0b10000111; 329 | } else if dutyIndex == 0b01 { 330 | 0b10000001; 331 | } else { 332 | 0b00000001; 333 | }; 334 | } 335 | -------------------------------------------------------------------------------- /gb/apu.as: -------------------------------------------------------------------------------- 1 | import "std"; 2 | import "libc"; 3 | 4 | import "./mmu"; 5 | import "./bits"; 6 | 7 | import "./channelSquare"; 8 | import "./channelWave"; 9 | import "./channelNoise"; 10 | 11 | #include "SDL2/SDL.h" 12 | 13 | struct Sample { 14 | // Left output terminal (L) 15 | L: int16; 16 | 17 | // Right output terminal (R) 18 | R: int16; 19 | } 20 | 21 | // TODO: Make configurable 22 | let BUFFER_SIZE: uint32 = 1024; 23 | 24 | // TODO: Make configurable 25 | let SAMPLE_RATE = 96000; 26 | 27 | struct APU { 28 | Channel1: channelSquare.ChannelSquare; 29 | Channel2: channelSquare.ChannelSquare; 30 | Channel3: channelWave.ChannelWave; 31 | Channel4: channelNoise.ChannelNoise; 32 | 33 | Buffer: *Sample; 34 | BufferIndex: uint32; 35 | 36 | // Sound Control Registers 37 | // ----------------------- 38 | 39 | // Frame sequencer 40 | SequencerTimer: uint16; 41 | SequencerStep: uint8; 42 | 43 | // Sample timer ( / ) 44 | SampleTimer: uint16; 45 | 46 | // FF24 - NR50 - Channel control / ON-OFF / Volume (R/W) 47 | // Bit 7 - Output Vin to SO2 terminal (1=Enable) 48 | // Bit 6-4 - SO2 output level (volume) (0-7) 49 | // Bit 3 - Output Vin to SO1 terminal (1=Enable) 50 | // Bit 2-0 - SO1 output level (volume) (0-7) 51 | // L = SO2 52 | // R = SO1 53 | VinL: bool; 54 | VolumeL: uint8; 55 | VinR: bool; 56 | VolumeR: uint8; 57 | 58 | // FF25 - NR51 - Selection of Sound output terminal (R/W) 59 | // Bit 7 - Output sound 4 to SO2 terminal 60 | // Bit 6 - Output sound 3 to SO2 terminal 61 | // Bit 5 - Output sound 2 to SO2 terminal 62 | // Bit 4 - Output sound 1 to SO2 terminal 63 | // Bit 3 - Output sound 4 to SO1 terminal 64 | // Bit 2 - Output sound 3 to SO1 terminal 65 | // Bit 1 - Output sound 2 to SO1 terminal 66 | // Bit 0 - Output sound 1 to SO1 terminal 67 | Channel4REnable: bool; 68 | Channel3REnable: bool; 69 | Channel2REnable: bool; 70 | Channel1REnable: bool; 71 | Channel4LEnable: bool; 72 | Channel3LEnable: bool; 73 | Channel2LEnable: bool; 74 | Channel1LEnable: bool; 75 | 76 | // FF26 - NR52 - Sound on/off 77 | // Bit 7 - All sound on/off (0: stop all sound circuits) (Read/Write) 78 | // Bit 3 - Sound 4 ON flag (Read Only) 79 | // Bit 2 - Sound 3 ON flag (Read Only) 80 | // Bit 1 - Sound 2 ON flag (Read Only) 81 | // Bit 0 - Sound 1 ON flag (Read Only) 82 | Enable: bool; 83 | } 84 | 85 | implement APU { 86 | def New(): Self { 87 | let component: APU; 88 | libc.memset(&component as *uint8, 0, std.size_of()); 89 | 90 | component.Buffer = libc.malloc(uint64(BUFFER_SIZE * 4)) as *Sample; 91 | component.BufferIndex = 0; 92 | 93 | return component; 94 | } 95 | 96 | def Acquire(self, this: *APU) { 97 | self.Channel1 = channelSquare.ChannelSquare.New(1, this); 98 | self.Channel2 = channelSquare.ChannelSquare.New(2, this); 99 | self.Channel3 = channelWave.ChannelWave.New(this); 100 | self.Channel4 = channelNoise.ChannelNoise.New(this); 101 | } 102 | 103 | def Release(self) { 104 | libc.free(self.Buffer as *uint8); 105 | self.Channel3.Release(); 106 | } 107 | 108 | def Reset(self) { 109 | self.Enable = false; 110 | 111 | libc.memset(self.Buffer as *uint8, 0, uint64(BUFFER_SIZE) * 4); 112 | self.BufferIndex = 0; 113 | 114 | self.Channel1.Reset(); 115 | self.Channel2.Reset(); 116 | self.Channel3.Reset(); 117 | self.Channel4.Reset(); 118 | 119 | self.VinL = false; 120 | self.VinR = false; 121 | 122 | self.SequencerTimer = 8192; 123 | self.SequencerStep = 0; 124 | self.SampleTimer = 4194304 / 48000; 125 | 126 | self.Channel4REnable = false; 127 | self.Channel3REnable = false; 128 | self.Channel2REnable = false; 129 | self.Channel1REnable = false; 130 | self.Channel4LEnable = false; 131 | self.Channel3LEnable = false; 132 | self.Channel2LEnable = false; 133 | self.Channel1LEnable = false; 134 | 135 | self.VinL = false; 136 | self.VolumeL = 0; 137 | self.VinR = false; 138 | self.VolumeR = 0; 139 | } 140 | 141 | def Read(self, address: uint16, ptr: *uint8): bool { 142 | if self.Channel1.Read(address, ptr) { return true; } 143 | if self.Channel2.Read(address, ptr) { return true; } 144 | if self.Channel3.Read(address, ptr) { return true; } 145 | if self.Channel4.Read(address, ptr) { return true; } 146 | 147 | *ptr = if address == 0xFF24 { 148 | ( 149 | bits.Bit(self.VinL, 7) | 150 | (self.VolumeL << 4) | 151 | bits.Bit(self.VinR, 3) | 152 | (self.VolumeR) 153 | ); 154 | } else if address == 0xFF25 { 155 | ( 156 | bits.Bit(self.Channel4LEnable, 7) | 157 | bits.Bit(self.Channel3LEnable, 6) | 158 | bits.Bit(self.Channel2LEnable, 5) | 159 | bits.Bit(self.Channel1LEnable, 4) | 160 | bits.Bit(self.Channel4REnable, 3) | 161 | bits.Bit(self.Channel3REnable, 2) | 162 | bits.Bit(self.Channel2REnable, 1) | 163 | bits.Bit(self.Channel1REnable, 0) 164 | ); 165 | } else if address == 0xFF26 { 166 | *ptr = ( 167 | bits.Bit(self.Enable, 7) | 168 | bits.Bit(true, 6) | 169 | bits.Bit(true, 5) | 170 | bits.Bit(true, 4) | 171 | bits.Bit(self.Channel4.Enable, 3) | 172 | bits.Bit(self.Channel3.Enable, 2) | 173 | bits.Bit(self.Channel2.Enable, 1) | 174 | bits.Bit(self.Channel1.Enable, 0) 175 | ); 176 | 177 | return true; 178 | } else { 179 | return false; 180 | }; 181 | 182 | return true; 183 | } 184 | 185 | def Write(self, address: uint16, value: uint8): bool { 186 | if self.Channel1.Write(address, value) { return true; } 187 | if self.Channel2.Write(address, value) { return true; } 188 | if self.Channel3.Write(address, value) { return true; } 189 | if self.Channel4.Write(address, value) { return true; } 190 | 191 | if address == 0xFF26 { 192 | if self.Enable and not bits.Test(value, 7) { 193 | // Disabling sound soft-resets the APU 194 | self.Reset(); 195 | } else if not self.Enable and bits.Test(value, 7) { 196 | self.Enable = true; 197 | 198 | // When powered on, the frame sequencer is reset so that the 199 | // next step will be 0 200 | self.SequencerStep = 0; 201 | 202 | // The square duty units are reset to the first step of the waveform, 203 | self.Channel1.DutyPosition = 0; 204 | self.Channel2.DutyPosition = 0; 205 | 206 | // and the wave channel's sample buffer is reset to 0. 207 | self.Channel3.Buffer = 0; 208 | } 209 | 210 | return true; 211 | } 212 | 213 | // If master is disabled; leave unhandled 214 | if not self.Enable { return false; } 215 | 216 | if address == 0xFF24 { 217 | self.VinL = bits.Test(value, 7); 218 | self.VolumeL = (value >> 4) & 0b111; 219 | self.VinR = bits.Test(value, 3); 220 | self.VolumeR = value & 0b111; 221 | } else if address == 0xFF25 { 222 | self.Channel4LEnable = bits.Test(value, 7); 223 | self.Channel3LEnable = bits.Test(value, 6); 224 | self.Channel2LEnable = bits.Test(value, 5); 225 | self.Channel1LEnable = bits.Test(value, 4); 226 | self.Channel4REnable = bits.Test(value, 3); 227 | self.Channel3REnable = bits.Test(value, 2); 228 | self.Channel2REnable = bits.Test(value, 1); 229 | self.Channel1REnable = bits.Test(value, 0); 230 | } else { 231 | return false; 232 | } 233 | 234 | return true; 235 | } 236 | 237 | def Tick(self) { 238 | let n = 0; 239 | while n < 4 { 240 | // Tick: channels 241 | self.Channel1.Tick(); 242 | self.Channel2.Tick(); 243 | self.Channel3.Tick(); 244 | self.Channel4.Tick(); 245 | 246 | // Tick: frame sequencer 247 | if self.SequencerTimer > 0 { self.SequencerTimer -= 1; } 248 | if self.SequencerTimer == 0 { 249 | // Length counter is updated every other step 250 | if self.SequencerStep % 2 == 0 { 251 | self.Channel1.TickLength(); 252 | self.Channel2.TickLength(); 253 | self.Channel3.TickLength(); 254 | self.Channel4.TickLength(); 255 | } 256 | 257 | // Volume is adjusted every 7th step 258 | if self.SequencerStep == 7 { 259 | self.Channel1.TickVolumeEnvelope(); 260 | self.Channel2.TickVolumeEnvelope(); 261 | self.Channel4.TickVolumeEnvelope(); 262 | } 263 | 264 | // Sweep is adjusted every 2nd and 6th steps 265 | if self.SequencerStep == 2 or self.SequencerStep == 6 { 266 | self.Channel1.TickSweep(); 267 | } 268 | 269 | // Step the sequencer 270 | self.SequencerStep += 1; 271 | if self.SequencerStep == 8 { self.SequencerStep = 0; } 272 | 273 | // Reload sequencer timer 274 | self.SequencerTimer = 8192; 275 | } 276 | 277 | // Sample 278 | if self.SampleTimer > 0 { self.SampleTimer -= 1; } 279 | if self.SampleTimer == 0 { 280 | let sample: Sample; 281 | sample.L = 0; 282 | sample.R = 0; 283 | 284 | if self.Enable { 285 | let ch1 = self.Channel1.Sample(); 286 | let ch2 = self.Channel2.Sample(); 287 | let ch3 = self.Channel3.Sample(); 288 | let ch4 = self.Channel4.Sample(); 289 | 290 | if self.Channel1LEnable { sample.L += ch1; } 291 | if self.Channel2LEnable { sample.L += ch2; } 292 | if self.Channel3LEnable { sample.L += ch3; } 293 | if self.Channel4LEnable { sample.L += ch4; } 294 | 295 | if self.Channel1REnable { sample.R += ch1; } 296 | if self.Channel2REnable { sample.R += ch2; } 297 | if self.Channel3REnable { sample.R += ch3; } 298 | if self.Channel4REnable { sample.R += ch4; } 299 | } 300 | 301 | sample.L *= int16(self.VolumeL) * 8; 302 | sample.R *= int16(self.VolumeR) * 8; 303 | 304 | *(self.Buffer + self.BufferIndex) = sample; 305 | self.BufferIndex += 1; 306 | 307 | if self.BufferIndex >= BUFFER_SIZE { 308 | self.BufferIndex = 0; 309 | 310 | // FIXME: Move to shell (SDL shouldn't be in here) 311 | 312 | // Drain audio buffer 313 | while SDL_GetQueuedAudioSize(1) > (BUFFER_SIZE * 4) { 314 | SDL_Delay(1); 315 | } 316 | 317 | SDL_QueueAudio(1, self.Buffer as *uint8, (BUFFER_SIZE * 4)); 318 | } 319 | 320 | // Reload sample timer 321 | self.SampleTimer = uint16(4194304 / SAMPLE_RATE); 322 | } 323 | 324 | n += 1; 325 | } 326 | } 327 | 328 | def AsMemoryController(self, this: *APU): mmu.MemoryController { 329 | let mc: mmu.MemoryController; 330 | mc.Read = MCRead; 331 | mc.Write = MCWrite; 332 | mc.Data = this as *uint8; 333 | mc.Release = MCRelease; 334 | 335 | return mc; 336 | } 337 | } 338 | 339 | def MCRelease(this: *mmu.MemoryController) { 340 | // Do nothing 341 | } 342 | 343 | def MCRead(this: *mmu.MemoryController, address: uint16, value: *uint8): bool { 344 | return (this.Data as *APU).Read(address, value); 345 | } 346 | 347 | def MCWrite(this: *mmu.MemoryController, address: uint16, value: uint8): bool { 348 | return (this.Data as *APU).Write(address, value); 349 | } 350 | -------------------------------------------------------------------------------- /gb/cartridge.as: -------------------------------------------------------------------------------- 1 | import "std"; 2 | import "libc"; 3 | 4 | struct Cartridge { 5 | /// Filename (from ROM) 6 | Filename: str; 7 | 8 | /// Read-only Memory (from Cart) 9 | ROM: *uint8; 10 | 11 | /// ROM Size (in B) 12 | ROMSize: uint64; 13 | 14 | /// Exernal RAM (if present) 15 | ExternalRAM: *uint8; 16 | 17 | /// External RAM Size (in B) 18 | ExternalRAMSize: uint64; 19 | 20 | /// Title (in ASCII) 21 | Title: str; 22 | 23 | /// Manufacturer Code (4-bytes) 24 | // TODO: Manufacturer: *uint8; 25 | 26 | /// CGB Support Flag 27 | /// 00 = None (default) 28 | /// 80 = Game supports both CGB and GB 29 | /// C0 = Game works on CGB only 30 | CGB: uint8; 31 | 32 | /// New Licensee Code (2-character, in ASCII) 33 | Licensee: str; 34 | 35 | /// SGB Support Flag 36 | /// 00 = None (default) 37 | /// 03 = Game supports SGB 38 | SGB: uint8; 39 | 40 | /// Cartridge Type 41 | /// NOTE: This is split up below 42 | /// 00 = ROM ONLY 13 = MBC3+RAM+BATTERY 43 | /// 01 = MBC1 15 = MBC4 44 | /// 02 = MBC1+RAM 16 = MBC4+RAM 45 | /// 03 = MBC1+RAM+BATTERY 17 = MBC4+RAM+BATTERY 46 | /// 05 = MBC2 19 = MBC5 47 | /// 06 = MBC2+BATTERY 1A = MBC5+RAM 48 | /// 08 = ROM+RAM 1B = MBC5+RAM+BATTERY 49 | /// 09 = ROM+RAM+BATTERY 1C = MBC5+RUMBLE 50 | /// 0B = MMM01 1D = MBC5+RUMBLE+RAM 51 | /// 0C = MMM01+RAM 1E = MBC5+RUMBLE+RAM+BATTERY 52 | /// 0D = MMM01+RAM+BATTERY FC = POCKET CAMERA 53 | /// 0F = MBC3+TIMER+BATTERY FD = BANDAI TAMA5 54 | /// 10 = MBC3+TIMER+RAM+BATTERY FE = HuC3 55 | /// 11 = MBC3 FF = HuC1+RAM+BATTERY 56 | /// 12 = MBC3+RAM 57 | Type: uint8; 58 | 59 | /// Memory Bank Controller (Mapper) 60 | /// 0 = None 61 | /// 1 = MBC1 62 | /// 2 = MBC2 63 | /// 3 = MMMO1 64 | /// 4 = MBC3 65 | /// 5 = MBC4 66 | /// 6 = MBC5 67 | /// 7 = POCKET CAMERA 68 | /// 8 = BANDAI TAMA5 69 | /// 9 = HuC3 70 | /// A = HuC1 71 | MC: uint8; 72 | 73 | /// Components 74 | HasExternalRAM: bool; 75 | HasBattery: bool; 76 | HasTimer: bool; 77 | HasRumble: bool; 78 | } 79 | 80 | /// Memory Controllers 81 | let MBC1: uint8 = 0x1; 82 | let MBC2: uint8 = 0x2; 83 | let MMMO1: uint8 = 0x3; 84 | let MBC3: uint8 = 0x4; 85 | let MBC4: uint8 = 0x5; 86 | let MBC5: uint8 = 0x6; 87 | let POCKET_CAMERA: uint8 = 0x7; 88 | let BANDAI_TAMA5: uint8 = 0x8; 89 | let HuC3: uint8 = 0x9; 90 | let HuC1: uint8 = 0xA; 91 | 92 | implement Cartridge { 93 | def New(): Self { 94 | let c: Cartridge; 95 | libc.memset(&c as *uint8, 0, std.size_of()); 96 | 97 | c.ROM = std.null(); 98 | 99 | return c; 100 | } 101 | 102 | def Release(self) { 103 | if self.ROM != std.null() { 104 | libc.free(self.ROM); 105 | } 106 | 107 | if self.ExternalRAM != std.null() { 108 | // Write out ERAM before release if backed by battery 109 | if self.HasBattery { 110 | self.WriteExternalSAV(); 111 | } 112 | 113 | libc.free(self.ExternalRAM); 114 | } 115 | } 116 | 117 | def Trace(self) { 118 | libc.printf("debug: title : %s\n", self.Title); 119 | libc.printf("debug: cartridge type : %02X\n", self.Type); 120 | libc.printf("debug: - has battery : %d\n", self.HasBattery); 121 | libc.printf("debug: - has timer : %d\n", self.HasTimer); 122 | libc.printf("debug: - has rumble : %d\n", self.HasRumble); 123 | libc.printf("debug: - has ext. ram : %d\n", self.HasExternalRAM); 124 | libc.printf("debug: rom size (B) : %d\n", self.ROMSize); 125 | libc.printf("debug: ext. ram size (B) : %d\n", self.ExternalRAMSize); 126 | libc.printf("debug: sgb support : %s\n", "yes" if self.SGB == 3 else "no"); 127 | libc.printf("debug: cgb support : %s\n", if self.CGB == 0x80 { 128 | "yes (compat. with gb)"; 129 | } else if self.CGB == 0xC0 { 130 | "yes (only on cgb)"; 131 | } else { 132 | "no"; 133 | }); 134 | } 135 | 136 | def Open(self, filename: str) { 137 | self.Filename = filename; 138 | 139 | let stream = libc.fopen(filename, "rb"); 140 | if stream == std.null() { 141 | libc.printf("error: couldn't read \"%s\"; couldn't open path as file\n", filename); 142 | libc.exit(-1); 143 | } 144 | 145 | // Determine size (in bytes) of file 146 | // 0 = SEEK_SET, 2 = SEEK_END 147 | libc.fseek(stream, 0, 2); 148 | let size = libc.ftell(stream); 149 | libc.fseek(stream, 0, 0); 150 | 151 | // Allocate ROM 152 | self.ROM = libc.malloc(uint64(size)); 153 | 154 | // Read the file into ROM 155 | libc.fread(self.ROM, 1, uint64(size), stream); 156 | libc.fclose(stream); 157 | 158 | // Get ROM size 159 | self.ROMSize = uint64(*(self.ROM + 0x0148)); 160 | if self.ROMSize < 0x10 { 161 | self.ROMSize = 32 << self.ROMSize; 162 | } else if self.ROMSize == 0x52 { 163 | self.ROMSize = 1152; 164 | } else if self.ROMSize == 0x53 { 165 | self.ROMSize = 1312; 166 | } else if self.ROMSize == 0x54 { 167 | self.ROMSize = 1536; 168 | } 169 | 170 | self.ROMSize *= 1024; 171 | 172 | // Get RAM size 173 | self.ExternalRAMSize = uint64(*(self.ROM + 0x0149)); 174 | if self.ExternalRAMSize == 0x01 { 175 | self.ExternalRAMSize = 2; 176 | } else if self.ExternalRAMSize == 0x02 { 177 | self.ExternalRAMSize = 8; 178 | } else if self.ExternalRAMSize == 0x03 { 179 | self.ExternalRAMSize = 32; 180 | } 181 | 182 | self.ExternalRAMSize *= 1024; 183 | 184 | // Get the memory mapper code (if present) 185 | // TODO: Find a cleaner way to do this 186 | self.Type = *(self.ROM + 0x147); 187 | if self.Type == 0x01 { // MBC1 188 | self.MC = MBC1; 189 | } else if self.Type == 0x02 { // MBC1+RAM 190 | self.MC = MBC1; 191 | self.HasExternalRAM = true; 192 | } else if self.Type == 0x03 { // MBC1+RAM+BATTERY 193 | self.MC = MBC1; 194 | self.HasExternalRAM = true; 195 | self.HasBattery = true; 196 | } else if self.Type == 0x05 { // MBC2 197 | self.MC = MBC2; 198 | self.HasExternalRAM = true; 199 | self.ExternalRAMSize = 512; 200 | } else if self.Type == 0x06 { // MBC2+BATTERY 201 | self.MC = MBC2; 202 | self.HasBattery = true; 203 | self.HasExternalRAM = true; 204 | self.ExternalRAMSize = 512; 205 | } else if self.Type == 0x08 { // ROM+RAM 206 | self.HasExternalRAM = true; 207 | } else if self.Type == 0x09 { // ROM+RAM+BATTERY 208 | self.HasExternalRAM = true; 209 | self.HasBattery = true; 210 | } else if self.Type == 0x0B { // MMM01 211 | self.MC = MMMO1; 212 | } else if self.Type == 0x0C { // MMM01+RAM 213 | self.MC = MMMO1; 214 | self.HasExternalRAM = true; 215 | } else if self.Type == 0x0D { // MMM01+RAM+BATTERY 216 | self.MC = MMMO1; 217 | self.HasExternalRAM = true; 218 | self.HasBattery = true; 219 | } else if self.Type == 0x0F { // MBC3+TIMER+BATTERY 220 | self.MC = MBC3; 221 | self.HasTimer = true; 222 | self.HasBattery = true; 223 | } else if self.Type == 0x10 { // MBC3+TIMER+RAM+BATTERY 224 | self.MC = MBC3; 225 | self.HasTimer = true; 226 | self.HasBattery = true; 227 | self.HasExternalRAM = true; 228 | } else if self.Type == 0x11 { // MBC3 229 | self.MC = MBC3; 230 | } else if self.Type == 0x12 { // MBC3+RAM 231 | self.MC = MBC3; 232 | self.HasExternalRAM = true; 233 | } else if self.Type == 0x13 { // MBC3+RAM+BATTERY 234 | self.MC = MBC3; 235 | self.HasExternalRAM = true; 236 | self.HasBattery = true; 237 | } else if self.Type == 0x15 { // MBC4 238 | self.MC = MBC4; 239 | } else if self.Type == 0x16 { // MBC4+RAM 240 | self.MC = MBC4; 241 | self.HasExternalRAM = true; 242 | } else if self.Type == 0x17 { // MBC4+RAM+BATTERY 243 | self.MC = MBC4; 244 | self.HasExternalRAM = true; 245 | self.HasBattery = true; 246 | } else if self.Type == 0x19 { // MBC5 247 | self.MC = MBC5; 248 | } else if self.Type == 0x1A { // MBC5+RAM 249 | self.MC = MBC5; 250 | self.HasExternalRAM = true; 251 | } else if self.Type == 0x1B { // MBC5+RAM+BATTERY 252 | self.MC = MBC5; 253 | self.HasExternalRAM = true; 254 | self.HasBattery = true; 255 | } else if self.Type == 0x1C { // MBC5+RUMBLE 256 | self.MC = MBC5; 257 | self.HasRumble = true; 258 | } else if self.Type == 0x1D { // MBC5+RUMBLE+RAM 259 | self.MC = MBC5; 260 | self.HasRumble = true; 261 | self.HasExternalRAM = true; 262 | } else if self.Type == 0x1E { // MBC5+RUMBLE+RAM+BATTERY 263 | self.MC = MBC5; 264 | self.HasRumble = true; 265 | self.HasExternalRAM = true; 266 | self.HasBattery = true; 267 | } else if self.Type == 0xFC { // POCKET CAMERA 268 | self.MC = POCKET_CAMERA; 269 | } else if self.Type == 0xFD { // BANDAI TAMA5 270 | self.MC = BANDAI_TAMA5; 271 | } else if self.Type == 0xFE { // HuC3 272 | // FIXME: No idea if this is correct or not 273 | self.MC = MBC3; 274 | } else if self.Type == 0xFF { // HuC1+RAM+BATTERY 275 | self.MC = HuC1; 276 | self.HasExternalRAM = true; 277 | self.HasBattery = true; 278 | } 279 | 280 | // Allocate External RAM (if present) 281 | if self.ExternalRAMSize > 0 { 282 | self.ExternalRAM = libc.malloc(self.ExternalRAMSize); 283 | } 284 | 285 | // Check for existing .sav file 286 | if self.HasBattery { 287 | self.ReadExternalSAV(); 288 | } 289 | 290 | // CGB Support Flag 291 | self.CGB = *(self.ROM + 0x0143); 292 | 293 | // SGB Support Flag 294 | self.SGB = *(self.ROM + 0x0146); 295 | 296 | // Get Title 297 | self.Title = (self.ROM + 0x0134) as str; 298 | 299 | // Nul out any characters in the title that are "strange" 300 | // let i = 0; 301 | // while i < 16 { 302 | // let c = *(self.Title + i); 303 | // if c < 0x20 or c >= 0x7E { 304 | // *(self.Title + i) = 0x0; 305 | // } 306 | // 307 | // i += 1; 308 | // } 309 | 310 | // Write the 256-B BIOS to ROM 311 | // stream = libc.fopen("/Users/mehcode/Documents/[BIOS] Nintendo Game Boy.gb", "rb"); 312 | // libc.fread(self.ROM, 1, 256, stream); 313 | // libc.fclose(stream); 314 | } 315 | 316 | def ReadExternalSAV(self) { 317 | // Make a `.sav` filename 318 | // TODO(arrow): Need some string and char utilities brah 319 | let filenameSz = libc.strlen(self.Filename) + 2; 320 | let savFilename = libc.malloc(filenameSz) as *int8; 321 | libc.strcpy(savFilename, self.Filename); 322 | *(savFilename + (filenameSz - 4)) = 0x73; // s 323 | *(savFilename + (filenameSz - 3)) = 0x61; // a 324 | *(savFilename + (filenameSz - 2)) = 0x76; // v 325 | *(savFilename + (filenameSz - 1)) = 0; // \x0 326 | 327 | // Check for an existing `.sav` file 328 | let stream = libc.fopen(savFilename, "rb"); 329 | if stream != std.null() { 330 | // Read in saved ERAM 331 | libc.fread(self.ExternalRAM, 1, self.ExternalRAMSize, stream); 332 | libc.fclose(stream); 333 | } 334 | 335 | libc.free(savFilename as *uint8); 336 | } 337 | 338 | def WriteExternalSAV(self) { 339 | // Make a `.sav` filename 340 | // TODO(arrow): Need some string and char utilities brah 341 | let filenameSz = libc.strlen(self.Filename) + 2; 342 | let savFilename = libc.malloc(filenameSz) as *int8; 343 | libc.strcpy(savFilename, self.Filename); 344 | *(savFilename + (filenameSz - 4)) = 0x73; // s 345 | *(savFilename + (filenameSz - 3)) = 0x61; // a 346 | *(savFilename + (filenameSz - 2)) = 0x76; // v 347 | *(savFilename + (filenameSz - 1)) = 0; // \x0 348 | 349 | // Check for an existing `.sav` file 350 | let stream = libc.fopen(savFilename, "wb"); 351 | if stream != std.null() { 352 | // Write out saved ERAM 353 | libc.fwrite(self.ExternalRAM, 1, self.ExternalRAMSize, stream); 354 | libc.fclose(stream); 355 | } 356 | 357 | libc.free(savFilename as *uint8); 358 | } 359 | } 360 | -------------------------------------------------------------------------------- /gb/cpu.as: -------------------------------------------------------------------------------- 1 | import "libc"; 2 | import "std"; 3 | 4 | import "./bits"; 5 | import "./mmu"; 6 | import "./machine"; 7 | import "./op"; 8 | import "./om"; 9 | 10 | struct CPU { 11 | /// 16-bit program counter 12 | PC: uint16; 13 | 14 | /// Previous PC (ignoring NOPs) for detecting inf. loops 15 | LastPC: uint16; 16 | 17 | /// 16-bit stack pointer 18 | SP: uint16; 19 | 20 | /// Interrupt Master Enable (IME) 21 | /// -1 - Pending state that goes to ON 22 | /// 0 - OFF 23 | /// +1 - ON 24 | IME: int8; 25 | 26 | // STOP 27 | // 0 = OFF 28 | // 1 = ON 29 | STOP: int8; 30 | 31 | // HALT 32 | // 0 - OFF 33 | // 1 - ON 34 | // -1 - Funny bug state that will replay the next opcode 35 | HALT: int8; 36 | 37 | /// 16-bit registers 38 | AF: uint16; 39 | BC: uint16; 40 | DE: uint16; 41 | HL: uint16; 42 | 43 | /// 8-bit registers (pointers to nybbles in the 16-bit registers) 44 | A: *uint8; 45 | F: *uint8; 46 | B: *uint8; 47 | C: *uint8; 48 | D: *uint8; 49 | E: *uint8; 50 | H: *uint8; 51 | L: *uint8; 52 | 53 | /// Interrupt Enable (IE) R/W — $FFFF 54 | IE: uint8; 55 | 56 | /// Interrupt Flag (IF) R/W — $FF0F 57 | IF: uint8; 58 | 59 | /// Number of M (machine) cycles for the current instruction 60 | /// Reset before each operation 61 | Cycles: uint32; 62 | 63 | /// [OAM DMA] Start Address for the current DMA 64 | OAM_DMA_Start: uint16; 65 | 66 | /// [OAM DMA] Start Address of the next/pending DMA 67 | OAM_DMA_NextStart: uint16; 68 | 69 | /// [OAM DMA] Delay Timer until the next OMA DMA starts 70 | OAM_DMA_DelayTimer: uint8; 71 | 72 | /// [OAM DMA] Current index into the OAM DMA 73 | OAM_DMA_Index: uint16; 74 | 75 | /// [OAM DMA] Timer (in M-Cycles) of how long we have left in OAM DMA 76 | OAM_DMA_Timer: uint8; 77 | 78 | /// [HDMA] FF51 - HDMA1 - CGB Mode Only - New DMA Source, High 79 | /// [HDMA] FF52 - HDMA2 - CGB Mode Only - New DMA Source, Low 80 | HDMA_Source: uint16; 81 | 82 | /// [HDMA] FF53 - HDMA3 - CGB Mode Only - New DMA Destination, High 83 | /// [HDMA] FF54 - HDMA4 - CGB Mode Only - New DMA Destination, Low 84 | HDMA_Destination: uint16; 85 | 86 | /// [HDMA] FF55 - HDMA5 - CGB Mode Only - New DMA Length/Mode/Start 87 | /// > Reading from Register FF55 returns the remaining length 88 | HDMA_Length: int16; 89 | /// 1 = H-Blank DMA / 0 = General DMA 90 | HDMA_Mode: bool; 91 | 92 | /// [HDMA] Current Index of the HDMA operation 93 | HDMA_Index: uint16; 94 | 95 | /// Memory management unit (reference) 96 | MMU: *mmu.MMU; 97 | 98 | /// Machine (reference) 99 | Machine: *machine.Machine; 100 | } 101 | 102 | implement CPU { 103 | def New(machine_: *machine.Machine, mmu_: *mmu.MMU): Self { 104 | let c: CPU; 105 | libc.memset(&c as *uint8, 0, std.size_of()); 106 | 107 | c.Machine = machine_; 108 | c.MMU = mmu_; 109 | 110 | return c; 111 | } 112 | 113 | def Acquire(self) { 114 | // Setup 8-bit register views 115 | self.A = ((&self.AF as *uint8) + 1); 116 | self.F = ((&self.AF as *uint8) + 0); 117 | self.B = ((&self.BC as *uint8) + 1); 118 | self.C = ((&self.BC as *uint8) + 0); 119 | self.D = ((&self.DE as *uint8) + 1); 120 | self.E = ((&self.DE as *uint8) + 0); 121 | self.H = ((&self.HL as *uint8) + 1); 122 | self.L = ((&self.HL as *uint8) + 0); 123 | } 124 | 125 | def Release(self) { 126 | // [...] 127 | } 128 | 129 | /// Reset 130 | def Reset(self) { 131 | self.PC = 0x0100; 132 | self.SP = 0xFFFE; 133 | self.IME = 1; 134 | self.HALT = 0; 135 | 136 | self.AF = 137 | if self.Machine.Mode == machine.MODE_CGB { 0x1180; } 138 | else { 0x01B0; }; 139 | 140 | self.BC = 141 | if self.Machine.Mode == machine.MODE_CGB { 0x0000; } 142 | else { 0x0013; }; 143 | 144 | self.DE = 145 | if self.Machine.Mode == machine.MODE_CGB { 0x0008; } 146 | else { 0x00D8; }; 147 | 148 | self.HL = 149 | if self.Machine.Mode == machine.MODE_CGB { 0x007C; } 150 | else { 0x014D; }; 151 | 152 | self.IE = 0x01; 153 | self.IF = 0x01; 154 | 155 | self.OAM_DMA_Start = 0; 156 | self.OAM_DMA_Index = 0; 157 | self.OAM_DMA_Timer = 0; 158 | self.OAM_DMA_NextStart = 0; 159 | self.OAM_DMA_DelayTimer = 0; 160 | 161 | self.HDMA_Source = 0; 162 | self.HDMA_Destination = 0; 163 | self.HDMA_Length = 0; 164 | self.HDMA_Mode = false; 165 | self.HDMA_Index = 0; 166 | } 167 | 168 | /// Tick 169 | /// Steps the machine and records the M-cycle 170 | def Tick(self) { 171 | // Run next iteration of OMA DMA (if active) 172 | if self.OAM_DMA_Timer > 0 { 173 | // Each tick does a single byte memory copy 174 | self.MMU.Write(0xFE00 + self.OAM_DMA_Index, 175 | self.MMU.Read(self.OAM_DMA_Start + self.OAM_DMA_Index)); 176 | 177 | self.OAM_DMA_Index += 1; 178 | self.OAM_DMA_Timer -= 1; 179 | } 180 | 181 | // When OAM DMA starts a delay timer is set to 2; the tick with the memory 182 | // write that starts DMA and the tick just after are wait cycles before 183 | // the actual DMA starts. If there was an existing DMA running; that DMA 184 | // does not stop until the next one starts 185 | if self.OAM_DMA_DelayTimer > 0 { 186 | self.OAM_DMA_DelayTimer -= 1; 187 | if self.OAM_DMA_DelayTimer == 0 { 188 | self.OAM_DMA_Timer = 160; 189 | self.OAM_DMA_Index = 0; 190 | self.OAM_DMA_Start = self.OAM_DMA_NextStart; 191 | } 192 | } 193 | 194 | // Continue on to tick the machine state 195 | self.Machine.Tick(); 196 | self.Cycles += 1; 197 | } 198 | 199 | /// Execute N instructions 200 | /// Returns the executed number of cycles 201 | def Run(self, this: *CPU, n: uint32): uint32 { 202 | let cycles: uint32 = 0; 203 | while (n + 1) > 0 { 204 | // Reset "current" cycle count 205 | self.Cycles = 0; 206 | 207 | // If during STOP .. 208 | if self.STOP == 1 { 209 | self.Tick(); 210 | cycles += self.Cycles; 211 | n -= 1; 212 | 213 | continue; 214 | } 215 | 216 | // If during HALT and no pending interrupts .. 217 | if self.HALT == 1 { 218 | if (self.IE & self.IF & 0x1F) == 0 { 219 | self.Tick(); 220 | cycles += self.Cycles; 221 | n -= 1; 222 | 223 | continue; 224 | } else if self.IME == 0 { 225 | // Just leave HALT mode (no interrupts are fired as 226 | // we do not have them enabled but we still exit HALT) 227 | self.HALT = 0; 228 | } 229 | } 230 | 231 | // Decide if we should service interrupts 232 | // No interrupts are allowed during OAM DMA 233 | let irq = self.IE & self.IF; 234 | if self.OAM_DMA_Timer == 0 and self.IME == 1 and irq > 0 { 235 | // Service interrupt (takes 5 cycles) 236 | 237 | // Wait 2 cycles 238 | self.Tick(); 239 | self.Tick(); 240 | 241 | // Push PC (as if we're making a CALL) – 2 cycles 242 | om.push16(this, &self.PC); 243 | 244 | // Jump to the appropriate vector (and reset IF bit) - 1 cycle 245 | if (irq & 0x01) != 0 { 246 | // V-Blank 247 | om.jp(this, 0x40, true); 248 | self.IF &= ~0x01; 249 | } else if (irq & 0x02) != 0 { 250 | // LCD STAT 251 | om.jp(this, 0x48, true); 252 | self.IF &= ~0x02; 253 | } else if (irq & 0x04) != 0 { 254 | // Timer 255 | om.jp(this, 0x50, true); 256 | self.IF &= ~0x04; 257 | } else if (irq & 0x08) != 0 { 258 | // Serial 259 | om.jp(this, 0x58, true); 260 | self.IF &= ~0x08; 261 | } else if (irq & 0x10) != 0 { 262 | // Joypad 263 | om.jp(this, 0x60, true); 264 | self.IF &= ~0x10; 265 | } 266 | 267 | // Disable IME 268 | self.IME = 0; 269 | 270 | // Come back from HALT 271 | if self.HALT == 1 { self.HALT = 0; } 272 | } 273 | 274 | // Re-enable IME from pending 275 | if self.IME == -1 { self.IME = 1; } 276 | 277 | // if self.PC == 0xFDFE { 278 | // libc.printf("Remaining DMA: %d\n", self.OAM_DMA_Timer); 279 | // } 280 | 281 | // Decode/lookup next operation 282 | let operation = op.next(this); 283 | 284 | // DEBUG: Ignore NOPs in inf. loop check 285 | if libc.strcmp(operation.disassembly, "NOP") != 0 { 286 | // DEBUG: Save PC (to detect inf. loop) 287 | self.LastPC = self.PC - 1; 288 | } 289 | 290 | // Print disassembly/trace 291 | // TODO: Make configurable from command line 292 | // self.Trace(operation); 293 | 294 | // Execute 295 | // HACK: Taking the address of a reference (`self`) dies 296 | operation.execute(this); 297 | 298 | // DEBUG: Is the PC now the same PC that we started with ( 299 | // possible inf. loop) 300 | if self.LastPC == self.PC and (self.IME == 0 or self.IE == 0) { 301 | // Infinite jump with IME=0 is an infinite loop 302 | // Enter STOP mode to stop CPU cycling 303 | self.STOP = 1; 304 | if not self.Machine.Test { 305 | libc.printf( 306 | "warn: infinite loop detected at $%02X (entering STOP mode)\n", 307 | self.PC); 308 | } 309 | } 310 | 311 | // libc.printf("--------------------------------------------------\n"); 312 | 313 | // Increment total cycle count and let's do this again 314 | cycles += self.Cycles; 315 | n -= 1; 316 | } 317 | 318 | return cycles; 319 | } 320 | 321 | def Trace(self, operation: op.Operation) { 322 | let buffer: str; 323 | buffer = libc.malloc(128) as str; 324 | 325 | let n0 = self.MMU.Read(self.PC + 0); 326 | let n1 = self.MMU.Read(self.PC + 1); 327 | 328 | if operation.size == 2 { 329 | libc.sprintf(buffer, operation.disassembly, n0); 330 | } else if operation.size == 3 { 331 | libc.sprintf(buffer, operation.disassembly, n1, n0); 332 | } else { 333 | libc.sprintf(buffer, operation.disassembly); 334 | } 335 | 336 | libc.printf("trace: %-25s PC: $%04X AF: $%04X BC: $%04X DE: $%04X HL: $%04X SP: $%04X\n", 337 | buffer, 338 | self.PC - 1, 339 | self.AF, 340 | self.BC, 341 | self.DE, 342 | self.HL, 343 | self.SP, 344 | ); 345 | } 346 | 347 | def Read(self, address: uint16, value: *uint8): bool { 348 | *value = if address == 0xFFFF { 349 | (self.IE | 0xE0); 350 | } else if address == 0xFF0F { 351 | (self.IF | 0xE0); 352 | } else { 353 | return false; 354 | }; 355 | 356 | return true; 357 | } 358 | 359 | def Write(self, address: uint16, value: uint8): bool { 360 | if address == 0xFFFF { 361 | self.IE = value & ~0xE0; 362 | } else if address == 0xFF0F { 363 | self.IF = value & ~0xE0; 364 | } else if address == 0xFF46 { 365 | // DMA - DMA Transfer and Start Address (W) 366 | self.OAM_DMA_NextStart = uint16(value) << 8; 367 | self.OAM_DMA_DelayTimer = 2; 368 | } else if address == 0xFF51 and not self.HDMA_Mode { 369 | self.HDMA_Source = (self.HDMA_Source & ~0xFF00) | (uint16(value) << 8); 370 | } else if address == 0xFF52 and not self.HDMA_Mode { 371 | self.HDMA_Source = (self.HDMA_Source & ~0xFF) | uint16(value); 372 | self.HDMA_Source &= ~0xF; 373 | } else if address == 0xFF53 and not self.HDMA_Mode { 374 | // FIXME: the upper 3 bits are ignored either (destination is always in VRAM). 375 | self.HDMA_Destination = ( 376 | (self.HDMA_Destination & ~0xFF00) | (uint16(value) << 8)); 377 | } else if address == 0xFF54 and not self.HDMA_Mode { 378 | self.HDMA_Destination = (self.HDMA_Destination & ~0xFF) | uint16(value); 379 | self.HDMA_Destination &= ~0xF; 380 | } else if address == 0xFF55 { 381 | self.HDMA_Index = 0; 382 | self.HDMA_Length = int16(value & 0x7F); 383 | 384 | if bits.Test(value, 7) { 385 | // Start a H-Blank DMA (ignored if already in one) 386 | if not self.HDMA_Mode { 387 | self.HDMA_Mode = true; 388 | 389 | libc.printf("error: H-Blank DMA unsupported\n"); 390 | libc.exit(-1); 391 | } 392 | } else { 393 | if self.HDMA_Mode { 394 | // Stop H-Blank DMA 395 | self.HDMA_Mode = false; 396 | } else { 397 | // Do G-DMA 398 | while self.HDMA_Length >= 0 { 399 | self.TickHDMA(); 400 | self.HDMA_Length -= 1; 401 | } 402 | } 403 | } 404 | } else { 405 | return false; 406 | } 407 | 408 | return true; 409 | } 410 | 411 | def TickHDMA(self) { 412 | let i = 0; 413 | while i < 0x10 { 414 | self.MMU.Write( 415 | self.HDMA_Destination + self.HDMA_Index, 416 | self.MMU.Read( 417 | self.HDMA_Source + self.HDMA_Index)); 418 | 419 | if i % 8 == 1 { 420 | self.Tick(); 421 | } 422 | 423 | self.HDMA_Index += 1; 424 | i += 1; 425 | } 426 | } 427 | 428 | def AsMemoryController(self, this: *CPU): mmu.MemoryController { 429 | let mc: mmu.MemoryController; 430 | mc.Read = MCRead; 431 | mc.Write = MCWrite; 432 | mc.Data = this as *uint8; 433 | mc.Release = MCRelease; 434 | 435 | return mc; 436 | } 437 | } 438 | 439 | def MCRelease(this: *mmu.MemoryController) { 440 | // Do nothing 441 | } 442 | 443 | def MCRead(this: *mmu.MemoryController, address: uint16, value: *uint8): bool { 444 | return (this.Data as *CPU).Read(address, value); 445 | } 446 | 447 | def MCWrite(this: *mmu.MemoryController, address: uint16, value: uint8): bool { 448 | return (this.Data as *CPU).Write(address, value); 449 | } 450 | -------------------------------------------------------------------------------- /ch8/op.as: -------------------------------------------------------------------------------- 1 | import "libc"; 2 | 3 | import "./machine"; 4 | import "./util"; 5 | import "./mmu"; 6 | 7 | /// An opcode in CHIP-8 is composed of an instruction (group) identifier, 8 | /// and (up to) 3 8-bit operands. An operand may be repuroposed into a second 9 | /// part of the instruction identifier. An instruction may use all 3 8-bit 10 | /// operands as a single 12-bit operand. 11 | /// 12 | /// Most CHIP-8 reference documents describe the operands as `XYN`. Following 13 | /// that, `.x()`, `.y()`, and `.n()` access the 3 8-bit operands. 14 | /// The single 12-bit operand is accessed via `.address()`. 15 | /// The insturction identifier is accessed via `.i()`. 16 | /// The second 2 8-bit operands can be accessed via `.data()`. 17 | struct Opcode { _address: *uint8 } 18 | implement Opcode { 19 | def new(address: *uint8): Opcode { 20 | let result: Opcode; 21 | result._address = address; 22 | 23 | return result; 24 | } 25 | 26 | def at(self, index: uint8): uint8 { 27 | let offset = if (index & 0x2) != 0 { 0; } else { 1; }; 28 | let value = *(self._address + offset); 29 | 30 | return (value >> ((index & 0x1) << 2)) & 0xF; 31 | } 32 | 33 | // @property (?) 34 | def address(self): uint16 { 35 | let h = *(self._address + 0); 36 | let l = *(self._address + 1); 37 | 38 | return (uint16((h & 0x0F)) << 8) | uint16(l); 39 | } 40 | 41 | // @property (?) 42 | def i(self): uint8 { 43 | return self.at(3); 44 | } 45 | 46 | // @property (?) 47 | def x(self): uint8 { 48 | return self.at(2); 49 | } 50 | 51 | // @property (?) 52 | def y(self): uint8 { 53 | return self.at(1); 54 | } 55 | 56 | // @property (?) 57 | def n(self): uint8 { 58 | return self.at(0); 59 | } 60 | 61 | // @property (?) 62 | def data(self): uint8 { 63 | return self.at(0) | (self.at(1) << 4); 64 | } 65 | } 66 | 67 | def execute(c: *machine.Context, address: *uint8) { 68 | let opcode = Opcode.new(address); 69 | let refresh = false; 70 | 71 | // libc.printf("[%04X] %04X\n", c.PC, oc); 72 | 73 | // TODO: HashMap for operations might make this look nicer 74 | 75 | if opcode.i() == 0x0 and opcode.address() == 0x0E0 { 76 | _00E0(c, opcode); 77 | } else if opcode.i() == 0x0 and opcode.address() == 0x0EE { 78 | _00EE(c, opcode); 79 | } else if opcode.i() == 0x0 and opcode.address() == 0x230 { 80 | _0230(c, opcode); 81 | } else if opcode.i() == 0x0 { 82 | _0nnn(c, opcode); 83 | } else if opcode.i() == 0x1 { 84 | _1nnn(c, opcode); 85 | } else if opcode.i() == 0x2 { 86 | _2nnn(c, opcode); 87 | } else if opcode.i() == 0x3 { 88 | _3xkk(c, opcode); 89 | } else if opcode.i() == 0x4 { 90 | _4xkk(c, opcode); 91 | } else if opcode.i() == 0x5 { 92 | _5xy0(c, opcode); 93 | } else if opcode.i() == 0x6 { 94 | _6xkk(c, opcode); 95 | } else if opcode.i() == 0x7 { 96 | _7xkk(c, opcode); 97 | } else if opcode.i() == 0x8 and opcode.n() == 0x0 { 98 | _8xy0(c, opcode); 99 | } else if opcode.i() == 0x8 and opcode.n() == 0x1 { 100 | _8xy1(c, opcode); 101 | } else if opcode.i() == 0x8 and opcode.n() == 0x2 { 102 | _8xy2(c, opcode); 103 | } else if opcode.i() == 0x8 and opcode.n() == 0x3 { 104 | _8xy3(c, opcode); 105 | } else if opcode.i() == 0x8 and opcode.n() == 0x4 { 106 | _8xy4(c, opcode); 107 | } else if opcode.i() == 0x8 and opcode.n() == 0x5 { 108 | _8xy5(c, opcode); 109 | } else if opcode.i() == 0x8 and opcode.n() == 0x6 { 110 | _8xy6(c, opcode); 111 | } else if opcode.i() == 0x8 and opcode.n() == 0x7 { 112 | _8xy7(c, opcode); 113 | } else if opcode.i() == 0x8 and opcode.n() == 0xE { 114 | _8xyE(c, opcode); 115 | } else if opcode.i() == 0x9 { 116 | _9xy0(c, opcode); 117 | } else if opcode.i() == 0xA { 118 | _Annn(c, opcode); 119 | } else if opcode.i() == 0xB { 120 | _Bnnn(c, opcode); 121 | } else if opcode.i() == 0xC { 122 | _Cxkk(c, opcode); 123 | } else if opcode.i() == 0xD { 124 | _Dxyn(c, opcode); 125 | refresh = true; 126 | } else if opcode.i() == 0xE and opcode.data() == 0x9E { 127 | _Ex9E(c, opcode); 128 | } else if opcode.i() == 0xE and opcode.data() == 0xA1 { 129 | _ExA1(c, opcode); 130 | } else if opcode.i() == 0xF and opcode.data() == 0x07 { 131 | _Fx07(c, opcode); 132 | } else if opcode.i() == 0xF and opcode.data() == 0x15 { 133 | _Fx15(c, opcode); 134 | } else if opcode.i() == 0xF and opcode.data() == 0x18 { 135 | _Fx18(c, opcode); 136 | } else if opcode.i() == 0xF and opcode.data() == 0x1E { 137 | _Fx1E(c, opcode); 138 | } else if opcode.i() == 0xF and opcode.data() == 0x29 { 139 | _Fx29(c, opcode); 140 | } else if opcode.i() == 0xF and opcode.data() == 0x33 { 141 | _Fx33(c, opcode); 142 | } else if opcode.i() == 0xF and opcode.data() == 0x55 { 143 | _Fx55(c, opcode); 144 | } else if opcode.i() == 0xF and opcode.data() == 0x65 { 145 | _Fx65(c, opcode); 146 | } else { 147 | _unknown(address); 148 | } 149 | 150 | if refresh { 151 | machine.refresh(c); 152 | } 153 | } 154 | 155 | // Unknown opcode 156 | def _unknown(opcode: *uint8) { 157 | libc.printf("error: unknown opcode: $%02X%02X\n", 158 | *(opcode), 159 | *(opcode + 1), 160 | ); 161 | 162 | libc.exit(1); 163 | } 164 | 165 | // SYS 166 | def _0nnn(c: *machine.Context, opcode: Opcode) { 167 | // Jump to a machine code routine at nnn. 168 | // NOTE: Ignore 169 | } 170 | 171 | // CLS 172 | def _00E0(c: *machine.Context, opcode: Opcode) { 173 | // Clear the Chip-8 64x32 display. 174 | // NOTE: Still clear this size screen, even when in High-Res mode. 175 | libc.memset(c.framebuffer, 0, 32 * 64); 176 | } 177 | 178 | // HRCLS 179 | def _0230(c: *machine.Context, opcode: Opcode) { 180 | // Clear the High-Res Chip-8 64x64 display. 181 | // NOTE: In normal-res mode this is identical to $00E0 182 | libc.memset(c.framebuffer, 0, 64 * 64); 183 | } 184 | 185 | // RET 186 | def _00EE(c: *machine.Context, opcode: Opcode) { 187 | // Return from a subroutine. 188 | // NOTE: The stack is only 16 "slots" 189 | c.SP = if c.SP == 0 { 0xF; } else { c.SP - 1; }; 190 | c.PC = *(c.stack + c.SP); 191 | } 192 | 193 | // JP nnn 194 | def _1nnn(c: *machine.Context, opcode: Opcode) { 195 | // Jump to address 196 | c.PC = opcode.address(); 197 | } 198 | 199 | // CALL nnn 200 | def _2nnn(c: *machine.Context, opcode: Opcode) { 201 | // Call subroutine. 202 | 203 | // Push PC on top of the stack. 204 | *(c.stack + c.SP) = c.PC; 205 | 206 | // Increment the stack pointer (for the push) 207 | // NOTE: The stack wraps around its 16-slot area 208 | c.SP += 1; 209 | if c.SP >= 0x10 { c.SP = 0; } 210 | 211 | // Set PC to address 212 | c.PC = opcode.address(); 213 | } 214 | 215 | // SE Vx, kk 216 | def _3xkk(c: *machine.Context, opcode: Opcode) { 217 | // Skip next instruction if Vx == kk 218 | if *(c.V + opcode.x()) == opcode.data() { 219 | c.PC += 2; 220 | } 221 | } 222 | 223 | // SNE Vx, kk 224 | def _4xkk(c: *machine.Context, opcode: Opcode) { 225 | // Skip next instruction if Vx != kk 226 | if *(c.V + opcode.x()) != opcode.data() { 227 | c.PC += 2; 228 | } 229 | } 230 | 231 | // SE Vx, Vy 232 | def _5xy0(c: *machine.Context, opcode: Opcode) { 233 | // Skip next instruction if Vx = Vy 234 | 235 | // if c.V[X(opcode)] == c.V[Y(opcode)] { 236 | // c.PC += 2; 237 | // } 238 | 239 | if *(c.V + opcode.x()) == *(c.V + opcode.y()) { 240 | c.PC += 2; 241 | } 242 | } 243 | 244 | // LD Vx, kk 245 | def _6xkk(c: *machine.Context, opcode: Opcode) { 246 | // Set Vx = kk 247 | *(c.V + opcode.x()) = opcode.data(); 248 | } 249 | 250 | // ADD Vx, kk 251 | def _7xkk(c: *machine.Context, opcode: Opcode) { 252 | // Set Vx = Vx + kk 253 | let x = opcode.x(); 254 | *(c.V + x) = *(c.V + x) + opcode.data(); 255 | } 256 | 257 | // LD Vx, Vy 258 | def _8xy0(c: *machine.Context, opcode: Opcode) { 259 | // Set Vx = Vy 260 | *(c.V + opcode.x()) = *(c.V + opcode.y()); 261 | } 262 | 263 | // OR Vx, Vy 264 | def _8xy1(c: *machine.Context, opcode: Opcode) { 265 | // Bitwise OR. Set Vx = Vx OR Vy 266 | *(c.V + opcode.x()) |= *(c.V + opcode.y()); 267 | } 268 | 269 | // AND Vx, Vy 270 | def _8xy2(c: *machine.Context, opcode: Opcode) { 271 | // Bitwise AND. Set Vx = Vx AND Vy 272 | *(c.V + opcode.x()) &= *(c.V + opcode.y()); 273 | } 274 | 275 | // XOR Vx, Vy 276 | def _8xy3(c: *machine.Context, opcode: Opcode) { 277 | // Bitwise XOR. Set Vx = Vx XOR Vy 278 | *(c.V + opcode.x()) ^= *(c.V + opcode.y()); 279 | } 280 | 281 | // ADD Vx, Vy 282 | def _8xy4(c: *machine.Context, opcode: Opcode) { 283 | // Set Vx = Vx + Vy, set VF = carry. 284 | 285 | let x = opcode.x(); 286 | let y = opcode.y(); 287 | let result = uint16(*(c.V + x)) + uint16(*(c.V + y)); 288 | 289 | *(c.V + x) = uint8(result); 290 | 291 | // If the result is greater than 8 bits (i.e., > 255,) VF is set to 1, else 0 292 | *(c.V + 0x0F) = if result > 255 { 1; } else { 0; }; 293 | } 294 | 295 | // SUB Vx, Vy 296 | def _8xy5(c: *machine.Context, opcode: Opcode) { 297 | // Set Vx = Vx - Vy, set VF = NOT borrow. 298 | 299 | let x = opcode.x(); 300 | let y = opcode.y(); 301 | 302 | let vx = *(c.V + x); 303 | let vy = *(c.V + y); 304 | 305 | // If Vx > Vy, then VF is set to 1, otherwise 0. 306 | *(c.V + 0x0F) = if vx > vy { 1; } else { 0; }; 307 | 308 | *(c.V + x) = vx - vy; 309 | } 310 | 311 | // SHR Vx 312 | def _8xy6(c: *machine.Context, opcode: Opcode) { 313 | // Right shift. Set Vx = Vx SHR 1 314 | let x = opcode.x(); 315 | let vx = *(c.V + x); 316 | 317 | // If the least-significant bit of Vx is 1, then VF is set to 1, otherwise 0. 318 | *(c.V + 0x0F) = if vx & 0x1 != 0 { 1; } else { 0; }; 319 | 320 | *(c.V + x) = vx >> 1; 321 | } 322 | 323 | // SUBN Vx, Vy 324 | def _8xy7(c: *machine.Context, opcode: Opcode) { 325 | // Set Vx = Vy - Vx, set VF = NOT borrow. 326 | 327 | let x = opcode.x(); 328 | let y = opcode.y(); 329 | 330 | let vx = *(c.V + x); 331 | let vy = *(c.V + y); 332 | 333 | // If Vy > Vx, then VF is set to 1, otherwise 0. 334 | *(c.V + 0x0F) = if vy > vx { 1; } else { 0; }; 335 | 336 | *(c.V + x) = vy - vx; 337 | } 338 | 339 | // SHL Vx 340 | def _8xyE(c: *machine.Context, opcode: Opcode) { 341 | // Left shift. Set Vx = Vx SHL 1 342 | let x = opcode.x(); 343 | let vx = *(c.V + x); 344 | 345 | // If the most-significant bit of Vx is 1, then VF is set to 1, otherwise 0. 346 | *(c.V + 0x0F) = if vx & 0x80 != 0 { 1; } else { 0; }; 347 | 348 | *(c.V + x) = vx << 1; 349 | } 350 | 351 | // SNE Vx, Vy 352 | def _9xy0(c: *machine.Context, opcode: Opcode) { 353 | // Skip next instruction if Vx != Vy 354 | if *(c.V + opcode.x()) != *(c.V + opcode.y()) { 355 | c.PC += 2; 356 | } 357 | } 358 | 359 | // LD I, nnn 360 | def _Annn(c: *machine.Context, opcode: Opcode) { 361 | // Set I = nnn 362 | c.I = opcode.address(); 363 | } 364 | 365 | // JP V0, nnn 366 | def _Bnnn(c: *machine.Context, opcode: Opcode) { 367 | // Jump to location nnn + V0 368 | c.PC = opcode.address() + uint16(*(c.V + 0)); 369 | } 370 | 371 | // RND Vx, kk 372 | def _Cxkk(c: *machine.Context, opcode: Opcode) { 373 | // Set Vx = random byte AND kk 374 | *(c.V + opcode.x()) = uint8(libc.rand() % 256) & opcode.data(); 375 | } 376 | 377 | // DRW Vx, Vy, nibble 378 | def _Dxyn(c: *machine.Context, opcode: Opcode) { 379 | // Display n-byte sprite starting at memory location I at (Vx, Vy), 380 | // set VF = collision. The interpreter reads n bytes from memory, starting 381 | // at the address stored in I. These bytes are then displayed as sprites 382 | // on screen at coordinates (Vx, Vy). Sprites are XORed onto the existing 383 | // screen. If this causes any pixels to be erased, VF is set to 1, 384 | // otherwise it is set to 0. If the sprite is positioned so part of it 385 | // is outside the coordinates of the display, it wraps around to the 386 | // opposite side of the screen. 387 | 388 | // Reset collision flag — VF 389 | *(c.V + 0xF) = 0; 390 | 391 | let sprite_size = opcode.n(); 392 | let y = opcode.y(); 393 | let x = opcode.x(); 394 | 395 | let ptr = mmu.at(c, c.I); 396 | 397 | let i = 0; 398 | while i < sprite_size { 399 | let j = 0; 400 | while j < 8 { 401 | // Get (x, y) of the sprite pixel 402 | let plot_x = (*(c.V + x) + j) & (c.width - 1); 403 | let plot_y = (*(c.V + y) + i) & (c.height - 1); 404 | 405 | // Get offset into the framebuffer 406 | let offset = uint16(plot_y) * uint16(c.width) + uint16(plot_x); 407 | 408 | // Get the pixel to be set and the pixel currently set 409 | let p_cur = *(c.framebuffer + offset); 410 | let p_new = if ((*(ptr + i) >> (7 - j)) & 1) != 0 { 1; } else { 0; }; 411 | 412 | // Set the collision flag if the displayed pixel is going to 413 | // be cleared. 414 | // NOTE: Ensure that we persist the collision flag 415 | // throughout the draw loop. If it gets set once; it must 416 | // be set at the end. 417 | *(c.V + 0xF) = if (p_cur == 1) and (p_new == 1) { 1; } else { *(c.V + 0xF); }; 418 | 419 | // Set the pixel (with XOR) 420 | *(c.framebuffer + offset) ^= p_new; 421 | 422 | j += 1; 423 | } 424 | i += 1; 425 | } 426 | } 427 | 428 | // SKP Vx 429 | def _Ex9E(c: *machine.Context, opcode: Opcode) { 430 | // Skip next instruction if key with the value of Vx is pressed 431 | 432 | let x = opcode.x(); 433 | let vx = *(c.V + x); 434 | let state = *(c.input + vx); 435 | 436 | if state { 437 | c.PC += 2; 438 | } 439 | } 440 | 441 | // SKNP Vx 442 | def _ExA1(c: *machine.Context, opcode: Opcode) { 443 | // Skip next instruction if key with the value of Vx is not pressed 444 | 445 | let x = opcode.x(); 446 | let vx = *(c.V + x); 447 | let state = *(c.input + vx); 448 | 449 | if not state { 450 | c.PC += 2; 451 | } 452 | } 453 | 454 | // LD Vx, DT 455 | def _Fx07(c: *machine.Context, opcode: Opcode) { 456 | // Set Vx = DT (Delay Timer) 457 | *(c.V + opcode.x()) = c.DT; 458 | } 459 | 460 | // LD DT, Vx 461 | def _Fx15(c: *machine.Context, opcode: Opcode) { 462 | // Set DT (Delay Timer) = Vx 463 | c.DT = *(c.V + opcode.x()); 464 | } 465 | 466 | // LD ST, Vx 467 | def _Fx18(c: *machine.Context, opcode: Opcode) { 468 | // Set ST (Sound Timer) = Vx 469 | c.ST = *(c.V + opcode.x()); 470 | } 471 | 472 | // ADD I, Vx 473 | def _Fx1E(c: *machine.Context, opcode: Opcode) { 474 | // Set I = I + Vx 475 | c.I += uint16(*(c.V + opcode.x())); 476 | } 477 | 478 | // LDF I, Vx 479 | def _Fx29(c: *machine.Context, opcode: Opcode) { 480 | // Set I = location of sprite for digit Vx. 481 | // The value of I is set to the location for the hexadecimal sprite 482 | // corresponding to the value of Vx. 483 | c.I = uint16(*(c.V + opcode.x())) * 5; 484 | } 485 | 486 | // BCD [I], Vx 487 | def _Fx33(c: *machine.Context, opcode: Opcode) { 488 | // Write the bcd representation of Vx to memory location I through I + 2. 489 | let value = *(c.V + opcode.x()); 490 | mmu.write(c, c.I + 0, value / 100); 491 | mmu.write(c, c.I + 1, (value % 100) / 10); 492 | mmu.write(c, c.I + 2, value % 10); 493 | } 494 | 495 | // LD [I], Vx 496 | def _Fx55(c: *machine.Context, opcode: Opcode) { 497 | // Store registers V0 through Vx in memory starting at location I. 498 | let x = opcode.x(); 499 | libc.memcpy(mmu.at(c, c.I), c.V, uint64(x + 1)); 500 | } 501 | 502 | // LD Vx, [I] 503 | def _Fx65(c: *machine.Context, opcode: Opcode) { 504 | // Read registers V0 through Vx from memory starting at location I. 505 | let x = opcode.x(); 506 | libc.memcpy(c.V, mmu.at(c, c.I), uint64(x + 1)); 507 | } 508 | --------------------------------------------------------------------------------