├── .circleci └── config.yml ├── .gitignore ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── cmd └── gopher-boy │ ├── main.go │ └── wasm_main.go ├── docs ├── debug.html ├── debugger │ └── index.js ├── dino.gb ├── gopher-boy.png ├── gopher.png ├── hello.gb ├── index.html ├── index.js ├── main.wasm ├── opus5.gb ├── style.css ├── tobu.gb └── wasm_exec.js ├── go.mod ├── go.sum ├── makefile ├── pkg ├── bus │ ├── bus.go │ └── bus_test.go ├── cartridge │ ├── cartridge.go │ ├── mbc.go │ ├── mbc0.go │ └── mbc1.go ├── constants │ └── window.go ├── cpu │ ├── cpu.go │ ├── cpu_test.go │ └── helpers.go ├── gb │ ├── gb.go │ └── gb_test.go ├── gpu │ ├── gpu.go │ └── gpu_test.go ├── interfaces │ ├── bus │ │ └── bus.go │ ├── interrupt │ │ └── interrupt.go │ ├── logger │ │ └── logger.go │ ├── pad │ │ └── pad.go │ └── window │ │ └── window.go ├── interrupt │ └── interrupt.go ├── logger │ └── logger.go ├── mocks │ └── bus.go ├── pad │ └── pad.go ├── ram │ └── ram.go ├── rom │ └── rom.go ├── serial │ └── serial.go ├── timer │ └── timer.go ├── types │ └── types.go ├── utils │ ├── byte.go │ └── loader.go └── window │ ├── native.go │ ├── wasm.go │ └── window.go ├── roms ├── acceptance │ ├── add_sp_e_timing.gb │ ├── bits │ │ ├── mem_oam.gb │ │ ├── reg_f.gb │ │ └── unused_hwio-GS.gb │ ├── boot_div-S.gb │ ├── boot_div-dmg0.gb │ ├── boot_div-dmgABCmgb.gb │ ├── boot_div2-S.gb │ ├── boot_hwio-S.gb │ ├── boot_hwio-dmg0.gb │ ├── boot_hwio-dmgABCmgb.gb │ ├── boot_regs-dmg0.gb │ ├── boot_regs-dmgABC.gb │ ├── boot_regs-mgb.gb │ ├── boot_regs-sgb.gb │ ├── boot_regs-sgb2.gb │ ├── call_cc_timing.gb │ ├── call_cc_timing2.gb │ ├── call_timing.gb │ ├── call_timing2.gb │ ├── di_timing-GS.gb │ ├── div_timing.gb │ ├── ei_sequence.gb │ ├── ei_timing.gb │ ├── halt_ime0_ei.gb │ ├── halt_ime0_nointr_timing.gb │ ├── halt_ime1_timing.gb │ ├── halt_ime1_timing2-GS.gb │ ├── if_ie_registers.gb │ ├── instr │ │ └── daa.gb │ ├── interrupts │ │ └── ie_push.gb │ ├── intr_timing.gb │ ├── jp_cc_timing.gb │ ├── jp_timing.gb │ ├── ld_hl_sp_e_timing.gb │ ├── oam_dma │ │ ├── basic.gb │ │ ├── reg_read.gb │ │ └── sources-dmgABCmgbS.gb │ ├── oam_dma_restart.gb │ ├── oam_dma_start.gb │ ├── oam_dma_timing.gb │ ├── pop_timing.gb │ ├── ppu │ │ ├── hblank_ly_scx_timing-GS.gb │ │ ├── intr_1_2_timing-GS.gb │ │ ├── intr_2_0_timing.gb │ │ ├── intr_2_mode0_timing.gb │ │ ├── intr_2_mode0_timing_sprites.gb │ │ ├── intr_2_mode3_timing.gb │ │ ├── intr_2_oam_ok_timing.gb │ │ ├── lcdon_timing-dmgABCmgbS.gb │ │ ├── lcdon_write_timing-GS.gb │ │ ├── stat_irq_blocking.gb │ │ ├── stat_lyc_onoff.gb │ │ └── vblank_stat_intr-GS.gb │ ├── push_timing.gb │ ├── rapid_di_ei.gb │ ├── ret_cc_timing.gb │ ├── ret_timing.gb │ ├── reti_intr_timing.gb │ ├── reti_timing.gb │ ├── rst_timing.gb │ ├── serial │ │ └── boot_sclk_align-dmgABCmgb.gb │ └── timer │ │ ├── div_write.gb │ │ ├── rapid_toggle.gb │ │ ├── tim00.gb │ │ ├── tim00_div_trigger.gb │ │ ├── tim01.gb │ │ ├── tim01_div_trigger.gb │ │ ├── tim10.gb │ │ ├── tim10_div_trigger.gb │ │ ├── tim11.gb │ │ ├── tim11_div_trigger.gb │ │ ├── tima_reload.gb │ │ ├── tima_write_reloading.gb │ │ └── tma_write_reloading.gb ├── cpu_instrs │ ├── 01-special.gb │ ├── 02-interrupts.gb │ ├── 03-op sp,hl.gb │ ├── 04-op r,imm.gb │ ├── 05-op rp.gb │ ├── 06-ld r,r.gb │ ├── 07-jr,jp,call,ret,rst.gb │ ├── 08-misc instrs.gb │ ├── 09-op r,r.gb │ ├── 10-bit ops.gb │ ├── 11-op a,(hl).gb │ └── cpu_instrs.gb ├── dino │ └── dino.gb ├── emulator-only │ ├── mbc1 │ │ ├── bits_bank1.gb │ │ ├── bits_bank2.gb │ │ ├── bits_mode.gb │ │ ├── bits_ramg.gb │ │ ├── multicart_rom_8Mb.gb │ │ ├── ram_256kb.gb │ │ ├── ram_64kb.gb │ │ ├── rom_16Mb.gb │ │ ├── rom_1Mb.gb │ │ ├── rom_2Mb.gb │ │ ├── rom_4Mb.gb │ │ ├── rom_512kb.gb │ │ └── rom_8Mb.gb │ ├── mbc2 │ │ ├── bits_ramg.gb │ │ ├── bits_romb.gb │ │ ├── bits_unused.gb │ │ ├── ram.gb │ │ ├── rom_1Mb.gb │ │ ├── rom_2Mb.gb │ │ └── rom_512kb.gb │ └── mbc5 │ │ ├── rom_16Mb.gb │ │ ├── rom_1Mb.gb │ │ ├── rom_2Mb.gb │ │ ├── rom_32Mb.gb │ │ ├── rom_4Mb.gb │ │ ├── rom_512kb.gb │ │ ├── rom_64Mb.gb │ │ └── rom_8Mb.gb ├── hangman │ └── Hangman (PD).gb ├── helloworld │ ├── LICENSE │ ├── README.md │ ├── hardware.inc │ ├── hello.gb │ ├── hello.o │ ├── hello.s │ ├── make.cmd │ └── speccy.chr ├── instr_timing │ ├── instr_timing.gb │ ├── readme.txt │ └── source │ │ ├── common │ │ ├── build_gbs.s │ │ ├── build_rom.s │ │ ├── console.bin │ │ ├── console.s │ │ ├── crc.s │ │ ├── delay.s │ │ ├── gb.inc │ │ ├── macros.inc │ │ ├── numbers.s │ │ ├── printing.s │ │ ├── runtime.s │ │ ├── testing.s │ │ └── timer.s │ │ ├── instr_timing.s │ │ ├── linkfile │ │ └── shell.inc ├── interrupt_time │ ├── interrupt_time.gb │ └── interrupt_time.s ├── mbc1 │ ├── bits_bank1.gb │ ├── bits_bank2.gb │ ├── bits_mode.gb │ ├── bits_ramg.gb │ ├── multicart_rom_8Mb.gb │ ├── ram_256kb.gb │ ├── ram_64kb.gb │ ├── rom_16Mb.gb │ ├── rom_1Mb.gb │ ├── rom_2Mb.gb │ ├── rom_4Mb.gb │ ├── rom_512kb.gb │ └── rom_8Mb.gb ├── mem_timing │ ├── individual │ │ ├── 01-read_timing.gb │ │ ├── 02-write_timing.gb │ │ └── 03-modify_timing.gb │ ├── mem_timing.gb │ ├── readme.txt │ └── source │ │ ├── 01-read_timing.s │ │ ├── 02-write_timing.s │ │ ├── 03-modify_timing.s │ │ ├── common │ │ ├── build_gbs.s │ │ ├── build_rom.s │ │ ├── console.bin │ │ ├── console.s │ │ ├── crc.s │ │ ├── delay.s │ │ ├── gb.inc │ │ ├── macros.inc │ │ ├── numbers.s │ │ ├── printing.s │ │ ├── runtime.s │ │ ├── testing.s │ │ └── tima_64.s │ │ ├── linkfile │ │ └── shell.inc ├── opus5 │ └── opus5.gb ├── tictactoe │ └── GB-TicTacToe.gb └── tobu │ └── tobu.gb ├── screenshot ├── dino.png ├── drmario.png ├── mario.png ├── tetris.png └── tobu.png └── test ├── actual ├── bits_bank1.png ├── cpu_instr.png ├── daa.png ├── div_write.png ├── hello_world.png ├── if_ie_registers.png ├── mem_oam.png ├── opus5.png ├── reg_f.png ├── tictactoe.png ├── tim00.png ├── tim01.png ├── tim10.png ├── tim11.png └── tobu.png ├── diff ├── bits_bank1.png ├── cpu_instr.png ├── hello_world.png ├── opus5.png └── tictactoe.png └── expect ├── bits_bank1.png ├── cpu_instr.png ├── daa.png ├── div_write.png ├── hello_world.png ├── if_ie_registers.png ├── mem_oam.png ├── opus5.png ├── reg_f.png ├── tictactoe.png ├── tim00.png ├── tim01.png ├── tim10.png ├── tim11.png └── tobu.png /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | defaults: &defaults 2 | working_directory: ~/gopher-boy 3 | docker: 4 | - image: bokuweb/node-go-with-browser:latest 5 | environment: 6 | TZ: Asia/Tokyo 7 | GOPATH: /home/circleci/go 8 | version: 2 9 | jobs: 10 | build: 11 | <<: *defaults 12 | steps: 13 | - checkout 14 | - run: 15 | name: Install dependencies 16 | command: apt-get install -yq xorg-dev libx11-dev libxrandr-dev libxinerama-dev libxcursor-dev libxi-dev libopenal-dev libasound2-dev libgl1-mesa-dev 17 | - run: 18 | name: Install reg 19 | command: npm i -g reg-cli 20 | - run: 21 | name: Test 22 | command: | 23 | make -B test 24 | - run: 25 | name: Reg 26 | command: reg-cli ./test/actual ./test/expect ./test/diff 27 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | reg.json 2 | logs/*.log 3 | *.sym 4 | *.wat 5 | logs 6 | gopher-boy 7 | docs/*.gb 8 | main.wat -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "Launch", 9 | "type": "go", 10 | "request": "launch", 11 | "mode": "auto", 12 | "program": "${workspaceRoot}/cmd/gopher-boy/main.go", 13 | "env": { 14 | "GO111MODULE": "on", 15 | "LEVEL": "info" 16 | }, 17 | "args": ["${workspaceRoot}/roms/cpu_instrs/02-interrupts.gb"] 18 | } 19 | ] 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 bokuweb 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # gopher-boy 2 | 3 | [![CircleCI](https://circleci.com/gh/bokuweb/gopher-boy/tree/master.svg?style=svg)](https://circleci.com/gh/bokuweb/gopher-boy/tree/master) [![Go Report Card](https://goreportcard.com/badge/github.com/bokuweb/gopher-boy)](https://goreportcard.com/report/github.com/bokuweb/gopher-boy) 4 | 5 |      6 | 7 | ## Installation 8 | 9 | you can install `gopher-boy` with following comand. 10 | 11 | ```sh 12 | go get github.com/bokuweb/gopher-boy/cmd/gopher-boy 13 | ``` 14 | 15 | This emulator uses the go library [pixel](https://github.com/faiface/pixel), which requires OpenGL. You may need to install some requirements which can be found on the [pixels readme](https://github.com/faiface/pixel#requirements). 16 | 17 | ## Requirements 18 | 19 | - go1.12.7 20 | 21 | ## Development 22 | 23 | #### Native 24 | 25 | ``` 26 | GO111MODULE=on go run -tags="native" cmd/gopher-boy/main.go hello.gb 27 | ``` 28 | 29 | #### WebAssembly 30 | 31 | ``` 32 | make build-wasm 33 | make serve 34 | ``` 35 | 36 | ## Usage 37 | 38 | ```sh 39 | gopher-boy YOUR_GAMEBOY_ROM.gb 40 | ``` 41 | 42 | ### Keymap 43 | 44 | | keyboard | game pad | 45 | | -------------------- | ------------- | 46 | | | ← button | 47 | | | ↑ button | 48 | | | ↓ button | 49 | | | → button | 50 | | Z | A button | 51 | | X | B button | 52 | | Enter | Start button | 53 | | Backspace | Select button | 54 | 55 | ## Testing 56 | 57 | ``` 58 | make test 59 | ``` 60 | 61 | ### Current status 62 | 63 | #### Blargg's test ROM 64 | 65 | | ROM | Result | 66 | | ------------ | --------- | 67 | | cpu_instrs | ✅ | 68 | | instr_timing | ❌ (#255) | 69 | 70 | #### mooneye-gb's test ROM 71 | 72 | | ROM | Result | 73 | | ----------------------------- | ------ | 74 | | emulator-only/mbc1/bits_bank1 | ✅ | 75 | | acceptance/instr/daa | ✅ | 76 | | acceptance/timer/div_write | ✅ | 77 | | acceptance/timer/tim00 | ✅ | 78 | | acceptance/timer/tim01 | ✅ | 79 | | acceptance/timer/tim10 | ✅ | 80 | | acceptance/timer/tim11 | ✅ | 81 | | acceptance/if_ie_registers | ✅ | 82 | 83 | ### Visual regression test 84 | 85 | Please install `reg-cli`. 86 | 87 | ```sh 88 | npm i -g reg-cli 89 | ``` 90 | 91 | Execute regression test 92 | 93 | ```sh 94 | make reg 95 | ``` 96 | 97 | Update expected images 98 | 99 | ```sh 100 | make reg-update 101 | ``` 102 | 103 | ## Credit 104 | 105 | "gopher" by Renée French CC-BY-3.0 106 | 107 | ## Known Bugs and TODO list 108 | 109 | PR welcome :) 110 | 111 | - [ ] Implement APU 112 | - [ ] Support scale option 113 | - [x] Support WebAssembly 114 | - [x] 8\*16 sprite 115 | - [ ] LCD interrupt 116 | - [ ] Keypad interrupt 117 | - [ ] cartridges 118 | - [x] Support ROM+MBC1+RAM+BATT catridge 119 | - [ ] Support ROM+MBC2 catridge 120 | - [ ] Support ROM+MBC2+BATTERY catridge 121 | - [ ] Support ROM+RAM catridge 122 | - [ ] Support ROM+RAM+BATTERY catridge 123 | - [ ] Support ROM+MMM01 catridge 124 | - [ ] Support ROM+MMM01+SRAM catridge 125 | - [ ] Support ROM+MMM01+SRAM+BATT catridge 126 | - [ ] Support ROM+MBC3+RAM catridge 127 | - [ ] Support ROM+MBC3+RAM+BATT catridge 128 | - [ ] Support ROM+MBC5 catridge 129 | - [ ] Support ROM+MBC5+RAM catridge 130 | - [ ] Support ROM+MBC5+RAM+BATT catridge 131 | - [ ] Support ROM+MBC5+RUMBLE catridge 132 | - [ ] Support ROM+MBC5+RUMBLE+SRAM catridge 133 | - [ ] Support ROM+MBC5+RUMBLE+SRAM+BATT catridge 134 | - [ ] Support Pocket Camera catridge 135 | - [ ] Support Bandai TAMA5 catridge 136 | - [ ] Support Hudson HuC-3 catridge 137 | -------------------------------------------------------------------------------- /cmd/gopher-boy/main.go: -------------------------------------------------------------------------------- 1 | // +build native 2 | 3 | package main 4 | 5 | import ( 6 | "errors" 7 | "log" 8 | "os" 9 | 10 | "github.com/bokuweb/gopher-boy/pkg/interrupt" 11 | "github.com/bokuweb/gopher-boy/pkg/pad" 12 | 13 | "github.com/bokuweb/gopher-boy/pkg/gpu" 14 | "github.com/bokuweb/gopher-boy/pkg/timer" 15 | "github.com/bokuweb/gopher-boy/pkg/utils" 16 | 17 | "github.com/bokuweb/gopher-boy/pkg/cpu" 18 | "github.com/bokuweb/gopher-boy/pkg/gb" 19 | "github.com/bokuweb/gopher-boy/pkg/logger" 20 | "github.com/bokuweb/gopher-boy/pkg/ram" 21 | 22 | "github.com/bokuweb/gopher-boy/pkg/bus" 23 | "github.com/bokuweb/gopher-boy/pkg/cartridge" 24 | "github.com/bokuweb/gopher-boy/pkg/window" 25 | ) 26 | 27 | func main() { 28 | level := "Debug" 29 | if os.Getenv("LEVEL") != "" { 30 | level = os.Getenv("LEVEL") 31 | } 32 | l := logger.NewLogger(logger.LogLevel(level)) 33 | if len(os.Args) != 2 { 34 | log.Fatalf("ERROR: %v", errors.New("Please specify the ROM")) 35 | } 36 | file := os.Args[1] 37 | log.Println(file) 38 | buf, err := utils.LoadROM(file) 39 | if err != nil { 40 | log.Fatalf("ERROR: %v", errors.New("Failed to load ROM")) 41 | } 42 | cart, err := cartridge.NewCartridge(buf) 43 | if err != nil { 44 | log.Fatalf("ERROR: %v", errors.New("Failed to create cartridge")) 45 | } 46 | vRAM := ram.NewRAM(0x2000) 47 | wRAM := ram.NewRAM(0x2000) 48 | hRAM := ram.NewRAM(0x80) 49 | oamRAM := ram.NewRAM(0xA0) 50 | gpu := gpu.NewGPU() 51 | t := timer.NewTimer() 52 | pad := pad.NewPad() 53 | irq := interrupt.NewInterrupt() 54 | b := bus.NewBus(l, cart, gpu, vRAM, wRAM, hRAM, oamRAM, t, irq, pad) 55 | gpu.Init(b, irq) 56 | win := window.NewWindow(pad) 57 | emu := gb.NewGB(cpu.NewCPU(l, b, irq), gpu, t, irq, win) 58 | win.Run(func() { 59 | win.Init() 60 | emu.Start() 61 | }) 62 | } 63 | -------------------------------------------------------------------------------- /cmd/gopher-boy/wasm_main.go: -------------------------------------------------------------------------------- 1 | // +build wasm 2 | 3 | package main 4 | 5 | import ( 6 | "errors" 7 | 8 | // "image/color" 9 | "log" 10 | "syscall/js" 11 | 12 | "github.com/bokuweb/gopher-boy/pkg/interrupt" 13 | "github.com/bokuweb/gopher-boy/pkg/logger" 14 | "github.com/bokuweb/gopher-boy/pkg/pad" 15 | "github.com/bokuweb/gopher-boy/pkg/types" 16 | "github.com/bokuweb/gopher-boy/pkg/window" 17 | 18 | "github.com/bokuweb/gopher-boy/pkg/gpu" 19 | "github.com/bokuweb/gopher-boy/pkg/timer" 20 | 21 | "github.com/bokuweb/gopher-boy/pkg/cpu" 22 | "github.com/bokuweb/gopher-boy/pkg/gb" 23 | "github.com/bokuweb/gopher-boy/pkg/ram" 24 | 25 | "github.com/bokuweb/gopher-boy/pkg/bus" 26 | "github.com/bokuweb/gopher-boy/pkg/cartridge" 27 | ) 28 | 29 | func newGB(this js.Value, args []js.Value) interface{} { 30 | buf := []byte{} 31 | for i := 0; i < args[0].Get("length").Int(); i++ { 32 | buf = append(buf, byte(args[0].Index(i).Int())) 33 | } 34 | l := logger.NewLogger(logger.LogLevel("INFO")) 35 | cart, err := cartridge.NewCartridge(buf) 36 | if err != nil { 37 | log.Fatalf("ERROR: %v", errors.New("Failed to create cartridge")) 38 | } 39 | vRAM := ram.NewRAM(0x2000) 40 | wRAM := ram.NewRAM(0x2000) 41 | hRAM := ram.NewRAM(0x80) 42 | oamRAM := ram.NewRAM(0xA0) 43 | gpu := gpu.NewGPU() 44 | t := timer.NewTimer() 45 | pad := pad.NewPad() 46 | irq := interrupt.NewInterrupt() 47 | b := bus.NewBus(l, cart, gpu, vRAM, wRAM, hRAM, oamRAM, t, irq, pad) 48 | gpu.Init(b, irq) 49 | 50 | win := window.NewWindow(pad) 51 | emu := gb.NewGB(cpu.NewCPU(l, b, irq), gpu, t, irq, win) 52 | 53 | this.Set("next", js.FuncOf(func(this js.Value, args []js.Value) interface{} { 54 | img := emu.Next() 55 | return js.CopyBytesToJS(args[0], img) 56 | })) 57 | this.Set("keyDown", js.FuncOf(func(this js.Value, args []js.Value) interface{} { 58 | win.KeyDown(byte(args[0].Int())) 59 | return nil 60 | })) 61 | this.Set("keyUp", js.FuncOf(func(this js.Value, args []js.Value) interface{} { 62 | win.KeyUp(byte(args[0].Int())) 63 | return nil 64 | })) 65 | // for Debuging 66 | this.Set("getVRAM", js.FuncOf(func(this js.Value, args []js.Value) interface{} { 67 | d := vRAM.GetBuf() 68 | return js.CopyBytesToJS(args[0], d) 69 | })) 70 | this.Set("getOAMRAM", js.FuncOf(func(this js.Value, args []js.Value) interface{} { 71 | d := oamRAM.GetBuf() 72 | return js.CopyBytesToJS(args[0], d) 73 | })) 74 | this.Set("readGPU", js.FuncOf(func(this js.Value, args []js.Value) interface{} { 75 | return gpu.Read(types.Word(args[0].Int())) 76 | })) 77 | return this 78 | } 79 | 80 | func main() { 81 | w := js.Global() 82 | w.Set("GB", js.FuncOf(newGB)) 83 | select {} 84 | } 85 | -------------------------------------------------------------------------------- /docs/dino.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/docs/dino.gb -------------------------------------------------------------------------------- /docs/gopher-boy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/docs/gopher-boy.png -------------------------------------------------------------------------------- /docs/gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/docs/gopher.png -------------------------------------------------------------------------------- /docs/hello.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/docs/hello.gb -------------------------------------------------------------------------------- /docs/index.js: -------------------------------------------------------------------------------- 1 | import { renderDebugInfo } from "./debugger/index.js"; 2 | 3 | window.onload = async () => { 4 | document.addEventListener( 5 | "touchmove", 6 | function(event) { 7 | if (event.scale !== 1) { 8 | event.preventDefault(); 9 | } 10 | }, 11 | false 12 | ); 13 | 14 | const go = new Go(); 15 | const res = await fetch("./main.wasm"); 16 | const bytes = await res.arrayBuffer(); 17 | const { instance } = await WebAssembly.instantiate(bytes, go.importObject); 18 | go.run(instance); 19 | 20 | const canvas = document.querySelector(".game"); 21 | const input = document.querySelector("#file_upload"); 22 | 23 | // GPU 24 | 25 | const init = async buf => { 26 | const ctx = canvas.getContext("2d"); 27 | const image = ctx.createImageData(160, 144); 28 | 29 | if (!buf) { 30 | const rom = await fetch("./tobu.gb"); 31 | buf = await rom.arrayBuffer(); 32 | } 33 | let gb = new GB(new Uint8Array(buf)); 34 | 35 | document.querySelector(".led").style.background = "red"; 36 | 37 | const frame = () => { 38 | if (!gb) return; 39 | gb.next(image.data); 40 | ctx.putImageData(image, 0, 0); 41 | renderDebugInfo(gb); 42 | window.requestAnimationFrame(frame); 43 | }; 44 | frame(); 45 | 46 | const onKeydown = e => { 47 | switch (e.key) { 48 | case "z": 49 | return gb.keyDown(0x01); 50 | case "x": 51 | return gb.keyDown(0x02); 52 | case "Backspace": 53 | return gb.keyDown(0x04); 54 | case "Enter": 55 | return gb.keyDown(0x08); 56 | case "ArrowLeft": 57 | return gb.keyDown(0x20); 58 | case "ArrowUp": 59 | return gb.keyDown(0x40); 60 | case "ArrowRight": 61 | return gb.keyDown(0x10); 62 | case "ArrowDown": 63 | return gb.keyDown(0x80); 64 | } 65 | }; 66 | 67 | const onKeyup = e => { 68 | switch (e.key) { 69 | case "z": 70 | return gb.keyUp(0x01); 71 | case "x": 72 | return gb.keyUp(0x02); 73 | case "Backspace": 74 | return gb.keyUp(0x04); 75 | case "Enter": 76 | return gb.keyUp(0x08); 77 | case "ArrowLeft": 78 | return gb.keyUp(0x20); 79 | case "ArrowUp": 80 | return gb.keyUp(0x40); 81 | case "ArrowRight": 82 | return gb.keyUp(0x10); 83 | case "ArrowDown": 84 | return gb.keyUp(0x80); 85 | } 86 | }; 87 | 88 | const removeHandler = classname => { 89 | const el = document.querySelector(`.${classname}`); 90 | const elClone = el.cloneNode(true); 91 | el.parentNode.replaceChild(elClone, el); 92 | }; 93 | 94 | const cleanup = () => { 95 | input.removeEventListener("change", onFileChange); 96 | window.removeEventListener("keydown", onKeydown); 97 | window.removeEventListener("keyup", onKeyup); 98 | removeHandler("buttonA"); 99 | removeHandler("buttonB"); 100 | removeHandler("buttonSelect"); 101 | removeHandler("buttonStart"); 102 | removeHandler("right"); 103 | removeHandler("left"); 104 | removeHandler("up"); 105 | removeHandler("down"); 106 | gb = null; 107 | }; 108 | 109 | window.addEventListener("keydown", onKeydown); 110 | 111 | window.addEventListener("keyup", onKeyup); 112 | 113 | const addHandler = (classname, bit) => { 114 | const el = document.querySelector(`.${classname}`); 115 | el.addEventListener("mousedown", e => { 116 | return gb.keyDown(bit); 117 | }); 118 | el.addEventListener( 119 | "touchstart", 120 | e => { 121 | e.preventDefault(); 122 | navigator.vibrate && navigator.vibrate(30); 123 | return gb.keyDown(bit); 124 | }, 125 | true 126 | ); 127 | 128 | el.addEventListener("mouseup", e => { 129 | return gb.keyUp(bit); 130 | }); 131 | el.addEventListener( 132 | "touchend", 133 | e => { 134 | e.preventDefault(); 135 | return gb.keyUp(bit); 136 | }, 137 | true 138 | ); 139 | }; 140 | 141 | addHandler("buttonA", 0x01); 142 | addHandler("buttonB", 0x02); 143 | addHandler("buttonSelect", 0x04); 144 | addHandler("buttonStart", 0x08); 145 | 146 | addHandler("right", 0x10); 147 | addHandler("left", 0x20); 148 | addHandler("up", 0x40); 149 | addHandler("down", 0x80); 150 | }; 151 | 152 | init(); 153 | }; 154 | -------------------------------------------------------------------------------- /docs/main.wasm: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/docs/main.wasm -------------------------------------------------------------------------------- /docs/opus5.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/docs/opus5.gb -------------------------------------------------------------------------------- /docs/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | padding: 0; 3 | margin: 0; 4 | 5 | -webkit-text-size-adjust: none; 6 | touch-action: manipulation; 7 | } 8 | 9 | .title { 10 | position: fixed; 11 | top: 48px; 12 | left: 48px; 13 | display: flex; 14 | align-items: center; 15 | } 16 | 17 | .title h1 { 18 | font-size: 32px; 19 | color: white; 20 | padding-top: 12px; 21 | padding-left: 12px; 22 | } 23 | 24 | .title img { 25 | height: 64px; 26 | width: auto; 27 | } 28 | 29 | .content { 30 | width: 400px; 31 | height: 680px; 32 | display: flex; 33 | flex-direction: column; 34 | align-items: center; 35 | } 36 | 37 | .content div.control { 38 | display: flex; 39 | width: 400px; 40 | height: 160px; 41 | align-items: center; 42 | justify-content: center; 43 | } 44 | 45 | .content input { 46 | display: none; 47 | } 48 | 49 | .gameboy { 50 | height: 480px; 51 | width: 300px; 52 | position: relative; 53 | background: #eee; 54 | border-radius: 10px 10px 50px 10px; 55 | overflow: hidden; 56 | box-shadow: 5px 5px rgba(0, 0, 0, 0.1); 57 | /*cursor: pointer*/ 58 | } 59 | .gameboy:before { 60 | position: absolute; 61 | width: 500px; 62 | height: 20px; 63 | border-bottom: 3px solid #e0e0e0; 64 | content: ""; 65 | } 66 | .gameboy:after { 67 | position: absolute; 68 | width: 240px; 69 | height: 20px; 70 | left: 24px; 71 | border-left: 3px solid #e0e0e0; 72 | border-right: 3px solid #e0e0e0; 73 | content: ""; 74 | } 75 | .screen { 76 | position: absolute; 77 | width: 252px; 78 | height: 188px; 79 | left: 24px; 80 | top: 40px; 81 | background: #777; 82 | border-radius: 5px 5px 40px 5px; 83 | } 84 | canvas.game { 85 | background: #aec5a0; 86 | position: absolute; 87 | top: 22px; 88 | left: 48px; 89 | } 90 | 91 | .led { 92 | position: absolute; 93 | height: 8px; 94 | width: 8px; 95 | top: 107px; 96 | left: 42px; 97 | border-radius: 100%; 98 | box-shadow: 1px 1px 0 #400 inset; 99 | background: #600; 100 | content: ""; 101 | } 102 | 103 | .pad { 104 | position: absolute; 105 | width: 26px; 106 | height: 80px; 107 | top: 296px; 108 | left: 50px; 109 | background: #444; 110 | border-radius: 3px; 111 | z-index: 998; 112 | } 113 | .pad:after { 114 | position: absolute; 115 | width: 80px; 116 | height: 26px; 117 | top: 26px; 118 | left: -26px; 119 | background: #444; 120 | border-radius: 3px; 121 | content: ""; 122 | z-index: 999; 123 | } 124 | .pad:before { 125 | position: absolute; 126 | height: 26px; 127 | width: 26px; 128 | top: 26px; 129 | border-radius: 100%; 130 | background: #333; 131 | content: ""; 132 | z-index: 1000; 133 | } 134 | 135 | .up { 136 | position: absolute; 137 | width: 46px; 138 | height: 50px; 139 | top: 276px; 140 | left: 40px; 141 | border-radius: 3px; 142 | z-index: 998; 143 | } 144 | 145 | .down { 146 | position: absolute; 147 | width: 46px; 148 | height: 50px; 149 | top: 350px; 150 | left: 40px; 151 | border-radius: 3px; 152 | z-index: 998; 153 | } 154 | 155 | .right { 156 | position: absolute; 157 | width: 46px; 158 | height: 50px; 159 | top: 310px; 160 | left: 78px; 161 | border-radius: 3px; 162 | z-index: 998; 163 | } 164 | 165 | .left { 166 | position: absolute; 167 | width: 46px; 168 | height: 50px; 169 | top: 310px; 170 | left: 8px; 171 | border-radius: 3px; 172 | z-index: 998; 173 | } 174 | 175 | .hole { 176 | position: absolute; 177 | height: 46px; 178 | width: 6px; 179 | top: 436px; 180 | left: 190px; 181 | border-radius: 2px; 182 | background: #ddd; 183 | -webkit-transform: rotate(-30deg); 184 | box-shadow: 0px 0px #ddd, 17px 0px #ddd, 34px 0px #ddd, 51px 0px #ddd, 185 | 68px 0px #ddd, 85px 0px #ddd; 186 | content: ""; 187 | } 188 | .buttonB { 189 | position: absolute; 190 | width: 36px; 191 | height: 36px; 192 | top: 322px; 193 | right: 69px; 194 | border-radius: 100%; 195 | background: #a93671; 196 | } 197 | 198 | .buttonA { 199 | position: absolute; 200 | width: 36px; 201 | height: 36px; 202 | top: 300px; 203 | right: 24px; 204 | border-radius: 100%; 205 | background: #a93671; 206 | } 207 | 208 | .buttonSelect { 209 | position: absolute; 210 | height: 40px; 211 | width: 12px; 212 | bottom: 52px; 213 | left: 102px; 214 | background: #999; 215 | border-radius: 10px; 216 | -webkit-transform: rotate(63deg); 217 | cursor: pointer; 218 | } 219 | 220 | .buttonStart { 221 | position: absolute; 222 | height: 40px; 223 | width: 12px; 224 | bottom: 58px; 225 | left: 156px; 226 | background: #999; 227 | border-radius: 10px; 228 | -webkit-transform: rotate(63deg); 229 | cursor: pointer; 230 | } 231 | 232 | .github-link { 233 | position: fixed; 234 | bottom: 68px; 235 | right: 68px; 236 | color: black; 237 | text-decoration: none; 238 | } 239 | 240 | .nes-octocat { 241 | position: absolute; 242 | top: 88px; 243 | left: 170px; 244 | } 245 | 246 | .nes-select { 247 | width: 160px; 248 | margin-left: 16px; 249 | } 250 | 251 | @media screen and (max-width: 780px) { 252 | .title { 253 | display: none; 254 | } 255 | 256 | .github-link { 257 | display: none; 258 | } 259 | } 260 | -------------------------------------------------------------------------------- /docs/tobu.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/docs/tobu.gb -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/bokuweb/gopher-boy 2 | 3 | require ( 4 | github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 // indirect 5 | github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 // indirect 6 | github.com/faiface/pixel v0.8.0 7 | github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 // indirect 8 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 // indirect 9 | github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7 // indirect 10 | github.com/pkg/errors v0.8.1 // indirect 11 | github.com/stretchr/testify v1.3.0 12 | golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff 13 | ) 14 | 15 | go 1.13 16 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= 2 | github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 3 | github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380 h1:FvZ0mIGh6b3kOITxUnxS3tLZMh7yEoHo75v3/AgUqg0= 4 | github.com/faiface/glhf v0.0.0-20181018222622-82a6317ac380/go.mod h1:zqnPFFIuYFFxl7uH2gYByJwIVKG7fRqlqQCbzAnHs9g= 5 | github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3 h1:baVdMKlASEHrj19iqjARrPbaRisD7EuZEVJj6ZMLl1Q= 6 | github.com/faiface/mainthread v0.0.0-20171120011319-8b78f0a41ae3/go.mod h1:VEPNJUlxl5KdWjDvz6Q1l+rJlxF2i6xqDeGuGAxa87M= 7 | github.com/faiface/pixel v0.8.0 h1:phOHW6ixfMAKRamjnvhI6FFI2VRyPEq7+LmmkDGXB/4= 8 | github.com/faiface/pixel v0.8.0/go.mod h1:CEUU/s9E82Kqp01Boj1O67KnBskqiLghANqvUJGgDAM= 9 | github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw= 10 | github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk= 11 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0= 12 | github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= 13 | github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7 h1:THttjeRn1iiz69E875U6gAik8KTWk/JYAHoSVpUxBBI= 14 | github.com/go-gl/mathgl v0.0.0-20190416160123-c4601bc793c7/go.mod h1:yhpkQzEiH9yPyxDUGzkmgScbaBVlhC06qodikEM0ZwQ= 15 | github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= 16 | github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= 17 | github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= 18 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 19 | github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= 20 | github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= 21 | github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= 22 | github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= 23 | golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f h1:FO4MZ3N56GnxbqxGKqh+YTzUWQ2sDwtFQEZgLOxh9Jc= 24 | golang.org/x/image v0.0.0-20190321063152-3fc05d484e9f/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 25 | golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff h1:+2zgJKVDVAz/BWSsuniCmU1kLCjL88Z8/kv39xCI9NQ= 26 | golang.org/x/image v0.0.0-20190523035834-f03afa92d3ff/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= 27 | golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= 28 | -------------------------------------------------------------------------------- /makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all clean 2 | 3 | test: 4 | GO111MODULE=on go test -tags=native github.com/bokuweb/gopher-boy/... 5 | 6 | reg: 7 | reg-cli ./test/actual ./test/expect ./test/diff 8 | 9 | reg-update: 10 | reg-cli ./test/actual ./test/expect ./test/diff -U 11 | 12 | build: 13 | GO111MODULE=on go build -tags="native" -o "gopher-boy" "cmd/gopher-boy/main.go" 14 | 15 | build-wasm: 16 | GOOS=js GOARCH=wasm go build -tags=wasm -o "docs/main.wasm" "cmd/gopher-boy/wasm_main.go" 17 | 18 | serve: 19 | xdg-open 'http://localhost:5000' 20 | serve -r docs -a :5000 || (go get -v github.com/mattn/serve && serve -r docs -a :5000) 21 | 22 | clean: 23 | rm -f *.wasm -------------------------------------------------------------------------------- /pkg/bus/bus.go: -------------------------------------------------------------------------------- 1 | package bus 2 | 3 | import ( 4 | "github.com/bokuweb/gopher-boy/pkg/interfaces/pad" 5 | "github.com/bokuweb/gopher-boy/pkg/interrupt" 6 | 7 | "github.com/bokuweb/gopher-boy/pkg/cartridge" 8 | "github.com/bokuweb/gopher-boy/pkg/gpu" 9 | "github.com/bokuweb/gopher-boy/pkg/interfaces/logger" 10 | "github.com/bokuweb/gopher-boy/pkg/ram" 11 | "github.com/bokuweb/gopher-boy/pkg/serial" 12 | "github.com/bokuweb/gopher-boy/pkg/timer" 13 | "github.com/bokuweb/gopher-boy/pkg/types" 14 | "github.com/bokuweb/gopher-boy/pkg/utils" 15 | ) 16 | 17 | const ( 18 | // DMGStatusReg is DMA status register 19 | DMGStatusReg types.Word = 0xFF50 20 | ) 21 | 22 | // Bus is gb bus 23 | type Bus struct { 24 | logger logger.Logger 25 | bootmode bool 26 | cartridge *cartridge.Cartridge 27 | gpu *gpu.GPU 28 | vRAM *ram.RAM 29 | wRAM *ram.RAM 30 | hRAM *ram.RAM 31 | oamRAM *ram.RAM 32 | timer *timer.Timer 33 | irq *interrupt.Interrupt 34 | pad pad.Pad 35 | } 36 | 37 | /* --------------------------+ 38 | | Interrupt Enable Register | 39 | ------------------------------ 0xFFFF 40 | | Internal RAM | 41 | ------------------------------ 0xFF80 42 | | Empty but unusable for I/O | 43 | ------------------------------ 0xFF4C 44 | | I/O ports | 45 | ------------------------------ 0xFF00 46 | | Empty but unusable for I/O | 47 | ------------------------------ 0xFEA0 48 | | Sprite Attrib Memory (OAM) | 49 | ------------------------------ 0xFE00 50 | | Echo of 8kB Internal RAM | 51 | ------------------------------ 0xE000 52 | | 8kB Internal RAM | 53 | ------------------------------ 0xC000 54 | | 8kB switchable RAM bank | 55 | ------------------------------ 0xA000 56 | | 8kB Video RAM | 57 | ------------------------------ 0x8000 --+ 58 | | 16kB switchable ROM bank | | 59 | ------------------------------ 0x4000 | = 32kB Cartrigbe 60 | | 16kB ROM bank #0 | | 61 | ------------------------------ 0x0000 --+ */ 62 | 63 | // NewBus is bus constructor 64 | func NewBus( 65 | logger logger.Logger, 66 | cartridge *cartridge.Cartridge, 67 | gpu *gpu.GPU, 68 | vram *ram.RAM, 69 | wram *ram.RAM, 70 | hRAM *ram.RAM, 71 | oamRAM *ram.RAM, 72 | timer *timer.Timer, 73 | irq *interrupt.Interrupt, 74 | pad pad.Pad) *Bus { 75 | return &Bus{ 76 | logger: logger, 77 | bootmode: true, 78 | cartridge: cartridge, 79 | gpu: gpu, 80 | vRAM: vram, 81 | wRAM: wram, 82 | hRAM: hRAM, 83 | oamRAM: oamRAM, 84 | timer: timer, 85 | irq: irq, 86 | pad: pad, 87 | } 88 | } 89 | 90 | // ReadByte is byte data reader from bus 91 | func (b *Bus) ReadByte(addr types.Word) byte { 92 | 93 | switch { 94 | case addr >= 0x0000 && addr <= 0x7FFF: 95 | if b.bootmode && addr < 0x0100 { 96 | return BIOS[addr] 97 | } 98 | if addr == 0x0100 { 99 | b.bootmode = false 100 | } 101 | return b.cartridge.ReadByte(addr) 102 | // Video RAM 103 | case addr >= 0x8000 && addr <= 0x9FFF: 104 | return b.vRAM.Read(addr - 0x8000) 105 | case addr >= 0xA000 && addr <= 0xBFFF: 106 | return b.cartridge.ReadByte(addr) 107 | // Working RAM 108 | case addr >= 0xC000 && addr <= 0xDFFF: 109 | return b.wRAM.Read(addr - 0xC000) 110 | // Shadow 111 | case addr >= 0xE000 && addr <= 0xFDFF: 112 | return b.wRAM.Read(addr - 0xE000) 113 | // OAM 114 | case addr >= 0xFE00 && addr <= 0xFE9F: 115 | return b.oamRAM.Read(addr - 0xFE00) 116 | // Pad 117 | case addr == 0xFF00: 118 | return b.pad.Read() 119 | // Timer 120 | case addr >= 0xFF04 && addr <= 0xFF07: 121 | return b.timer.Read(addr - 0xFF00) 122 | // IF 123 | case addr == 0xFF0F: 124 | return b.irq.Read(addr - 0xFF00) 125 | // GPU 126 | case addr >= 0xFF40 && addr <= 0xFF7F: 127 | return b.gpu.Read(addr - 0xFF40) 128 | // Zero page RAM 129 | case addr >= 0xFF80 && addr <= 0xFFFE: 130 | return b.hRAM.Read(addr - 0xFF80) 131 | // IE 132 | case addr == 0xFFFF: 133 | return b.irq.Read(addr - 0xFF00) 134 | default: 135 | } 136 | return 0 137 | } 138 | 139 | // ReadWord is word data reader from bus 140 | func (b *Bus) ReadWord(addr types.Word) types.Word { 141 | l := b.ReadByte(addr) 142 | u := b.ReadByte(addr + 1) 143 | return utils.Bytes2Word(u, l) 144 | } 145 | 146 | // WriteByte is byte data writer to bus 147 | func (b *Bus) WriteByte(addr types.Word, data byte) { 148 | switch { 149 | case addr >= 0x0000 && addr <= 0x7FFF: 150 | b.cartridge.WriteByte(addr, data) 151 | // Video RAM 152 | case addr >= 0x8000 && addr <= 0x9FFF: 153 | b.vRAM.Write(addr-0x8000, data) 154 | case addr >= 0xA000 && addr <= 0xBFFF: 155 | b.cartridge.WriteByte(addr, data) 156 | // Working RAM 157 | case addr >= 0xC000 && addr <= 0xDFFF: 158 | b.wRAM.Write(addr-0xC000, data) 159 | // Shadow 160 | case addr >= 0xE000 && addr <= 0xFDFF: 161 | b.wRAM.Write(addr-0xE000, data) 162 | // OAM 163 | case addr >= 0xFE00 && addr <= 0xFE9F: 164 | b.oamRAM.Write(addr-0xFE00, data) 165 | // Pad 166 | case addr == 0xFF00: 167 | b.pad.Write(data) 168 | // Serial 169 | case addr == 0xFF01: 170 | serial.Send(data) 171 | // Timer 172 | case addr >= 0xFF04 && addr <= 0xFF07: 173 | b.timer.Write(addr-0xFF00, data) 174 | // IF 175 | case addr == 0xFF0F: 176 | b.irq.Write(addr-0xFF00, data) 177 | // GPU 178 | case addr >= 0xFF40 && addr <= 0xFF7F: 179 | b.gpu.Write(addr-0xFF40, data) 180 | //Zero page RAM 181 | case addr >= 0xFF80 && addr <= 0xFFFE: 182 | b.hRAM.Write(addr-0xFF80, data) 183 | // IE 184 | case addr == 0xFFFF: 185 | b.irq.Write(addr-0xFF00, data) 186 | default: 187 | // fmt.Printf("Error: You can not write 0x%X, this area is invalid or unimplemented area.\n", addr) 188 | } 189 | } 190 | 191 | // WriteWord is word data writer to bus 192 | func (b *Bus) WriteWord(addr types.Word, data types.Word) { 193 | upper, lower := utils.Word2Bytes(data) 194 | b.WriteByte(addr, lower) 195 | b.WriteByte(addr+1, upper) 196 | } 197 | 198 | // BIOS is 199 | var BIOS = []byte{ /* Removed*/ } 200 | -------------------------------------------------------------------------------- /pkg/bus/bus_test.go: -------------------------------------------------------------------------------- 1 | package bus 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/bokuweb/gopher-boy/pkg/cartridge" 7 | "github.com/bokuweb/gopher-boy/pkg/gpu" 8 | "github.com/bokuweb/gopher-boy/pkg/interrupt" 9 | "github.com/bokuweb/gopher-boy/pkg/logger" 10 | "github.com/bokuweb/gopher-boy/pkg/pad" 11 | "github.com/bokuweb/gopher-boy/pkg/ram" 12 | "github.com/bokuweb/gopher-boy/pkg/timer" 13 | "github.com/bokuweb/gopher-boy/pkg/types" 14 | "github.com/stretchr/testify/assert" 15 | ) 16 | 17 | func setup() (*Bus, *ram.RAM, *ram.RAM) { 18 | buf := make([]byte, 0x8000) 19 | cart, _ := cartridge.NewCartridge(buf) 20 | vRAM := ram.NewRAM(0x2000) 21 | wRAM := ram.NewRAM(0x2000) 22 | hRAM := ram.NewRAM(0x80) 23 | oamRAM := ram.NewRAM(0xA0) 24 | gpu := gpu.NewGPU() 25 | pad := pad.NewPad() 26 | l := logger.NewLogger(logger.LogLevel("Debug")) 27 | t := timer.NewTimer() 28 | irq := interrupt.NewInterrupt() 29 | return NewBus(l, cart, gpu, vRAM, wRAM, hRAM, oamRAM, t, irq, pad), wRAM, hRAM 30 | } 31 | 32 | func TestWRAMReadWrite(t *testing.T) { 33 | assert := assert.New(t) 34 | b, wRAM, _ := setup() 35 | b.WriteByte(0xC000, 0xA5) 36 | b.WriteWord(0xC100, 0xDEAD) 37 | assert.Equal(byte(0xA5), wRAM.Read(0x0000)) 38 | assert.Equal(byte(0xA5), b.ReadByte(0xC000)) 39 | assert.Equal(byte(0xAD), wRAM.Read(0x0100)) 40 | assert.Equal(byte(0xDE), wRAM.Read(0x0101)) 41 | assert.Equal(types.Word(0xDEAD), b.ReadWord(0xC100)) 42 | } 43 | 44 | func TestShadowWRAMReadWrite(t *testing.T) { 45 | assert := assert.New(t) 46 | b, wRAM, _ := setup() 47 | b.WriteByte(0xE000, 0xA5) 48 | b.WriteWord(0xE100, 0xDEAD) 49 | assert.Equal(byte(0xA5), wRAM.Read(0x0000)) 50 | assert.Equal(byte(0xA5), b.ReadByte(0xE000)) 51 | assert.Equal(byte(0xAD), wRAM.Read(0x0100)) 52 | assert.Equal(byte(0xDE), wRAM.Read(0x0101)) 53 | assert.Equal(types.Word(0xDEAD), b.ReadWord(0xE100)) 54 | } 55 | 56 | func TestZRAMReadWrite(t *testing.T) { 57 | assert := assert.New(t) 58 | b, _, hRAM := setup() 59 | b.WriteByte(0xFF80, 0xA5) 60 | b.WriteWord(0xFF90, 0xDEAD) 61 | assert.Equal(byte(0xA5), hRAM.Read(0x0000)) 62 | assert.Equal(types.Word(0xDEAD), b.ReadWord(0xFF90)) 63 | } 64 | -------------------------------------------------------------------------------- /pkg/cartridge/cartridge.go: -------------------------------------------------------------------------------- 1 | package cartridge 2 | 3 | import ( 4 | "fmt" 5 | "strings" 6 | 7 | "github.com/bokuweb/gopher-boy/pkg/types" 8 | ) 9 | 10 | // Cartridge is GameBoy cartridge 11 | type Cartridge struct { 12 | mbc MBC 13 | Title string 14 | ROM []byte 15 | RAMSize int 16 | } 17 | 18 | /* 19 | CartridgeType is 20 | 0x00: ROM ONLY 21 | 0x01: ROM+MBC1 22 | 0x02: ROM+MBC1+RAM 23 | 0x03: ROM+MBC1+RAM+BATT 24 | 0x05: ROM+MBC2 25 | 0x06: ROM+MBC2+BATTERY 26 | 0x08: ROM+RAM 27 | 0x09: ROM+RAM+BATTERY 28 | 0x0B: ROM+MMM01 29 | 0x0C: ROM+MMM01+SRAM 30 | 0x0D: ROM+MMM01+SRAM+BATT 31 | 0x12: ROM+MBC3+RAM 32 | 0x13: ROM+MBC3+RAM+BATT 33 | 0x19: ROM+MBC5 34 | 0x1A: ROM+MBC5+RAM 35 | 0x1B: ROM+MBC5+RAM+BATT 36 | 0x1C: ROM+MBC5+RUMBLE 37 | 0x1D: ROM+MBC5+RUMBLE+SRAM 38 | 0x1E: ROM+MBC5+RUMBLE+SRAM+BATT 39 | 0x1F: Pocket Camera 40 | 0xFD: Bandai TAMA5 41 | 0xFE: Hudson HuC-3 42 | */ 43 | type CartridgeType byte 44 | 45 | const ( 46 | MBC_0 CartridgeType = 0x00 47 | MBC_1 = 0x01 48 | MBC_1_RAM = 0x02 49 | MBC_1_RAM_BATT = 0x03 50 | MBC_3_RAM_BATT = 0x13 51 | MBC_3_RAM_BATT_RTC = 0x10 52 | MBC_5 = 0x19 53 | MBC_5_RAM = 0x1A 54 | MBC_5_RAM_BATT = 0x1B 55 | MBC_5_RUMBLE = 0x1C 56 | MBC_5_RAM_RUMBLE = 0x1D 57 | MBC_5_RAM_BATT_RUMBLE = 0x1E 58 | ) 59 | 60 | // NewCartridge is cartridge constructure 61 | func NewCartridge(buf []byte) (*Cartridge, error) { 62 | title := strings.TrimSpace(string(buf[0x0134:0x0142])) 63 | // romSize := 0x8000 << buf[0x0148] 64 | ramSize := getRAMSize(buf[0x0149]) 65 | cartridgeType := CartridgeType(buf[0x0147]) 66 | fmt.Println("cartridge type is ", cartridgeType) 67 | var mbc MBC 68 | switch cartridgeType { 69 | case MBC_0: 70 | mbc = NewMBC0(buf[0x0000:0x8000]) 71 | case MBC_1: 72 | mbc = NewMBC1(buf, ramSize, false) 73 | case MBC_1_RAM: 74 | mbc = NewMBC1(buf, ramSize, false) 75 | case MBC_1_RAM_BATT: 76 | mbc = NewMBC1(buf, ramSize, true) 77 | } 78 | 79 | return &Cartridge{ 80 | mbc: mbc, 81 | Title: title, 82 | ROM: buf, 83 | RAMSize: ramSize, 84 | }, nil 85 | } 86 | 87 | //RAM size: 88 | // 0 - None 89 | // 1 - 16kBit = 2kB = 1 bank 90 | // 2 - 64kBit = 8kB = 1 bank 91 | // 3 - 256kBit = 32kB = 4 banks 92 | // 4 - 1MBit =128kB =16 banks 93 | func getRAMSize(size byte) int { 94 | switch size { 95 | case 0x00: 96 | return 0 97 | case 0x01: 98 | return 2 * 1024 99 | case 0x02: 100 | return 8 * 1024 101 | case 0x03: 102 | return 32 * 1024 103 | case 0x04: 104 | return 128 * 1024 105 | } 106 | return 0 107 | } 108 | 109 | func (c *Cartridge) ReadByte(addr types.Word) byte { 110 | return c.mbc.Read(addr) 111 | } 112 | 113 | func (c *Cartridge) WriteByte(addr types.Word, data byte) { 114 | c.mbc.Write(addr, data) 115 | } 116 | -------------------------------------------------------------------------------- /pkg/cartridge/mbc.go: -------------------------------------------------------------------------------- 1 | package cartridge 2 | 3 | import ( 4 | "github.com/bokuweb/gopher-boy/pkg/types" 5 | ) 6 | 7 | type MBC interface { 8 | Write(addr types.Word, value byte) 9 | Read(addr types.Word) byte 10 | switchROMBank(bank int) 11 | switchRAMBank(bank int) 12 | } 13 | -------------------------------------------------------------------------------- /pkg/cartridge/mbc0.go: -------------------------------------------------------------------------------- 1 | package cartridge 2 | 3 | import ( 4 | "github.com/bokuweb/gopher-boy/pkg/rom" 5 | "github.com/bokuweb/gopher-boy/pkg/types" 6 | ) 7 | 8 | // MBC0 ROM ONLY 9 | // This is a 32kB (256kb) ROM and occupies 0000-7FFF. 10 | type MBC0 struct { 11 | rom *rom.ROM 12 | } 13 | 14 | // NewMBC0 constracts MBC0 15 | func NewMBC0(data []byte) *MBC0 { 16 | m := new(MBC0) 17 | m.rom = rom.NewROM(data) 18 | return m 19 | } 20 | 21 | func (m *MBC0) Write(addr types.Word, value byte) { 22 | } 23 | 24 | func (m *MBC0) Read(addr types.Word) byte { 25 | return m.rom.Read(uint32(addr)) 26 | } 27 | 28 | func (m *MBC0) switchROMBank(bank int) { 29 | // nop 30 | } 31 | 32 | func (m *MBC0) switchRAMBank(bank int) { 33 | // nop 34 | } 35 | -------------------------------------------------------------------------------- /pkg/cartridge/mbc1.go: -------------------------------------------------------------------------------- 1 | package cartridge 2 | 3 | import ( 4 | "github.com/bokuweb/gopher-boy/pkg/ram" 5 | "github.com/bokuweb/gopher-boy/pkg/rom" 6 | "github.com/bokuweb/gopher-boy/pkg/types" 7 | ) 8 | 9 | // MBC1 is (Memory Bank Controller 1 10 | // MBC1 has two different maximum memory modes: 16Mbit ROM/8KByte RAM or 4Mbit ROM/32KByte RAM. 11 | type MBC1 struct { 12 | rom *rom.ROM 13 | ram *ram.RAM 14 | selectedROMBank int 15 | selectedRAMBank int 16 | romBanking bool 17 | ramEnabled bool 18 | hasBattery bool 19 | memoryMode MBC1MemoryMode 20 | RAMSize int 21 | } 22 | 23 | // MBC1MemoryMode is MBC1 max memory mode 24 | // The MBC1 defaults to 16Mbit ROM/8KByte RAM mode 25 | // on power up. Writing a value (XXXXXXXS - X = Don't care, S = Memory model select) into 6000-7FFF area 26 | // will select the memory model to use. 27 | // S = 0 selects 16/8 mode. S = 1 selects 4/32 mode. 28 | type MBC1MemoryMode = string 29 | 30 | const ( 31 | // ROM16mRAM8kMode is 4/32 memory mode 32 | // Writing a value (XXXXXXBB - X = Don't care, B = bank select bits) into 4000-5FFF area 33 | // will set the two most significant ROM address lines. 34 | // * NOTE: The Super Smart Card doesn't require this operation because it's RAM bank is ALWAYS enabled. 35 | // Include this operation anyway to allow your code to work with both 36 | ROM16mRAM8kMode MBC1MemoryMode = "ROM16M/RAM8K" 37 | // ROM4mRAM32kMode is 4/32 memory mode 38 | // Writing a value (XXXXXXBB - X = Don't care, B = bank select bits) into 4000-5FFF area 39 | // will select an appropriate RAM bank at A000-C000. 40 | // Before you can read or write to a RAM bank you have to enable it by writing a XXXX1010 into 0000-1FFF area*. 41 | // To disable RAM bank operations write any value but XXXX1010 into 0000-1FFF area. 42 | // Disabling a RAM bank probably protects that bank from false writes during power down of the GameBoy. 43 | // (NOTE: Nintendo suggests values $0A to enable and $00 to disable RAM bank!!) 44 | ROM4mRAM32kMode = "ROM4M/RAM32K" 45 | ) 46 | 47 | // NewMBC1 constracts MBC1 48 | func NewMBC1(buf []byte, ramSize int, hasBattery bool) *MBC1 { 49 | m := &MBC1{ 50 | selectedROMBank: 1, 51 | } 52 | m.memoryMode = ROM16mRAM8kMode 53 | m.hasBattery = hasBattery 54 | m.RAMSize = ramSize 55 | if ramSize > 0 { 56 | m.ramEnabled = true 57 | m.selectedRAMBank = 0 58 | m.ram = ram.NewRAM(0x8000) 59 | } 60 | m.rom = rom.NewROM(buf) 61 | 62 | return m 63 | } 64 | 65 | func (m *MBC1) Write(addr types.Word, value byte) { 66 | switch { 67 | // 4 bits wide; value of 0x0A enables RAM, any other value disables 68 | case addr < 0x2000: 69 | if m.memoryMode == ROM4mRAM32kMode { 70 | m.ramEnabled = value&0x0F == 0x0A 71 | } 72 | // Writing a value (XXXBBBBB - X = Don't cares, B = bank select bits) into 2000-3FFF area 73 | // will select an appropriate ROM bank at 4000-7FFF 74 | // Values of 0 and 1 do the same thing and point to ROM bank 1. 75 | // Rom bank 0 is not accessible from 4000-7FFF and can only be read from 0000-3FFF. 76 | case addr < 0x4000: 77 | m.switchROMBank(int(value & 0x1F)) 78 | case addr < 0x6000: 79 | if m.romBanking { 80 | m.switchROMBank((m.selectedROMBank & 0x1F) | int(value&0xE0)) 81 | break 82 | } 83 | m.switchRAMBank(int(value & 0x03)) 84 | 85 | case addr < 0x8000: 86 | m.romBanking = value&0x01 == 0x00 87 | if m.romBanking { 88 | m.switchRAMBank(0) 89 | break 90 | } 91 | case addr < 0xC000: 92 | if m.ramEnabled { 93 | switch m.memoryMode { 94 | case ROM4mRAM32kMode: 95 | m.ram.Write(types.Word((int(addr)+m.selectedRAMBank*0x2000)-0xA000), value) 96 | case ROM16mRAM8kMode: 97 | m.ram.Write(types.Word((int(addr))-0xA000), value) 98 | } 99 | } 100 | } 101 | } 102 | 103 | func (m *MBC1) Read(addr types.Word) byte { 104 | if addr < 0x4000 { 105 | return m.rom.Read(uint32(addr)) 106 | } else if addr < 0x8000 { 107 | base := uint32(m.selectedROMBank * 0x4000) 108 | return m.rom.Read(base + uint32(addr) - 0x4000) 109 | } else if addr < 0xC000 { 110 | if m.ramEnabled { 111 | switch m.memoryMode { 112 | case ROM4mRAM32kMode: 113 | return m.ram.Read(types.Word((int(addr) + m.selectedRAMBank*0x2000) - 0xA000)) 114 | case ROM16mRAM8kMode: 115 | return m.ram.Read(types.Word((int(addr)) - 0xA000)) 116 | } 117 | } 118 | } 119 | return 0x00 120 | } 121 | 122 | func (m *MBC1) switchROMBank(bank int) { 123 | m.selectedROMBank = bank 124 | if m.selectedROMBank == 0x00 || m.selectedROMBank == 0x20 || m.selectedROMBank == 0x40 || m.selectedROMBank == 0x60 { 125 | m.selectedROMBank++ 126 | } 127 | } 128 | 129 | func (m *MBC1) switchRAMBank(bank int) { 130 | m.selectedRAMBank = bank 131 | } 132 | -------------------------------------------------------------------------------- /pkg/constants/window.go: -------------------------------------------------------------------------------- 1 | package constants 2 | 3 | const ( 4 | // ScreenWidth is the number of pixels width on the GameBoy LCD panel. 5 | ScreenWidth = 160 6 | 7 | // ScreenHeight is the number of pixels height on the GameBoy LCD panel. 8 | ScreenHeight = 144 9 | ) 10 | -------------------------------------------------------------------------------- /pkg/cpu/cpu_test.go: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/bokuweb/gopher-boy/pkg/interrupt" 7 | "github.com/bokuweb/gopher-boy/pkg/logger" 8 | "github.com/bokuweb/gopher-boy/pkg/mocks" 9 | "github.com/bokuweb/gopher-boy/pkg/types" 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func setupCPU(offset types.Word, data []byte) (*CPU, *mocks.MockBus) { 14 | b := mocks.MockBus{} 15 | b.SetMemory(offset, data) 16 | irq := interrupt.NewInterrupt() 17 | l := logger.NewLogger(logger.LogLevel("Debug")) 18 | return NewCPU(l, &b, irq), &b 19 | } 20 | 21 | func TestNOP(t *testing.T) { 22 | cpu, _ := setupCPU(0, []byte{0}) 23 | cpu.Step() 24 | } 25 | 26 | func TestLDn_nn(t *testing.T) { 27 | assert := assert.New(t) 28 | cpu, _ := setupCPU(0, []byte{0x01, 0xDE, 0xAD}) 29 | cpu.PC = 0x00 30 | cpu.Step() 31 | assert.Equal(byte(0xAD), cpu.Regs.B, "should B equals 0xad") 32 | assert.Equal(byte(0xDE), cpu.Regs.C, "should C equals 0xde") 33 | } 34 | 35 | func TestLDrr_r(t *testing.T) { 36 | assert := assert.New(t) 37 | cpu, bus := setupCPU(0, []byte{0x02}) 38 | cpu.PC = 0x00 39 | cpu.Regs.A = 0xA5 40 | cpu.Regs.B = 0x10 41 | cpu.Regs.C = 0x20 42 | cpu.Step() 43 | assert.Equal(byte(0xA5), bus.MockMemory[0x1020], "should memory equals 0xa5") 44 | } 45 | 46 | func TestIncrr(t *testing.T) { 47 | assert := assert.New(t) 48 | cpu, _ := setupCPU(0, []byte{0x03}) 49 | cpu.PC = 0x00 50 | cpu.Regs.B = 0x10 51 | cpu.Regs.C = 0x20 52 | cpu.Step() 53 | assert.Equal(byte(0x10), cpu.Regs.B, "should not B incremented") 54 | assert.Equal(byte(0x21), cpu.Regs.C, "should C incremented") 55 | } 56 | 57 | func TestIncB(t *testing.T) { 58 | assert := assert.New(t) 59 | cpu, _ := setupCPU(0, []byte{0x04}) 60 | cpu.PC = 0x00 61 | cpu.Regs.B = 0x10 62 | cpu.Step() 63 | assert.Equal(byte(0x11), cpu.Regs.B, "should B incremented") 64 | } 65 | 66 | func TestDecB(t *testing.T) { 67 | assert := assert.New(t) 68 | cpu, _ := setupCPU(0, []byte{0x05}) 69 | cpu.PC = 0x00 70 | cpu.Regs.B = 0x10 71 | cpu.Step() 72 | assert.Equal(byte(0x0F), cpu.Regs.B, "should B decremented") 73 | } 74 | 75 | func TestLDnn_n(t *testing.T) { 76 | assert := assert.New(t) 77 | cpu, _ := setupCPU(0, []byte{0x06, 0xA5}) 78 | cpu.PC = 0x00 79 | cpu.Step() 80 | assert.Equal(cpu.Regs.B, byte(0xA5), "should B equals 0xa5") 81 | } 82 | -------------------------------------------------------------------------------- /pkg/cpu/helpers.go: -------------------------------------------------------------------------------- 1 | package cpu 2 | 3 | import ( 4 | "github.com/bokuweb/gopher-boy/pkg/types" 5 | "github.com/bokuweb/gopher-boy/pkg/utils" 6 | ) 7 | 8 | func (cpu *CPU) inc(value byte) byte { 9 | incremented := value + 0x01 10 | cpu.clearFlag(N) 11 | if incremented == 0 { 12 | cpu.setFlag(Z) 13 | } else { 14 | cpu.clearFlag(Z) 15 | } 16 | if (incremented^0x01^value)&0x10 == 0x10 { 17 | cpu.setFlag(H) 18 | } else { 19 | cpu.clearFlag(H) 20 | } 21 | return incremented 22 | } 23 | 24 | func (cpu *CPU) dec(value byte) byte { 25 | decremented := value - 1 26 | cpu.setFlag(N) 27 | cpu.applyZeroBy(decremented) 28 | if (decremented^0x01^value)&0x10 == 0x10 { 29 | cpu.setFlag(H) 30 | } else { 31 | cpu.clearFlag(H) 32 | } 33 | return decremented 34 | } 35 | 36 | func (cpu *CPU) clearFlag(flag flags) { 37 | switch flag { 38 | case Z: 39 | cpu.Regs.F = cpu.clearBit(7, cpu.Regs.F) 40 | case N: 41 | cpu.Regs.F = cpu.clearBit(6, cpu.Regs.F) 42 | case H: 43 | cpu.Regs.F = cpu.clearBit(5, cpu.Regs.F) 44 | case C: 45 | cpu.Regs.F = cpu.clearBit(4, cpu.Regs.F) 46 | } 47 | cpu.Regs.F &= 0xF0 48 | } 49 | 50 | func (cpu *CPU) setFlag(flag flags) { 51 | switch flag { 52 | case Z: 53 | cpu.Regs.F = cpu.setBit(7, cpu.Regs.F) 54 | case N: 55 | cpu.Regs.F = cpu.setBit(6, cpu.Regs.F) 56 | case H: 57 | cpu.Regs.F = cpu.setBit(5, cpu.Regs.F) 58 | case C: 59 | cpu.Regs.F = cpu.setBit(4, cpu.Regs.F) 60 | } 61 | cpu.Regs.F &= 0xF0 62 | } 63 | 64 | func (cpu *CPU) isSet(flag flags) bool { 65 | switch flag { 66 | case Z: 67 | return cpu.Regs.F&0x80 != 0 68 | case N: 69 | return cpu.Regs.F&0x40 != 0 70 | case H: 71 | return cpu.Regs.F&0x20 != 0 72 | case C: 73 | return cpu.Regs.F&0x10 != 0 74 | } 75 | return false 76 | } 77 | 78 | func (cpu *CPU) setBit(bit byte, a byte) byte { 79 | return a | (1 << uint(bit)) 80 | } 81 | 82 | func (cpu *CPU) clearBit(bit byte, a byte) byte { 83 | return a & ^(1 << uint(bit)) 84 | } 85 | 86 | func (cpu *CPU) addBytes(a, b byte) byte { 87 | computed := a + b 88 | cpu.clearFlag(N) 89 | if computed == 0x00 { 90 | cpu.setFlag(Z) 91 | } else { 92 | cpu.clearFlag(Z) 93 | } 94 | if (computed^b^a)&0x10 == 0x10 { 95 | cpu.setFlag(H) 96 | } else { 97 | cpu.clearFlag(H) 98 | } 99 | if computed < a { 100 | cpu.setFlag(C) 101 | } else { 102 | cpu.clearFlag(C) 103 | } 104 | return computed 105 | } 106 | 107 | func (cpu *CPU) addWords(a, b types.Word) types.Word { 108 | computed := a + b 109 | if computed < a { 110 | cpu.setFlag(C) 111 | } else { 112 | cpu.clearFlag(C) 113 | } 114 | if (computed^b^a)&0x1000 == 0x1000 { 115 | cpu.setFlag(H) 116 | } else { 117 | cpu.clearFlag(H) 118 | } 119 | 120 | cpu.clearFlag(N) 121 | 122 | return computed 123 | } 124 | 125 | func (cpu *CPU) subBytes(a, b byte) byte { 126 | cpu.setFlag(N) 127 | if a&0xF < b&0xF { 128 | cpu.setFlag(H) 129 | } else { 130 | cpu.clearFlag(H) 131 | } 132 | if a&0xFF < b&0xFF { 133 | cpu.setFlag(C) 134 | } else { 135 | cpu.clearFlag(C) 136 | } 137 | computed := a - b 138 | if computed == 0x00 { 139 | cpu.setFlag(Z) 140 | } else { 141 | cpu.clearFlag(Z) 142 | } 143 | return computed 144 | } 145 | 146 | func (cpu *CPU) getHL() types.Word { 147 | return utils.Bytes2Word(cpu.Regs.H, cpu.Regs.L) 148 | } 149 | 150 | func (cpu *CPU) toHLRegs(v types.Word) { 151 | cpu.Regs.H, cpu.Regs.L = utils.Word2Bytes(v) 152 | } 153 | 154 | func (cpu *CPU) and(a, b byte) byte { 155 | cpu.setFlag(H) 156 | cpu.clearFlag(N) 157 | cpu.clearFlag(C) 158 | computed := a & b 159 | if computed == 0x00 { 160 | cpu.setFlag(Z) 161 | } else { 162 | cpu.clearFlag(Z) 163 | } 164 | return computed 165 | } 166 | 167 | func (cpu *CPU) xor(a, b byte) byte { 168 | cpu.clearFlag(H) 169 | cpu.clearFlag(N) 170 | cpu.clearFlag(C) 171 | computed := a ^ b 172 | if computed == 0x00 { 173 | cpu.setFlag(Z) 174 | } else { 175 | cpu.clearFlag(Z) 176 | } 177 | return computed 178 | } 179 | 180 | func (cpu *CPU) or(a, b byte) byte { 181 | cpu.clearFlag(H) 182 | cpu.clearFlag(N) 183 | cpu.clearFlag(C) 184 | computed := a | b 185 | if computed == 0x00 { 186 | cpu.setFlag(Z) 187 | } else { 188 | cpu.clearFlag(Z) 189 | } 190 | return computed 191 | } 192 | 193 | func (cpu *CPU) pop() byte { 194 | b := cpu.bus.ReadByte(cpu.SP) 195 | cpu.SP++ 196 | return b 197 | } 198 | 199 | func (cpu *CPU) push(v byte) { 200 | cpu.SP-- 201 | cpu.bus.WriteByte(cpu.SP, v) 202 | } 203 | 204 | func (cpu *CPU) pushPC() { 205 | upper, lower := utils.Word2Bytes(cpu.PC) 206 | cpu.push(upper) 207 | cpu.push(lower) 208 | } 209 | 210 | func (cpu *CPU) pop2PC() { 211 | lower := cpu.pop() 212 | upper := cpu.pop() 213 | cpu.PC = utils.Bytes2Word(upper, lower) 214 | } 215 | 216 | func (cpu *CPU) applyZeroBy(computed byte) { 217 | if computed == 0 { 218 | cpu.setFlag(Z) 219 | } else { 220 | cpu.clearFlag(Z) 221 | } 222 | } 223 | -------------------------------------------------------------------------------- /pkg/gb/gb.go: -------------------------------------------------------------------------------- 1 | package gb 2 | 3 | import ( 4 | "time" 5 | 6 | "github.com/bokuweb/gopher-boy/pkg/cpu" 7 | "github.com/bokuweb/gopher-boy/pkg/gpu" 8 | "github.com/bokuweb/gopher-boy/pkg/interfaces/window" 9 | "github.com/bokuweb/gopher-boy/pkg/interrupt" 10 | "github.com/bokuweb/gopher-boy/pkg/timer" 11 | ) 12 | 13 | // CyclesPerFrame is cpu clock num for 1frame. 14 | const CyclesPerFrame = 70224 15 | 16 | // GB is gameboy emulator struct 17 | type GB struct { 18 | currentCycle uint 19 | cpu *cpu.CPU 20 | gpu *gpu.GPU 21 | timer *timer.Timer 22 | irq *interrupt.Interrupt 23 | win window.Window 24 | } 25 | 26 | // NewGB is gb initializer 27 | func NewGB(cpu *cpu.CPU, gpu *gpu.GPU, timer *timer.Timer, irq *interrupt.Interrupt, win window.Window) *GB { 28 | return &GB{ 29 | currentCycle: 0, 30 | cpu: cpu, 31 | gpu: gpu, 32 | timer: timer, 33 | irq: irq, 34 | win: win, 35 | } 36 | } 37 | 38 | // Start is 39 | func (g *GB) Start() { 40 | t := time.NewTicker(16 * time.Millisecond) 41 | for { 42 | select { 43 | case <-t.C: 44 | buf := g.Next() 45 | g.win.Render(buf) 46 | } 47 | } 48 | t.Stop() 49 | } 50 | func (g *GB) Next() []byte { 51 | for { 52 | var cycles uint 53 | if g.gpu.DMAStarted() { 54 | g.gpu.Transfer() 55 | // https://github.com/Gekkio/mooneye-gb/blob/master/docs/accuracy.markdown#how-many-cycles-does-oam-dma-take 56 | cycles = 162 57 | } else { 58 | cycles = g.cpu.Step() 59 | } 60 | g.gpu.Step(cycles * 4) 61 | if overflowed := g.timer.Update(cycles); overflowed { 62 | g.irq.SetIRQ(interrupt.TimerOverflowFlag) 63 | } 64 | g.currentCycle += cycles * 4 65 | if g.currentCycle >= CyclesPerFrame { 66 | g.win.PollKey() 67 | g.currentCycle -= CyclesPerFrame 68 | return g.gpu.GetImageData() 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /pkg/gb/gb_test.go: -------------------------------------------------------------------------------- 1 | package gb 2 | 3 | import ( 4 | "image" 5 | "image/color" 6 | "image/png" 7 | "os" 8 | "testing" 9 | 10 | "github.com/bokuweb/gopher-boy/pkg/constants" 11 | "github.com/bokuweb/gopher-boy/pkg/interrupt" 12 | "github.com/bokuweb/gopher-boy/pkg/pad" 13 | "github.com/bokuweb/gopher-boy/pkg/timer" 14 | 15 | "github.com/bokuweb/gopher-boy/pkg/bus" 16 | "github.com/bokuweb/gopher-boy/pkg/cartridge" 17 | "github.com/bokuweb/gopher-boy/pkg/cpu" 18 | "github.com/bokuweb/gopher-boy/pkg/gpu" 19 | "github.com/bokuweb/gopher-boy/pkg/interfaces/window" 20 | "github.com/bokuweb/gopher-boy/pkg/logger" 21 | "github.com/bokuweb/gopher-boy/pkg/ram" 22 | "github.com/bokuweb/gopher-boy/pkg/utils" 23 | ) 24 | 25 | const ( 26 | RomPathPrefix = "../../roms/" 27 | ImagePathPrefix = "../../test/actual/" 28 | ) 29 | 30 | // MockWindow is 31 | type mockWindow struct { 32 | window.Window 33 | } 34 | 35 | func (m mockWindow) PollKey() { 36 | } 37 | 38 | func setup(file string) *GB { 39 | l := logger.NewLogger(logger.LogLevel("DEBUG")) 40 | buf, err := utils.LoadROM(file) 41 | if err != nil { 42 | panic(err) 43 | } 44 | cart, err := cartridge.NewCartridge(buf) 45 | if err != nil { 46 | panic(err) 47 | } 48 | 49 | vRAM := ram.NewRAM(0x2000) 50 | wRAM := ram.NewRAM(0x2000) 51 | hRAM := ram.NewRAM(0x80) 52 | oamRAM := ram.NewRAM(0xA0) 53 | gpu := gpu.NewGPU() 54 | t := timer.NewTimer() 55 | pad := pad.NewPad() 56 | irq := interrupt.NewInterrupt() 57 | b := bus.NewBus(l, cart, gpu, vRAM, wRAM, hRAM, oamRAM, t, irq, pad) 58 | gpu.Init(b, irq) 59 | win := mockWindow{} 60 | emu := NewGB(cpu.NewCPU(l, b, irq), gpu, t, irq, win) 61 | return emu 62 | } 63 | 64 | func set(img *image.RGBA, buf []byte) { 65 | imgData := make([]color.RGBA, constants.ScreenWidth*constants.ScreenHeight) 66 | i := 0 67 | for i*4 < len(buf) { 68 | y := constants.ScreenHeight - (i / constants.ScreenWidth) - 1 69 | imgData[y*constants.ScreenWidth+i%constants.ScreenWidth] = color.RGBA{buf[i*4], buf[i*4+1], buf[i*4+2], buf[i*4+3]} 70 | i++ 71 | } 72 | 73 | rect := img.Rect 74 | for y := rect.Min.Y; y < rect.Max.Y; y++ { 75 | for x := rect.Min.X; x < rect.Max.X; x++ { 76 | img.Set(x, rect.Max.Y-y, imgData[y*rect.Max.X+x]) 77 | } 78 | } 79 | } 80 | 81 | func skipFrame(emu *GB, n int) []byte { 82 | var image []byte 83 | for i := 0; i < n; i++ { 84 | image = emu.Next() 85 | } 86 | return image 87 | } 88 | 89 | func TestROMs(t *testing.T) { 90 | tests := []struct { 91 | name string 92 | path string 93 | frame int 94 | }{ 95 | { 96 | "hello_world", 97 | RomPathPrefix + "helloworld/hello.gb", 98 | 100, 99 | }, 100 | { 101 | "tictactoe", 102 | RomPathPrefix + "tictactoe/GB-TicTacToe.gb", 103 | 100, 104 | }, 105 | { 106 | "cpu_instr", 107 | RomPathPrefix + "cpu_instrs/cpu_instrs.gb", 108 | 4000, 109 | }, 110 | { 111 | "opus5", 112 | RomPathPrefix + "opus5/opus5.gb", 113 | 100, 114 | }, 115 | { 116 | "div_write", 117 | RomPathPrefix + "acceptance/timer/div_write.gb", 118 | 1000, 119 | }, 120 | { 121 | "tim00", 122 | RomPathPrefix + "acceptance/timer/tim00.gb", 123 | 100, 124 | }, 125 | { 126 | "tim01", 127 | RomPathPrefix + "acceptance/timer/tim01.gb", 128 | 100, 129 | }, 130 | { 131 | "tim10", 132 | RomPathPrefix + "acceptance/timer/tim10.gb", 133 | 100, 134 | }, 135 | { 136 | "tim11", 137 | RomPathPrefix + "acceptance/timer/tim10.gb", 138 | 100, 139 | }, 140 | { 141 | "bits_bank1", 142 | RomPathPrefix + "mbc1/bits_bank1.gb", 143 | 100, 144 | }, 145 | { 146 | "reg_f", 147 | RomPathPrefix + "acceptance/bits/reg_f.gb", 148 | 100, 149 | }, 150 | { 151 | "mem_oam", 152 | RomPathPrefix + "acceptance/bits/mem_oam.gb", 153 | 100, 154 | }, 155 | { 156 | "daa", 157 | RomPathPrefix + "acceptance/instr/daa.gb", 158 | 100, 159 | }, 160 | { 161 | "if_ie_registers", 162 | RomPathPrefix + "acceptance/if_ie_registers.gb", 163 | 100, 164 | }, 165 | { 166 | "tobu", 167 | RomPathPrefix + "tobu/tobu.gb", 168 | 100, 169 | }, 170 | } 171 | for _, tt := range tests { 172 | t.Run(tt.name, func(t *testing.T) { 173 | emu := setup(tt.path) 174 | buf := skipFrame(emu, tt.frame) 175 | file, err := os.Create(ImagePathPrefix + tt.name + ".png") 176 | defer file.Close() 177 | if err != nil { 178 | panic(err) 179 | } 180 | img := image.NewRGBA(image.Rect(0, 0, constants.ScreenWidth, constants.ScreenHeight)) 181 | set(img, buf) 182 | if err := png.Encode(file, img); err != nil { 183 | panic(err) 184 | } 185 | }) 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /pkg/gpu/gpu_test.go: -------------------------------------------------------------------------------- 1 | package gpu 2 | 3 | import ( 4 | "testing" 5 | 6 | "github.com/bokuweb/gopher-boy/pkg/constants" 7 | "github.com/bokuweb/gopher-boy/pkg/interrupt" 8 | "github.com/bokuweb/gopher-boy/pkg/mocks" 9 | 10 | "github.com/stretchr/testify/assert" 11 | ) 12 | 13 | func setup() *GPU { 14 | g := NewGPU() 15 | irq := interrupt.NewInterrupt() 16 | g.Init(&mocks.MockBus{}, irq) 17 | return g 18 | } 19 | 20 | func TestLY(t *testing.T) { 21 | assert := assert.New(t) 22 | g := setup() 23 | for y := 0; y < int(constants.ScreenHeight+LCDVBlankHeight+10); y++ { 24 | if y == int(constants.ScreenHeight+LCDVBlankHeight) { 25 | assert.Equal(uint8(0x9a), g.Read(LY)) 26 | } else { 27 | assert.Equal(byte(y%int(constants.ScreenHeight+LCDVBlankHeight)), g.Read(LY), y) 28 | } 29 | 30 | g.Step(CyclePerLine) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /pkg/interfaces/bus/bus.go: -------------------------------------------------------------------------------- 1 | package bus 2 | 3 | import "github.com/bokuweb/gopher-boy/pkg/types" 4 | 5 | // Accessor bus accessor interface 6 | type Accessor interface { 7 | WriteByte(addr types.Word, data byte) 8 | WriteWord(addr types.Word, data types.Word) 9 | 10 | ReadByte(addr types.Word) byte 11 | ReadWord(addr types.Word) types.Word 12 | } 13 | -------------------------------------------------------------------------------- /pkg/interfaces/interrupt/interrupt.go: -------------------------------------------------------------------------------- 1 | package interrupt 2 | 3 | import ( 4 | "github.com/bokuweb/gopher-boy/pkg/interrupt" 5 | "github.com/bokuweb/gopher-boy/pkg/types" 6 | ) 7 | 8 | // IRQFlag is 9 | type IRQFlag = interrupt.IRQFlag 10 | 11 | // Interrupt defined irq interface 12 | type Interrupt interface { 13 | SetIRQ(f interrupt.IRQFlag) 14 | Enable() 15 | Disable() 16 | Enabled() bool 17 | Read(addr types.Word) byte 18 | Write(addr types.Word, data byte) 19 | HasIRQ() bool 20 | ResolveISRAddr() *types.Word 21 | } 22 | -------------------------------------------------------------------------------- /pkg/interfaces/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | // Logger is 4 | type Logger interface { 5 | Debug(args ...interface{}) 6 | Info(args ...interface{}) 7 | Error(args ...interface{}) 8 | Warn(args ...interface{}) 9 | } 10 | -------------------------------------------------------------------------------- /pkg/interfaces/pad/pad.go: -------------------------------------------------------------------------------- 1 | package pad 2 | 3 | import "github.com/bokuweb/gopher-boy/pkg/pad" 4 | 5 | // Pad defined keypad interface 6 | type Pad interface { 7 | Press(b pad.Button) 8 | Release(b pad.Button) 9 | Read() byte 10 | Write(v byte) 11 | } 12 | -------------------------------------------------------------------------------- /pkg/interfaces/window/window.go: -------------------------------------------------------------------------------- 1 | package window 2 | 3 | // Window is 4 | type Window interface { 5 | Render(imageData []byte) 6 | Run(run func()) 7 | PollKey() 8 | KeyDown(button byte) 9 | KeyUp(button byte) 10 | } 11 | -------------------------------------------------------------------------------- /pkg/interrupt/interrupt.go: -------------------------------------------------------------------------------- 1 | package interrupt 2 | 3 | import ( 4 | "github.com/bokuweb/gopher-boy/pkg/types" 5 | ) 6 | 7 | var ( 8 | // VerticalBlankISRAddr is Vertical Blank Interrupt Start Address 9 | VerticalBlankISRAddr types.Word = 0x0040 10 | // LCDCStatusTriggersISRAddr is LCDC Status Interrupt Start Address 11 | LCDCStatusTriggersISRAddr types.Word = 0x0048 12 | // TimeroverflowISRAddr is Timer Overflow Interrupt Start Address 13 | TimeroverflowISRAddr types.Word = 0x0050 14 | // SerialTransferISRAddr is Serial Transfer Completion Interrupt 15 | SerialTransferISRAddr types.Word = 0x0058 16 | // JoypadPressISRAddr is High-to-Low of P10-P13 Interrupt 17 | JoypadPressISRAddr types.Word = 0x0060 18 | ) 19 | 20 | // IRQFlag is 21 | type IRQFlag = byte 22 | 23 | const ( 24 | VerticalBlankFlag IRQFlag = 0x01 25 | LCDSFlag IRQFlag = 0x02 26 | TimerOverflowFlag IRQFlag = 0x04 27 | SerialTransferFlag IRQFlag = 0x08 28 | JoypadPressFlag IRQFlag = 0x10 29 | ) 30 | 31 | const ( 32 | RegisterOffset = 0xFF00 33 | // IF - Interrupt Flag (R/W) 34 | // Bit 4: Transition from High to Low of Pin 35 | // number P10-P13 36 | // Bit 3: Serial I/O transfer complete 37 | // Bit 2: Timer Overflow 38 | // Bit 1: LCDC (see STAT) 39 | // Bit 0: V-Blank 40 | // The priority and jump address for the above 5 41 | // interrupts are: 42 | // Interrupt Priority Start Address 43 | // V-Blank 1 $0040 44 | // LCDC Status 2 $0048 - Modes 0, 1, 2 45 | // LYC=LY coincide 46 | // (selectable) 47 | // Timer Overflow 3 $0050 48 | // Serial Transfer 4 $0058 - when transfer 49 | // is complete 50 | // Hi-Lo of P10-P13 5 $0060 51 | // * When more than 1 interrupts occur at the same 52 | // time only the interrupt with the highest priority 53 | // can be acknowledged. When an interrupt is used a 54 | // '0' should be stored in the IF register before the 55 | // IE register is set. 56 | IF = 0x0F 57 | // IE - Interrupt Enable (R/W) 58 | // Bit 4: 59 | // Transition from High to Low of Pin number P10-P13. 60 | // Serial I/O transfer complete 61 | // Timer Overflow 62 | // Bit 3: 63 | // Bit 2: 64 | // Bit 1: 65 | // Bit 0: V-Blank 66 | // 0: disable 67 | // 1: enable 68 | IE = 0xFF 69 | ) 70 | 71 | const ( 72 | InterruptFlagAddr = RegisterOffset + IF 73 | InterruptEnableFlagAddr = RegisterOffset + IE 74 | ) 75 | 76 | // Interrupt has 2 registers to manage 77 | type Interrupt struct { 78 | IF byte 79 | IE byte 80 | enabled bool 81 | } 82 | 83 | // NewInterrupt constructs irq peripheral. 84 | func NewInterrupt() *Interrupt { 85 | return &Interrupt{ 86 | IF: 0x00, 87 | IE: 0x00, 88 | enabled: false, 89 | } 90 | } 91 | 92 | // SetIRQ set flag 93 | func (irq *Interrupt) SetIRQ(f IRQFlag) { 94 | irq.IF |= f 95 | } 96 | 97 | func (irq *Interrupt) Read(addr types.Word) byte { 98 | switch addr { 99 | case IE: 100 | return irq.IE 101 | case IF: 102 | return irq.IF | 0xE0 103 | } 104 | panic("Illegal access detected.") 105 | } 106 | 107 | func (irq *Interrupt) Write(addr types.Word, data byte) { 108 | switch addr { 109 | case IE: 110 | irq.IE = data 111 | return 112 | case IF: 113 | irq.IF = data 114 | return 115 | } 116 | panic("Illegal access detected.") 117 | } 118 | 119 | func (irq *Interrupt) HasIRQ() bool { 120 | i := irq.IF & irq.IE 121 | return i != 0x00 122 | } 123 | 124 | func (irq *Interrupt) Enable() { 125 | irq.enabled = true 126 | } 127 | 128 | func (irq *Interrupt) Enabled() bool { 129 | return irq.enabled 130 | } 131 | 132 | func (irq *Interrupt) Disable() { 133 | irq.enabled = false 134 | } 135 | 136 | func (irq *Interrupt) ResolveISRAddr() *types.Word { 137 | i := irq.IF & irq.IE 138 | switch { 139 | case i&VerticalBlankFlag != 0: 140 | irq.IF &= ^VerticalBlankFlag 141 | return &VerticalBlankISRAddr 142 | case i&LCDSFlag != 0: 143 | irq.IF &= ^LCDSFlag 144 | return &LCDCStatusTriggersISRAddr 145 | case i&TimerOverflowFlag != 0: 146 | irq.IF &= ^TimerOverflowFlag 147 | return &TimeroverflowISRAddr 148 | case i&SerialTransferFlag != 0: 149 | irq.IF &= ^SerialTransferFlag 150 | return &SerialTransferISRAddr 151 | case i&JoypadPressFlag != 0: 152 | irq.IF &= ^JoypadPressFlag 153 | return &JoypadPressISRAddr 154 | } 155 | return nil 156 | } 157 | -------------------------------------------------------------------------------- /pkg/logger/logger.go: -------------------------------------------------------------------------------- 1 | package logger 2 | 3 | import ( 4 | "log" 5 | ) 6 | 7 | // LogLevel is kind of Loglavel 8 | type LogLevel string 9 | 10 | const ( 11 | // LogDebug is 12 | LogDebug LogLevel = "Debug" 13 | // LogInfo is 14 | LogInfo LogLevel = "Info" 15 | // LogSilent is 16 | LogSilent LogLevel = "Silent" 17 | ) 18 | 19 | // Log is 20 | type Log struct { 21 | Level LogLevel 22 | } 23 | 24 | // NewLogger is logger constructor 25 | func NewLogger(level LogLevel) *Log { 26 | // now := time.Now().Unix() 27 | // n := strconv.FormatInt(now, 10) 28 | // logfile, err := os.OpenFile("./logs/test-"+n+".log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666) 29 | // if err != nil { 30 | // panic("cannnot open test.log:" + err.Error()) 31 | // } 32 | // log.SetOutput(io.MultiWriter(logfile)) 33 | // log.SetFlags(log.Ldate | log.Ltime) 34 | 35 | return &Log{ 36 | Level: level, 37 | } 38 | } 39 | 40 | // Debug is 41 | func (l *Log) Debug(args ...interface{}) { 42 | if l.Level != "Debug" { 43 | return 44 | } 45 | log.Println("[DEBUG] ", args) 46 | } 47 | 48 | // Info is 49 | func (l *Log) Info(args ...interface{}) { 50 | log.Println("[Info] ", args) 51 | } 52 | 53 | // Error is 54 | func (l *Log) Error(args ...interface{}) { 55 | log.Println("[ERROR] ", args) 56 | } 57 | 58 | // Warn is 59 | func (l *Log) Warn(args ...interface{}) { 60 | log.Println("[WARNING] ", args) 61 | } 62 | -------------------------------------------------------------------------------- /pkg/mocks/bus.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "github.com/bokuweb/gopher-boy/pkg/types" 5 | ) 6 | 7 | type MockBus struct { 8 | MockMemory [0x10000]byte 9 | } 10 | 11 | func (b *MockBus) WriteByte(addr types.Word, data byte) { 12 | b.MockMemory[addr] = data 13 | } 14 | 15 | func (b *MockBus) WriteWord(addr types.Word, data types.Word) { 16 | b.MockMemory[addr] = byte(data & 0xFF) 17 | b.MockMemory[addr+1] = byte(data >> 8) 18 | } 19 | 20 | func (b *MockBus) ReadByte(addr types.Word) byte { 21 | return b.MockMemory[addr] 22 | } 23 | 24 | func (b *MockBus) ReadWord(addr types.Word) types.Word { 25 | upper := types.Word(b.MockMemory[addr]) << 8 26 | return upper + types.Word(b.MockMemory[addr+1]) 27 | } 28 | 29 | func (b *MockBus) SetMemory(offset types.Word, data []byte) { 30 | for i, d := range data { 31 | b.MockMemory[offset+types.Word(i)] = d 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /pkg/pad/pad.go: -------------------------------------------------------------------------------- 1 | package pad 2 | 3 | // This is the matrix layout for register $FF00: 4 | // P14 P15 5 | // | | 6 | // P10-------O-Right----O-A 7 | // | | 8 | // P11-------O-Left-----O-B 9 | // | | 10 | // P12-------O-Up-------O-Select 11 | // | | 12 | // P13-------O-Down-----O-Start 13 | // 14 | 15 | type Pad struct { 16 | // Bit 7 - Not used 17 | // Bit 6 - Not used 18 | // Bit 5 - P15 out port 19 | // Bit 4 - P14 out port 20 | // Bit 3 - P13 in port 21 | // Bit 2 - P12 in port 22 | // Bit 1 - P11 in port 23 | // Bit 0 - P10 in port 24 | reg byte 25 | state Button 26 | } 27 | 28 | type Button byte 29 | 30 | const ( 31 | // A is the A button on the GameBoy. 32 | A Button = 0x01 33 | // B is the B button on the GameBoy. 34 | B Button = 0x02 35 | // Select is the select button on the GameBoy. 36 | Select Button = 0x04 37 | // Start is the start button on the GameBoy. 38 | Start Button = 0x08 39 | // Right is the right pad direction on the GameBoy. 40 | Right Button = 0x10 41 | // Left is the left pad direction on the GameBoy. 42 | Left Button = 0x20 43 | // Up is the up pad direction on the GameBoy. 44 | Up Button = 0x40 45 | // Down is the down pad direction on the GameBoy. 46 | Down Button = 0x80 47 | ) 48 | 49 | // NewPad constructs pad peripheral. 50 | func NewPad() *Pad { 51 | return &Pad{ 52 | reg: 0x3F, 53 | } 54 | } 55 | 56 | func (pad *Pad) Read() byte { 57 | if pad.isP14On() { 58 | return pad.reg & ^byte(pad.state>>4) 59 | } 60 | if pad.isP15On() { 61 | return pad.reg & ^byte(pad.state&0x0F) 62 | } 63 | return pad.reg | 0x0f 64 | } 65 | 66 | func (pad *Pad) Write(data byte) { 67 | pad.reg = (pad.reg & 0xCF) | (data & 0x30) 68 | } 69 | 70 | func (pad *Pad) isP14On() bool { 71 | return pad.reg&0x10 == 0 72 | } 73 | 74 | func (pad *Pad) isP15On() bool { 75 | return pad.reg&0x20 == 0 76 | } 77 | 78 | func (pad *Pad) Press(button Button) { 79 | pad.state |= button 80 | } 81 | 82 | func (pad *Pad) Release(button Button) { 83 | pad.state &= ^button 84 | } 85 | -------------------------------------------------------------------------------- /pkg/ram/ram.go: -------------------------------------------------------------------------------- 1 | package ram 2 | 3 | import ( 4 | "github.com/bokuweb/gopher-boy/pkg/types" 5 | ) 6 | 7 | // RAM is 8 | type RAM struct { 9 | data []byte 10 | } 11 | 12 | // NewRAM is RAM constructor 13 | func NewRAM(size int) *RAM { 14 | data := make([]byte, size) 15 | return &RAM{ 16 | data: data, 17 | } 18 | } 19 | 20 | func (r *RAM) Read(addr types.Word) byte { 21 | return r.data[addr] 22 | } 23 | 24 | func (r *RAM) Write(addr types.Word, data byte) { 25 | r.data[addr] = data 26 | } 27 | 28 | // Debugging 29 | func (r *RAM) GetBuf() []byte { 30 | return r.data 31 | } 32 | -------------------------------------------------------------------------------- /pkg/rom/rom.go: -------------------------------------------------------------------------------- 1 | package rom 2 | 3 | // ROM 4 | type ROM struct { 5 | data []byte 6 | } 7 | 8 | // NewROM is ROM constructor 9 | func NewROM(v []byte) *ROM { 10 | return &ROM{ 11 | data: v, 12 | } 13 | } 14 | 15 | func (r *ROM) Read(addr uint32) byte { 16 | return r.data[addr] 17 | } 18 | -------------------------------------------------------------------------------- /pkg/serial/serial.go: -------------------------------------------------------------------------------- 1 | package serial 2 | 3 | import "fmt" 4 | 5 | func Send(b byte) { 6 | fmt.Printf("%v", string(b)) 7 | } 8 | -------------------------------------------------------------------------------- /pkg/timer/timer.go: -------------------------------------------------------------------------------- 1 | package timer 2 | 3 | import ( 4 | "github.com/bokuweb/gopher-boy/pkg/types" 5 | ) 6 | 7 | const ( 8 | // TimerRegisterOffset is register offset address 9 | TimerRegisterOffset types.Word = 0xFF00 10 | // DIV - Divider Register (R/W) 11 | // This register is incremented 16384 (~16779 on SGB) times a second. 12 | // Writing any value sets it to $00. 13 | DIV = 0x04 14 | // TIMA - Timer counter (R/W) 15 | // This timer is incremented by a clock frequency specified by the TAC register ($FF07). 16 | // The timer generates an interrupt when it overflows. 17 | TIMA = 0x05 18 | // TMA - Timer Modulo (R/W) 19 | // When the TIMA overflows, this data will be loaded. 20 | TMA = 0x06 21 | // TAC - Timer Control (R/W) 22 | // Bit 2 - Timer Stop 23 | // 0: Stop Timer 24 | // 1: Start Timer 25 | // Bits 1+0 - Input Clock Select 26 | // 00: 4.096 KHz (~4.194 KHz SGB) 27 | // 01: 262.144 Khz (~268.4 KHz SGB) 28 | // 10: 65.536 KHz (~67.11 KHz SGB) 29 | // 11: 16.384 KHz (~16.78 KHz SGB) 30 | TAC = 0x07 31 | ) 32 | 33 | // Timer has 4 registers. 34 | type Timer struct { 35 | internalCounter uint16 36 | TIMA byte 37 | TAC byte 38 | TMA byte 39 | } 40 | 41 | // NewTimer constructs timer peripheral. 42 | func NewTimer() *Timer { 43 | return &Timer{ 44 | // 4.194304MHz / 256 = 16.384KHz 45 | internalCounter: 0, 46 | TIMA: 0x00, 47 | TAC: 0x00, 48 | TMA: 0x00, 49 | } 50 | } 51 | 52 | // Update timer counter registers 53 | // If timer is overflowed return true 54 | func (timer *Timer) Update(cycles uint) bool { 55 | r := false 56 | for cycles > 0 { 57 | cycles-- 58 | old := timer.internalCounter 59 | timer.internalCounter += 4 60 | 61 | if !timer.isStarted() { 62 | continue 63 | } 64 | if !timer.hasFallingEdgeDetected(old, timer.internalCounter) { 65 | continue 66 | } 67 | timer.TIMA++ 68 | if timer.TIMA == 0 { 69 | timer.TIMA = timer.TMA 70 | r = true 71 | } 72 | } 73 | return r 74 | } 75 | 76 | func (timer *Timer) Read(addr types.Word) byte { 77 | switch addr { 78 | case DIV: 79 | return byte(timer.internalCounter >> 8) 80 | case TIMA: 81 | return timer.TIMA 82 | case TMA: 83 | return timer.TMA 84 | case TAC: 85 | return timer.TAC 86 | } 87 | panic("Illegal access detected.") 88 | } 89 | 90 | func (timer *Timer) Write(addr types.Word, data byte) { 91 | switch addr { 92 | case DIV: 93 | // Writing any value sets it to $00. 94 | // When writing to DIV, the whole counter is reseted, so the timer is also affected. 95 | // When writing to DIV, if the current output is '1' and timer is enabled 96 | // as the new value after reseting DIV will be '0', the falling edge detector will detect a falling edge and TIMA will increase. 97 | if timer.hasFallingEdgeDetected(timer.internalCounter, 0) { 98 | timer.TIMA++ 99 | } 100 | timer.internalCounter = 0 101 | case TIMA: 102 | timer.TIMA = data 103 | case TMA: 104 | timer.TMA = data 105 | case TAC: 106 | timer.TAC = data 107 | } 108 | } 109 | 110 | func (timer *Timer) isStarted() bool { 111 | return timer.TAC&0x04 == 0x04 112 | } 113 | 114 | func (timer *Timer) hasFallingEdgeDetected(old, new uint16) bool { 115 | mask := uint16(1 << timer.getMaskBit()) 116 | return ((old & mask) != 0) && ((new & mask) == 0) 117 | } 118 | 119 | func (timer *Timer) getMaskBit() uint { 120 | switch timer.TAC & 0x03 { 121 | case 0x00: 122 | return 9 123 | case 0x01: 124 | return 3 125 | case 0x02: 126 | return 5 127 | case 0x03: 128 | return 7 129 | } 130 | return 0 131 | } 132 | -------------------------------------------------------------------------------- /pkg/types/types.go: -------------------------------------------------------------------------------- 1 | package types 2 | 3 | import "image/color" 4 | 5 | // Register is generic register 6 | type Register = byte 7 | 8 | // RGB display rgb color 9 | type RGB struct { 10 | Red byte 11 | Green byte 12 | Blue byte 13 | } 14 | 15 | // Word for gb 16 | type Word uint16 17 | 18 | type ImageData = []color.RGBA 19 | 20 | type Bit int 21 | 22 | const ( 23 | Bit0 Bit = 0x01 24 | Bit1 Bit = 0x02 25 | Bit2 Bit = 0x04 26 | Bit3 Bit = 0x08 27 | Bit4 Bit = 0x10 28 | Bit5 Bit = 0x20 29 | Bit6 Bit = 0x40 30 | Bit7 Bit = 0x80 31 | ) 32 | -------------------------------------------------------------------------------- /pkg/utils/byte.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import "github.com/bokuweb/gopher-boy/pkg/types" 4 | 5 | func Bytes2Word(upper, lower byte) types.Word { 6 | return (types.Word(upper) << 8) ^ types.Word(lower) 7 | } 8 | 9 | func Word2Bytes(w types.Word) (byte, byte) { 10 | return byte(w >> 8), byte(w & 0x00FF) 11 | } 12 | -------------------------------------------------------------------------------- /pkg/utils/loader.go: -------------------------------------------------------------------------------- 1 | package utils 2 | 3 | import ( 4 | "bufio" 5 | "os" 6 | ) 7 | 8 | // LoadROM loads gameboy ROMfile to buf 9 | func LoadROM(filename string) ([]byte, error) { 10 | file, err := os.Open(filename) 11 | if err != nil { 12 | return nil, err 13 | } 14 | defer file.Close() 15 | stats, statsErr := file.Stat() 16 | if statsErr != nil { 17 | return nil, statsErr 18 | } 19 | size := stats.Size() 20 | bytes := make([]byte, size) 21 | b := bufio.NewReader(file) 22 | _, err = b.Read(bytes) 23 | if err != nil { 24 | return nil, err 25 | } 26 | return bytes, nil 27 | } 28 | -------------------------------------------------------------------------------- /pkg/window/native.go: -------------------------------------------------------------------------------- 1 | // +build native 2 | 3 | package window 4 | 5 | import ( 6 | "image/color" 7 | "math" 8 | 9 | "github.com/bokuweb/gopher-boy/pkg/constants" 10 | "github.com/bokuweb/gopher-boy/pkg/pad" 11 | "github.com/faiface/pixel" 12 | "github.com/faiface/pixel/pixelgl" 13 | "golang.org/x/image/colornames" 14 | ) 15 | 16 | // Window is 17 | type Window struct { 18 | win *pixelgl.Window 19 | image *pixel.PictureData 20 | pad *pad.Pad 21 | } 22 | 23 | // Render renders the pixels on the window. 24 | func (w *Window) Render(buf []byte) { 25 | 26 | imgData := make([]color.RGBA, constants.ScreenWidth*constants.ScreenHeight) 27 | i := 0 28 | for i*4 < len(buf) { 29 | y := constants.ScreenHeight - (i / constants.ScreenWidth) - 1 30 | imgData[y*constants.ScreenWidth+i%constants.ScreenWidth] = color.RGBA{buf[i*4], buf[i*4+1], buf[i*4+2], buf[i*4+3]} 31 | i++ 32 | } 33 | 34 | w.image.Pix = imgData 35 | 36 | bg := color.RGBA{R: 0x0F, G: 0x38, B: 0x0F, A: 0xFF} 37 | w.win.Clear(bg) 38 | 39 | spr := pixel.NewSprite(pixel.Picture(w.image), pixel.R(0, 0, constants.ScreenWidth, constants.ScreenHeight)) 40 | spr.Draw(w.win, pixel.IM) 41 | w.updateCamera() 42 | w.win.Update() 43 | } 44 | 45 | func (w *Window) Run(f func()) { 46 | pixelgl.Run(f) 47 | } 48 | 49 | func (w *Window) updateCamera() { 50 | xScale := w.win.Bounds().W() / constants.ScreenWidth 51 | yScale := w.win.Bounds().H() / constants.ScreenHeight 52 | scale := math.Min(yScale, xScale) 53 | 54 | shift := w.win.Bounds().Size().Scaled(0.5).Sub(pixel.ZV) 55 | cam := pixel.IM.Scaled(pixel.ZV, scale).Moved(shift) 56 | w.win.SetMatrix(cam) 57 | } 58 | 59 | func (w *Window) Init() { 60 | cfg := pixelgl.WindowConfig{ 61 | Title: "gopher-boy", 62 | Bounds: pixel.R(0, 0, constants.ScreenWidth, constants.ScreenHeight), 63 | } 64 | win, err := pixelgl.NewWindow(cfg) 65 | if err != nil { 66 | panic(err) 67 | } 68 | win.Clear(colornames.Skyblue) 69 | w.win = win 70 | w.image = &pixel.PictureData{ 71 | Pix: make([]color.RGBA, constants.ScreenWidth*constants.ScreenHeight), 72 | Stride: constants.ScreenWidth, 73 | Rect: pixel.R(0, 0, constants.ScreenWidth, constants.ScreenHeight), 74 | } 75 | 76 | // Hack: https://github.com/faiface/pixel/issues/140 77 | pos := win.GetPos() 78 | win.SetPos(pixel.ZV) 79 | win.SetPos(pos) 80 | w.updateCamera() 81 | win.Update() 82 | } 83 | 84 | func (w *Window) PollKey() { 85 | for key, button := range keyMap { 86 | if w.win.JustPressed(key) { 87 | w.pad.Press(button) 88 | } 89 | if w.win.JustReleased(key) { 90 | w.pad.Release(button) 91 | } 92 | } 93 | } 94 | 95 | func (w *Window) KeyDown(button byte) { 96 | /* NOP */ 97 | } 98 | 99 | func (w *Window) KeyUp(button byte) { 100 | /* NOP */ 101 | } 102 | 103 | var keyMap = map[pixelgl.Button]pad.Button{ 104 | pixelgl.KeyZ: pad.A, 105 | pixelgl.KeyX: pad.B, 106 | pixelgl.KeyBackspace: pad.Select, 107 | pixelgl.KeyEnter: pad.Start, 108 | pixelgl.KeyRight: pad.Right, 109 | pixelgl.KeyLeft: pad.Left, 110 | pixelgl.KeyUp: pad.Up, 111 | pixelgl.KeyDown: pad.Down, 112 | } 113 | -------------------------------------------------------------------------------- /pkg/window/wasm.go: -------------------------------------------------------------------------------- 1 | // +build wasm 2 | 3 | package window 4 | 5 | import ( 6 | "github.com/bokuweb/gopher-boy/pkg/pad" 7 | ) 8 | 9 | // Window is 10 | type Window struct { 11 | keyState byte 12 | pad *pad.Pad 13 | } 14 | 15 | // Render renders the pixels on the window. 16 | func (w *Window) Render(imageData []byte) { 17 | /* NOP */ 18 | } 19 | 20 | func (w *Window) Run(f func()) { 21 | /* NOP */ 22 | } 23 | 24 | func (w *Window) Init() { 25 | /* NOP */ 26 | } 27 | 28 | func (w *Window) PollKey() { 29 | i := byte(0) 30 | for i < 8 { 31 | b := byte(0x01 << i) 32 | if w.keyState&b != 0 { 33 | w.pad.Press(pad.Button(b)) 34 | } else { 35 | w.pad.Release(pad.Button(b)) 36 | } 37 | i++ 38 | } 39 | } 40 | 41 | func (w *Window) KeyDown(button byte) { 42 | w.keyState |= byte(button) 43 | } 44 | 45 | func (w *Window) KeyUp(button byte) { 46 | w.keyState &= ^byte(button) 47 | } 48 | -------------------------------------------------------------------------------- /pkg/window/window.go: -------------------------------------------------------------------------------- 1 | package window 2 | 3 | import ( 4 | "github.com/bokuweb/gopher-boy/pkg/pad" 5 | ) 6 | 7 | func NewWindow(pad *pad.Pad) *Window { 8 | return &Window{pad: pad} 9 | } 10 | -------------------------------------------------------------------------------- /roms/acceptance/add_sp_e_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/add_sp_e_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/bits/mem_oam.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/bits/mem_oam.gb -------------------------------------------------------------------------------- /roms/acceptance/bits/reg_f.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/bits/reg_f.gb -------------------------------------------------------------------------------- /roms/acceptance/bits/unused_hwio-GS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/bits/unused_hwio-GS.gb -------------------------------------------------------------------------------- /roms/acceptance/boot_div-S.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/boot_div-S.gb -------------------------------------------------------------------------------- /roms/acceptance/boot_div-dmg0.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/boot_div-dmg0.gb -------------------------------------------------------------------------------- /roms/acceptance/boot_div-dmgABCmgb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/boot_div-dmgABCmgb.gb -------------------------------------------------------------------------------- /roms/acceptance/boot_div2-S.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/boot_div2-S.gb -------------------------------------------------------------------------------- /roms/acceptance/boot_hwio-S.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/boot_hwio-S.gb -------------------------------------------------------------------------------- /roms/acceptance/boot_hwio-dmg0.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/boot_hwio-dmg0.gb -------------------------------------------------------------------------------- /roms/acceptance/boot_hwio-dmgABCmgb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/boot_hwio-dmgABCmgb.gb -------------------------------------------------------------------------------- /roms/acceptance/boot_regs-dmg0.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/boot_regs-dmg0.gb -------------------------------------------------------------------------------- /roms/acceptance/boot_regs-dmgABC.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/boot_regs-dmgABC.gb -------------------------------------------------------------------------------- /roms/acceptance/boot_regs-mgb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/boot_regs-mgb.gb -------------------------------------------------------------------------------- /roms/acceptance/boot_regs-sgb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/boot_regs-sgb.gb -------------------------------------------------------------------------------- /roms/acceptance/boot_regs-sgb2.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/boot_regs-sgb2.gb -------------------------------------------------------------------------------- /roms/acceptance/call_cc_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/call_cc_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/call_cc_timing2.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/call_cc_timing2.gb -------------------------------------------------------------------------------- /roms/acceptance/call_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/call_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/call_timing2.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/call_timing2.gb -------------------------------------------------------------------------------- /roms/acceptance/di_timing-GS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/di_timing-GS.gb -------------------------------------------------------------------------------- /roms/acceptance/div_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/div_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/ei_sequence.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/ei_sequence.gb -------------------------------------------------------------------------------- /roms/acceptance/ei_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/ei_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/halt_ime0_ei.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/halt_ime0_ei.gb -------------------------------------------------------------------------------- /roms/acceptance/halt_ime0_nointr_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/halt_ime0_nointr_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/halt_ime1_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/halt_ime1_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/halt_ime1_timing2-GS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/halt_ime1_timing2-GS.gb -------------------------------------------------------------------------------- /roms/acceptance/if_ie_registers.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/if_ie_registers.gb -------------------------------------------------------------------------------- /roms/acceptance/instr/daa.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/instr/daa.gb -------------------------------------------------------------------------------- /roms/acceptance/interrupts/ie_push.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/interrupts/ie_push.gb -------------------------------------------------------------------------------- /roms/acceptance/intr_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/intr_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/jp_cc_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/jp_cc_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/jp_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/jp_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/ld_hl_sp_e_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/ld_hl_sp_e_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/oam_dma/basic.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/oam_dma/basic.gb -------------------------------------------------------------------------------- /roms/acceptance/oam_dma/reg_read.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/oam_dma/reg_read.gb -------------------------------------------------------------------------------- /roms/acceptance/oam_dma/sources-dmgABCmgbS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/oam_dma/sources-dmgABCmgbS.gb -------------------------------------------------------------------------------- /roms/acceptance/oam_dma_restart.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/oam_dma_restart.gb -------------------------------------------------------------------------------- /roms/acceptance/oam_dma_start.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/oam_dma_start.gb -------------------------------------------------------------------------------- /roms/acceptance/oam_dma_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/oam_dma_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/pop_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/pop_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/ppu/hblank_ly_scx_timing-GS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/ppu/hblank_ly_scx_timing-GS.gb -------------------------------------------------------------------------------- /roms/acceptance/ppu/intr_1_2_timing-GS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/ppu/intr_1_2_timing-GS.gb -------------------------------------------------------------------------------- /roms/acceptance/ppu/intr_2_0_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/ppu/intr_2_0_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/ppu/intr_2_mode0_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/ppu/intr_2_mode0_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/ppu/intr_2_mode0_timing_sprites.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/ppu/intr_2_mode0_timing_sprites.gb -------------------------------------------------------------------------------- /roms/acceptance/ppu/intr_2_mode3_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/ppu/intr_2_mode3_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/ppu/intr_2_oam_ok_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/ppu/intr_2_oam_ok_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/ppu/lcdon_timing-dmgABCmgbS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/ppu/lcdon_timing-dmgABCmgbS.gb -------------------------------------------------------------------------------- /roms/acceptance/ppu/lcdon_write_timing-GS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/ppu/lcdon_write_timing-GS.gb -------------------------------------------------------------------------------- /roms/acceptance/ppu/stat_irq_blocking.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/ppu/stat_irq_blocking.gb -------------------------------------------------------------------------------- /roms/acceptance/ppu/stat_lyc_onoff.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/ppu/stat_lyc_onoff.gb -------------------------------------------------------------------------------- /roms/acceptance/ppu/vblank_stat_intr-GS.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/ppu/vblank_stat_intr-GS.gb -------------------------------------------------------------------------------- /roms/acceptance/push_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/push_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/rapid_di_ei.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/rapid_di_ei.gb -------------------------------------------------------------------------------- /roms/acceptance/ret_cc_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/ret_cc_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/ret_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/ret_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/reti_intr_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/reti_intr_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/reti_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/reti_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/rst_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/rst_timing.gb -------------------------------------------------------------------------------- /roms/acceptance/serial/boot_sclk_align-dmgABCmgb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/serial/boot_sclk_align-dmgABCmgb.gb -------------------------------------------------------------------------------- /roms/acceptance/timer/div_write.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/timer/div_write.gb -------------------------------------------------------------------------------- /roms/acceptance/timer/rapid_toggle.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/timer/rapid_toggle.gb -------------------------------------------------------------------------------- /roms/acceptance/timer/tim00.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/timer/tim00.gb -------------------------------------------------------------------------------- /roms/acceptance/timer/tim00_div_trigger.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/timer/tim00_div_trigger.gb -------------------------------------------------------------------------------- /roms/acceptance/timer/tim01.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/timer/tim01.gb -------------------------------------------------------------------------------- /roms/acceptance/timer/tim01_div_trigger.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/timer/tim01_div_trigger.gb -------------------------------------------------------------------------------- /roms/acceptance/timer/tim10.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/timer/tim10.gb -------------------------------------------------------------------------------- /roms/acceptance/timer/tim10_div_trigger.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/timer/tim10_div_trigger.gb -------------------------------------------------------------------------------- /roms/acceptance/timer/tim11.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/timer/tim11.gb -------------------------------------------------------------------------------- /roms/acceptance/timer/tim11_div_trigger.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/timer/tim11_div_trigger.gb -------------------------------------------------------------------------------- /roms/acceptance/timer/tima_reload.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/timer/tima_reload.gb -------------------------------------------------------------------------------- /roms/acceptance/timer/tima_write_reloading.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/timer/tima_write_reloading.gb -------------------------------------------------------------------------------- /roms/acceptance/timer/tma_write_reloading.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/acceptance/timer/tma_write_reloading.gb -------------------------------------------------------------------------------- /roms/cpu_instrs/01-special.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/cpu_instrs/01-special.gb -------------------------------------------------------------------------------- /roms/cpu_instrs/02-interrupts.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/cpu_instrs/02-interrupts.gb -------------------------------------------------------------------------------- /roms/cpu_instrs/03-op sp,hl.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/cpu_instrs/03-op sp,hl.gb -------------------------------------------------------------------------------- /roms/cpu_instrs/04-op r,imm.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/cpu_instrs/04-op r,imm.gb -------------------------------------------------------------------------------- /roms/cpu_instrs/05-op rp.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/cpu_instrs/05-op rp.gb -------------------------------------------------------------------------------- /roms/cpu_instrs/06-ld r,r.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/cpu_instrs/06-ld r,r.gb -------------------------------------------------------------------------------- /roms/cpu_instrs/07-jr,jp,call,ret,rst.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/cpu_instrs/07-jr,jp,call,ret,rst.gb -------------------------------------------------------------------------------- /roms/cpu_instrs/08-misc instrs.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/cpu_instrs/08-misc instrs.gb -------------------------------------------------------------------------------- /roms/cpu_instrs/09-op r,r.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/cpu_instrs/09-op r,r.gb -------------------------------------------------------------------------------- /roms/cpu_instrs/10-bit ops.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/cpu_instrs/10-bit ops.gb -------------------------------------------------------------------------------- /roms/cpu_instrs/11-op a,(hl).gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/cpu_instrs/11-op a,(hl).gb -------------------------------------------------------------------------------- /roms/cpu_instrs/cpu_instrs.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/cpu_instrs/cpu_instrs.gb -------------------------------------------------------------------------------- /roms/dino/dino.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/dino/dino.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc1/bits_bank1.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc1/bits_bank1.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc1/bits_bank2.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc1/bits_bank2.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc1/bits_mode.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc1/bits_mode.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc1/bits_ramg.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc1/bits_ramg.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc1/multicart_rom_8Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc1/multicart_rom_8Mb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc1/ram_256kb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc1/ram_256kb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc1/ram_64kb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc1/ram_64kb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc1/rom_16Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc1/rom_16Mb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc1/rom_1Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc1/rom_1Mb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc1/rom_2Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc1/rom_2Mb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc1/rom_4Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc1/rom_4Mb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc1/rom_512kb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc1/rom_512kb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc1/rom_8Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc1/rom_8Mb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc2/bits_ramg.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc2/bits_ramg.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc2/bits_romb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc2/bits_romb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc2/bits_unused.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc2/bits_unused.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc2/ram.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc2/ram.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc2/rom_1Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc2/rom_1Mb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc2/rom_2Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc2/rom_2Mb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc2/rom_512kb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc2/rom_512kb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc5/rom_16Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc5/rom_16Mb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc5/rom_1Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc5/rom_1Mb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc5/rom_2Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc5/rom_2Mb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc5/rom_32Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc5/rom_32Mb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc5/rom_4Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc5/rom_4Mb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc5/rom_512kb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc5/rom_512kb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc5/rom_64Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc5/rom_64Mb.gb -------------------------------------------------------------------------------- /roms/emulator-only/mbc5/rom_8Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/emulator-only/mbc5/rom_8Mb.gb -------------------------------------------------------------------------------- /roms/hangman/Hangman (PD).gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/hangman/Hangman (PD).gb -------------------------------------------------------------------------------- /roms/helloworld/README.md: -------------------------------------------------------------------------------- 1 | # hello 8-bit world! 2 | Example of Gameboy (DMG) "Hello world!" written in assembly language. This is simple version that doesn't use interrupts. Assemble and link with [RGBASM](https://github.com/rednex/rgbds). 3 | 4 | ``` 5 | rgbasm -ohello.o hello.s 6 | rgblink -p00 -ohello.gb hello.o 7 | rgbfix -v -m00 -p00 -tHello hello.gb 8 | ``` 9 | -------------------------------------------------------------------------------- /roms/helloworld/hello.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/helloworld/hello.gb -------------------------------------------------------------------------------- /roms/helloworld/hello.o: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/helloworld/hello.o -------------------------------------------------------------------------------- /roms/helloworld/hello.s: -------------------------------------------------------------------------------- 1 | INCLUDE "hardware.inc" 2 | 3 | SECTION "Start",ROM0[$100] ; start vector, followed by header data applied by rgbfix.exe 4 | nop 5 | jp Start 6 | 7 | SECTION "Example",ROM0[$150] ; code starts here 8 | 9 | Start: 10 | di ; disable interrupts 11 | ld sp,$E000 ; setup stack 12 | 13 | .wait_vbl 14 | ld a,[rLY] 15 | cp $90 16 | jr nz,.wait_vbl ; wait for vblank to properly disable lcd 17 | 18 | xor a 19 | ld [rIF],a ; reset usual regs 20 | ld [rLCDC],a 21 | ld [rSTAT],a 22 | ld [rSCX],a 23 | ld [rSCY],a 24 | ld [rLYC],a 25 | ld [rIE],a 26 | 27 | ld hl,_RAM ; fill ram with a, a = 0 here 28 | ld bc,$2000-2 ; watch out for stack ;) 29 | call fill 30 | 31 | ld hl,_HRAM 32 | ld c,$80 ; a = 0, b = 0 here, so let's save a cycle or two 33 | call fill 34 | 35 | ld hl,_VRAM 36 | ld b,$18 ; a = 0, bc should be $1800; c = 0 here, so.. 37 | call fill 38 | 39 | ld a,$20 ; ascii code for 'space' 40 | ; no need to setup hl since _SCRN0 and _SCRN1 are part of _VRAM, just continue 41 | ld b,8 ; bc should be $800; c = 0 here, so.. 42 | call fill 43 | 44 | ld a,%10010011 ; 00 - light, 01 - gray, 10 - dark grey, 11 - dark 45 | ld [rBGP],a ; bg palette 46 | ld [rOBP0],a ; obj palettes 47 | ld [rOBP1],a 48 | 49 | ld hl,font ; font data 50 | ld de,_VRAM+$200 ; place it here to get ascii mapping 51 | ld bc,1776 ; speccy.chr file size 52 | call copy 53 | 54 | ld hl,text ; hello message 55 | ld de,_SCRN0+$100 ; center it a bit 56 | ld c,text_end-text ; b = 0, our string = 18 chars, so.. 57 | call copy ; lcdc is disabled so you have 'easy' access to vram 58 | 59 | ld a,LCDCF_ON | LCDCF_BG8000 | LCDCF_BG9800 | LCDCF_OBJ8 | LCDCF_OBJOFF | LCDCF_WINOFF | LCDCF_BGON 60 | ld [rLCDC],a ; enable lcd 61 | 62 | .the_end 63 | halt 64 | nop 65 | 66 | jr .the_end 67 | 68 | ;------------------------------------------------------------------------------- 69 | copy: 70 | ;------------------------------------------------------------------------------- 71 | ; hl - source address 72 | ; de - destination 73 | ; bc - size 74 | inc b 75 | inc c 76 | jr .skip 77 | .copy 78 | ld a,[hl+] 79 | ld [de],a 80 | inc de 81 | .skip 82 | dec c 83 | jr nz,.copy 84 | dec b 85 | jr nz,.copy 86 | ret 87 | 88 | ;------------------------------------------------------------------------------- 89 | fill: 90 | ;------------------------------------------------------------------------------- 91 | ; a - byte to fill with 92 | ; hl - destination address 93 | ; bc - size of area to fill 94 | 95 | inc b 96 | inc c 97 | jr .skip 98 | .fill 99 | ld [hl+],a 100 | .skip 101 | dec c 102 | jr nz,.fill 103 | dec b 104 | jr nz,.fill 105 | ret 106 | 107 | ;------------------------------------------------------------------------------- 108 | 109 | font: 110 | INCBIN "speccy.chr" ; created with https://github.com/gitendo/bmp2cgb 111 | 112 | text: 113 | DB " Hello 8-bit world! " 114 | text_end: -------------------------------------------------------------------------------- /roms/helloworld/make.cmd: -------------------------------------------------------------------------------- 1 | @echo off 2 | cls 3 | rgbasm -ohello.o hello.s 4 | rgblink -p00 -ohello.gb hello.o 5 | rgbfix -v -m00 -p00 -tHello hello.gb 6 | -------------------------------------------------------------------------------- /roms/helloworld/speccy.chr: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/helloworld/speccy.chr -------------------------------------------------------------------------------- /roms/instr_timing/instr_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/instr_timing/instr_timing.gb -------------------------------------------------------------------------------- /roms/instr_timing/readme.txt: -------------------------------------------------------------------------------- 1 | Game Boy CPU Instruction Timing Test 2 | ------------------------------------ 3 | This ROM tests the timings of all CPU instructions except HALT, STOP, 4 | and the 11 illegal opcodes. For conditional instructions, it tests taken 5 | and not taken timings. This test requires proper timer operation (TAC, 6 | TIMA, TMA). 7 | 8 | Failed instructions are listed as 9 | 10 | [CB] opcode:measured time-correct time 11 | 12 | Times are in terms of instruction cycles, where NOP takes one cycle. 13 | 14 | 15 | Verified cycle timing tables 16 | ---------------------------- 17 | The test internally uses a table of proper cycle times, which can be 18 | used in an emulator to ensure proper timing. The only changes below are 19 | removal of the .byte prefixes, and addition of commas at the ends, so 20 | that they can be used without changes in most programming languages. For 21 | added correctness assurance, the original tables can be found at the end 22 | of the source code. 23 | 24 | Normal instructions: 25 | 26 | 1,3,2,2,1,1,2,1,5,2,2,2,1,1,2,1, 27 | 0,3,2,2,1,1,2,1,3,2,2,2,1,1,2,1, 28 | 2,3,2,2,1,1,2,1,2,2,2,2,1,1,2,1, 29 | 2,3,2,2,3,3,3,1,2,2,2,2,1,1,2,1, 30 | 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, 31 | 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, 32 | 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, 33 | 2,2,2,2,2,2,0,2,1,1,1,1,1,1,2,1, 34 | 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, 35 | 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, 36 | 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, 37 | 1,1,1,1,1,1,2,1,1,1,1,1,1,1,2,1, 38 | 2,3,3,4,3,4,2,4,2,4,3,0,3,6,2,4, 39 | 2,3,3,0,3,4,2,4,2,4,3,0,3,0,2,4, 40 | 3,3,2,0,0,4,2,4,4,1,4,0,0,0,2,4, 41 | 3,3,2,1,0,4,2,4,3,2,4,1,0,0,2,4 42 | 43 | CB-prefixed instructions: 44 | 45 | 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, 46 | 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, 47 | 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, 48 | 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, 49 | 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2, 50 | 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2, 51 | 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2, 52 | 2,2,2,2,2,2,3,2,2,2,2,2,2,2,3,2, 53 | 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, 54 | 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, 55 | 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, 56 | 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, 57 | 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, 58 | 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, 59 | 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2, 60 | 2,2,2,2,2,2,4,2,2,2,2,2,2,2,4,2 61 | 62 | 63 | Internal operation 64 | ------------------ 65 | Before each instruction is executed, the test sets up registers and 66 | memory in such a way that the instruction will cleanly execute and then 67 | end up at a common destination, without trashing anything important. The 68 | timing itself is done by first synchronizing to the timer via a loop, 69 | executing the instruction, then using a similar loop to determine how 70 | many clocks elapsed. 71 | 72 | 73 | Failure codes 74 | ------------- 75 | Failed tests may print a failure code, and also short description of the 76 | problem. For more information about a failure code, look in the 77 | corresponding source file in source/; the point in the code where 78 | "set_test n" occurs is where that failure code will be generated. 79 | Failure code 1 is a general failure of the test; any further information 80 | will be printed. 81 | 82 | Note that once a sub-test fails, no further tests for that file are run. 83 | 84 | 85 | Console output 86 | -------------- 87 | Information is printed on screen in a way that needs only minimum LCD 88 | support, and won't hang if LCD output isn't supported at all. 89 | Specifically, while polling LY to wait for vblank, it will time out if 90 | it takes too long, so LY always reading back as the same value won't 91 | hang the test. It's also OK if scrolling isn't supported; in this case, 92 | text will appear starting at the top of the screen. 93 | 94 | Everything printed on screen is also sent to the game link port by 95 | writing the character to SB, then writing $81 to SC. This is useful for 96 | tests which print lots of information that scrolls off screen. 97 | 98 | 99 | Source code 100 | ----------- 101 | Source code is included for all tests, in source/. It can be used to 102 | build the individual test ROMs. Code for the multi test isn't included 103 | due to the complexity of putting everything together. 104 | 105 | Code is written for the wla-dx assembler. To assemble a particular test, 106 | execute 107 | 108 | wla -o "source_filename.s" test.o 109 | wlalink linkfile test.gb 110 | 111 | Test code uses a common shell framework contained in common/. 112 | 113 | 114 | Internal framework operation 115 | ---------------------------- 116 | Tests use a common framework for setting things up, reporting results, 117 | and ending. All files first include "shell.inc", which sets up the ROM 118 | header and shell code, and includes other commonly-used modules. 119 | 120 | One oddity is that test code is first copied to internal RAM at $D000, 121 | then executed there. This allows self-modification, and ensures the code 122 | is executed the same way it is on my devcart, which doesn't have a 123 | rewritable ROM as most do. 124 | 125 | Some macros are used to simplify common tasks: 126 | 127 | Macro Behavior 128 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 129 | wreg addr,data Writes data to addr using LDH 130 | lda addr Loads byte from addr into A using LDH 131 | sta addr Stores A at addr using LDH 132 | delay n Delays n cycles, where NOP = 1 cycle 133 | delay_msec n Delays n milliseconds 134 | set_test n,"Cause" Sets failure code and optional string 135 | 136 | Routines and macros are documented where they are defined. 137 | 138 | -- 139 | Shay Green 140 | -------------------------------------------------------------------------------- /roms/instr_timing/source/common/build_gbs.s: -------------------------------------------------------------------------------- 1 | ; Build as GBS music file 2 | 3 | .memoryMap 4 | defaultSlot 0 5 | slot 0 $3000 size $1000 6 | slot 1 $C000 size $1000 7 | .endMe 8 | 9 | .romBankSize $1000 10 | .romBanks 2 11 | 12 | 13 | ;;;; GBS music file header 14 | 15 | .byte "GBS" 16 | .byte 1 ; vers 17 | .byte 1 ; songs 18 | .byte 1 ; first song 19 | .word load_addr 20 | .word reset 21 | .word gbs_play 22 | .word std_stack 23 | .byte 0,0 ; timer 24 | .ds $60,0 25 | load_addr: 26 | 27 | ; WLA assumes we're building ROM and messes 28 | ; with bytes at the beginning, so skip them. 29 | .ds $100,0 30 | 31 | 32 | ;;;; Shell 33 | 34 | .include "runtime.s" 35 | 36 | init_runtime: 37 | ld a,$01 ; Identify as DMG hardware 38 | ld (gb_id),a 39 | .ifdef TEST_NAME 40 | print_str TEST_NAME,newline,newline 41 | .endif 42 | ret 43 | 44 | std_print: 45 | sta SB 46 | wreg SC,$81 47 | delay 2304 48 | ret 49 | 50 | post_exit: 51 | call play_byte 52 | forever: 53 | wreg NR52,0 ; sound off 54 | - jp - 55 | 56 | .ifndef CUSTOM_RESET 57 | gbs_play: 58 | .endif 59 | console_flush: 60 | console_normal: 61 | console_inverse: 62 | console_set_mode: 63 | ret 64 | 65 | ; Reports A in binary as high and low tones, with 66 | ; leading low tone for reference. Omits leading 67 | ; zeroes. 68 | ; Preserved: AF, BC, DE, HL 69 | play_byte: 70 | push af 71 | push hl 72 | 73 | ; HL = (A << 1) | 1 74 | scf 75 | rla 76 | ld l,a 77 | ld h,0 78 | rl h 79 | 80 | ; Shift left until next-to-top bit is 1 81 | - add hl,hl 82 | bit 6,h 83 | jr z,- 84 | 85 | ; Reset sound 86 | delay_msec 400 87 | wreg NR52,0 ; sound off 88 | wreg NR52,$80 ; sound on 89 | wreg NR51,$FF ; mono 90 | wreg NR50,$77 ; volume 91 | 92 | - add hl,hl 93 | 94 | ; Low or high pitch based on bit shifted out 95 | ; of HL 96 | ld a,0 97 | jr nc,+ 98 | ld a,$FF 99 | + sta NR23 100 | 101 | ; Play short tone 102 | wreg NR21,$A0 103 | wreg NR22,$F0 104 | wreg NR24,$86 105 | delay_msec 75 106 | wreg NR22,0 107 | wreg NR23,$F8 108 | wreg NR24,$87 109 | delay_msec 200 110 | 111 | ; Loop until HL = $8000 112 | ld a,h 113 | xor $80 114 | or l 115 | jr nz,- 116 | 117 | pop hl 118 | pop af 119 | ret 120 | 121 | .ends 122 | -------------------------------------------------------------------------------- /roms/instr_timing/source/common/build_rom.s: -------------------------------------------------------------------------------- 1 | ; Build as GB ROM 2 | 3 | .memoryMap 4 | defaultSlot 0 5 | slot 0 $0000 size $4000 6 | slot 1 $C000 size $4000 7 | .endMe 8 | 9 | .romBankSize $4000 ; generates $8000 byte ROM 10 | .romBanks 2 11 | 12 | .cartridgeType 1 ; MBC1 13 | .computeChecksum 14 | .computeComplementCheck 15 | 16 | 17 | ;;;; GB ROM header 18 | 19 | ; GB header read by bootrom 20 | .org $100 21 | nop 22 | jp reset 23 | 24 | ; Nintendo logo required for proper boot 25 | .byte $CE,$ED,$66,$66,$CC,$0D,$00,$0B 26 | .byte $03,$73,$00,$83,$00,$0C,$00,$0D 27 | .byte $00,$08,$11,$1F,$88,$89,$00,$0E 28 | .byte $DC,$CC,$6E,$E6,$DD,$DD,$D9,$99 29 | .byte $BB,$BB,$67,$63,$6E,$0E,$EC,$CC 30 | .byte $DD,$DC,$99,$9F,$BB,$B9,$33,$3E 31 | 32 | ; Internal name 33 | .ifdef ROM_NAME 34 | .byte ROM_NAME 35 | .endif 36 | 37 | ; CGB/DMG requirements 38 | .org $143 39 | .ifdef REQUIRE_CGB 40 | .byte $C0 41 | .else 42 | .ifndef REQUIRE_DMG 43 | .byte $80 44 | .endif 45 | .endif 46 | 47 | .org $200 48 | 49 | 50 | ;;;; Shell 51 | 52 | .include "runtime.s" 53 | .include "console.s" 54 | 55 | init_runtime: 56 | call console_init 57 | .ifdef TEST_NAME 58 | print_str TEST_NAME,newline,newline 59 | .endif 60 | ret 61 | 62 | std_print: 63 | push af 64 | sta SB 65 | wreg SC,$81 66 | delay 2304 67 | pop af 68 | jp console_print 69 | 70 | post_exit: 71 | call console_show 72 | call play_byte 73 | forever: 74 | wreg NR52,0 ; sound off 75 | - jr - 76 | 77 | play_byte: 78 | ret 79 | 80 | .ends 81 | -------------------------------------------------------------------------------- /roms/instr_timing/source/common/console.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/instr_timing/source/common/console.bin -------------------------------------------------------------------------------- /roms/instr_timing/source/common/console.s: -------------------------------------------------------------------------------- 1 | ; Scrolling text console 2 | 3 | ; Console is 20x18 characters. Buffers lines, so 4 | ; output doesn't appear until a newline or flush. 5 | ; If scrolling isn't supported (i.e. SCY is treated 6 | ; as if always zero), the first 18 lines will 7 | ; still print properly). Also works properly if 8 | ; LY isn't supported (always reads back as the same 9 | ; value). 10 | 11 | .define console_width 20 12 | 13 | .define console_buf bss+0 14 | .define console_pos bss+console_width 15 | .define console_mode bss+console_width+1 16 | .define console_scroll bss+console_width+2 17 | .redefine bss bss+console_width+3 18 | 19 | 20 | ; Waits for start of LCD blanking period 21 | ; Preserved: BC, DE, HL 22 | console_wait_vbl: 23 | push bc 24 | 25 | ; Wait for start of vblank, with 26 | ; timeout in case LY doesn't work 27 | ; or LCD is disabled. 28 | ld bc,-1250 29 | - inc bc 30 | ld a,b 31 | or c 32 | jr z,@timeout 33 | lda LY 34 | cp 144 35 | jr nz,- 36 | @timeout: 37 | 38 | pop bc 39 | ret 40 | 41 | 42 | ; Initializes text console 43 | console_init: 44 | call console_hide 45 | 46 | ; CGB-specific inits 47 | ld a,(gb_id) 48 | and gb_id_cgb 49 | call nz,@init_cgb 50 | 51 | ; Clear nametable 52 | ld a,' ' 53 | call @fill_nametable 54 | 55 | ; Load tiles 56 | ld hl,TILES+$200 57 | ld c,0 58 | call @load_tiles 59 | ld hl,TILES+$A00 60 | ld c,$FF 61 | call @load_tiles 62 | 63 | ; Init state 64 | ld a,console_width 65 | ld (console_pos),a 66 | ld a,0 67 | ld (console_mode),a 68 | ld a,-8 69 | ld (console_scroll),a 70 | call console_scroll_up_ 71 | jr console_show 72 | 73 | @fill_nametable: 74 | ld hl,BGMAP0 75 | ld b,4 76 | - ld (hl),a 77 | inc l 78 | jr nz,- 79 | inc h 80 | dec b 81 | jr nz,- 82 | ret 83 | 84 | @init_cgb: 85 | ; Clear palette 86 | wreg $FF68,$80 87 | ld b,16 88 | - wreg $FF69,$FF 89 | wreg $FF69,$7F 90 | wreg $FF69,$00 91 | wreg $FF69,$00 92 | wreg $FF69,$00 93 | wreg $FF69,$00 94 | wreg $FF69,$00 95 | wreg $FF69,$00 96 | dec b 97 | jr nz,- 98 | 99 | ; Clear attributes 100 | ld a,1 101 | ld (VBK),a 102 | ld a,0 103 | call @fill_nametable 104 | 105 | ld a,0 106 | ld (VBK),a 107 | ret 108 | 109 | @load_tiles: 110 | ld de,ASCII 111 | ld b,96 112 | -- push bc 113 | ld b,8 114 | - ld a,(de) 115 | inc de 116 | xor c 117 | ldi (hl),a 118 | ldi (hl),a 119 | dec b 120 | jr nz,- 121 | pop bc 122 | dec b 123 | jr nz,-- 124 | ret 125 | 126 | 127 | ; Shows console display 128 | ; Preserved: AF, BC, DE, HL 129 | console_show: 130 | push af 131 | 132 | ; Enable LCD 133 | call console_wait_vbl 134 | wreg LCDC,$91 135 | wreg SCX,0 136 | wreg BGP,$E4 137 | 138 | jp console_apply_scroll_ 139 | 140 | 141 | ; Hides console display by turning LCD off 142 | ; Preserved: AF, BC, DE, HL 143 | console_hide: 144 | push af 145 | 146 | ; LCD off 147 | call console_wait_vbl 148 | wreg LCDC,$11 149 | 150 | pop af 151 | ret 152 | 153 | 154 | ; Changes to normal text mode 155 | ; Preserved: BC, DE, HL 156 | console_normal: 157 | xor a 158 | jr console_set_mode 159 | 160 | ; Changes to inverse text mode 161 | ; Preserved: BC, DE, HL 162 | console_inverse: 163 | ld a,$80 164 | 165 | ; Changes console mode to A. 166 | ; 0: Normal, $80: Inverse 167 | ; Preserved: BC, DE, HL 168 | console_set_mode: 169 | and $80 170 | ld (console_mode),a 171 | ret 172 | 173 | 174 | ; Prints char A to console. Will not appear until 175 | ; a newline or flush occurs. 176 | ; Preserved: AF, BC, DE, HL 177 | console_print: 178 | push af 179 | 180 | cp 10 181 | jr z,console_newline_ 182 | 183 | push hl 184 | push af 185 | ld hl,console_pos 186 | ldi a,(hl) 187 | cp BGMAP0) >> 2 273 | add hl,hl 274 | add hl,hl 275 | 276 | ; Copy line 277 | ld de,console_buf + console_width 278 | - dec e 279 | ld a,(de) 280 | ldi (hl),a 281 | ld a,e 282 | cp checksum 67 | ldi (hl),a 68 | ld (hl),d 69 | inc l 70 | ld (hl),c 71 | inc l 72 | ld (hl),b 73 | 74 | pop hl 75 | pop de 76 | pop bc 77 | pop af 78 | ret 79 | -------------------------------------------------------------------------------- /roms/instr_timing/source/common/gb.inc: -------------------------------------------------------------------------------- 1 | ; Game Boy hardware addresses 2 | 3 | ; Memory 4 | .define VRAM $8000 ; video memory 5 | .define TILES $8000 ; tile images 6 | .define BGMAP0 $9800 ; first 32x32 tilemap 7 | .define BGMAP1 $9C00 ; second 32x32 tilemap 8 | .define WRAM $C000 ; internal memory 9 | .define OAM $FE00 ; sprite memory 10 | .define HRAM $FF80 ; fast memory for LDH 11 | 12 | .define P1 $FF00 13 | 14 | ; Game link I/O 15 | .define SB $FF01 16 | .define SC $FF02 17 | 18 | ; Interrupts 19 | .define DIV $FF04 20 | .define TIMA $FF05 21 | .define TMA $FF06 22 | .define TAC $FF07 23 | .define IF $FF0F 24 | .define IE $FFFF 25 | 26 | ; LCD registers 27 | .define LCDC $FF40 ; control 28 | .define STAT $FF41 ; status 29 | .define SCY $FF42 ; scroll Y 30 | .define SCX $FF43 ; scroll X 31 | .define LY $FF44 ; current Y being rendered 32 | .define BGP $FF47 33 | 34 | .define KEY1 $FF4D ; for changing CPU speed 35 | .define VBK $FF4F 36 | 37 | ; Sound registers 38 | .define NR10 $FF10 39 | .define NR11 $FF11 40 | .define NR12 $FF12 41 | .define NR13 $FF13 42 | .define NR14 $FF14 43 | 44 | .define NR21 $FF16 45 | .define NR22 $FF17 46 | .define NR23 $FF18 47 | .define NR24 $FF19 48 | 49 | .define NR30 $FF1A 50 | .define NR31 $FF1B 51 | .define NR32 $FF1C 52 | .define NR33 $FF1D 53 | .define NR34 $FF1E 54 | 55 | .define NR41 $FF20 56 | .define NR42 $FF21 57 | .define NR43 $FF22 58 | .define NR44 $FF23 59 | 60 | .define NR50 $FF24 61 | .define NR51 $FF25 62 | .define NR52 $FF26 63 | 64 | .define WAVE $FF30 65 | -------------------------------------------------------------------------------- /roms/instr_timing/source/common/macros.inc: -------------------------------------------------------------------------------- 1 | ; General macros 2 | 3 | ; Reads A from addr, from $FF00 to $FFFF 4 | ; Preserved: F, BC, DE, HL 5 | ; Time: 3 cycles 6 | .macro lda ARGS addr 7 | ldh a,(addr - $FF00) 8 | .endm 9 | 10 | ; Writes A to addr, from $FF00 to $FFFF 11 | ; Preserved: AF, BC, DE, HL 12 | ; Time: 3 cycles 13 | .macro sta ARGS addr 14 | ldh (addr - $FF00),a 15 | .endm 16 | 17 | ; Writes immediate data to addr, from $FF00 to $FFFF 18 | ; Preserved: F, BC, DE, HL 19 | ; Time: 5 cycles 20 | .macro wreg ARGS addr, data 21 | ld a,data 22 | sta addr 23 | .endm 24 | 25 | ; Calls routine multiple times, with A having the 26 | ; value 'start' the first time, 'start+step' the 27 | ; second time, up to 'end' for the last time. 28 | ; Preserved: BC, DE, HL 29 | .macro for_loop ; routine,start,end,step 30 | ld a,\2 31 | 32 | for_loop\@: 33 | push af 34 | call \1 35 | pop af 36 | 37 | add \4 38 | cp <(\3 + \4) 39 | jr nz,for_loop\@ 40 | .endm 41 | 42 | ; Calls routine n times. The value of A in the routine 43 | ; counts from 0 to n-1. 44 | ; Preserved: BC, DE, HL 45 | .macro loop_n_times ; routine,n 46 | for_loop \1,0,\2 - 1,+1 47 | .endm 48 | 49 | ; Same as for_loop, but counts with 16-bit value in BC. 50 | ; Preserved: DE, HL 51 | .macro for_loop16 ; routine,start,end,step 52 | ld bc,\2 53 | 54 | for_loop16\@: 55 | push bc 56 | call \1 57 | pop bc 58 | 59 | ld a,c 60 | add <\4 61 | ld c,a 62 | 63 | ld a,b 64 | adc >\4 65 | ld b,a 66 | 67 | cp >(\3+\4) 68 | jr nz,for_loop16\@ 69 | 70 | ld a,c 71 | cp <(\3+\4) 72 | jr nz,for_loop16\@ 73 | .endm 74 | -------------------------------------------------------------------------------- /roms/instr_timing/source/common/numbers.s: -------------------------------------------------------------------------------- 1 | ; Printing of numeric values 2 | 3 | ; Prints value of indicated register/pair 4 | ; as 2/4 hex digits, followed by a space. 5 | ; Updates checksum with printed values. 6 | ; Preserved: AF, BC, DE, HL 7 | 8 | print_regs: 9 | call print_af 10 | call print_bc 11 | call print_de 12 | call print_hl 13 | call print_newline 14 | ret 15 | 16 | print_a: 17 | push af 18 | print_a_: 19 | call print_hex 20 | ld a,' ' 21 | call print_char_nocrc 22 | pop af 23 | ret 24 | 25 | print_af: 26 | push af 27 | call print_hex 28 | pop af 29 | print_f: 30 | push bc 31 | push af 32 | pop bc 33 | call print_c 34 | pop bc 35 | ret 36 | 37 | print_b: 38 | push af 39 | ld a,b 40 | jr print_a_ 41 | 42 | print_c: 43 | push af 44 | ld a,c 45 | jr print_a_ 46 | 47 | print_d: 48 | push af 49 | ld a,d 50 | jr print_a_ 51 | 52 | print_e: 53 | push af 54 | ld a,e 55 | jr print_a_ 56 | 57 | print_h: 58 | push af 59 | ld a,h 60 | jr print_a_ 61 | 62 | print_l: 63 | push af 64 | ld a,l 65 | jr print_a_ 66 | 67 | print_bc: 68 | push af 69 | push bc 70 | print_bc_: 71 | ld a,b 72 | call print_hex 73 | ld a,c 74 | pop bc 75 | jr print_a_ 76 | 77 | print_de: 78 | push af 79 | push bc 80 | ld b,d 81 | ld c,e 82 | jr print_bc_ 83 | 84 | print_hl: 85 | push af 86 | push bc 87 | ld b,h 88 | ld c,l 89 | jr print_bc_ 90 | 91 | 92 | ; Prints A as two hex chars and updates checksum 93 | ; Preserved: BC, DE, HL 94 | print_hex: 95 | call update_crc 96 | print_hex_nocrc: 97 | push af 98 | swap a 99 | call + 100 | pop af 101 | 102 | + and $0F 103 | cp 10 104 | jr c,+ 105 | add 7 106 | + add '0' 107 | jp print_char_nocrc 108 | 109 | 110 | ; Prints char_nz if Z flag is clear, 111 | ; char_z if Z flag is set. 112 | ; Preserved: AF, BC, DE, HL 113 | .macro print_nz ARGS char_nz, char_z 114 | push af 115 | ld a,char_nz 116 | jr nz,print_nz\@ 117 | ld a,char_z 118 | print_nz\@: 119 | call print_char 120 | pop af 121 | .endm 122 | 123 | 124 | ; Prints char_nc if C flag is clear, 125 | ; char_c if C flag is set. 126 | ; Preserved: AF, BC, DE, HL 127 | .macro print_nc ARGS char_nc, char_c 128 | push af 129 | ld a,char_nc 130 | jr nz,print_nc\@ 131 | ld a,char_c 132 | print_nc\@: 133 | call print_char 134 | pop af 135 | .endm 136 | 137 | 138 | ; Prints A as 2 decimal digits 139 | ; Preserved: AF, BC, DE, HL 140 | print_dec2: 141 | push af 142 | push bc 143 | jr + 144 | 145 | 146 | ; Prints A as 1-3 digit decimal value 147 | ; Preserved: AF, BC, DE, HL 148 | print_dec: 149 | push af 150 | push bc 151 | 152 | cp 10 153 | jr c,++ 154 | ld c,100 155 | cp c 156 | call nc,@digit 157 | + ld c,10 158 | call @digit 159 | ++ add '0' 160 | call print_char 161 | 162 | pop bc 163 | pop af 164 | ret 165 | 166 | @digit: 167 | ld b,'0'-1 168 | - inc b 169 | sub c 170 | jr nc,- 171 | add c 172 | 173 | ld c,a 174 | ld a,b 175 | call print_char 176 | ld a,c 177 | ret 178 | -------------------------------------------------------------------------------- /roms/instr_timing/source/common/printing.s: -------------------------------------------------------------------------------- 1 | ; Main printing routine that checksums and 2 | ; prints to output device 3 | 4 | ; Character that does equivalent of print_newline 5 | .define newline 10 6 | 7 | ; Prints char without updating checksum 8 | ; Preserved: BC, DE, HL 9 | .define print_char_nocrc bss 10 | .redefine bss bss+3 11 | 12 | 13 | ; Initializes printing. HL = print routine 14 | init_printing: 15 | ld a,l 16 | ld (print_char_nocrc+1),a 17 | ld a,h 18 | ld (print_char_nocrc+2),a 19 | jr show_printing 20 | 21 | 22 | ; Hides/shows further printing 23 | ; Preserved: BC, DE, HL 24 | hide_printing: 25 | ld a,$C9 ; RET 26 | jr + 27 | show_printing: 28 | ld a,$C3 ; JP (nn) 29 | + ld (print_char_nocrc),a 30 | ret 31 | 32 | 33 | ; Prints character and updates checksum UNLESS 34 | ; it's a newline. 35 | ; Preserved: AF, BC, DE, HL 36 | print_char: 37 | push af 38 | cp newline 39 | call nz,update_crc 40 | call print_char_nocrc 41 | pop af 42 | ret 43 | 44 | 45 | ; Prints space. Does NOT update checksum. 46 | ; Preserved: AF, BC, DE, HL 47 | print_space: 48 | push af 49 | ld a,' ' 50 | call print_char_nocrc 51 | pop af 52 | ret 53 | 54 | 55 | ; Advances to next line. Does NOT update checksum. 56 | ; Preserved: AF, BC, DE, HL 57 | print_newline: 58 | push af 59 | ld a,newline 60 | call print_char_nocrc 61 | pop af 62 | ret 63 | 64 | 65 | ; Prints immediate string 66 | ; Preserved: AF, BC, DE, HL 67 | .macro print_str ; string,string2 68 | push hl 69 | call print_str_ 70 | .byte \1 71 | .if NARGS > 1 72 | .byte \2 73 | .endif 74 | .if NARGS > 2 75 | .byte \3 76 | .endif 77 | .byte 0 78 | pop hl 79 | .endm 80 | 81 | print_str_: 82 | pop hl 83 | call print_str_hl 84 | jp hl 85 | 86 | 87 | ; Prints zero-terminated string pointed to by HL. 88 | ; On return, HL points to byte AFTER zero terminator. 89 | ; Preserved: AF, BC, DE 90 | print_str_hl: 91 | push af 92 | jr + 93 | - call print_char 94 | + ldi a,(hl) 95 | or a 96 | jr nz,- 97 | pop af 98 | ret 99 | -------------------------------------------------------------------------------- /roms/instr_timing/source/common/runtime.s: -------------------------------------------------------------------------------- 1 | ; Common routines and runtime 2 | 3 | ; Must be defined by target-specific runtime: 4 | ; 5 | ; init_runtime: ; target-specific inits 6 | ; std_print: ; default routine to print char A 7 | ; post_exit: ; called at end of std_exit 8 | ; report_byte: ; report A to user 9 | 10 | .define RUNTIME_INCLUDED 1 11 | 12 | .ifndef bss 13 | ; address of next normal variable 14 | .define bss $D800 15 | .endif 16 | 17 | .ifndef dp 18 | ; address of next direct-page ($FFxx) variable 19 | .define dp $FF80 20 | .endif 21 | 22 | ; DMG/CGB hardware identifier 23 | .define gb_id_cgb $10 ; mask for testing CGB bit 24 | .define gb_id_devcart $04 ; mask for testing "on devcart" bit 25 | 26 | .define gb_id bss 27 | .redefine bss bss+1 28 | 29 | ; Stack is normally here 30 | .define std_stack $DFFF 31 | 32 | ; Copies $1000 bytes from HL to $C000, then jumps to it. 33 | ; A is preserved for jumped-to code. 34 | copy_to_wram_then_run: 35 | ld b,a 36 | 37 | ld de,$C000 38 | ld c,$10 39 | - ldi a,(hl) 40 | ld (de),a 41 | inc e 42 | jr nz,- 43 | inc d 44 | dec c 45 | jr nz,- 46 | 47 | ld a,b 48 | jp $C000 49 | 50 | .ifndef CUSTOM_RESET 51 | reset: 52 | ; Run code from $C000, as is done on devcart. This 53 | ; ensures minimal difference in how it behaves. 54 | ld hl,$4000 55 | jp copy_to_wram_then_run 56 | 57 | .bank 1 slot 1 58 | .org $0 ; otherwise wla pads with lots of zeroes 59 | jp std_reset 60 | .endif 61 | 62 | ; Common routines 63 | .include "gb.inc" 64 | .include "macros.inc" 65 | .include "delay.s" 66 | .include "crc.s" 67 | .include "printing.s" 68 | .include "numbers.s" 69 | .include "testing.s" 70 | 71 | ; Sets up hardware and runs main 72 | std_reset: 73 | 74 | ; Init hardware 75 | di 76 | ld sp,std_stack 77 | 78 | ; Save DMG/CGB id 79 | ld (gb_id),a 80 | 81 | ; Init hardware 82 | .ifndef BUILD_GBS 83 | wreg TAC,$00 84 | wreg IF,$00 85 | wreg IE,$00 86 | .endif 87 | 88 | wreg NR52,0 ; sound off 89 | wreg NR52,$80 ; sound on 90 | wreg NR51,$FF ; mono 91 | wreg NR50,$77 ; volume 92 | 93 | ; TODO: clear all memory? 94 | 95 | ld hl,std_print 96 | call init_printing 97 | call init_testing 98 | call init_runtime 99 | call reset_crc ; in case init_runtime prints anything 100 | 101 | delay_msec 250 102 | 103 | ; Run user code 104 | call main 105 | 106 | ; Default is to successful exit 107 | ld a,0 108 | jp exit 109 | 110 | 111 | ; Exits code and reports value of A 112 | exit: 113 | ld sp,std_stack 114 | push af 115 | call + 116 | pop af 117 | jp post_exit 118 | 119 | + push af 120 | call print_newline 121 | call show_printing 122 | pop af 123 | 124 | ; Report exit status 125 | cp 1 126 | 127 | ; 0: "" 128 | ret c 129 | 130 | ; 1: "Failed" 131 | jr nz,+ 132 | print_str "Failed",newline 133 | ret 134 | 135 | ; n: "Failed #n" 136 | + print_str "Failed #" 137 | call print_dec 138 | call print_newline 139 | ret 140 | 141 | ; returnOrg puts this code AFTER user code. 142 | .section "runtime" returnOrg 143 | -------------------------------------------------------------------------------- /roms/instr_timing/source/common/testing.s: -------------------------------------------------------------------------------- 1 | ; Diagnostic and testing utilities 2 | 3 | .define result bss+0 4 | .define test_name bss+1 5 | .redefine bss bss+3 6 | 7 | 8 | ; Sets test code and optional error text 9 | ; Preserved: AF, BC, DE, HL 10 | .macro set_test ; code[,text[,text2]] 11 | push hl 12 | call set_test_ 13 | jr @set_test\@ 14 | .byte \1 15 | .if NARGS > 1 16 | .byte \2 17 | .endif 18 | .if NARGS > 2 19 | .byte \3 20 | .endif 21 | .byte 0 22 | @set_test\@: 23 | pop hl 24 | .endm 25 | 26 | set_test_: 27 | pop hl 28 | push hl 29 | push af 30 | inc hl 31 | inc hl 32 | ldi a,(hl) 33 | ld (result),a 34 | ld a,l 35 | ld (test_name),a 36 | ld a,h 37 | ld (test_name+1),a 38 | pop af 39 | ret 40 | 41 | 42 | ; Initializes testing module 43 | init_testing: 44 | set_test $FF 45 | call init_crc 46 | ret 47 | 48 | 49 | ; Reports "Passed", then exits with code 0 50 | tests_passed: 51 | call print_newline 52 | print_str "Passed" 53 | ld a,0 54 | jp exit 55 | 56 | 57 | ; Reports "Done" if set_test has never been used, 58 | ; "Passed" if set_test 0 was last used, or 59 | ; failure if set_test n was last used. 60 | tests_done: 61 | ld a,(result) 62 | inc a 63 | jr z,+ 64 | dec a 65 | jr z,tests_passed 66 | jr test_failed 67 | + print_str "Done" 68 | ld a,0 69 | jp exit 70 | 71 | 72 | ; Reports current error text and exits with result code 73 | test_failed: 74 | ld a,(test_name) 75 | ld l,a 76 | ld a,(test_name+1) 77 | ld h,a 78 | ld a,(hl) 79 | or a 80 | jr z,+ 81 | call print_newline 82 | call print_str_hl 83 | call print_newline 84 | + 85 | ld a,(result) 86 | cp 1 ; if a = 0 then a = 1 87 | adc 0 88 | jp exit 89 | 90 | 91 | ; Prints checksum as 8-character hex value 92 | ; Preserved: AF, BC, DE, HL 93 | print_crc: 94 | push af 95 | 96 | ; Must read checksum entirely before printing, 97 | ; since printing updates it. 98 | lda checksum 99 | cpl 100 | push af 101 | 102 | lda checksum+1 103 | cpl 104 | push af 105 | 106 | lda checksum+2 107 | cpl 108 | push af 109 | 110 | lda checksum+3 111 | cpl 112 | 113 | call print_hex 114 | pop af 115 | call print_hex 116 | pop af 117 | call print_hex 118 | pop af 119 | call print_a 120 | 121 | pop af 122 | ret 123 | 124 | 125 | ; If checksum doesn't match expected, reports failed test. 126 | ; Passing 0 just prints checksum. Clears checksum afterwards. 127 | .macro check_crc ARGS crc 128 | .if crc == 0 129 | call show_printing 130 | call print_newline 131 | call print_crc 132 | .else 133 | ld bc,(crc >> 16) ~ $FFFF 134 | ld de,(crc & $FFFF) ~ $FFFF 135 | call check_crc_ 136 | .endif 137 | .endm 138 | 139 | check_crc_: 140 | lda checksum+0 141 | cp e 142 | jr nz,+ 143 | 144 | lda checksum+1 145 | cp d 146 | jr nz,+ 147 | 148 | lda checksum+2 149 | cp c 150 | jr nz,+ 151 | 152 | lda checksum+3 153 | cp b 154 | jr nz,+ 155 | 156 | jp reset_crc 157 | 158 | + call print_crc 159 | jp test_failed 160 | 161 | 162 | ; Updates checksum with bytes from addr to addr+size-1 163 | .macro checksum_mem ARGS addr,size 164 | ld hl,addr 165 | ld bc,size 166 | call checksum_mem_ 167 | .endm 168 | 169 | checksum_mem_: 170 | - ldi a,(hl) 171 | call update_crc 172 | dec bc 173 | ld a,b 174 | or c 175 | jr nz,- 176 | ret 177 | -------------------------------------------------------------------------------- /roms/instr_timing/source/common/timer.s: -------------------------------------------------------------------------------- 1 | ; Cycle-accurate timer 2 | 3 | ; TIMA is incremented every 4 cycles. Loops 4 | ; check for increment within 3-cycle window, 5 | ; so when it occurs outside this, loop is 6 | ; exactly synchronized. Loop iterations are 7 | ; one more or less than 12 cycles, so they 8 | ; will never run more than 4 times. 9 | 10 | ; Initializes timer 11 | ; Preserved: AF, BC, DE, HL 12 | init_timer: 13 | push af 14 | di 15 | lda IE ; disable timer interrupt 16 | and ~$04 17 | sta IE 18 | wreg TMA,0 ; max period 19 | wreg TAC,$05 ; 262144 Hz 20 | 21 | ; Be sure timer doesn't expire 22 | ; immediately or take too long 23 | wreg IF,0 24 | wreg TIMA,-20 25 | delay 70 26 | lda IF 27 | and $04 28 | jp nz,test_failed 29 | lda IF 30 | and $04 31 | jp z,test_failed 32 | 33 | pop af 34 | ret 35 | 36 | 37 | ; Starts timer 38 | ; Preserved: AF, BC, DE, HL 39 | start_timer: 40 | push af 41 | 42 | - xor a ; 1 43 | sta TIMA ; 3 44 | lda TIMA ; 3 45 | or a ; 1 46 | jr nz,- ; 3 47 | 48 | pop af 49 | ret 50 | 51 | 52 | ; Stops timer and determines cycles since 53 | ; it was started. A = cycles (0 to 255). 54 | ; Preserved: BC, DE, HL 55 | stop_timer: 56 | push de 57 | call stop_timer_word 58 | ld a,e 59 | sub 10 60 | pop de 61 | ret 62 | 63 | 64 | ; Same as stop_timer, but with greater range. 65 | ; DE = cycles (0 to 1019). 66 | ; Preserved: BC, HL 67 | stop_timer_word: 68 | 69 | ld d,0 70 | 71 | ; Get main count (TIMA*4) 72 | lda TIMA 73 | sub 5 74 | add a 75 | rl d 76 | add a 77 | rl d 78 | ld e,a 79 | 80 | ; One iteration per remaining cycle 81 | - xor a ; 1 82 | sta TIMA ; 3 83 | lda TIMA ; 3 84 | dec de ; 2 85 | or a ; 1 86 | jr nz,- ; 3 87 | 88 | ret 89 | -------------------------------------------------------------------------------- /roms/instr_timing/source/linkfile: -------------------------------------------------------------------------------- 1 | [objects] 2 | test.o 3 | -------------------------------------------------------------------------------- /roms/instr_timing/source/shell.inc: -------------------------------------------------------------------------------- 1 | .incdir "common" 2 | 3 | ; GBS music file 4 | .ifdef BUILD_GBS 5 | .include "build_gbs.s" 6 | .endif 7 | 8 | ; Devcart 9 | .ifdef BUILD_DEVCART 10 | .include "build_devcart.s" 11 | .endif 12 | 13 | ; Sub-test in a multi-test ROM 14 | .ifdef BUILD_MULTI 15 | .include "build_multi.s" 16 | .endif 17 | 18 | ; GB ROM (default) 19 | .ifndef RUNTIME_INCLUDED 20 | .include "build_rom.s" 21 | .endif 22 | -------------------------------------------------------------------------------- /roms/interrupt_time/interrupt_time.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/interrupt_time/interrupt_time.gb -------------------------------------------------------------------------------- /roms/interrupt_time/interrupt_time.s: -------------------------------------------------------------------------------- 1 | ; Tests interrupt handling time for slow and fast 2 | ; CPU. First value is CPU speed (0=slow, 1=fast), 3 | ; second is number of cycles taken by interrupt. 4 | ; Should take 13 cycles at either speed. 5 | .define REQUIRE_CGB 1 6 | 7 | .include "shell.inc" 8 | .include "cpu_speed.s" 9 | .include "timer.s" 10 | .include "apu.s" 11 | 12 | ; $58: JP $DEC3 13 | ; $DEC3: RET 14 | .define sint $DEC3 15 | 16 | main: 17 | call init_timer 18 | 19 | ld d,0 20 | call test_interrupt 21 | ld d,8 22 | call test_interrupt 23 | call cpu_fast 24 | ld d,0 25 | call test_interrupt 26 | ld d,8 27 | call test_interrupt 28 | 29 | check_crc $C86CC74D 30 | jp tests_passed 31 | 32 | test_interrupt: 33 | call get_cpu_speed 34 | call print_a 35 | call print_d 36 | 37 | ld a,$C9 ; RET 38 | ld (sint),a 39 | 40 | wreg IE,$08 41 | wreg IF,$00 42 | call start_timer 43 | ei 44 | ld a,d 45 | ld (IF),a ; $00 = 0 clocks, $08 = 13 clocks 46 | di 47 | call stop_timer 48 | sub 3+4 ; instruction overhead 49 | call print_a 50 | call print_newline 51 | 52 | ret 53 | 54 | ; RST handler that matches the one in my devcart 55 | .bank 0 slot 0 56 | .org $58 57 | jp $DEC3 58 | -------------------------------------------------------------------------------- /roms/mbc1/bits_bank1.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/mbc1/bits_bank1.gb -------------------------------------------------------------------------------- /roms/mbc1/bits_bank2.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/mbc1/bits_bank2.gb -------------------------------------------------------------------------------- /roms/mbc1/bits_mode.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/mbc1/bits_mode.gb -------------------------------------------------------------------------------- /roms/mbc1/bits_ramg.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/mbc1/bits_ramg.gb -------------------------------------------------------------------------------- /roms/mbc1/multicart_rom_8Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/mbc1/multicart_rom_8Mb.gb -------------------------------------------------------------------------------- /roms/mbc1/ram_256kb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/mbc1/ram_256kb.gb -------------------------------------------------------------------------------- /roms/mbc1/ram_64kb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/mbc1/ram_64kb.gb -------------------------------------------------------------------------------- /roms/mbc1/rom_16Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/mbc1/rom_16Mb.gb -------------------------------------------------------------------------------- /roms/mbc1/rom_1Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/mbc1/rom_1Mb.gb -------------------------------------------------------------------------------- /roms/mbc1/rom_2Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/mbc1/rom_2Mb.gb -------------------------------------------------------------------------------- /roms/mbc1/rom_4Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/mbc1/rom_4Mb.gb -------------------------------------------------------------------------------- /roms/mbc1/rom_512kb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/mbc1/rom_512kb.gb -------------------------------------------------------------------------------- /roms/mbc1/rom_8Mb.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/mbc1/rom_8Mb.gb -------------------------------------------------------------------------------- /roms/mem_timing/individual/01-read_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/mem_timing/individual/01-read_timing.gb -------------------------------------------------------------------------------- /roms/mem_timing/individual/02-write_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/mem_timing/individual/02-write_timing.gb -------------------------------------------------------------------------------- /roms/mem_timing/individual/03-modify_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/mem_timing/individual/03-modify_timing.gb -------------------------------------------------------------------------------- /roms/mem_timing/mem_timing.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/mem_timing/mem_timing.gb -------------------------------------------------------------------------------- /roms/mem_timing/readme.txt: -------------------------------------------------------------------------------- 1 | Game Boy CPU Memory Access Timing Test 2 | -------------------------------------- 3 | This ROM tests the timing of memory reads and writes made by 4 | instructions, except stack and program counter accesses. These tests 5 | require correct instruction timing and proper timer operation (TAC, 6 | TIMA, TMA). 7 | 8 | The read and write tests list failing instructions as 9 | 10 | [CB] opcode:tested-correct 11 | 12 | The read-modify-write test lists failing instructions as 13 | 14 | [CB] opcode:tested read/tested write-correct read/correct write 15 | 16 | The values after the opcode refer to which instruction cycle the access 17 | occurs on, with 1 being the first. If a time couldn't be determined due 18 | to some other problem, it prints 0. 19 | 20 | For instructions which either read or write, but not both, the CPU makes 21 | the access on the last cycle. For instructions which read, modify, then 22 | write back, the CPU reads on the next-to-last cycle, and writes on the 23 | last cycle. 24 | 25 | 26 | Internal operation 27 | ------------------ 28 | The tests have the timer increment TIMA every 64 cycles, synchronize 29 | with this, delay a variable amount, then have the instruction under test 30 | access the timer. By varying the delay in one-cycle increments, the 31 | memory access made by the instruction can be made to fall before and 32 | after a TIMA increment. By then examining the registers and value in 33 | TIMA, it can be determined which occurred. 34 | 35 | 36 | Multi-ROM 37 | --------- 38 | In the main directory is a single ROM which runs all the tests. It 39 | prints a test's number, runs the test, then "ok" if it passes, otherwise 40 | a failure code. Once all tests have completed it either reports that all 41 | tests passed, or prints the number of failed tests. Finally, it makes 42 | several beeps. If a test fails, it can be run on its own by finding the 43 | corresponding ROM in individual/. 44 | 45 | Ths compact format on screen is to avoid having the results scroll off 46 | the top, so the test can be started and allowed to run without having to 47 | constantly monitor the display. 48 | 49 | Currently there is no well-defined way for an emulator test rig to 50 | programatically find the result of the test; contact me if you're trying 51 | to do completely automated testing of your emulator. One simple approach 52 | is to take a screenshot after all tests have run, or even just a 53 | checksum of one, and compare this with a previous run. 54 | 55 | 56 | Failure codes 57 | ------------- 58 | Failed tests may print a failure code, and also short description of the 59 | problem. For more information about a failure code, look in the 60 | corresponding source file in source/; the point in the code where 61 | "set_test n" occurs is where that failure code will be generated. 62 | Failure code 1 is a general failure of the test; any further information 63 | will be printed. 64 | 65 | Note that once a sub-test fails, no further tests for that file are run. 66 | 67 | 68 | Console output 69 | -------------- 70 | Information is printed on screen in a way that needs only minimum LCD 71 | support, and won't hang if LCD output isn't supported at all. 72 | Specifically, while polling LY to wait for vblank, it will time out if 73 | it takes too long, so LY always reading back as the same value won't 74 | hang the test. It's also OK if scrolling isn't supported; in this case, 75 | text will appear starting at the top of the screen. 76 | 77 | Everything printed on screen is also sent to the game link port by 78 | writing the character to SB, then writing $81 to SC. This is useful for 79 | tests which print lots of information that scrolls off screen. 80 | 81 | 82 | Source code 83 | ----------- 84 | Source code is included for all tests, in source/. It can be used to 85 | build the individual test ROMs. Code for the multi test isn't included 86 | due to the complexity of putting everything together. 87 | 88 | Code is written for the wla-dx assembler. To assemble a particular test, 89 | execute 90 | 91 | wla -o "source_filename.s" test.o 92 | wlalink linkfile test.gb 93 | 94 | Test code uses a common shell framework contained in common/. 95 | 96 | 97 | Internal framework operation 98 | ---------------------------- 99 | Tests use a common framework for setting things up, reporting results, 100 | and ending. All files first include "shell.inc", which sets up the ROM 101 | header and shell code, and includes other commonly-used modules. 102 | 103 | One oddity is that test code is first copied to internal RAM at $D000, 104 | then executed there. This allows self-modification, and ensures the code 105 | is executed the same way it is on my devcart, which doesn't have a 106 | rewritable ROM as most do. 107 | 108 | Some macros are used to simplify common tasks: 109 | 110 | Macro Behavior 111 | - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 112 | wreg addr,data Writes data to addr using LDH 113 | lda addr Loads byte from addr into A using LDH 114 | sta addr Stores A at addr using LDH 115 | delay n Delays n cycles, where NOP = 1 cycle 116 | delay_msec n Delays n milliseconds 117 | set_test n,"Cause" Sets failure code and optional string 118 | 119 | Routines and macros are documented where they are defined. 120 | 121 | -- 122 | Shay Green 123 | -------------------------------------------------------------------------------- /roms/mem_timing/source/01-read_timing.s: -------------------------------------------------------------------------------- 1 | ; Tests timing of accesses made by 2 | ; memory read instructions 3 | 4 | .include "shell.inc" 5 | .include "tima_64.s" 6 | 7 | instructions: 8 | ; last value is time of read 9 | .byte $B6,$00,$00,2 ; OR (HL) 10 | .byte $BE,$00,$00,2 ; CP (HL) 11 | .byte $86,$00,$00,2 ; ADD (HL) 12 | .byte $8E,$00,$00,2 ; ADC (HL) 13 | .byte $96,$00,$00,2 ; SUB (HL) 14 | .byte $9E,$00,$00,2 ; SBC (HL) 15 | .byte $A6,$00,$00,2 ; AND (HL) 16 | .byte $AE,$00,$00,2 ; XOR (HL) 17 | .byte $46,$00,$00,2 ; LD B,(HL) 18 | .byte $4E,$00,$00,2 ; LD C,(HL) 19 | .byte $56,$00,$00,2 ; LD D,(HL) 20 | .byte $5E,$00,$00,2 ; LD E,(HL) 21 | .byte $66,$00,$00,2 ; LD H,(HL) 22 | .byte $6E,$00,$00,2 ; LD L,(HL) 23 | .byte $7E,$00,$00,2 ; LD A,(HL) 24 | .byte $F2,$00,$00,2 ; LDH A,(C) 25 | .byte $0A,$00,$00,2 ; LD A,(BC) 26 | .byte $1A,$00,$00,2 ; LD A,(DE) 27 | .byte $2A,$00,$00,2 ; LD A,(HL+) 28 | .byte $3A,$00,$00,2 ; LD A,(HL-) 29 | .byte $F0,tima_64,4 ; LD A,($0000) 31 | 32 | .byte $CB,$46,$00,3 ; BIT 0,(HL) 33 | .byte $CB,$4E,$00,3 ; BIT 1,(HL) 34 | .byte $CB,$56,$00,3 ; BIT 2,(HL) 35 | .byte $CB,$5E,$00,3 ; BIT 3,(HL) 36 | .byte $CB,$66,$00,3 ; BIT 4,(HL) 37 | .byte $CB,$6E,$00,3 ; BIT 5,(HL) 38 | .byte $CB,$76,$00,3 ; BIT 6,(HL) 39 | .byte $CB,$7E,$00,3 ; BIT 7,(HL) 40 | instructions_end: 41 | 42 | main: 43 | call init_tima_64 44 | set_test 0 45 | 46 | ; Test instructions 47 | ld hl,instructions 48 | - call @time_instr 49 | cp (hl) 50 | call nz,@print_failed 51 | inc hl 52 | ld a,l 53 | cp 3-byte instruction 84 | ; HL <- HL + 3 85 | @time_instr: ; 86 | ; Copy instr ; PC = 0xc353 87 | ld a,(hl+) 88 | ld (instr+0),a 89 | ld a,(hl+) 90 | ld (instr+1),a 91 | ld a,(hl+) 92 | ld (instr+2),a 93 | push hl ; PC = 0xc3a9 94 | 95 | ; Find result when access doesn't occur 96 | ld b,0 97 | call @time_access 98 | ld c,a 99 | 100 | ; Test for accesses on each cycle 101 | ld b,0 102 | - push bc 103 | call @time_access 104 | pop bc 105 | cp c 106 | jr nz,@found 107 | inc b 108 | ld a,b 109 | cp 10 110 | jr nz,- 111 | ld b,0 112 | 113 | @found: 114 | ld a,b 115 | pop hl 116 | ret 117 | 118 | ; Tests for read 119 | ; B -> which cycle to test 120 | ; A <- timer value after test 121 | @time_access: 122 | call sync_tima_64 123 | ld a,9 124 | sub b 125 | call delay_a_20_cycles 126 | xor a ; clear flags 127 | ld hl,tima_64 128 | ld (hl),$7F 129 | ld bc,tima_64 130 | ld de,tima_64 131 | ld a,$7F 132 | instr: 133 | nop 134 | nop 135 | nop 136 | 137 | ; Add all registers together to yield 138 | ; unique value that differs based on 139 | ; read occurring before or after tima_64 140 | ; increments. 141 | push af 142 | add hl,bc 143 | add hl,de 144 | pop de 145 | add hl,de 146 | ld a,h 147 | add l 148 | ret 149 | -------------------------------------------------------------------------------- /roms/mem_timing/source/02-write_timing.s: -------------------------------------------------------------------------------- 1 | ; Tests timing of accesses made by 2 | ; memory write instructions 3 | 4 | .include "shell.inc" 5 | .include "tima_64.s" 6 | 7 | instructions: 8 | ; last value is time of write 9 | .byte $36,$FF,$00,3 ; LD (HL),n 10 | .byte $70,$00,$00,2 ; LD (HL),B 11 | .byte $71,$00,$00,2 ; LD (HL),C 12 | .byte $72,$00,$00,2 ; LD (HL),D 13 | .byte $73,$00,$00,2 ; LD (HL),E 14 | .byte $74,$00,$00,2 ; LD (HL),H 15 | .byte $75,$00,$00,2 ; LD (HL),L 16 | .byte $77,$00,$00,2 ; LD (HL),A 17 | .byte $02,$00,$00,2 ; LD (BC),A 18 | .byte $12,$00,$00,2 ; LD (DE),A 19 | .byte $22,$00,$00,2 ; LD (HL+),A 20 | .byte $32,$00,$00,2 ; LD (HL-),A 21 | .byte $E2,$00,$00,2 ; LDH (C),A 22 | .byte $E0,tima_64,4 ; LD (nn),A 24 | instructions_end: 25 | 26 | main: 27 | call init_tima_64 28 | set_test 0 29 | 30 | ; Test instructions 31 | ld hl,instructions 32 | - call @test_instr 33 | cp (hl) 34 | call nz,@print_failed 35 | inc hl 36 | ld a,l 37 | cp 3-byte instruction 64 | ; HL <- HL + 3 65 | @test_instr: 66 | ; Copy instr 67 | ld a,(hl+) 68 | ld (instr+0),a 69 | ld a,(hl+) 70 | ld (instr+1),a 71 | ld a,(hl+) 72 | ld (instr+2),a 73 | push hl 74 | 75 | ; Test for writes on each cycle 76 | ld b,0 77 | - push bc 78 | call @time_write 79 | pop bc 80 | cp tima_64 83 | jr z,@no_write 84 | jr @found 85 | @no_write: 86 | inc b 87 | ld a,b 88 | cp 10 89 | jr nz,- 90 | ld b,0 91 | 92 | @found: 93 | ld a,b 94 | pop hl 95 | ret 96 | 97 | ; Tests for write 98 | ; B -> which cycle to test 99 | ; A <- timer value after test 100 | @time_write: 101 | call sync_tima_64 102 | ld a,13 103 | sub b 104 | call delay_a_20_cycles 105 | ld hl,tima_64 106 | ld bc,tima_64 107 | ld de,tima_64 108 | ld a, 3-byte instruction 93 | ; HL <- HL + 3 94 | @time_instr: 95 | ; Copy instr 96 | ld a,(hl+) 97 | ld (instr+0),a 98 | ld a,(hl+) 99 | ld (instr+1),a 100 | ld a,(hl+) 101 | ld (instr+2),a 102 | push hl 103 | 104 | ; Find result when access doesn't occur 105 | ld b,0 106 | call @time_access 107 | 108 | ; Find first access 109 | call @find_next_access 110 | ld d,b 111 | 112 | ; Find second access 113 | call @find_next_access 114 | ld e,b 115 | 116 | pop hl 117 | ret 118 | 119 | ; A -> current timer result 120 | ; B -> starting clock 121 | ; B <- clock next access occurs on 122 | ; A <- new timer result 123 | @find_next_access: 124 | ld c,a 125 | - call @time_access 126 | cp c 127 | ret nz 128 | inc b 129 | ld a,b 130 | cp 10 131 | jr c,- 132 | 133 | ; Couldn't find time, so return 0/0 134 | ld a,c 135 | ld b,0 136 | ld d,b 137 | ret 138 | 139 | ; Tests for access 140 | ; B -> which cycle to test 141 | ; A <- timer value after test 142 | @time_access: 143 | call sync_tima_64 144 | ld hl,tima_64 145 | ld (hl),$7F 146 | ld a,17 147 | sub b 148 | call delay_a_20_cycles 149 | xor a ; clear flags 150 | instr: 151 | nop 152 | nop 153 | nop 154 | delay 32 155 | ld a,(tima_64) 156 | ret 157 | -------------------------------------------------------------------------------- /roms/mem_timing/source/common/build_gbs.s: -------------------------------------------------------------------------------- 1 | ; Build as GBS music file 2 | 3 | .memoryMap 4 | defaultSlot 0 5 | slot 0 $3000 size $1000 6 | slot 1 $C000 size $1000 7 | .endMe 8 | 9 | .romBankSize $1000 10 | .romBanks 2 11 | 12 | 13 | ;;;; GBS music file header 14 | 15 | .byte "GBS" 16 | .byte 1 ; vers 17 | .byte 1 ; songs 18 | .byte 1 ; first song 19 | .word load_addr 20 | .word reset 21 | .word gbs_play 22 | .word std_stack 23 | .byte 0,0 ; timer 24 | .ds $60,0 25 | load_addr: 26 | 27 | ; WLA assumes we're building ROM and messes 28 | ; with bytes at the beginning, so skip them. 29 | .ds $100,0 30 | 31 | 32 | ;;;; Shell 33 | 34 | .include "runtime.s" 35 | 36 | init_runtime: 37 | ld a,$01 ; Identify as DMG hardware 38 | ld (gb_id),a 39 | .ifdef TEST_NAME 40 | print_str TEST_NAME,newline,newline 41 | .endif 42 | ret 43 | 44 | std_print: 45 | sta SB 46 | wreg SC,$81 47 | delay 2304 48 | ret 49 | 50 | post_exit: 51 | call play_byte 52 | forever: 53 | wreg NR52,0 ; sound off 54 | - jp - 55 | 56 | .ifndef CUSTOM_RESET 57 | gbs_play: 58 | .endif 59 | console_flush: 60 | console_normal: 61 | console_inverse: 62 | console_set_mode: 63 | ret 64 | 65 | ; Reports A in binary as high and low tones, with 66 | ; leading low tone for reference. Omits leading 67 | ; zeroes. 68 | ; Preserved: AF, BC, DE, HL 69 | play_byte: 70 | push af 71 | push hl 72 | 73 | ; HL = (A << 1) | 1 74 | scf 75 | rla 76 | ld l,a 77 | ld h,0 78 | rl h 79 | 80 | ; Shift left until next-to-top bit is 1 81 | - add hl,hl 82 | bit 6,h 83 | jr z,- 84 | 85 | ; Reset sound 86 | delay_msec 400 87 | wreg NR52,0 ; sound off 88 | wreg NR52,$80 ; sound on 89 | wreg NR51,$FF ; mono 90 | wreg NR50,$77 ; volume 91 | 92 | - add hl,hl 93 | 94 | ; Low or high pitch based on bit shifted out 95 | ; of HL 96 | ld a,0 97 | jr nc,+ 98 | ld a,$FF 99 | + sta NR23 100 | 101 | ; Play short tone 102 | wreg NR21,$A0 103 | wreg NR22,$F0 104 | wreg NR24,$86 105 | delay_msec 75 106 | wreg NR22,0 107 | wreg NR23,$F8 108 | wreg NR24,$87 109 | delay_msec 200 110 | 111 | ; Loop until HL = $8000 112 | ld a,h 113 | xor $80 114 | or l 115 | jr nz,- 116 | 117 | pop hl 118 | pop af 119 | ret 120 | 121 | .ends 122 | -------------------------------------------------------------------------------- /roms/mem_timing/source/common/build_rom.s: -------------------------------------------------------------------------------- 1 | ; Build as GB ROM 2 | 3 | .memoryMap 4 | defaultSlot 0 5 | slot 0 $0000 size $4000 6 | slot 1 $C000 size $4000 7 | .endMe 8 | 9 | .romBankSize $4000 ; generates $8000 byte ROM 10 | .romBanks 2 11 | 12 | .cartridgeType 1 ; MBC1 13 | .computeChecksum 14 | .computeComplementCheck 15 | 16 | 17 | ;;;; GB ROM header 18 | 19 | ; GB header read by bootrom 20 | .org $100 21 | nop 22 | jp reset 23 | 24 | ; Nintendo logo required for proper boot 25 | .byte $CE,$ED,$66,$66,$CC,$0D,$00,$0B 26 | .byte $03,$73,$00,$83,$00,$0C,$00,$0D 27 | .byte $00,$08,$11,$1F,$88,$89,$00,$0E 28 | .byte $DC,$CC,$6E,$E6,$DD,$DD,$D9,$99 29 | .byte $BB,$BB,$67,$63,$6E,$0E,$EC,$CC 30 | .byte $DD,$DC,$99,$9F,$BB,$B9,$33,$3E 31 | 32 | ; Internal name 33 | .ifdef ROM_NAME 34 | .byte ROM_NAME 35 | .endif 36 | 37 | ; CGB/DMG requirements 38 | .org $143 39 | .ifdef REQUIRE_CGB 40 | .byte $C0 41 | .else 42 | .ifndef REQUIRE_DMG 43 | .byte $80 44 | .endif 45 | .endif 46 | 47 | .org $200 48 | 49 | 50 | ;;;; Shell 51 | 52 | .include "runtime.s" 53 | .include "console.s" 54 | 55 | init_runtime: 56 | call console_init 57 | .ifdef TEST_NAME 58 | print_str TEST_NAME,newline,newline 59 | .endif 60 | ret 61 | 62 | std_print: 63 | push af 64 | sta SB 65 | wreg SC,$81 66 | delay 2304 67 | pop af 68 | jp console_print 69 | 70 | post_exit: 71 | call console_show 72 | call play_byte 73 | forever: 74 | wreg NR52,0 ; sound off 75 | - jr - 76 | 77 | play_byte: 78 | ret 79 | 80 | .ends 81 | -------------------------------------------------------------------------------- /roms/mem_timing/source/common/console.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/mem_timing/source/common/console.bin -------------------------------------------------------------------------------- /roms/mem_timing/source/common/console.s: -------------------------------------------------------------------------------- 1 | ; Scrolling text console 2 | 3 | ; Console is 20x18 characters. Buffers lines, so 4 | ; output doesn't appear until a newline or flush. 5 | ; If scrolling isn't supported (i.e. SCY is treated 6 | ; as if always zero), the first 18 lines will 7 | ; still print properly). Also works properly if 8 | ; LY isn't supported (always reads back as the same 9 | ; value). 10 | 11 | .define console_width 20 12 | 13 | .define console_buf bss+0 14 | .define console_pos bss+console_width 15 | .define console_mode bss+console_width+1 16 | .define console_scroll bss+console_width+2 17 | .redefine bss bss+console_width+3 18 | 19 | 20 | ; Waits for start of LCD blanking period 21 | ; Preserved: BC, DE, HL 22 | console_wait_vbl: 23 | push bc 24 | 25 | ; Wait for start of vblank, with 26 | ; timeout in case LY doesn't work 27 | ; or LCD is disabled. 28 | ld bc,-1250 29 | - inc bc 30 | ld a,b 31 | or c 32 | jr z,@timeout 33 | lda LY 34 | cp 144 35 | jr nz,- 36 | @timeout: 37 | 38 | pop bc 39 | ret 40 | 41 | 42 | ; Initializes text console 43 | console_init: 44 | call console_hide 45 | 46 | ; CGB-specific inits 47 | ld a,(gb_id) 48 | and gb_id_cgb 49 | call nz,@init_cgb 50 | 51 | ; Clear nametable 52 | ld a,' ' 53 | call @fill_nametable 54 | 55 | ; Load tiles 56 | ld hl,TILES+$200 57 | ld c,0 58 | call @load_tiles 59 | ld hl,TILES+$A00 60 | ld c,$FF 61 | call @load_tiles 62 | 63 | ; Init state 64 | ld a,console_width 65 | ld (console_pos),a 66 | ld a,0 67 | ld (console_mode),a 68 | ld a,-8 69 | ld (console_scroll),a 70 | call console_scroll_up_ 71 | jr console_show 72 | 73 | @fill_nametable: 74 | ld hl,BGMAP0 75 | ld b,4 76 | - ld (hl),a 77 | inc l 78 | jr nz,- 79 | inc h 80 | dec b 81 | jr nz,- 82 | ret 83 | 84 | @init_cgb: 85 | ; Clear palette 86 | wreg $FF68,$80 87 | ld b,16 88 | - wreg $FF69,$FF 89 | wreg $FF69,$7F 90 | wreg $FF69,$00 91 | wreg $FF69,$00 92 | wreg $FF69,$00 93 | wreg $FF69,$00 94 | wreg $FF69,$00 95 | wreg $FF69,$00 96 | dec b 97 | jr nz,- 98 | 99 | ; Clear attributes 100 | ld a,1 101 | ld (VBK),a 102 | ld a,0 103 | call @fill_nametable 104 | 105 | ld a,0 106 | ld (VBK),a 107 | ret 108 | 109 | @load_tiles: 110 | ld de,ASCII 111 | ld b,96 112 | -- push bc 113 | ld b,8 114 | - ld a,(de) 115 | inc de 116 | xor c 117 | ldi (hl),a 118 | ldi (hl),a 119 | dec b 120 | jr nz,- 121 | pop bc 122 | dec b 123 | jr nz,-- 124 | ret 125 | 126 | 127 | ; Shows console display 128 | ; Preserved: AF, BC, DE, HL 129 | console_show: 130 | push af 131 | 132 | ; Enable LCD 133 | call console_wait_vbl 134 | wreg LCDC,$91 135 | wreg SCX,0 136 | wreg BGP,$E4 137 | 138 | jp console_apply_scroll_ 139 | 140 | 141 | ; Hides console display by turning LCD off 142 | ; Preserved: AF, BC, DE, HL 143 | console_hide: 144 | push af 145 | 146 | ; LCD off 147 | call console_wait_vbl 148 | wreg LCDC,$11 149 | 150 | pop af 151 | ret 152 | 153 | 154 | ; Changes to normal text mode 155 | ; Preserved: BC, DE, HL 156 | console_normal: 157 | xor a 158 | jr console_set_mode 159 | 160 | ; Changes to inverse text mode 161 | ; Preserved: BC, DE, HL 162 | console_inverse: 163 | ld a,$80 164 | 165 | ; Changes console mode to A. 166 | ; 0: Normal, $80: Inverse 167 | ; Preserved: BC, DE, HL 168 | console_set_mode: 169 | and $80 170 | ld (console_mode),a 171 | ret 172 | 173 | 174 | ; Prints char A to console. Will not appear until 175 | ; a newline or flush occurs. 176 | ; Preserved: AF, BC, DE, HL 177 | console_print: 178 | push af 179 | 180 | cp 10 181 | jr z,console_newline_ 182 | 183 | push hl 184 | push af 185 | ld hl,console_pos 186 | ldi a,(hl) 187 | cp BGMAP0) >> 2 273 | add hl,hl 274 | add hl,hl 275 | 276 | ; Copy line 277 | ld de,console_buf + console_width 278 | - dec e 279 | ld a,(de) 280 | ldi (hl),a 281 | ld a,e 282 | cp checksum 67 | ldi (hl),a 68 | ld (hl),d 69 | inc l 70 | ld (hl),c 71 | inc l 72 | ld (hl),b 73 | 74 | pop hl 75 | pop de 76 | pop bc 77 | pop af 78 | ret 79 | -------------------------------------------------------------------------------- /roms/mem_timing/source/common/gb.inc: -------------------------------------------------------------------------------- 1 | ; Game Boy hardware addresses 2 | 3 | ; Memory 4 | .define VRAM $8000 ; video memory 5 | .define TILES $8000 ; tile images 6 | .define BGMAP0 $9800 ; first 32x32 tilemap 7 | .define BGMAP1 $9C00 ; second 32x32 tilemap 8 | .define WRAM $C000 ; internal memory 9 | .define OAM $FE00 ; sprite memory 10 | .define HRAM $FF80 ; fast memory for LDH 11 | 12 | .define P1 $FF00 13 | 14 | ; Game link I/O 15 | .define SB $FF01 16 | .define SC $FF02 17 | 18 | ; Interrupts 19 | .define DIV $FF04 20 | .define TIMA $FF05 21 | .define TMA $FF06 22 | .define TAC $FF07 23 | .define IF $FF0F 24 | .define IE $FFFF 25 | 26 | ; LCD registers 27 | .define LCDC $FF40 ; control 28 | .define STAT $FF41 ; status 29 | .define SCY $FF42 ; scroll Y 30 | .define SCX $FF43 ; scroll X 31 | .define LY $FF44 ; current Y being rendered 32 | .define BGP $FF47 33 | 34 | .define KEY1 $FF4D ; for changing CPU speed 35 | .define VBK $FF4F 36 | 37 | ; Sound registers 38 | .define NR10 $FF10 39 | .define NR11 $FF11 40 | .define NR12 $FF12 41 | .define NR13 $FF13 42 | .define NR14 $FF14 43 | 44 | .define NR21 $FF16 45 | .define NR22 $FF17 46 | .define NR23 $FF18 47 | .define NR24 $FF19 48 | 49 | .define NR30 $FF1A 50 | .define NR31 $FF1B 51 | .define NR32 $FF1C 52 | .define NR33 $FF1D 53 | .define NR34 $FF1E 54 | 55 | .define NR41 $FF20 56 | .define NR42 $FF21 57 | .define NR43 $FF22 58 | .define NR44 $FF23 59 | 60 | .define NR50 $FF24 61 | .define NR51 $FF25 62 | .define NR52 $FF26 63 | 64 | .define WAVE $FF30 65 | -------------------------------------------------------------------------------- /roms/mem_timing/source/common/macros.inc: -------------------------------------------------------------------------------- 1 | ; General macros 2 | 3 | ; Reads A from addr, from $FF00 to $FFFF 4 | ; Preserved: F, BC, DE, HL 5 | ; Time: 3 cycles 6 | .macro lda ARGS addr 7 | ldh a,(addr - $FF00) 8 | .endm 9 | 10 | ; Writes A to addr, from $FF00 to $FFFF 11 | ; Preserved: AF, BC, DE, HL 12 | ; Time: 3 cycles 13 | .macro sta ARGS addr 14 | ldh (addr - $FF00),a 15 | .endm 16 | 17 | ; Writes immediate data to addr, from $FF00 to $FFFF 18 | ; Preserved: F, BC, DE, HL 19 | ; Time: 5 cycles 20 | .macro wreg ARGS addr, data 21 | ld a,data 22 | sta addr 23 | .endm 24 | 25 | ; Calls routine multiple times, with A having the 26 | ; value 'start' the first time, 'start+step' the 27 | ; second time, up to 'end' for the last time. 28 | ; Preserved: BC, DE, HL 29 | .macro for_loop ; routine,start,end,step 30 | ld a,\2 31 | 32 | for_loop\@: 33 | push af 34 | call \1 35 | pop af 36 | 37 | add \4 38 | cp <(\3 + \4) 39 | jr nz,for_loop\@ 40 | .endm 41 | 42 | ; Calls routine n times. The value of A in the routine 43 | ; counts from 0 to n-1. 44 | ; Preserved: BC, DE, HL 45 | .macro loop_n_times ; routine,n 46 | for_loop \1,0,\2 - 1,+1 47 | .endm 48 | 49 | ; Same as for_loop, but counts with 16-bit value in BC. 50 | ; Preserved: DE, HL 51 | .macro for_loop16 ; routine,start,end,step 52 | ld bc,\2 53 | 54 | for_loop16\@: 55 | push bc 56 | call \1 57 | pop bc 58 | 59 | ld a,c 60 | add <\4 61 | ld c,a 62 | 63 | ld a,b 64 | adc >\4 65 | ld b,a 66 | 67 | cp >(\3+\4) 68 | jr nz,for_loop16\@ 69 | 70 | ld a,c 71 | cp <(\3+\4) 72 | jr nz,for_loop16\@ 73 | .endm 74 | -------------------------------------------------------------------------------- /roms/mem_timing/source/common/numbers.s: -------------------------------------------------------------------------------- 1 | ; Printing of numeric values 2 | 3 | ; Prints value of indicated register/pair 4 | ; as 2/4 hex digits, followed by a space. 5 | ; Updates checksum with printed values. 6 | ; Preserved: AF, BC, DE, HL 7 | 8 | print_regs: 9 | call print_af 10 | call print_bc 11 | call print_de 12 | call print_hl 13 | call print_newline 14 | ret 15 | 16 | print_a: 17 | push af 18 | print_a_: 19 | call print_hex 20 | ld a,' ' 21 | call print_char_nocrc 22 | pop af 23 | ret 24 | 25 | print_af: 26 | push af 27 | call print_hex 28 | pop af 29 | print_f: 30 | push bc 31 | push af 32 | pop bc 33 | call print_c 34 | pop bc 35 | ret 36 | 37 | print_b: 38 | push af 39 | ld a,b 40 | jr print_a_ 41 | 42 | print_c: 43 | push af 44 | ld a,c 45 | jr print_a_ 46 | 47 | print_d: 48 | push af 49 | ld a,d 50 | jr print_a_ 51 | 52 | print_e: 53 | push af 54 | ld a,e 55 | jr print_a_ 56 | 57 | print_h: 58 | push af 59 | ld a,h 60 | jr print_a_ 61 | 62 | print_l: 63 | push af 64 | ld a,l 65 | jr print_a_ 66 | 67 | print_bc: 68 | push af 69 | push bc 70 | print_bc_: 71 | ld a,b 72 | call print_hex 73 | ld a,c 74 | pop bc 75 | jr print_a_ 76 | 77 | print_de: 78 | push af 79 | push bc 80 | ld b,d 81 | ld c,e 82 | jr print_bc_ 83 | 84 | print_hl: 85 | push af 86 | push bc 87 | ld b,h 88 | ld c,l 89 | jr print_bc_ 90 | 91 | 92 | ; Prints A as two hex chars and updates checksum 93 | ; Preserved: BC, DE, HL 94 | print_hex: 95 | call update_crc 96 | print_hex_nocrc: 97 | push af 98 | swap a 99 | call + 100 | pop af 101 | 102 | + and $0F 103 | cp 10 104 | jr c,+ 105 | add 7 106 | + add '0' 107 | jp print_char_nocrc 108 | 109 | 110 | ; Prints char_nz if Z flag is clear, 111 | ; char_z if Z flag is set. 112 | ; Preserved: AF, BC, DE, HL 113 | .macro print_nz ARGS char_nz, char_z 114 | push af 115 | ld a,char_nz 116 | jr nz,print_nz\@ 117 | ld a,char_z 118 | print_nz\@: 119 | call print_char 120 | pop af 121 | .endm 122 | 123 | 124 | ; Prints char_nc if C flag is clear, 125 | ; char_c if C flag is set. 126 | ; Preserved: AF, BC, DE, HL 127 | .macro print_nc ARGS char_nc, char_c 128 | push af 129 | ld a,char_nc 130 | jr nz,print_nc\@ 131 | ld a,char_c 132 | print_nc\@: 133 | call print_char 134 | pop af 135 | .endm 136 | 137 | 138 | ; Prints A as 2 decimal digits 139 | ; Preserved: AF, BC, DE, HL 140 | print_dec2: 141 | push af 142 | push bc 143 | jr + 144 | 145 | 146 | ; Prints A as 1-3 digit decimal value 147 | ; Preserved: AF, BC, DE, HL 148 | print_dec: 149 | push af 150 | push bc 151 | 152 | cp 10 153 | jr c,++ 154 | ld c,100 155 | cp c 156 | call nc,@digit 157 | + ld c,10 158 | call @digit 159 | ++ add '0' 160 | call print_char 161 | 162 | pop bc 163 | pop af 164 | ret 165 | 166 | @digit: 167 | ld b,'0'-1 168 | - inc b 169 | sub c 170 | jr nc,- 171 | add c 172 | 173 | ld c,a 174 | ld a,b 175 | call print_char 176 | ld a,c 177 | ret 178 | -------------------------------------------------------------------------------- /roms/mem_timing/source/common/printing.s: -------------------------------------------------------------------------------- 1 | ; Main printing routine that checksums and 2 | ; prints to output device 3 | 4 | ; Character that does equivalent of print_newline 5 | .define newline 10 6 | 7 | ; Prints char without updating checksum 8 | ; Preserved: BC, DE, HL 9 | .define print_char_nocrc bss 10 | .redefine bss bss+3 11 | 12 | 13 | ; Initializes printing. HL = print routine 14 | init_printing: 15 | ld a,l 16 | ld (print_char_nocrc+1),a 17 | ld a,h 18 | ld (print_char_nocrc+2),a 19 | jr show_printing 20 | 21 | 22 | ; Hides/shows further printing 23 | ; Preserved: BC, DE, HL 24 | hide_printing: 25 | ld a,$C9 ; RET 26 | jr + 27 | show_printing: 28 | ld a,$C3 ; JP (nn) 29 | + ld (print_char_nocrc),a 30 | ret 31 | 32 | 33 | ; Prints character and updates checksum UNLESS 34 | ; it's a newline. 35 | ; Preserved: AF, BC, DE, HL 36 | print_char: 37 | push af 38 | cp newline 39 | call nz,update_crc 40 | call print_char_nocrc 41 | pop af 42 | ret 43 | 44 | 45 | ; Prints space. Does NOT update checksum. 46 | ; Preserved: AF, BC, DE, HL 47 | print_space: 48 | push af 49 | ld a,' ' 50 | call print_char_nocrc 51 | pop af 52 | ret 53 | 54 | 55 | ; Advances to next line. Does NOT update checksum. 56 | ; Preserved: AF, BC, DE, HL 57 | print_newline: 58 | push af 59 | ld a,newline 60 | call print_char_nocrc 61 | pop af 62 | ret 63 | 64 | 65 | ; Prints immediate string 66 | ; Preserved: AF, BC, DE, HL 67 | .macro print_str ; string,string2 68 | push hl 69 | call print_str_ 70 | .byte \1 71 | .if NARGS > 1 72 | .byte \2 73 | .endif 74 | .if NARGS > 2 75 | .byte \3 76 | .endif 77 | .byte 0 78 | pop hl 79 | .endm 80 | 81 | print_str_: 82 | pop hl 83 | call print_str_hl 84 | jp hl 85 | 86 | 87 | ; Prints zero-terminated string pointed to by HL. 88 | ; On return, HL points to byte AFTER zero terminator. 89 | ; Preserved: AF, BC, DE 90 | print_str_hl: 91 | push af 92 | jr + 93 | - call print_char 94 | + ldi a,(hl) 95 | or a 96 | jr nz,- 97 | pop af 98 | ret 99 | -------------------------------------------------------------------------------- /roms/mem_timing/source/common/runtime.s: -------------------------------------------------------------------------------- 1 | ; Common routines and runtime 2 | 3 | ; Must be defined by target-specific runtime: 4 | ; 5 | ; init_runtime: ; target-specific inits 6 | ; std_print: ; default routine to print char A 7 | ; post_exit: ; called at end of std_exit 8 | ; report_byte: ; report A to user 9 | 10 | .define RUNTIME_INCLUDED 1 11 | 12 | .ifndef bss 13 | ; address of next normal variable 14 | .define bss $D800 15 | .endif 16 | 17 | .ifndef dp 18 | ; address of next direct-page ($FFxx) variable 19 | .define dp $FF80 20 | .endif 21 | 22 | ; DMG/CGB hardware identifier 23 | .define gb_id_cgb $10 ; mask for testing CGB bit 24 | .define gb_id_devcart $04 ; mask for testing "on devcart" bit 25 | 26 | .define gb_id bss 27 | .redefine bss bss+1 28 | 29 | ; Stack is normally here 30 | .define std_stack $DFFF 31 | 32 | ; Copies $1000 bytes from HL to $C000, then jumps to it. 33 | ; A is preserved for jumped-to code. 34 | copy_to_wram_then_run: 35 | ld b,a 36 | 37 | ld de,$C000 38 | ld c,$10 39 | - ldi a,(hl) 40 | ld (de),a 41 | inc e 42 | jr nz,- 43 | inc d 44 | dec c 45 | jr nz,- 46 | 47 | ld a,b 48 | jp $C000 49 | 50 | .ifndef CUSTOM_RESET 51 | reset: 52 | ; Run code from $C000, as is done on devcart. This 53 | ; ensures minimal difference in how it behaves. 54 | ld hl,$4000 55 | jp copy_to_wram_then_run 56 | 57 | .bank 1 slot 1 58 | .org $0 ; otherwise wla pads with lots of zeroes 59 | jp std_reset 60 | .endif 61 | 62 | ; Common routines 63 | .include "gb.inc" 64 | .include "macros.inc" 65 | .include "delay.s" 66 | .include "crc.s" 67 | .include "printing.s" 68 | .include "numbers.s" 69 | .include "testing.s" 70 | 71 | ; Sets up hardware and runs main 72 | std_reset: 73 | 74 | ; Init hardware 75 | di 76 | ld sp,std_stack 77 | 78 | ; Save DMG/CGB id 79 | ld (gb_id),a 80 | 81 | ; Init hardware 82 | .ifndef BUILD_GBS 83 | wreg TAC,$00 84 | wreg IF,$00 85 | wreg IE,$00 86 | .endif 87 | 88 | wreg NR52,0 ; sound off 89 | wreg NR52,$80 ; sound on 90 | wreg NR51,$FF ; mono 91 | wreg NR50,$77 ; volume 92 | 93 | ; TODO: clear all memory? 94 | 95 | ld hl,std_print 96 | call init_printing 97 | call init_testing 98 | call init_runtime 99 | call reset_crc ; in case init_runtime prints anything 100 | 101 | delay_msec 250 102 | 103 | ; Run user code 104 | call main 105 | 106 | ; Default is to successful exit 107 | ld a,0 108 | jp exit 109 | 110 | 111 | ; Exits code and reports value of A 112 | exit: 113 | ld sp,std_stack 114 | push af 115 | call + 116 | pop af 117 | jp post_exit 118 | 119 | + push af 120 | call print_newline 121 | call show_printing 122 | pop af 123 | 124 | ; Report exit status 125 | cp 1 126 | 127 | ; 0: "" 128 | ret c 129 | 130 | ; 1: "Failed" 131 | jr nz,+ 132 | print_str "Failed",newline 133 | ret 134 | 135 | ; n: "Failed #n" 136 | + print_str "Failed #" 137 | call print_dec 138 | call print_newline 139 | ret 140 | 141 | ; returnOrg puts this code AFTER user code. 142 | .section "runtime" returnOrg 143 | -------------------------------------------------------------------------------- /roms/mem_timing/source/common/testing.s: -------------------------------------------------------------------------------- 1 | ; Diagnostic and testing utilities 2 | 3 | .define result bss+0 4 | .define test_name bss+1 5 | .redefine bss bss+3 6 | 7 | 8 | ; Sets test code and optional error text 9 | ; Preserved: AF, BC, DE, HL 10 | .macro set_test ; code[,text[,text2]] 11 | push hl 12 | call set_test_ 13 | jr @set_test\@ 14 | .byte \1 15 | .if NARGS > 1 16 | .byte \2 17 | .endif 18 | .if NARGS > 2 19 | .byte \3 20 | .endif 21 | .byte 0 22 | @set_test\@: 23 | pop hl 24 | .endm 25 | 26 | set_test_: 27 | pop hl 28 | push hl 29 | push af 30 | inc hl 31 | inc hl 32 | ldi a,(hl) 33 | ld (result),a 34 | ld a,l 35 | ld (test_name),a 36 | ld a,h 37 | ld (test_name+1),a 38 | pop af 39 | ret 40 | 41 | 42 | ; Initializes testing module 43 | init_testing: 44 | set_test $FF 45 | call init_crc 46 | ret 47 | 48 | 49 | ; Reports "Passed", then exits with code 0 50 | tests_passed: 51 | call print_newline 52 | print_str "Passed" 53 | ld a,0 54 | jp exit 55 | 56 | 57 | ; Reports "Done" if set_test has never been used, 58 | ; "Passed" if set_test 0 was last used, or 59 | ; failure if set_test n was last used. 60 | tests_done: 61 | ld a,(result) 62 | inc a 63 | jr z,+ 64 | dec a 65 | jr z,tests_passed 66 | jr test_failed 67 | + print_str "Done" 68 | ld a,0 69 | jp exit 70 | 71 | 72 | ; Reports current error text and exits with result code 73 | test_failed: 74 | ld a,(test_name) 75 | ld l,a 76 | ld a,(test_name+1) 77 | ld h,a 78 | ld a,(hl) 79 | or a 80 | jr z,+ 81 | call print_newline 82 | call print_str_hl 83 | call print_newline 84 | + 85 | ld a,(result) 86 | cp 1 ; if a = 0 then a = 1 87 | adc 0 88 | jp exit 89 | 90 | 91 | ; Prints checksum as 8-character hex value 92 | ; Preserved: AF, BC, DE, HL 93 | print_crc: 94 | push af 95 | 96 | ; Must read checksum entirely before printing, 97 | ; since printing updates it. 98 | lda checksum 99 | cpl 100 | push af 101 | 102 | lda checksum+1 103 | cpl 104 | push af 105 | 106 | lda checksum+2 107 | cpl 108 | push af 109 | 110 | lda checksum+3 111 | cpl 112 | 113 | call print_hex 114 | pop af 115 | call print_hex 116 | pop af 117 | call print_hex 118 | pop af 119 | call print_a 120 | 121 | pop af 122 | ret 123 | 124 | 125 | ; If checksum doesn't match expected, reports failed test. 126 | ; Passing 0 just prints checksum. Clears checksum afterwards. 127 | .macro check_crc ARGS crc 128 | .if crc == 0 129 | call show_printing 130 | call print_newline 131 | call print_crc 132 | .else 133 | ld bc,(crc >> 16) ~ $FFFF 134 | ld de,(crc & $FFFF) ~ $FFFF 135 | call check_crc_ 136 | .endif 137 | .endm 138 | 139 | check_crc_: 140 | lda checksum+0 141 | cp e 142 | jr nz,+ 143 | 144 | lda checksum+1 145 | cp d 146 | jr nz,+ 147 | 148 | lda checksum+2 149 | cp c 150 | jr nz,+ 151 | 152 | lda checksum+3 153 | cp b 154 | jr nz,+ 155 | 156 | jp reset_crc 157 | 158 | + call print_crc 159 | jp test_failed 160 | 161 | 162 | ; Updates checksum with bytes from addr to addr+size-1 163 | .macro checksum_mem ARGS addr,size 164 | ld hl,addr 165 | ld bc,size 166 | call checksum_mem_ 167 | .endm 168 | 169 | checksum_mem_: 170 | - ldi a,(hl) 171 | call update_crc 172 | dec bc 173 | ld a,b 174 | or c 175 | jr nz,- 176 | ret 177 | -------------------------------------------------------------------------------- /roms/mem_timing/source/common/tima_64.s: -------------------------------------------------------------------------------- 1 | ; Timer that's incremented every 64 cycles 2 | 3 | ; Initializes timer for use by sync_tima_64 4 | init_tima_64: 5 | wreg TMA,0 6 | wreg TAC,$07 7 | ret 8 | 9 | ; Synchronizes to timer 10 | sync_tima_64: 11 | push af 12 | push hl 13 | 14 | ld a,0 15 | ld hl,TIMA 16 | ld (hl),a 17 | - or (hl) 18 | jr z,- 19 | 20 | - delay 65-12 21 | xor a 22 | ld (hl),a 23 | or (hl) 24 | delay 4 25 | jr z,- 26 | 27 | pop hl 28 | pop af 29 | ret 30 | 31 | ; Read from this to get count that's incremented 32 | ; every 64 cycles. 33 | .define tima_64 TIMA 34 | -------------------------------------------------------------------------------- /roms/mem_timing/source/linkfile: -------------------------------------------------------------------------------- 1 | [objects] 2 | test.o 3 | -------------------------------------------------------------------------------- /roms/mem_timing/source/shell.inc: -------------------------------------------------------------------------------- 1 | .incdir "common" 2 | 3 | ; GBS music file 4 | .ifdef BUILD_GBS 5 | .include "build_gbs.s" 6 | .endif 7 | 8 | ; Devcart 9 | .ifdef BUILD_DEVCART 10 | .include "build_devcart.s" 11 | .endif 12 | 13 | ; Sub-test in a multi-test ROM 14 | .ifdef BUILD_MULTI 15 | .include "build_multi.s" 16 | .endif 17 | 18 | ; GB ROM (default) 19 | .ifndef RUNTIME_INCLUDED 20 | .include "build_rom.s" 21 | .endif 22 | -------------------------------------------------------------------------------- /roms/opus5/opus5.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/opus5/opus5.gb -------------------------------------------------------------------------------- /roms/tictactoe/GB-TicTacToe.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/tictactoe/GB-TicTacToe.gb -------------------------------------------------------------------------------- /roms/tobu/tobu.gb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/roms/tobu/tobu.gb -------------------------------------------------------------------------------- /screenshot/dino.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/screenshot/dino.png -------------------------------------------------------------------------------- /screenshot/drmario.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/screenshot/drmario.png -------------------------------------------------------------------------------- /screenshot/mario.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/screenshot/mario.png -------------------------------------------------------------------------------- /screenshot/tetris.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/screenshot/tetris.png -------------------------------------------------------------------------------- /screenshot/tobu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/screenshot/tobu.png -------------------------------------------------------------------------------- /test/actual/bits_bank1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/actual/bits_bank1.png -------------------------------------------------------------------------------- /test/actual/cpu_instr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/actual/cpu_instr.png -------------------------------------------------------------------------------- /test/actual/daa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/actual/daa.png -------------------------------------------------------------------------------- /test/actual/div_write.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/actual/div_write.png -------------------------------------------------------------------------------- /test/actual/hello_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/actual/hello_world.png -------------------------------------------------------------------------------- /test/actual/if_ie_registers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/actual/if_ie_registers.png -------------------------------------------------------------------------------- /test/actual/mem_oam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/actual/mem_oam.png -------------------------------------------------------------------------------- /test/actual/opus5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/actual/opus5.png -------------------------------------------------------------------------------- /test/actual/reg_f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/actual/reg_f.png -------------------------------------------------------------------------------- /test/actual/tictactoe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/actual/tictactoe.png -------------------------------------------------------------------------------- /test/actual/tim00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/actual/tim00.png -------------------------------------------------------------------------------- /test/actual/tim01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/actual/tim01.png -------------------------------------------------------------------------------- /test/actual/tim10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/actual/tim10.png -------------------------------------------------------------------------------- /test/actual/tim11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/actual/tim11.png -------------------------------------------------------------------------------- /test/actual/tobu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/actual/tobu.png -------------------------------------------------------------------------------- /test/diff/bits_bank1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/diff/bits_bank1.png -------------------------------------------------------------------------------- /test/diff/cpu_instr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/diff/cpu_instr.png -------------------------------------------------------------------------------- /test/diff/hello_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/diff/hello_world.png -------------------------------------------------------------------------------- /test/diff/opus5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/diff/opus5.png -------------------------------------------------------------------------------- /test/diff/tictactoe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/diff/tictactoe.png -------------------------------------------------------------------------------- /test/expect/bits_bank1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/expect/bits_bank1.png -------------------------------------------------------------------------------- /test/expect/cpu_instr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/expect/cpu_instr.png -------------------------------------------------------------------------------- /test/expect/daa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/expect/daa.png -------------------------------------------------------------------------------- /test/expect/div_write.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/expect/div_write.png -------------------------------------------------------------------------------- /test/expect/hello_world.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/expect/hello_world.png -------------------------------------------------------------------------------- /test/expect/if_ie_registers.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/expect/if_ie_registers.png -------------------------------------------------------------------------------- /test/expect/mem_oam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/expect/mem_oam.png -------------------------------------------------------------------------------- /test/expect/opus5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/expect/opus5.png -------------------------------------------------------------------------------- /test/expect/reg_f.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/expect/reg_f.png -------------------------------------------------------------------------------- /test/expect/tictactoe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/expect/tictactoe.png -------------------------------------------------------------------------------- /test/expect/tim00.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/expect/tim00.png -------------------------------------------------------------------------------- /test/expect/tim01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/expect/tim01.png -------------------------------------------------------------------------------- /test/expect/tim10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/expect/tim10.png -------------------------------------------------------------------------------- /test/expect/tim11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/expect/tim11.png -------------------------------------------------------------------------------- /test/expect/tobu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bokuweb/gopher-boy/556099049402dd19f5e6b0841d7dcc2be78a2c9d/test/expect/tobu.png --------------------------------------------------------------------------------