├── .gitignore ├── README.md ├── cmd ├── gopherboy_sdl │ ├── input_driver.go │ ├── main.go │ ├── save_game_driver.go │ ├── sound_driver.go │ └── video_driver.go └── gopherboy_wasm │ ├── input_driver.go │ ├── main.go │ ├── message_handlers.go │ ├── save_game_driver.go │ ├── static │ ├── emulator_worker.js │ ├── main.js │ ├── test.js │ └── wasm_exec.js │ ├── video_driver.go │ └── views │ └── index.html ├── gameboy ├── addresses.go ├── arithmetic_ops.go ├── bit_ops.go ├── bit_utils.go ├── call_return_ops.go ├── cpu_control_ops.go ├── cpu_registers.go ├── debugger.go ├── device.go ├── driver_interfaces.go ├── interrupt_manager.go ├── joypad.go ├── jump_ops.go ├── loading_ops.go ├── mbc1.go ├── mbc3.go ├── mbc5.go ├── mmu.go ├── mmu_utils.go ├── opcode_to_op.go ├── rom_header.go ├── rom_only_mbc.go ├── rotate_shift_ops.go ├── serial.go ├── sound_controller.go ├── state.go ├── timers.go ├── unmapped_addresses.go └── video_controller.go ├── go.mod └── go.sum /.gitignore: -------------------------------------------------------------------------------- 1 | gopherboy_server 2 | cmd/gopherboy_sdl/gopherboy_sdl 3 | cmd/gopherboy_wasm/gopherboy_wasm 4 | 5 | # Ignore Gameboy code and compiled files 6 | *.asm 7 | *.o 8 | *.gb 9 | *.sav 10 | *.inc 11 | roms/ 12 | docs/ 13 | 14 | # Ignore memory dumps 15 | *.dump 16 | 17 | # Profiling and other docs 18 | *.pdf 19 | 20 | # Build results 21 | *.wasm 22 | 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Gopherboy 2 | 3 | Gopherboy is a Game Boy emulator written in Go. It plays a majority of tested 4 | games properly and has support for sound. 5 | 6 | ## Screenshots 7 | 8 | ![Screenshots][screenshots] 9 | 10 | ## Performance 11 | 12 | With the SDL back ends, it runs at roughly 1200% native speed on my machine 13 | (i7-8750H @ 2.20 GHz). With the in-progress WebAssembly back end, it runs at 14 | roughly 75% native speed. 15 | 16 | [screenshots]: https://i.imgur.com/UlDcNVC.png 17 | 18 | -------------------------------------------------------------------------------- /cmd/gopherboy_sdl/input_driver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/veandco/go-sdl2/sdl" 5 | "github.com/velovix/gopherboy/gameboy" 6 | ) 7 | 8 | var scancodeToButton = map[sdl.Scancode]gameboy.Button{ 9 | sdl.SCANCODE_W: gameboy.ButtonStart, 10 | sdl.SCANCODE_Q: gameboy.ButtonSelect, 11 | sdl.SCANCODE_Z: gameboy.ButtonB, 12 | sdl.SCANCODE_X: gameboy.ButtonA, 13 | sdl.SCANCODE_DOWN: gameboy.ButtonDown, 14 | sdl.SCANCODE_UP: gameboy.ButtonUp, 15 | sdl.SCANCODE_LEFT: gameboy.ButtonLeft, 16 | sdl.SCANCODE_RIGHT: gameboy.ButtonRight, 17 | } 18 | 19 | type inputDriver struct { 20 | buttonStates map[gameboy.Button]bool 21 | } 22 | 23 | func newInputDriver() *inputDriver { 24 | var driver inputDriver 25 | 26 | // Initialize all relevant joypad values 27 | driver.buttonStates = map[gameboy.Button]bool{ 28 | gameboy.ButtonStart: false, 29 | gameboy.ButtonSelect: false, 30 | gameboy.ButtonB: false, 31 | gameboy.ButtonA: false, 32 | gameboy.ButtonDown: false, 33 | gameboy.ButtonUp: false, 34 | gameboy.ButtonLeft: false, 35 | gameboy.ButtonRight: false, 36 | } 37 | 38 | return &driver 39 | } 40 | 41 | func (driver *inputDriver) State(btn gameboy.Button) bool { 42 | return driver.buttonStates[btn] 43 | } 44 | 45 | // Update polls the keyboard for the latest input events and updates the button 46 | // states accordingly. Returns true if a new button was pressed since the last 47 | // call to update. 48 | func (driver *inputDriver) Update() bool { 49 | buttonPressed := false 50 | 51 | doOnMainThread(func() { 52 | for event := sdl.PollEvent(); event != nil; event = sdl.PollEvent() { 53 | switch event := event.(type) { 54 | case *sdl.KeyboardEvent: 55 | btn := scancodeToButton[event.Keysym.Scancode] 56 | 57 | if _, ok := driver.buttonStates[btn]; ok { 58 | if event.State == sdl.PRESSED { 59 | buttonPressed = true 60 | driver.buttonStates[btn] = true 61 | } else { 62 | driver.buttonStates[btn] = false 63 | } 64 | } 65 | } 66 | } 67 | }, false) 68 | 69 | return buttonPressed 70 | } 71 | -------------------------------------------------------------------------------- /cmd/gopherboy_sdl/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "os" 8 | "os/signal" 9 | "runtime" 10 | 11 | "github.com/pkg/profile" 12 | "github.com/velovix/gopherboy/gameboy" 13 | ) 14 | 15 | func init() { 16 | // Necessary because SDL2 is not thread-safe and does not like being moved 17 | // around 18 | runtime.LockOSThread() 19 | } 20 | 21 | // mainThreadFuncs contains functions queued up to run on the main thread. 22 | var mainThreadFuncs = make(chan func()) 23 | 24 | // doOnMainThread runs the given function in the main thread and returns when 25 | // done. 26 | func doOnMainThread(f func(), async bool) { 27 | done := make(chan bool, 1) 28 | mainThreadFuncs <- func() { 29 | f() 30 | done <- true 31 | } 32 | if !async { 33 | <-done 34 | } 35 | } 36 | 37 | func main() { 38 | bootROM := flag.String("boot-rom", "", 39 | "Path to a file containing the Game Boy boot ROM") 40 | scaleFactor := flag.Float64("scale", 2, 41 | "The amount to scale the window by, with 1 being native resolution") 42 | breakOnPC := flag.Int("break-on-pc", -1, 43 | "A program counter value to break at") 44 | breakOnOpcode := flag.Int("break-on-opcode", -1, 45 | "An opcode to break at") 46 | breakOnAddrRead := flag.Int("break-on-addr-read", -1, 47 | "A memory address to break at on read") 48 | breakOnAddrWrite := flag.Int("break-on-addr-write", -1, 49 | "A memory address to break at on write") 50 | enableProfiling := flag.Bool("profile", false, 51 | "Generates a pprof file if set") 52 | unlimitedFPS := flag.Bool("unlimited-fps", false, 53 | "If true, frame rate will not be capped. Games will run as quickly as possible.") 54 | saveGameDirectory := flag.String("save-game-dir", ".", 55 | "The directory to find save games in") 56 | benchmarkComponents := flag.Bool("benchmark-components", false, 57 | "If true, some performance information will be printed out about each "+ 58 | "component, then the emulator will exit.") 59 | _ = unlimitedFPS 60 | 61 | flag.Parse() 62 | 63 | if len(flag.Args()) < 1 { 64 | fmt.Println("Usage: gopherboy --boot-rom boot_rom_file [OPTIONS] rom_file") 65 | os.Exit(1) 66 | } 67 | 68 | var bootROMData []byte 69 | var err error 70 | if *bootROM == "" { 71 | fmt.Println("A boot ROM must be provided") 72 | os.Exit(1) 73 | } 74 | // Load the boot ROM 75 | bootROMData, err = ioutil.ReadFile(*bootROM) 76 | if err != nil { 77 | fmt.Println("Error: While reading boot ROM:", err) 78 | os.Exit(1) 79 | } 80 | 81 | if *scaleFactor <= 0 { 82 | fmt.Println("Scale factor must be higher than 0") 83 | os.Exit(1) 84 | } 85 | 86 | if stat, err := os.Stat(*saveGameDirectory); os.IsNotExist(err) { 87 | fmt.Println("The specified save game directory does not exist") 88 | os.Exit(1) 89 | } else if err != nil { 90 | fmt.Println("Error with save game directory:", err) 91 | os.Exit(1) 92 | } else if !stat.IsDir() { 93 | fmt.Println("The specified save game directory is not a directory") 94 | os.Exit(1) 95 | } 96 | 97 | if *enableProfiling { 98 | fmt.Println("Profiling has been enabled") 99 | defer profile.Start(profile.NoShutdownHook).Stop() 100 | } 101 | 102 | // Load the ROM file 103 | cartridgeData, err := ioutil.ReadFile(flag.Args()[0]) 104 | if err != nil { 105 | fmt.Println("Error: While reading cartridge:", err) 106 | os.Exit(1) 107 | } 108 | 109 | video, err := newVideoDriver(*scaleFactor, *unlimitedFPS) 110 | if err != nil { 111 | fmt.Println("Error: While initializing video driver:", err) 112 | os.Exit(1) 113 | } 114 | input := newInputDriver() 115 | if err != nil { 116 | fmt.Println("Error: While initializing input driver:", err) 117 | os.Exit(1) 118 | } 119 | saveGames := &fileSaveGameDriver{ 120 | directory: *saveGameDirectory, 121 | } 122 | 123 | var dbConfig gameboy.DebugConfiguration 124 | 125 | if *breakOnPC != -1 || *breakOnOpcode != -1 || *breakOnAddrRead != -1 || 126 | *breakOnAddrWrite != -1 { 127 | 128 | dbConfig.Debugging = true 129 | if *breakOnPC != -1 { 130 | val := uint16(*breakOnPC) 131 | dbConfig.BreakOnPC = &val 132 | } 133 | if *breakOnOpcode != -1 { 134 | val := uint8(*breakOnOpcode) 135 | dbConfig.BreakOnOpcode = &val 136 | } 137 | if *breakOnAddrRead != -1 { 138 | val := uint16(*breakOnAddrRead) 139 | dbConfig.BreakOnAddrRead = &val 140 | } 141 | if *breakOnAddrWrite != -1 { 142 | val := uint16(*breakOnAddrWrite) 143 | dbConfig.BreakOnAddrWrite = &val 144 | } 145 | } 146 | 147 | device, err := gameboy.NewDevice(bootROMData, cartridgeData, video, input, saveGames, dbConfig) 148 | if err != nil { 149 | fmt.Println("Error: While initializing Game Boy:", err) 150 | os.Exit(1) 151 | } 152 | 153 | _, err = newSoundDriver(device) 154 | if err != nil { 155 | fmt.Println("Error: While initializing sound driver:", err) 156 | os.Exit(1) 157 | } 158 | 159 | if *benchmarkComponents { 160 | device.BenchmarkComponents() 161 | return 162 | } 163 | 164 | // Stop main loop on sigint 165 | sigint := make(chan os.Signal, 1) 166 | signal.Notify(sigint, os.Interrupt) 167 | onExitRequest := make(chan bool) 168 | go func() { 169 | for range sigint { 170 | onExitRequest <- true 171 | break 172 | } 173 | }() 174 | 175 | onDeviceExit := make(chan bool) 176 | 177 | // Start the device 178 | go func() { 179 | err = device.Start(onExitRequest) 180 | if err != nil { 181 | fmt.Println("Error:", err) 182 | os.Exit(1) 183 | } 184 | 185 | onDeviceExit <- true 186 | }() 187 | 188 | running := true 189 | for running { 190 | select { 191 | case f := <-mainThreadFuncs: 192 | f() 193 | case <-onDeviceExit: 194 | running = false 195 | } 196 | } 197 | 198 | fmt.Println("Buh-bye!") 199 | } 200 | -------------------------------------------------------------------------------- /cmd/gopherboy_sdl/save_game_driver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "os" 6 | "path" 7 | 8 | "golang.org/x/xerrors" 9 | ) 10 | 11 | type fileSaveGameDriver struct { 12 | directory string 13 | } 14 | 15 | func (driver *fileSaveGameDriver) Save(name string, data []uint8) error { 16 | err := ioutil.WriteFile(driver.nameToPath(name), data, 0644) 17 | if err != nil { 18 | return xerrors.Errorf("saving game save: %w", err) 19 | } 20 | 21 | return nil 22 | } 23 | 24 | func (driver *fileSaveGameDriver) Load(name string) ([]uint8, error) { 25 | filename := driver.nameToPath(name) 26 | data, err := ioutil.ReadFile(filename) 27 | if err != nil { 28 | return nil, xerrors.Errorf("loading game save %v: %w", filename, err) 29 | } 30 | 31 | return data, nil 32 | } 33 | 34 | func (driver *fileSaveGameDriver) Has(name string) (bool, error) { 35 | filename := driver.nameToPath(name) 36 | 37 | _, err := os.Stat(filename) 38 | if err == nil { 39 | return true, nil 40 | } else if os.IsNotExist(err) { 41 | return false, nil 42 | } else { 43 | return false, xerrors.Errorf("checking if game save %v exists under name %v: %w", 44 | name, filename, err) 45 | } 46 | } 47 | 48 | func (driver *fileSaveGameDriver) nameToPath(name string) string { 49 | return path.Join(driver.directory, name+".sav") 50 | } 51 | -------------------------------------------------------------------------------- /cmd/gopherboy_sdl/sound_driver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | // #include 4 | // void SoundCallback(void *userdata, uint8_t *stream, int len); 5 | import "C" 6 | import ( 7 | "log" 8 | "math" 9 | "reflect" 10 | "unsafe" 11 | 12 | "github.com/veandco/go-sdl2/sdl" 13 | "github.com/velovix/gopherboy/gameboy" 14 | ) 15 | 16 | const ( 17 | toneHz = 440 18 | sampleHz = 128 19 | totalHz = 44100 20 | ) 21 | 22 | var myDevice *gameboy.Device 23 | var samples = make(chan float64, totalHz*3) 24 | 25 | var ( 26 | pulseAPhase = 0.0 27 | pulseBPhase = 0.0 28 | wavePhase = 0.0 29 | shiftCount = 0.0 30 | lfsr = uint16(0x38C2) 31 | ) 32 | 33 | const tau = math.Pi * 2.0 34 | 35 | func square(phase, dutyCycle float64) float64 { 36 | phase -= tau * math.Floor(phase/tau) 37 | 38 | if phase < tau*dutyCycle { 39 | return 1.0 40 | } 41 | return 0.0 42 | } 43 | 44 | func wave(phase float64, pattern [32]float64) float64 { 45 | wavePos := math.Floor((math.Mod(phase, tau) / tau) * 32) 46 | 47 | return pattern[int(wavePos)] 48 | } 49 | 50 | //export SoundCallback 51 | func SoundCallback(userdata unsafe.Pointer, bufferRaw *C.uint8_t, rawLength C.int) { 52 | length := int(rawLength) 53 | 54 | // Construct a Go slice from the C pointer to the buffer 55 | sliceHeader := reflect.SliceHeader{ 56 | Data: uintptr(unsafe.Pointer(bufferRaw)), 57 | Len: length, 58 | Cap: length, 59 | } 60 | buffer := *(*[]C.uint8_t)(unsafe.Pointer(&sliceHeader)) 61 | 62 | pulseAPhaseDelta := tau * float64(myDevice.SoundController.PulseA.Frequency()) / totalHz 63 | pulseBPhaseDelta := tau * myDevice.SoundController.PulseB.Frequency() / totalHz 64 | wavePhaseDelta := tau * myDevice.SoundController.Wave.Frequency() / totalHz 65 | 66 | if !myDevice.SoundController.Enabled { 67 | // Fill the buffer with zeros 68 | for i := 0; i < length; i++ { 69 | buffer[i] = 0 70 | } 71 | return 72 | } 73 | 74 | for i := 0; i < length; i += 2 { 75 | var sampleLeft, sampleRight float64 76 | 77 | // Handle pulse A voice 78 | if myDevice.SoundController.PulseA.On { 79 | pulseAPhase += pulseAPhaseDelta 80 | 81 | sample := square( 82 | pulseAPhase, 83 | myDevice.SoundController.PulseA.DutyCycle()) 84 | sample *= myDevice.SoundController.PulseA.Volume() 85 | 86 | if myDevice.SoundController.PulseA.LeftEnabled { 87 | sampleLeft += sample 88 | } 89 | if myDevice.SoundController.PulseA.RightEnabled { 90 | sampleRight += sample 91 | } 92 | } 93 | 94 | // Handle pulse B voice 95 | if myDevice.SoundController.PulseB.On { 96 | pulseBPhase += pulseBPhaseDelta 97 | 98 | sample := square( 99 | pulseBPhase, 100 | myDevice.SoundController.PulseB.DutyCycle()) 101 | sample *= myDevice.SoundController.PulseB.Volume() 102 | 103 | if myDevice.SoundController.PulseB.LeftEnabled { 104 | sampleLeft += sample 105 | } 106 | if myDevice.SoundController.PulseB.RightEnabled { 107 | sampleRight += sample 108 | } 109 | } 110 | 111 | // Handle wave voice 112 | if myDevice.SoundController.Wave.On { 113 | wavePhase += wavePhaseDelta 114 | 115 | sample := wave( 116 | wavePhase, 117 | myDevice.SoundController.Wave.Pattern()) 118 | sample *= myDevice.SoundController.Wave.Volume() 119 | 120 | if myDevice.SoundController.Wave.LeftEnabled { 121 | sampleLeft += sample 122 | } 123 | if myDevice.SoundController.Wave.RightEnabled { 124 | sampleRight += sample 125 | } 126 | } 127 | 128 | // Handle noise voice 129 | if myDevice.SoundController.Noise.On { 130 | // Shift the LFSR by the necessary amount 131 | shiftCount += myDevice.SoundController.Noise.ShiftFrequency() / totalHz 132 | for shiftCount >= 1 { 133 | lfsr >>= 1 134 | xorVal := (lfsr & 0x1) ^ ((lfsr & 0x2) >> 1) 135 | lfsr |= xorVal << 14 136 | 137 | if myDevice.SoundController.Noise.WidthMode() == gameboy.WidthMode7Bit { 138 | lfsr |= xorVal << 6 139 | } 140 | shiftCount-- 141 | } 142 | 143 | sample := float64(lfsr&0x1) * myDevice.SoundController.Noise.Volume() 144 | 145 | if myDevice.SoundController.Noise.LeftEnabled { 146 | sampleLeft += sample 147 | } 148 | if myDevice.SoundController.Noise.RightEnabled { 149 | sampleRight += sample 150 | } 151 | } 152 | 153 | // Normalize samples such that all devices on highest volume = 1.0 154 | sampleLeft /= 4.0 155 | sampleRight /= 4.0 156 | 157 | sampleLeft *= myDevice.SoundController.LeftVolume() 158 | sampleRight *= myDevice.SoundController.RightVolume() 159 | 160 | buffer[i] = C.uint8_t(sampleLeft * 25) 161 | buffer[i+1] = C.uint8_t(sampleRight * 25) 162 | } 163 | } 164 | 165 | type soundDriver struct{} 166 | 167 | func newSoundDriver(device *gameboy.Device) (*soundDriver, error) { 168 | myDevice = device 169 | 170 | spec := &sdl.AudioSpec{ 171 | Freq: totalHz, 172 | Format: sdl.AUDIO_U8, 173 | Channels: 2, 174 | Samples: sampleHz, 175 | Callback: sdl.AudioCallback(C.SoundCallback), 176 | } 177 | err := sdl.OpenAudio(spec, nil) 178 | if err != nil { 179 | log.Println(err) 180 | return nil, err 181 | } 182 | 183 | sdl.PauseAudio(false) 184 | 185 | return &soundDriver{}, nil 186 | } 187 | -------------------------------------------------------------------------------- /cmd/gopherboy_sdl/video_driver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "time" 5 | "unsafe" 6 | 7 | "github.com/veandco/go-sdl2/sdl" 8 | "github.com/velovix/gopherboy/gameboy" 9 | "golang.org/x/xerrors" 10 | ) 11 | 12 | // targetFPS is the FPS of the Game Boy screen. 13 | const targetFPS = 60 14 | 15 | // videoDriver provides a video driver interface with SDL as its back end. This 16 | // can be used on most platforms as a native application. 17 | type videoDriver struct { 18 | window *sdl.Window 19 | renderer *sdl.Renderer 20 | 21 | unlimitedFPS bool 22 | lastFrameTime time.Time 23 | 24 | readyForNewFrame bool 25 | } 26 | 27 | // newVideoDriver creates a new SDL video driver. The scale factor resizes the 28 | // window by that value. 29 | func newVideoDriver(scaleFactor float64, unlimitedFPS bool) (*videoDriver, error) { 30 | var vd videoDriver 31 | 32 | vd.unlimitedFPS = unlimitedFPS 33 | 34 | err := sdl.Init(sdl.INIT_EVERYTHING) 35 | if err != nil { 36 | return nil, xerrors.Errorf("initializing SDL: %v", err) 37 | } 38 | 39 | vd.window, err = sdl.CreateWindow( 40 | "Gopherboy", 41 | sdl.WINDOWPOS_UNDEFINED, sdl.WINDOWPOS_UNDEFINED, 42 | int32(gameboy.ScreenWidth*scaleFactor), 43 | int32(gameboy.ScreenHeight*scaleFactor), 44 | sdl.WINDOW_OPENGL) 45 | if err != nil { 46 | return nil, xerrors.Errorf("initializing window: %v", err) 47 | } 48 | 49 | vd.renderer, err = sdl.CreateRenderer(vd.window, -1, sdl.RENDERER_ACCELERATED) 50 | if err != nil { 51 | return nil, xerrors.Errorf("initializing renderer: %v", err) 52 | } 53 | 54 | vd.renderer.SetDrawColor(255, 255, 255, 255) 55 | 56 | vd.readyForNewFrame = true 57 | 58 | return &vd, nil 59 | } 60 | 61 | // Render renders the given RGBA frame data on-screen. This is done by turning 62 | // it into a texture and copying it onto the renderer. 63 | func (vd *videoDriver) Render(frameData []uint8) error { 64 | if !vd.readyForNewFrame { 65 | return nil 66 | } 67 | 68 | vd.readyForNewFrame = false 69 | 70 | doOnMainThread(func() { 71 | surface, err := sdl.CreateRGBSurfaceFrom( 72 | unsafe.Pointer(&frameData[0]), 73 | gameboy.ScreenWidth, 74 | gameboy.ScreenHeight, 75 | 32, // Bits per pixel 76 | 4*gameboy.ScreenWidth, // Bytes per row 77 | 0x000000FF, // Bitmask for R value 78 | 0x0000FF00, // Bitmask for G value 79 | 0x00FF0000, // Bitmask for B value 80 | 0xFF000000, // Bitmask for alpha value 81 | ) 82 | if err != nil { 83 | err = xerrors.Errorf("creating surface: %w", err) 84 | return 85 | } 86 | defer surface.Free() 87 | 88 | texture, err := vd.renderer.CreateTextureFromSurface(surface) 89 | if err != nil { 90 | err = xerrors.Errorf("converting surface to a texture: %w", err) 91 | return 92 | } 93 | defer texture.Destroy() 94 | 95 | err = vd.renderer.Copy(texture, nil, nil) 96 | if err != nil { 97 | err = xerrors.Errorf("copying frame to screen: %w", err) 98 | return 99 | } 100 | 101 | vd.renderer.Present() 102 | 103 | if !vd.unlimitedFPS { 104 | if time.Since(vd.lastFrameTime) < time.Second/targetFPS { 105 | time.Sleep((time.Second / targetFPS) - time.Since(vd.lastFrameTime)) 106 | } 107 | vd.lastFrameTime = time.Now() 108 | } 109 | 110 | vd.readyForNewFrame = true 111 | }, true) 112 | 113 | return nil 114 | } 115 | 116 | // close de-initializes the video driver in preparation for exit. 117 | func (vd *videoDriver) Close() { 118 | doOnMainThread(func() { 119 | vd.renderer.Destroy() 120 | vd.window.Destroy() 121 | }, false) 122 | } 123 | -------------------------------------------------------------------------------- /cmd/gopherboy_wasm/input_driver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/velovix/gopherboy/gameboy" 7 | ) 8 | 9 | type inputDriver struct { 10 | buttonStates map[gameboy.Button]bool 11 | 12 | messages chan message 13 | } 14 | 15 | func newInputDriver() *inputDriver { 16 | return &inputDriver{ 17 | buttonStates: make(map[gameboy.Button]bool), 18 | messages: make(chan message, 20), 19 | } 20 | } 21 | 22 | func (driver *inputDriver) State(btn gameboy.Button) bool { 23 | return driver.buttonStates[btn] 24 | } 25 | 26 | func (driver *inputDriver) Update() bool { 27 | newButtonPressed := false 28 | 29 | select { 30 | case msg := <-driver.messages: 31 | switch msg.kind { 32 | case "ButtonPressed": 33 | newButtonPressed = true 34 | driver.buttonStates[gameboy.Button(msg.data.Int())] = true 35 | case "ButtonReleased": 36 | driver.buttonStates[gameboy.Button(msg.data.Int())] = false 37 | default: 38 | fmt.Println("emulator: Ignoring message", msg) 39 | } 40 | default: 41 | } 42 | 43 | return newButtonPressed 44 | } 45 | -------------------------------------------------------------------------------- /cmd/gopherboy_wasm/main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "syscall/js" 6 | 7 | "github.com/velovix/gopherboy/gameboy" 8 | ) 9 | 10 | func main() { 11 | fmt.Println("emulator: Waiting for cartridge data") 12 | 13 | // Set up the event handler 14 | eventHandler := eventHandler{} 15 | js.Global().Set("onmessage", js.FuncOf(eventHandler.onMessage)) 16 | 17 | var cartridgeData []byte 18 | var bootROMData []byte 19 | 20 | // Wait for data 21 | dataEvents := make(chan message) 22 | eventHandler.subscribers = append(eventHandler.subscribers, dataEvents) 23 | 24 | for cartridgeData == nil || bootROMData == nil { 25 | msg := <-dataEvents 26 | 27 | switch msg.kind { 28 | case "BootROMData": 29 | bootROMData = make([]uint8, msg.data.Length()) 30 | for i := 0; i < msg.data.Length(); i++ { 31 | bootROMData[i] = uint8(msg.data.Index(i).Int()) 32 | } 33 | 34 | fmt.Println("emulator: Boot ROM data message received") 35 | case "CartridgeData": 36 | cartridgeData = make([]uint8, msg.data.Length()) 37 | for i := 0; i < msg.data.Length(); i++ { 38 | cartridgeData[i] = uint8(msg.data.Index(i).Int()) 39 | } 40 | 41 | fmt.Println("emulator: Cartridge data message received") 42 | default: 43 | fmt.Println("emulator: Ignoring message", msg) 44 | } 45 | } 46 | eventHandler.subscribers = []chan message{} 47 | 48 | fmt.Println("emulator: Main thread received cartridge data") 49 | 50 | video, err := newVideoDriver(2) 51 | if err != nil { 52 | fmt.Println("Error: While initializing video driver:", err) 53 | return 54 | } 55 | input := newInputDriver() 56 | if err != nil { 57 | fmt.Println("Error: While initializing input driver:", err) 58 | return 59 | } 60 | eventHandler.subscribers = append(eventHandler.subscribers, input.messages) 61 | 62 | device, err := gameboy.NewDevice(bootROMData, cartridgeData, video, input, &mockSaveGameDriver{}, gameboy.DebugConfiguration{}) 63 | if err != nil { 64 | fmt.Println("Error: While initializing Game Boy:", err) 65 | return 66 | } 67 | 68 | onExit := make(chan bool) 69 | 70 | onStopMessage := make(chan message) 71 | eventHandler.subscribers = append(eventHandler.subscribers, onStopMessage) 72 | go func() { 73 | for { 74 | msg := <-onStopMessage 75 | 76 | if msg.kind == "Stop" { 77 | fmt.Println("Received stop request") 78 | onExit <- true 79 | break 80 | } 81 | } 82 | }() 83 | 84 | err = device.Start(onExit) 85 | if err != nil { 86 | fmt.Println("Error:", err) 87 | return 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /cmd/gopherboy_wasm/message_handlers.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "syscall/js" 5 | ) 6 | 7 | type message struct { 8 | kind string 9 | data js.Value 10 | } 11 | 12 | type eventHandler struct { 13 | subscribers []chan message 14 | } 15 | 16 | func (eh *eventHandler) onMessage(this js.Value, args []js.Value) interface{} { 17 | event := args[0] 18 | 19 | event.Call("preventDefault") 20 | 21 | msg := message{ 22 | kind: event.Get("data").Index(0).String(), 23 | data: event.Get("data").Index(1), 24 | } 25 | 26 | for _, subscriber := range eh.subscribers { 27 | subscriber <- msg 28 | } 29 | 30 | return nil 31 | } 32 | -------------------------------------------------------------------------------- /cmd/gopherboy_wasm/save_game_driver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "fmt" 4 | 5 | type mockSaveGameDriver struct{} 6 | 7 | func (driver *mockSaveGameDriver) Save(name string, data []uint8) error { 8 | fmt.Println("Save games are not yet supported in WASM") 9 | return nil 10 | } 11 | 12 | func (driver *mockSaveGameDriver) Load(name string) ([]uint8, error) { 13 | panic("loading games is not yet supported in WASM") 14 | } 15 | 16 | func (driver *mockSaveGameDriver) Has(name string) (bool, error) { 17 | return false, nil 18 | } 19 | -------------------------------------------------------------------------------- /cmd/gopherboy_wasm/static/emulator_worker.js: -------------------------------------------------------------------------------- 1 | // Import the Go wasm helper script 2 | self.importScripts('/static/wasm_exec.js'); 3 | 4 | // Start running the emulator binary 5 | const go = new Go(); 6 | WebAssembly.instantiateStreaming( 7 | fetch('/static/emulator.wasm'), 8 | go.importObject, 9 | ).then(result => { 10 | go.run(result.instance); 11 | }); 12 | -------------------------------------------------------------------------------- /cmd/gopherboy_wasm/static/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const KEY_CODE_TO_BUTTON_CODE = { 4 | 87: 1, 5 | 81: 2, 6 | 90: 3, 7 | 88: 4, 8 | 40: 5, 9 | 38: 6, 10 | 37: 7, 11 | 39: 8, 12 | }; 13 | 14 | function main() { 15 | let emulatorWorker = new Worker('/static/emulator_worker.js'); 16 | 17 | let romSelector = document.getElementById('rom-selector'); 18 | romSelector.addEventListener('change', function(ev) { 19 | let files = ev.target.files; 20 | if (files.length == 0) { 21 | console.log('js: No files received!'); 22 | return; 23 | } 24 | 25 | let fileReader = new FileReader(); 26 | fileReader.onload = function(ev) { 27 | let array = new Uint8Array(ev.target.result); 28 | 29 | emulatorWorker.postMessage(['CartridgeData', array]); 30 | console.log('js: Sent ROM data to emulator'); 31 | }; 32 | fileReader.readAsArrayBuffer(files[0]); 33 | }); 34 | 35 | let bootROMSelector = document.getElementById('boot-rom-selector'); 36 | bootROMSelector.addEventListener('change', function(ev) { 37 | let files = ev.target.files; 38 | if (files.length == 0) { 39 | console.log('js: No files received!'); 40 | return; 41 | } 42 | 43 | let fileReader = new FileReader(); 44 | fileReader.onload = function(ev) { 45 | let array = new Uint8Array(ev.target.result); 46 | 47 | emulatorWorker.postMessage(['BootROMData', array]); 48 | console.log('js: Sent boot ROM data to emulator'); 49 | }; 50 | fileReader.readAsArrayBuffer(files[0]); 51 | }); 52 | 53 | let display = document.getElementById('frame-display'); 54 | let displayContext = display.getContext('2d'); 55 | 56 | emulatorWorker.onmessage = function(ev) { 57 | switch (ev.data[0]) { 58 | case 'NewFrame': 59 | let frame = new ImageData(ev.data[1], 160, 144); 60 | createImageBitmap(frame, 0, 0, 160, 144, { 61 | resizeWidth: 160 * 3, 62 | resizeHeight: 144 * 3, 63 | resizeQuality: 'pixelated', 64 | }).then(function(response) { 65 | displayContext.drawImage(response, 0, 0); 66 | }); 67 | break; 68 | } 69 | }; 70 | 71 | document.onkeydown = function(ev) { 72 | if (ev.keyCode in KEY_CODE_TO_BUTTON_CODE) { 73 | emulatorWorker.postMessage([ 74 | 'ButtonPressed', 75 | KEY_CODE_TO_BUTTON_CODE[ev.keyCode], 76 | ]); 77 | } 78 | }; 79 | 80 | document.onkeyup = function(ev) { 81 | if (ev.keyCode in KEY_CODE_TO_BUTTON_CODE) { 82 | emulatorWorker.postMessage([ 83 | 'ButtonReleased', 84 | KEY_CODE_TO_BUTTON_CODE[ev.keyCode], 85 | ]); 86 | } 87 | }; 88 | 89 | let stopButton = document.getElementById('stopButton'); 90 | stopButton.onclick = function() { 91 | emulatorWorker.postMessage(['Stop', '']); 92 | }; 93 | } 94 | 95 | window.onload = main; 96 | -------------------------------------------------------------------------------- /cmd/gopherboy_wasm/static/test.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/velovix/gopherboy/cdcba72c938e267e71ccef828bb65c7f50a27de4/cmd/gopherboy_wasm/static/test.js -------------------------------------------------------------------------------- /cmd/gopherboy_wasm/static/wasm_exec.js: -------------------------------------------------------------------------------- 1 | // Copyright 2018 The Go Authors. All rights reserved. 2 | // Use of this source code is governed by a BSD-style 3 | // license that can be found in the LICENSE file. 4 | 5 | (() => { 6 | // Map multiple JavaScript environments to a single common API, 7 | // preferring web standards over Node.js API. 8 | // 9 | // Environments considered: 10 | // - Browsers 11 | // - Node.js 12 | // - Electron 13 | // - Parcel 14 | 15 | if (typeof global !== "undefined") { 16 | // global already exists 17 | } else if (typeof window !== "undefined") { 18 | window.global = window; 19 | } else if (typeof self !== "undefined") { 20 | self.global = self; 21 | } else { 22 | throw new Error("cannot export Go (neither global, window nor self is defined)"); 23 | } 24 | 25 | if (!global.require && typeof require !== "undefined") { 26 | global.require = require; 27 | } 28 | 29 | if (!global.fs && global.require) { 30 | const fs = require("fs"); 31 | if (Object.keys(fs) !== 0) { 32 | global.fs = fs; 33 | } 34 | } 35 | 36 | const enosys = () => { 37 | const err = new Error("not implemented"); 38 | err.code = "ENOSYS"; 39 | return err; 40 | }; 41 | 42 | if (!global.fs) { 43 | let outputBuf = ""; 44 | global.fs = { 45 | constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused 46 | writeSync(fd, buf) { 47 | outputBuf += decoder.decode(buf); 48 | const nl = outputBuf.lastIndexOf("\n"); 49 | if (nl != -1) { 50 | console.log(outputBuf.substr(0, nl)); 51 | outputBuf = outputBuf.substr(nl + 1); 52 | } 53 | return buf.length; 54 | }, 55 | write(fd, buf, offset, length, position, callback) { 56 | if (offset !== 0 || length !== buf.length || position !== null) { 57 | callback(enosys()); 58 | return; 59 | } 60 | const n = this.writeSync(fd, buf); 61 | callback(null, n); 62 | }, 63 | chmod(path, mode, callback) { callback(enosys()); }, 64 | chown(path, uid, gid, callback) { callback(enosys()); }, 65 | close(fd, callback) { callback(enosys()); }, 66 | fchmod(fd, mode, callback) { callback(enosys()); }, 67 | fchown(fd, uid, gid, callback) { callback(enosys()); }, 68 | fstat(fd, callback) { callback(enosys()); }, 69 | fsync(fd, callback) { callback(null); }, 70 | ftruncate(fd, length, callback) { callback(enosys()); }, 71 | lchown(path, uid, gid, callback) { callback(enosys()); }, 72 | link(path, link, callback) { callback(enosys()); }, 73 | lstat(path, callback) { callback(enosys()); }, 74 | mkdir(path, perm, callback) { callback(enosys()); }, 75 | open(path, flags, mode, callback) { callback(enosys()); }, 76 | read(fd, buffer, offset, length, position, callback) { callback(enosys()); }, 77 | readdir(path, callback) { callback(enosys()); }, 78 | readlink(path, callback) { callback(enosys()); }, 79 | rename(from, to, callback) { callback(enosys()); }, 80 | rmdir(path, callback) { callback(enosys()); }, 81 | stat(path, callback) { callback(enosys()); }, 82 | symlink(path, link, callback) { callback(enosys()); }, 83 | truncate(path, length, callback) { callback(enosys()); }, 84 | unlink(path, callback) { callback(enosys()); }, 85 | utimes(path, atime, mtime, callback) { callback(enosys()); }, 86 | }; 87 | } 88 | 89 | if (!global.process) { 90 | global.process = { 91 | getuid() { return -1; }, 92 | getgid() { return -1; }, 93 | geteuid() { return -1; }, 94 | getegid() { return -1; }, 95 | getgroups() { throw enosys(); }, 96 | pid: -1, 97 | ppid: -1, 98 | umask() { throw enosys(); }, 99 | cwd() { throw enosys(); }, 100 | chdir() { throw enosys(); }, 101 | } 102 | } 103 | 104 | if (!global.crypto) { 105 | const nodeCrypto = require("crypto"); 106 | global.crypto = { 107 | getRandomValues(b) { 108 | nodeCrypto.randomFillSync(b); 109 | }, 110 | }; 111 | } 112 | 113 | if (!global.performance) { 114 | global.performance = { 115 | now() { 116 | const [sec, nsec] = process.hrtime(); 117 | return sec * 1000 + nsec / 1000000; 118 | }, 119 | }; 120 | } 121 | 122 | if (!global.TextEncoder) { 123 | global.TextEncoder = require("util").TextEncoder; 124 | } 125 | 126 | if (!global.TextDecoder) { 127 | global.TextDecoder = require("util").TextDecoder; 128 | } 129 | 130 | // End of polyfills for common API. 131 | 132 | const encoder = new TextEncoder("utf-8"); 133 | const decoder = new TextDecoder("utf-8"); 134 | 135 | global.Go = class { 136 | constructor() { 137 | this.argv = ["js"]; 138 | this.env = {}; 139 | this.exit = (code) => { 140 | if (code !== 0) { 141 | console.warn("exit code:", code); 142 | } 143 | }; 144 | this._exitPromise = new Promise((resolve) => { 145 | this._resolveExitPromise = resolve; 146 | }); 147 | this._pendingEvent = null; 148 | this._scheduledTimeouts = new Map(); 149 | this._nextCallbackTimeoutID = 1; 150 | 151 | const setInt64 = (addr, v) => { 152 | this.mem.setUint32(addr + 0, v, true); 153 | this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true); 154 | } 155 | 156 | const getInt64 = (addr) => { 157 | const low = this.mem.getUint32(addr + 0, true); 158 | const high = this.mem.getInt32(addr + 4, true); 159 | return low + high * 4294967296; 160 | } 161 | 162 | const loadValue = (addr) => { 163 | const f = this.mem.getFloat64(addr, true); 164 | if (f === 0) { 165 | return undefined; 166 | } 167 | if (!isNaN(f)) { 168 | return f; 169 | } 170 | 171 | const id = this.mem.getUint32(addr, true); 172 | return this._values[id]; 173 | } 174 | 175 | const storeValue = (addr, v) => { 176 | const nanHead = 0x7FF80000; 177 | 178 | if (typeof v === "number" && v !== 0) { 179 | if (isNaN(v)) { 180 | this.mem.setUint32(addr + 4, nanHead, true); 181 | this.mem.setUint32(addr, 0, true); 182 | return; 183 | } 184 | this.mem.setFloat64(addr, v, true); 185 | return; 186 | } 187 | 188 | if (v === undefined) { 189 | this.mem.setFloat64(addr, 0, true); 190 | return; 191 | } 192 | 193 | let id = this._ids.get(v); 194 | if (id === undefined) { 195 | id = this._idPool.pop(); 196 | if (id === undefined) { 197 | id = this._values.length; 198 | } 199 | this._values[id] = v; 200 | this._goRefCounts[id] = 0; 201 | this._ids.set(v, id); 202 | } 203 | this._goRefCounts[id]++; 204 | let typeFlag = 0; 205 | switch (typeof v) { 206 | case "object": 207 | if (v !== null) { 208 | typeFlag = 1; 209 | } 210 | break; 211 | case "string": 212 | typeFlag = 2; 213 | break; 214 | case "symbol": 215 | typeFlag = 3; 216 | break; 217 | case "function": 218 | typeFlag = 4; 219 | break; 220 | } 221 | this.mem.setUint32(addr + 4, nanHead | typeFlag, true); 222 | this.mem.setUint32(addr, id, true); 223 | } 224 | 225 | const loadSlice = (addr) => { 226 | const array = getInt64(addr + 0); 227 | const len = getInt64(addr + 8); 228 | return new Uint8Array(this._inst.exports.mem.buffer, array, len); 229 | } 230 | 231 | const loadSliceOfValues = (addr) => { 232 | const array = getInt64(addr + 0); 233 | const len = getInt64(addr + 8); 234 | const a = new Array(len); 235 | for (let i = 0; i < len; i++) { 236 | a[i] = loadValue(array + i * 8); 237 | } 238 | return a; 239 | } 240 | 241 | const loadString = (addr) => { 242 | const saddr = getInt64(addr + 0); 243 | const len = getInt64(addr + 8); 244 | return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len)); 245 | } 246 | 247 | const timeOrigin = Date.now() - performance.now(); 248 | this.importObject = { 249 | go: { 250 | // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters) 251 | // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported 252 | // function. A goroutine can switch to a new stack if the current stack is too small (see morestack function). 253 | // This changes the SP, thus we have to update the SP used by the imported function. 254 | 255 | // func wasmExit(code int32) 256 | "runtime.wasmExit": (sp) => { 257 | const code = this.mem.getInt32(sp + 8, true); 258 | this.exited = true; 259 | delete this._inst; 260 | delete this._values; 261 | delete this._goRefCounts; 262 | delete this._ids; 263 | delete this._idPool; 264 | this.exit(code); 265 | }, 266 | 267 | // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32) 268 | "runtime.wasmWrite": (sp) => { 269 | const fd = getInt64(sp + 8); 270 | const p = getInt64(sp + 16); 271 | const n = this.mem.getInt32(sp + 24, true); 272 | fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n)); 273 | }, 274 | 275 | // func resetMemoryDataView() 276 | "runtime.resetMemoryDataView": (sp) => { 277 | this.mem = new DataView(this._inst.exports.mem.buffer); 278 | }, 279 | 280 | // func nanotime1() int64 281 | "runtime.nanotime1": (sp) => { 282 | setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000); 283 | }, 284 | 285 | // func walltime1() (sec int64, nsec int32) 286 | "runtime.walltime1": (sp) => { 287 | const msec = (new Date).getTime(); 288 | setInt64(sp + 8, msec / 1000); 289 | this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true); 290 | }, 291 | 292 | // func scheduleTimeoutEvent(delay int64) int32 293 | "runtime.scheduleTimeoutEvent": (sp) => { 294 | const id = this._nextCallbackTimeoutID; 295 | this._nextCallbackTimeoutID++; 296 | this._scheduledTimeouts.set(id, setTimeout( 297 | () => { 298 | this._resume(); 299 | while (this._scheduledTimeouts.has(id)) { 300 | // for some reason Go failed to register the timeout event, log and try again 301 | // (temporary workaround for https://github.com/golang/go/issues/28975) 302 | console.warn("scheduleTimeoutEvent: missed timeout event"); 303 | this._resume(); 304 | } 305 | }, 306 | getInt64(sp + 8) + 1, // setTimeout has been seen to fire up to 1 millisecond early 307 | )); 308 | this.mem.setInt32(sp + 16, id, true); 309 | }, 310 | 311 | // func clearTimeoutEvent(id int32) 312 | "runtime.clearTimeoutEvent": (sp) => { 313 | const id = this.mem.getInt32(sp + 8, true); 314 | clearTimeout(this._scheduledTimeouts.get(id)); 315 | this._scheduledTimeouts.delete(id); 316 | }, 317 | 318 | // func getRandomData(r []byte) 319 | "runtime.getRandomData": (sp) => { 320 | crypto.getRandomValues(loadSlice(sp + 8)); 321 | }, 322 | 323 | // func finalizeRef(v ref) 324 | "syscall/js.finalizeRef": (sp) => { 325 | const id = this.mem.getUint32(sp + 8, true); 326 | this._goRefCounts[id]--; 327 | if (this._goRefCounts[id] === 0) { 328 | const v = this._values[id]; 329 | this._values[id] = null; 330 | this._ids.delete(v); 331 | this._idPool.push(id); 332 | } 333 | }, 334 | 335 | // func stringVal(value string) ref 336 | "syscall/js.stringVal": (sp) => { 337 | storeValue(sp + 24, loadString(sp + 8)); 338 | }, 339 | 340 | // func valueGet(v ref, p string) ref 341 | "syscall/js.valueGet": (sp) => { 342 | const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16)); 343 | sp = this._inst.exports.getsp(); // see comment above 344 | storeValue(sp + 32, result); 345 | }, 346 | 347 | // func valueSet(v ref, p string, x ref) 348 | "syscall/js.valueSet": (sp) => { 349 | Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32)); 350 | }, 351 | 352 | // func valueDelete(v ref, p string) 353 | "syscall/js.valueDelete": (sp) => { 354 | Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16)); 355 | }, 356 | 357 | // func valueIndex(v ref, i int) ref 358 | "syscall/js.valueIndex": (sp) => { 359 | storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16))); 360 | }, 361 | 362 | // valueSetIndex(v ref, i int, x ref) 363 | "syscall/js.valueSetIndex": (sp) => { 364 | Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24)); 365 | }, 366 | 367 | // func valueCall(v ref, m string, args []ref) (ref, bool) 368 | "syscall/js.valueCall": (sp) => { 369 | try { 370 | const v = loadValue(sp + 8); 371 | const m = Reflect.get(v, loadString(sp + 16)); 372 | const args = loadSliceOfValues(sp + 32); 373 | const result = Reflect.apply(m, v, args); 374 | sp = this._inst.exports.getsp(); // see comment above 375 | storeValue(sp + 56, result); 376 | this.mem.setUint8(sp + 64, 1); 377 | } catch (err) { 378 | storeValue(sp + 56, err); 379 | this.mem.setUint8(sp + 64, 0); 380 | } 381 | }, 382 | 383 | // func valueInvoke(v ref, args []ref) (ref, bool) 384 | "syscall/js.valueInvoke": (sp) => { 385 | try { 386 | const v = loadValue(sp + 8); 387 | const args = loadSliceOfValues(sp + 16); 388 | const result = Reflect.apply(v, undefined, args); 389 | sp = this._inst.exports.getsp(); // see comment above 390 | storeValue(sp + 40, result); 391 | this.mem.setUint8(sp + 48, 1); 392 | } catch (err) { 393 | storeValue(sp + 40, err); 394 | this.mem.setUint8(sp + 48, 0); 395 | } 396 | }, 397 | 398 | // func valueNew(v ref, args []ref) (ref, bool) 399 | "syscall/js.valueNew": (sp) => { 400 | try { 401 | const v = loadValue(sp + 8); 402 | const args = loadSliceOfValues(sp + 16); 403 | const result = Reflect.construct(v, args); 404 | sp = this._inst.exports.getsp(); // see comment above 405 | storeValue(sp + 40, result); 406 | this.mem.setUint8(sp + 48, 1); 407 | } catch (err) { 408 | storeValue(sp + 40, err); 409 | this.mem.setUint8(sp + 48, 0); 410 | } 411 | }, 412 | 413 | // func valueLength(v ref) int 414 | "syscall/js.valueLength": (sp) => { 415 | setInt64(sp + 16, parseInt(loadValue(sp + 8).length)); 416 | }, 417 | 418 | // valuePrepareString(v ref) (ref, int) 419 | "syscall/js.valuePrepareString": (sp) => { 420 | const str = encoder.encode(String(loadValue(sp + 8))); 421 | storeValue(sp + 16, str); 422 | setInt64(sp + 24, str.length); 423 | }, 424 | 425 | // valueLoadString(v ref, b []byte) 426 | "syscall/js.valueLoadString": (sp) => { 427 | const str = loadValue(sp + 8); 428 | loadSlice(sp + 16).set(str); 429 | }, 430 | 431 | // func valueInstanceOf(v ref, t ref) bool 432 | "syscall/js.valueInstanceOf": (sp) => { 433 | this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0); 434 | }, 435 | 436 | // func copyBytesToGo(dst []byte, src ref) (int, bool) 437 | "syscall/js.copyBytesToGo": (sp) => { 438 | const dst = loadSlice(sp + 8); 439 | const src = loadValue(sp + 32); 440 | if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { 441 | this.mem.setUint8(sp + 48, 0); 442 | return; 443 | } 444 | const toCopy = src.subarray(0, dst.length); 445 | dst.set(toCopy); 446 | setInt64(sp + 40, toCopy.length); 447 | this.mem.setUint8(sp + 48, 1); 448 | }, 449 | 450 | // func copyBytesToJS(dst ref, src []byte) (int, bool) 451 | "syscall/js.copyBytesToJS": (sp) => { 452 | const dst = loadValue(sp + 8); 453 | const src = loadSlice(sp + 16); 454 | if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { 455 | this.mem.setUint8(sp + 48, 0); 456 | return; 457 | } 458 | const toCopy = src.subarray(0, dst.length); 459 | dst.set(toCopy); 460 | setInt64(sp + 40, toCopy.length); 461 | this.mem.setUint8(sp + 48, 1); 462 | }, 463 | 464 | "debug": (value) => { 465 | console.log(value); 466 | }, 467 | } 468 | }; 469 | } 470 | 471 | async run(instance) { 472 | this._inst = instance; 473 | this.mem = new DataView(this._inst.exports.mem.buffer); 474 | this._values = [ // JS values that Go currently has references to, indexed by reference id 475 | NaN, 476 | 0, 477 | null, 478 | true, 479 | false, 480 | global, 481 | this, 482 | ]; 483 | this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id 484 | this._ids = new Map([ // mapping from JS values to reference ids 485 | [0, 1], 486 | [null, 2], 487 | [true, 3], 488 | [false, 4], 489 | [global, 5], 490 | [this, 6], 491 | ]); 492 | this._idPool = []; // unused ids that have been garbage collected 493 | this.exited = false; // whether the Go program has exited 494 | 495 | // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory. 496 | let offset = 4096; 497 | 498 | const strPtr = (str) => { 499 | const ptr = offset; 500 | const bytes = encoder.encode(str + "\0"); 501 | new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes); 502 | offset += bytes.length; 503 | if (offset % 8 !== 0) { 504 | offset += 8 - (offset % 8); 505 | } 506 | return ptr; 507 | }; 508 | 509 | const argc = this.argv.length; 510 | 511 | const argvPtrs = []; 512 | this.argv.forEach((arg) => { 513 | argvPtrs.push(strPtr(arg)); 514 | }); 515 | argvPtrs.push(0); 516 | 517 | const keys = Object.keys(this.env).sort(); 518 | keys.forEach((key) => { 519 | argvPtrs.push(strPtr(`${key}=${this.env[key]}`)); 520 | }); 521 | argvPtrs.push(0); 522 | 523 | const argv = offset; 524 | argvPtrs.forEach((ptr) => { 525 | this.mem.setUint32(offset, ptr, true); 526 | this.mem.setUint32(offset + 4, 0, true); 527 | offset += 8; 528 | }); 529 | 530 | this._inst.exports.run(argc, argv); 531 | if (this.exited) { 532 | this._resolveExitPromise(); 533 | } 534 | await this._exitPromise; 535 | } 536 | 537 | _resume() { 538 | if (this.exited) { 539 | throw new Error("Go program has already exited"); 540 | } 541 | this._inst.exports.resume(); 542 | if (this.exited) { 543 | this._resolveExitPromise(); 544 | } 545 | } 546 | 547 | _makeFuncWrapper(id) { 548 | const go = this; 549 | return function () { 550 | const event = { id: id, this: this, args: arguments }; 551 | go._pendingEvent = event; 552 | go._resume(); 553 | return event.result; 554 | }; 555 | } 556 | } 557 | 558 | if ( 559 | global.require && 560 | global.require.main === module && 561 | global.process && 562 | global.process.versions && 563 | !global.process.versions.electron 564 | ) { 565 | if (process.argv.length < 3) { 566 | console.error("usage: go_js_wasm_exec [wasm binary] [arguments]"); 567 | process.exit(1); 568 | } 569 | 570 | const go = new Go(); 571 | go.argv = process.argv.slice(2); 572 | go.env = Object.assign({ TMPDIR: require("os").tmpdir() }, process.env); 573 | go.exit = process.exit; 574 | WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then((result) => { 575 | process.on("exit", (code) => { // Node.js exits if no event handler is pending 576 | if (code === 0 && !go.exited) { 577 | // deadlock, make Go print error and stack traces 578 | go._pendingEvent = { id: 0 }; 579 | go._resume(); 580 | } 581 | }); 582 | return go.run(result.instance); 583 | }).catch((err) => { 584 | console.error(err); 585 | process.exit(1); 586 | }); 587 | } 588 | })(); 589 | -------------------------------------------------------------------------------- /cmd/gopherboy_wasm/video_driver.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "syscall/js" 6 | "time" 7 | ) 8 | 9 | // videoDriver provides a video driver interface with WebGL as its back end. 10 | // This can be used inside a WebGL-capable browser as a WebAssembly 11 | // application. 12 | type videoDriver struct { 13 | targetFPS int 14 | lastFrameTime time.Time 15 | frameBuffer js.Value 16 | } 17 | 18 | const framePeriod = time.Second / 50 19 | 20 | func newVideoDriver(scaleFactor float64) (*videoDriver, error) { 21 | return &videoDriver{}, nil 22 | } 23 | 24 | func (vd *videoDriver) Render(frameData []uint8) error { 25 | if vd.frameBuffer.Equal(js.Undefined()) { 26 | fmt.Println("Initializing frame buffer...") 27 | vd.frameBuffer = js.Global().Get("Uint8ClampedArray").New(len(frameData)) 28 | } 29 | js.CopyBytesToJS(vd.frameBuffer, frameData) 30 | 31 | js.Global().Call( 32 | "postMessage", 33 | []interface{}{ 34 | "NewFrame", 35 | vd.frameBuffer, 36 | }, 37 | ) 38 | 39 | now := time.Now() 40 | if now.Sub(vd.lastFrameTime) < framePeriod { 41 | time.Sleep(framePeriod - now.Sub(vd.lastFrameTime)) 42 | } 43 | vd.lastFrameTime = now 44 | 45 | return nil 46 | } 47 | 48 | func (vd *videoDriver) Close() { 49 | 50 | } 51 | -------------------------------------------------------------------------------- /cmd/gopherboy_wasm/views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | WebGL Test 7 | 8 | 9 | 10 |
11 |
12 | 13 |
14 |
15 |

Boot ROM:

16 | 17 |
18 |
19 |

ROM:

20 | 21 |
22 |
23 | 24 |
25 |
26 | 27 | 28 | -------------------------------------------------------------------------------- /gameboy/addresses.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | const ( 4 | _ uint16 = 0x0 5 | // dividerAddr points to an 8-bit integer that is incremented every 64 6 | // clocks. 7 | dividerAddr = 0xFF04 8 | // timaAddr points to the TIMA, an 8-bit integer that can be configured to 9 | // increment at various rates. 10 | timaAddr = 0xFF05 11 | // tmaAddr points to the TMA, an 8-bit integer that configures what value 12 | // to set the TIMA when it overflows. 13 | tmaAddr = 0xFF06 14 | // tacAddr points to the TAC, a one byte area where bits can be flipped to 15 | // configure the TIMA. 16 | tacAddr = 0xFF07 17 | 18 | // ifAddr points to the interrupt flags, whose value indicates whether or 19 | // not an interrupt is happening. 20 | // 21 | // Bit 7: Unused, always 1 22 | // Bit 6: Unused, always 1 23 | // Bit 5: Unused, always 1 24 | // Bit 4: P10-P13 high-to-low interrupts. Happens when a button is pressed. 25 | // Bit 3: Serial I/O transfer interrupts 26 | // Bit 2: TIMA overflow interrupts. Happens when the 8-bit configurable 27 | // timer overflows. 28 | // Bit 1: LCDC interrupts. Happens when certain events happen on the 29 | // display. What exactly triggers this interrupt can be configured 30 | // using the LCDC memory register. 31 | // Bit 0: V-blank interrupts. Happens when the V-blank period starts on the 32 | // display. 33 | ifAddr = 0xFF0F 34 | 35 | // nr10Addr points to the "Sound Mode 1 Sweep" register. This controls the 36 | // frequency sweep effect of the pulse A sound channel. 37 | // 38 | // Bit 7: Unused, always 1 39 | // Bits 6-4: Controls the length of the frequency sweep. The length is 40 | // n/128Hz, where N is the value written here. 41 | // Bit 3: If 0, the frequency increases. If 1, the frequency decreases. 42 | // Bits 2-0: The number to shift the last frequency by in order to get the 43 | // next frequency 44 | nr10Addr = 0xFF10 45 | // nr11Addr points to the "Sound Mode 1 Length and Wave Pattern Duty" 46 | // register. This controls the duty cycle of the pulse A square wave and 47 | // the length that the wave should play for. 48 | // 49 | // Bits 7-6: Wave pattern duty cycle. 50 | // 00: 12.5% duty cycle 51 | // 01: 25% duty cycle 52 | // 10: 50% duty cycle 53 | // 11: 75% duty cycle, sounds the same as 25% 54 | // Bits 5-0: The duration to play the sound for. The length is 55 | // (64-t)*(1/256) seconds, wehre t is the value at these bits. 56 | nr11Addr = 0xFF11 57 | // nr12Addr points to the "Sound Mode 1 Envelope" register. This allows for 58 | // a volume sweep effect. 59 | // 60 | // Bits 7-4: The initial volume of the note, with 0 being no sound. 61 | // Bits 3: Controls whether the volume sweeps up or down. 0 is down, 1 is 62 | // up. 63 | // Bits 2-0: Controls at what rate the volume is changed, from 0-7. If 0, 64 | // the sweep is disabled. When set, the volume sweeps up or down 65 | // one unit every n/64 seconds. 66 | nr12Addr = 0xFF12 67 | // nr13Addr points to the "Sound Mode 1 Frequency Lo" register. This 68 | // register sets the lower 8 bits of pulse A's frequency. 69 | nr13Addr = 0xFF13 70 | // nr14Addr points to the "Sound Mode 1 Frequency Hi" register. This 71 | // register has some bits to start pulse A and the upper three bits of the 72 | // frequency selection. 73 | // 74 | // Bit 7: When set, pulse A restarts 75 | // Bit 6: If 1, this voice will be turned off when its duration finishes. 76 | // If 0, it will play indefinitely. 77 | // Bits 2-0: The most significant 3 bits of pulse A's frequency 78 | nr14Addr = 0xFF14 79 | 80 | // nr21Addr points to the "Sound Mode 2 Length and Wave Pattern Duty" 81 | // register. This controls the duty cycle of the pulse B square wave and 82 | // the length that the wave should play for. 83 | // 84 | // Bits 7-6: Wave pattern duty cycle. 85 | // 00: 12.5% duty cycle 86 | // 01: 25% duty cycle 87 | // 10: 50% duty cycle 88 | // 11: 75% duty cycle, sounds the same as 25% 89 | // Bits 5-0: The duration to play the sound for. The length is 90 | // (64-t)*(1/256) seconds, wehre t is the value at these bits. 91 | nr21Addr = 0xFF16 92 | 93 | // nr22Addr points to the "Sound Mode 2 Envelope" register. This allows for 94 | // a volume sweep effect. 95 | // 96 | // Bits 7-4: The initial volume of the note, with 0 being no sound. 97 | // Bits 3: Controls whether the volume sweeps up or down. 0 is down, 1 is 98 | // up. 99 | // Bits 2-0: Controls at what rate the volume is changed, from 0-7. If 0, 100 | // the sweep is disabled. When set, the volume sweeps up or down 101 | // one unit every n/64 seconds. 102 | nr22Addr = 0xFF17 103 | 104 | // nr13Addr points to the "Sound Mode 2 Frequency Lo" register. This 105 | // register sets the lower 8 bits of pulse B's frequency. 106 | nr23Addr = 0xFF18 107 | 108 | // nr24Addr points to the "Sound Mode 2 Frequency Hi" register. This 109 | // register has some bits to start pulse B and the upper three bits of the 110 | // frequency selection. 111 | // 112 | // Bit 7: When set, pulse B restarts 113 | // Bit 6: If 1, this voice will be turned off when its duration finishes. 114 | // If 0, it will play indefinitely. 115 | // Bits 2-0: The most significant 3 bits of the pulse B's frequency 116 | nr24Addr = 0xFF19 117 | 118 | // nr30Addr points to the Sound Mode 3 On/Off Memory Register. It turns on 119 | // and off the wave channel. 120 | // 121 | // Bit 7: If 1, the wave channel is turned on. 122 | // Bits 6-0: Unused, always 1 123 | nr30Addr = 0xFF1A 124 | 125 | // nr31Addr is the "Sound Mode 3 Length" register. This register sets the 126 | // length at which the note will be played. 127 | // 128 | // Length in seconds = (256-nr31)*(1/2) 129 | nr31Addr = 0xFF1B 130 | 131 | // nr32Addr points to the Sound Mode 3 Select Output Level Memory Register. 132 | // This register controls what is effectively the volume of the wave voice. 133 | // The wave voice's volume is decreased by shifting the wave value right by 134 | // the specified amount. 135 | // 136 | // Bit 7: Unused, always 1 137 | // Bits 6-5: Select output level 138 | // 00: Shift 4 bits, effectively muting the wave voice 139 | // 01: Produce wave pattern data as-is 140 | // 10: Produce wave pattern data shifted once to the right 141 | // 11: Produce wave pattern data shifted twice to the right 142 | // Bits 4-0: Unused, always 1 143 | nr32Addr = 0xFF1C 144 | 145 | // nr33Addr points to the "Sound Mode 3 Frequency Lo" register. This 146 | // register sets the lower 8 bits of the wave channel's frequency. 147 | nr33Addr = 0xFF1D 148 | 149 | // nr34Addr points to the "Sound Mode 3 Frequency Hi" register. This 150 | // register has some bits to start the wave channel and the upper three 151 | // bits of the frequency selection. 152 | // 153 | // Bit 7: When set, the wave channel restarts 154 | // Bit 6: If 1, this voice will be turned off when its duration finishes. 155 | // If 0, it will play indefinitely. 156 | // Bits 2-0: The most significant 3 bits of the wave channel's frequency 157 | nr34Addr = 0xFF1E 158 | 159 | // nr41Addr points to the Sound Mode 4 Sound Length Memory Register. It 160 | // controls the length that the noise channel plays for. 161 | // 162 | // Bits 7-6: Unused, always 1 163 | // Bits 5-0: Controls sound length. If this value is t1, then the sound 164 | // will play for (64-t1)*(1/256) seconds. 165 | nr41Addr = 0xFF20 166 | 167 | // nr42Addr points to the Sound Mode 4 Envelope Register. It controls the 168 | // volume sweep for the noise voice. 169 | // 170 | // Bits 7-4: The initial volume of the note, with 0 being no sound. 171 | // Bit 3: Controls whether the volume sweeps up or down. 0 is down, 1 is 172 | // up. 173 | // Bits 2-0: Controls at what rate the volume is changed, from 0-7. If 0, 174 | // the sweep is disabled. When set, the volume sweeps up or down 175 | // one unit every n/64 seconds. 176 | nr42Addr = 0xFF21 177 | 178 | // nr43Addr points to the Sound Mode 4 Polynomial Register. It controls 179 | // various aspects of how the random noise is generated. 180 | // 181 | // The noise generation is done using a linear feedback shift register, 182 | // which provides pseudo-random numbers. We will refer to this as the LFSR. 183 | // 184 | // Bits 7-4: Controls the frequency that the LFSR is shifted at. Values 185 | // 0b1110 and 0b1111 are invalid. TODO(velovix): What happens? 186 | // (dividing ratio) * 1/2^n 187 | // Bit 3: Selects the LFSR's size. If 0, it is 15-bit. If 1, it is 7-bit. 188 | // Bits 2-0: The dividing ratio of frequencies. 189 | // 0: f * 1/2^3 * 2 190 | // n: f * 1/2^3 * 1/n 191 | // Where f=4194304 Hz 192 | nr43Addr = 0xFF22 193 | 194 | // nr44Addr points to the Sound Mode 4 Counter/Consecutive Initial Memory 195 | // Register. 196 | // 197 | // Bit 7: When set, the noise channel restarts 198 | // Bit 6: If 1, this voice will be turned off when its duration finishes. 199 | // If 0, it will play indefinitely. 200 | // Bit 5-0: Unused, always 1 201 | nr44Addr = 0xFF23 202 | 203 | // nr50Addr points to the Cartridge Channel Control and Volume Register. 204 | // This register controls the volume of the left and right audio channels 205 | // for all sound. It also has an obscure purpose for turning on and off 206 | // arbitrary sound input from the cartridge. This would be useful if a game 207 | // wanted to provide its own sound hardware, but no game ever did this. 208 | // 209 | // Bit 7: Vin->Left Side Toggle. Unused in practice. 210 | // Bits 6-4: Left side volume, 0 being mute and 7 being max 211 | // Bit 3: Vin->Right Side Toggle. Unused in practice. 212 | // Bits 2-0: Right side volume, 0 being mute and 7 being max 213 | nr50Addr = 0xFF24 214 | 215 | // nr51Addr points to the Selection of Sound Output Terminal. It's a set of 216 | // bits which turn on and off the sound of each voice for the left and 217 | // right audio channels. 1 is on, 0 is off. 218 | // 219 | // Bit 7: Sound mode 4 for left side on/off 220 | // Bit 6: Sound mode 3 for left side on/off 221 | // Bit 5: Sound mode 2 for left side on/off 222 | // Bit 4: Sound mode 1 for left side on/off 223 | // Bit 3: Sound mode 4 for right side on/off 224 | // Bit 2: Sound mode 3 for right side on/off 225 | // Bit 1: Sound mode 2 for right side on/off 226 | // Bit 0: Sound mode 1 for right side on/off 227 | nr51Addr = 0xFF25 228 | 229 | // nr52Addr points to the Sound On/Off Memory Register. It's a set of 230 | // on/off indicators for each channel and all sound. 231 | // 232 | // Bit 7: All sound on/off switch. 1 if on. 233 | // Bits 6-4: Unused, always 1 234 | // Bit 3: Sound 4 on/off switch. 1 if on. Read-only. 235 | // Bit 2: Sound 3 on/off switch. 1 if on. Read-only. 236 | // Bit 1: Sound 2 on/off switch. 1 if on. Read-only. 237 | // Bit 0: Sound 1 on/off switch. 1 if on. Read-only. 238 | nr52Addr = 0xFF26 239 | 240 | // Designates the area in RAM where the custom wave pattern is specified. 241 | wavePatternRAMStart = 0xFF30 242 | wavePatternRAMEnd = 0xFF40 243 | 244 | // scrollYAddr points to the "Scroll Y" memory register. This controls the 245 | // position of the top left of the background. 246 | scrollYAddr = 0xFF42 247 | // scrollXAddr points to the "Scroll X" memory register. This controls the 248 | // position of the top left of the background. 249 | scrollXAddr = 0xFF43 250 | // ieAddr points to the interrupt enable flags, which can be flipped to 251 | // configure which interrupts are enabled. A value in 1 in a bit means 252 | // "enabled". 253 | // 254 | // Bit order is the same as the interrupt flag. 255 | ieAddr = 0xFFFF 256 | // windowPosYAddr points to the "Window Position Y" memory register. This 257 | // controls the position of the window in the Y direction. 258 | windowPosYAddr = 0xFF4A 259 | // windowPosXAddr points to the "Window Position X" memory register. This 260 | // controls the position of the window in the X direction. 261 | windowPosXAddr = 0xFF4B 262 | 263 | // key1Addr has something to do with switching to double-speed mode on the 264 | // CGB. Since this is a DMG emulator for the time being, this is unused. 265 | key1Addr = 0xFF4D 266 | 267 | // vbkAddr is a VRAM bank register. It's a CGB thing that this emulator 268 | // doesn't have to worry about yet 269 | vbkAddr = 0xFF4F 270 | 271 | // lcdcAddr points to the LCDC memory register, which controls various 272 | // aspects of how a frame is drawn. 273 | // 274 | // Bit 7: Turns the LCD on and off. 275 | // 0: LCD is off 276 | // 1: LCD is on 277 | // Bit 6: Selects which tile map should be used for the window. 278 | // 0: 0x9800-0x9BFF 279 | // 1: 0x9C00-0x9FFF 280 | // Bit 5: Turns the window on and off 281 | // 0: Window is off 282 | // 1: Window is on 283 | // Bit 4: Selects which tile data table the window and background use. 284 | // 0: 0x8800-0x97FF 285 | // 1: 0x8000-0x8FFF 286 | // Bit 3: Selects which tile map should be used for the background. 287 | // 0: 0x9800-0x9BFF 288 | // 1: 0x9C00-0x9FFF 289 | // Bit 2: Selects which sprite size we're currently using 290 | // 0: 8x8 sprites 291 | // 1: 8x16 sprites 292 | // Bit 1: Turns sprites on and off 293 | // 0: Sprites are off 294 | // 1: Sprites are on 295 | // Bit 0: Turns the background and window on and off 296 | // 0: Both are off 297 | // 1: Both are on 298 | lcdcAddr = 0xFF40 299 | // bgpAddr points to the BGP memory register, which controls the 300 | // background and window palette. This register maps dot data to actual 301 | // colors. 302 | // 303 | // Colors go from 0b11 to 0b00, where 0b11 is the darkest and 0b00 is the 304 | // lightest. 305 | // 306 | // Bits 7-6: The color for dot data 0b11 307 | // Bits 5-4: The color for dot data 0b10 308 | // Bits 3-2: The color for dot data 0b01 309 | // Bits 1-0: The color for dot data 0b00 310 | bgpAddr = 0xFF47 311 | // lyAddr points to the LCD Current Scanline memory register, which 312 | // indicates what line of the display is currently being drawn. Starts at 0 313 | // and ends at 153. 314 | lyAddr = 0xFF44 315 | // lycAddr points to the LY Compare memory register. Users can write a 316 | // value to this register. When the LY memory register value is the same as 317 | // this register's value, an interrupt can be generated. 318 | lycAddr = 0xFF45 319 | // statAddr points to the STAT memory register. This register is used to 320 | // check the status of the LCD and to configure LCD-related interrupts. 321 | // 322 | // Bit 7: Unused, always 1 323 | // Bit 6: If 1, an interrupt will be triggered when the LY register equals 324 | // the LYC register 325 | // Bit 5: If 1, the LCDC interrupt will be triggered when the video 326 | // controller enters mode 2. 327 | // Bit 4: If 1, the LCDC interrupt will be triggered when the video 328 | // controller enters mode 1. 329 | // Bit 3: If 1, the LCDC interrupt will be triggered when the video 330 | // controller enters mode 0. 331 | // Bit 2: Set to 1 if the LY memory register value is the same as the LYC 332 | // memory register value. (read-only) 333 | // Bit 1-0: The current mode as a 2-bit number. 334 | // Mode 0: We're in an HBlank period 335 | // Mode 1: We're in a VBlank period 336 | // Mode 2: Controller is reading from OAM RAM. OAM cannot be written 337 | // to. 338 | // Mode 3: Controller is transferring data from OAM and VRAM. OAM and 339 | // VRAM cannot be written to. 340 | statAddr = 0xFF41 341 | 342 | // bootROMDisableAddr is the address that, when written to, disables the 343 | // boot ROM. The boot ROM itself writes to this when it is finished. 344 | bootROMDisableAddr = 0xFF50 345 | 346 | // These addresses have something to do with a "new" DMA transfer mode 347 | // introduced in the CGB. We don't have to worry about this since this is a 348 | // DMG emulator. 349 | hdma1Addr = 0xFF51 350 | hdma2Addr = 0xFF52 351 | hdma3Addr = 0xFF53 352 | hdma4Addr = 0xFF54 353 | hdma5Addr = 0xFF55 354 | 355 | // svbkAddr is a WRAM bank controller thing for the CGB. Unused on the DMG. 356 | svbkAddr = 0xFF70 357 | 358 | // rpAddr is some register for controlling infrared communications. Only 359 | // the CGB has infrared so this goes unused on the DMG. 360 | rpAddr = 0xFF56 361 | 362 | // These addresses have something to do with the CGB color palette. They go 363 | // unused on the DMG. 364 | bcpsAddr = 0xFF68 365 | bcpdAddr = 0xFF69 366 | ocpsAddr = 0xFF6A 367 | ocpdAddr = 0xFF6B 368 | 369 | // These addresses probably have something to do with sound on the CGB. 370 | // They're unused on the DMG. 371 | pcm12Ch2Addr = 0xFF76 372 | pcm34Ch4Addr = 0xFF77 373 | 374 | // videoRAMStart is the address where video RAM starts in memory. 375 | videoRAMStart = 0x8000 376 | // videoRAMEnd is the address where video RAM stops in memory. 377 | videoRAMEnd = 0xA000 378 | 379 | // Memory addresses for the two available tile data tables. 380 | // Note that this data table actually starts at 0x8800, but tile values 381 | // that reference this table can be negative, allowing them to access the 382 | // data before this address. 383 | tileDataTable0 = 0x9000 384 | tileDataTable1 = 0x8000 385 | 386 | // Memory address for sprite data. 387 | spriteDataTable = 0x8000 388 | 389 | // Memory addresses for the two available tile maps. 390 | tileMap0 = 0x9800 391 | tileMap1 = 0x9C00 392 | 393 | // p1Addr points to the Joypad Memory Register, which is used to query 394 | // button/joypad input. 395 | // 396 | // Bit 7: Unused, always 1 397 | // Bit 6: Unused, always 1 398 | // Bit 5: If set to 0, bits 3-0 will provide the status of the buttons (A, 399 | // B, Select, Start) 400 | // Bit 4: If set to 0, bits 3-0 will provide the status of the d-pad (Up, 401 | // Down, Left, Right) 402 | // Bit 3: Represents the state of the down button on the d-pad or the start 403 | // button, depending on the values of bits 4 and 3. 0=pressed. 404 | // Bit 2: Represents the state of the up button on the d-pad or the select 405 | // button, depending on the values of bits 4 and 3. 0=pressed. 406 | // Bit 1: Represents the state of the left button on the d-pad or the B 407 | // button, depending on the values of bits 4 and 3. 0=pressed. 408 | // Bit 0: Represents the state of the right button on the d-pad or the A 409 | // button, depending on the values of bits 4 and 3. 0=pressed. 410 | p1Addr = 0xFF00 411 | 412 | // sbAddr points to the Serial Transfer Data Memory Register, which is a 413 | // place where 8 bits of serial data is read from or written to. 414 | sbAddr = 0xFF01 415 | 416 | // scAddr points to the SIO Control Memory Register, which allows for 417 | // serial communication control. 418 | // TODO(velovix): Fill this out more as I learn more about Game Boy serial. 419 | // 420 | // Bit 7: If set to 1, a transfer will be initiated 421 | // Bit 6-1: Unused, always 1 422 | // Bit 0: Shift clock TODO(velovix): What is this? 423 | // 0: External clock 424 | // 1: Internal clock 425 | scAddr = 0xFF02 426 | 427 | // These variables points to the OBP0 and OBP1 memory registers, which 428 | // control the two available sprite palettes. These registers maps dot data 429 | // to actual colors. 430 | // 431 | // Colors go from 0b11 to 0b00, where 0b11 is the darkest and 0b00 is the 432 | // lightest. 433 | // 434 | // Bits 7-6: The color for dot data 0b11 435 | // Bits 5-4: The color for dot data 0b10 436 | // Bits 3-2: The color for dot data 0b01 437 | // Bits 1-0: Unused. This dot data is always interpreted as transparent. 438 | obp0Addr = 0xFF48 439 | obp1Addr = 0xFF49 440 | 441 | // dmaAddr points to the DMA Transfer and Start Address register. When this 442 | // register is written to, a transfer will happen between a specified 443 | // memory address and OAM RAM. Games use this to update sprite information 444 | // automatically. 445 | // 446 | // The transfer uses the written value as the upper byte of the source 447 | // address for OAM data. For example, if a game writes a 0x28 to the DMA, 448 | // a transfer will begin from 0x2800-0x289F to 0xFE00-0xFE9F (OAM RAM). 449 | dmaAddr = 0xFF46 450 | 451 | // vblankInterruptTarget points to the location that will be jumped to on a 452 | // vblank interrupt. 453 | vblankInterruptTarget = 0x0040 454 | // lcdcInterruptTarget points to the location that will be jumped to on an 455 | // LCDC interrupt. 456 | lcdcInterruptTarget = 0x0048 457 | // timaOverflowInterruptTarget points to the location that will be jumped 458 | // to on a TIMA overflow interrupt. 459 | timaOverflowInterruptTarget = 0x0050 460 | // serialInterruptTarget points to the location that will be jumped to on a 461 | // serial interrupt. 462 | serialInterruptTarget = 0x0058 463 | // p1Thru4InterruptTarget points to the location that will be jumped to 464 | // when a keypad is pressed. 465 | p1Thru4InterruptTarget = 0x0060 466 | ) 467 | -------------------------------------------------------------------------------- /gameboy/bit_ops.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | // makeRES creates an instruction that sets the specified bit of the given 4 | // register to zero. 5 | func makeRES(bitNum uint8, reg register8) instruction { 6 | return func(state *State) instruction { 7 | // M-Cycle 1: Fetch instruction and do operation 8 | 9 | reg.set(reg.get() & ^(0x1 << bitNum)) 10 | 11 | return nil 12 | } 13 | } 14 | 15 | // makeRESMemHL creates an instruction that sets the specified bit of the value 16 | // at the address specified by register HL to zero. 17 | func makeRESMemHL(bitNum uint8) instruction { 18 | return func(state *State) instruction { 19 | // M-Cycle 1: Fetch instruction 20 | 21 | return func(state *State) instruction { 22 | // M-Cycle 2: Read from memory 23 | 24 | memVal := state.mmu.at(state.regHL.get()) 25 | 26 | return func(state *State) instruction { 27 | // M-Cycle 3: Do operation and write to memory 28 | 29 | memVal &= ^(0x1 << bitNum) 30 | state.mmu.set(state.regHL.get(), memVal) 31 | 32 | return nil 33 | } 34 | } 35 | } 36 | } 37 | 38 | // makeBIT creates an instruction that checks the given bit of the given 39 | // register value. 40 | func makeBIT(bitNum uint8, reg register8) instruction { 41 | return func(state *State) instruction { 42 | // M-Cycle 1: Fetch instruction and do operation 43 | 44 | bitSet := reg.get()&(0x1<> 4) 54 | lower = uint8(val & 0x0F) 55 | return lower, upper 56 | } 57 | 58 | // split16 splits the given 16-bit value into its two 8-bit values. 59 | func split16(val uint16) (lower, upper uint8) { 60 | lower = uint8(val & 0xFF) 61 | upper = uint8(val >> 8) 62 | return lower, upper 63 | } 64 | 65 | // isHalfCarry checks if a half carry would occur between two or more 8-bit 66 | // integers if they were added. 67 | // 68 | // This algorithm extracts the first four bits of each integer, adds them 69 | // together, and checks the 5th bit to see if it's 1. If it is, that means the 70 | // addition half-carried. 71 | func isHalfCarry(vals ...uint8) bool { 72 | sumSoFar := vals[0] 73 | for i := 1; i < len(vals); i++ { 74 | if ((sumSoFar&0xF)+(vals[i]&0xF))&0x10 == 0x10 { 75 | return true 76 | } 77 | sumSoFar += vals[i] 78 | } 79 | 80 | return false 81 | } 82 | 83 | // isBorrow checks if a borrow would occur between two or more 8-bit integers 84 | // if they are subtracted. In this case, a borrow is equivalent to an 85 | // underflow. 86 | // 87 | // This algorithm is simple. If the number we're subtracting by is larger than 88 | // the original number, a borrow must be necessary 89 | func isBorrow(vals ...uint8) bool { 90 | diffSoFar := vals[0] 91 | for i := 1; i < len(vals); i++ { 92 | if diffSoFar < vals[i] { 93 | return true 94 | } 95 | diffSoFar -= vals[i] 96 | } 97 | 98 | return false 99 | } 100 | 101 | // isHalfBorrow checks if a half borrow would occur between two 8-bit integers 102 | // if they were subtracted. 103 | // 104 | // This algorithm extracts the first four bits of each integer and checks if 105 | // the a value bits are less than the b value bits. This tells us if a borrow 106 | // will be necessary. 107 | func isHalfBorrow(vals ...uint8) bool { 108 | diffSoFar := vals[0] 109 | for i := 1; i < len(vals); i++ { 110 | if diffSoFar&0xF < vals[i]&0xF { 111 | return true 112 | } 113 | diffSoFar -= vals[i] 114 | } 115 | 116 | return false 117 | } 118 | 119 | // isCarry checks if there would be a carry past the 8th bit if two or more 120 | // 8-bit integers were added. 121 | func isCarry(vals ...uint8) bool { 122 | sumSoFar := vals[0] 123 | for i := 1; i < len(vals); i++ { 124 | if (uint16(sumSoFar)+uint16(vals[i]))&0x100 == 0x100 { 125 | return true 126 | } 127 | sumSoFar += vals[i] 128 | } 129 | 130 | return false 131 | } 132 | 133 | // isHalfCarry16 checks if a half carry would occur between two 16-bit integers 134 | // if they were added. 135 | // 136 | // This algorithm extracts the first 11 bits of each register, adds them 137 | // together, and checks the 12th bit to see if it's 1. If it is, that means the 138 | // addition half-carried. 139 | func isHalfCarry16(a, b uint16) bool { 140 | return ((a&0xFFF)+(b&0xFFF))&0x1000 == 0x1000 141 | } 142 | 143 | // isCarry16 checks if there would be a carry past the 16th bit if two 16-bit 144 | // integers were added. 145 | func isCarry16(a, b uint16) bool { 146 | return (uint32(a)+uint32(b))&0x10000 == 0x10000 147 | } 148 | -------------------------------------------------------------------------------- /gameboy/call_return_ops.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | // call loads a 16-bit address, pushes the address of the next instruction onto 4 | // the stack, and jumps to the loaded address. 5 | func call(state *State) instruction { 6 | // M-Cycle 0: Fetch instruction 7 | 8 | return func(state *State) instruction { 9 | // M-Cycle 1: Read lower byte of destination address 10 | 11 | addressLower := state.incrementPC() 12 | 13 | return func(state *State) instruction { 14 | // M-Cycle 2: Read upper byte of destination address 15 | 16 | addressUpper := state.incrementPC() 17 | 18 | return func(state *State) instruction { 19 | // M-Cycle 3: Some unknown internal operation 20 | 21 | return func(state *State) instruction { 22 | // M-Cycle 4: Push the upper byte of the current PC onto 23 | // the stack 24 | 25 | _, pcUpper := split16(state.regPC.get()) 26 | state.pushToStack(pcUpper) 27 | 28 | return func(state *State) instruction { 29 | // M-Cycle 5: Push the lower byte of the current PC 30 | // onto the stack and set the PC to the 31 | // target address 32 | 33 | pcLower, _ := split16(state.regPC.get()) 34 | state.pushToStack(pcLower) 35 | 36 | // TODO(velovix): Is this actually where the PC is set? 37 | state.regPC.setLower(addressLower) 38 | state.regPC.setUpper(addressUpper) 39 | 40 | return nil 41 | } 42 | } 43 | } 44 | } 45 | 46 | } 47 | } 48 | 49 | // makeCALLIfFlag creates an instruction that loads a 16-bit address, pushes 50 | // the address of the next instruction onto the stack, and jumps to the loaded 51 | // address if the given flag is at the expected setting. 52 | func makeCALLIfFlag(flagMask uint8, isSet bool) instruction { 53 | return func(state *State) instruction { 54 | // M-Cycle 0: Fetch instruction 55 | 56 | return func(state *State) instruction { 57 | // M-Cycle 1: Read lower byte of destination address 58 | 59 | addressLower := state.incrementPC() 60 | 61 | return func(state *State) instruction { 62 | // M-Cycle 2: Read upper byte of destination address and check 63 | // the flag value 64 | 65 | addressUpper := state.incrementPC() 66 | 67 | flagState := state.regF.get()&flagMask == flagMask 68 | 69 | if flagState != isSet { 70 | // Condition evaluated to false, don't jump to the target 71 | // address 72 | return nil 73 | } 74 | 75 | return func(state *State) instruction { 76 | // M-Cycle 3: Some unknown internal operation 77 | 78 | return func(state *State) instruction { 79 | // M-Cycle 4: Push the upper byte of the current PC 80 | // onto the stack 81 | 82 | _, pcUpper := split16(state.regPC.get()) 83 | state.pushToStack(pcUpper) 84 | 85 | return func(state *State) instruction { 86 | // M-Cycle 5: Push the lower byte of the current PC 87 | // onto the stack and set the PC to the 88 | // target address 89 | 90 | pcLower, _ := split16(state.regPC.get()) 91 | state.pushToStack(pcLower) 92 | 93 | // TODO(velovix): Is this actually where the PC is set? 94 | state.regPC.setLower(addressLower) 95 | state.regPC.setUpper(addressUpper) 96 | 97 | return nil 98 | } 99 | } 100 | } 101 | } 102 | } 103 | } 104 | } 105 | 106 | // ret pops a 16-bit address from the stack and jumps to it. 107 | func ret(state *State) instruction { 108 | // M-Cycle 0: Fetch instruction 109 | 110 | return func(state *State) instruction { 111 | // M-Cycle 1: Pop the lower byte of the target address off the stack 112 | 113 | addressLower := state.popFromStack() 114 | 115 | return func(state *State) instruction { 116 | // M-Cycle 2: Pop the upper byte of the target address off the 117 | // stack 118 | 119 | addressUpper := state.popFromStack() 120 | 121 | return func(state *State) instruction { 122 | // M-Cycle 3: Probably set the PC to the target address 123 | 124 | state.regPC.setLower(addressLower) 125 | state.regPC.setUpper(addressUpper) 126 | 127 | return nil 128 | } 129 | } 130 | } 131 | } 132 | 133 | // makeRETIfFlag creates an instruction that pops a 16-bit address from the 134 | // stack and jumps to it, but only if the given flag is at the expected value. 135 | func makeRETIfFlag(flagMask uint8, isSet bool) instruction { 136 | return func(state *State) instruction { 137 | // M-Cycle 0: Fetch instruction 138 | 139 | return func(state *State) instruction { 140 | // M-Cycle 1: Check flag value 141 | 142 | flagState := state.regF.get()&flagMask == flagMask 143 | 144 | if flagState != isSet { 145 | // Condition evaluated to false, don't jump to the target 146 | // address 147 | return nil 148 | } 149 | 150 | return func(state *State) instruction { 151 | // M-Cycle 2: Read the lower byte of the target address 152 | 153 | addressLower := state.popFromStack() 154 | 155 | return func(state *State) instruction { 156 | // M-Cycle 3: Read the upper byte of the target address 157 | 158 | addressUpper := state.popFromStack() 159 | 160 | return func(state *State) instruction { 161 | // M-Cycle 4: Set the PC to the target address 162 | 163 | state.regPC.setLower(addressLower) 164 | state.regPC.setUpper(addressUpper) 165 | 166 | return nil 167 | } 168 | } 169 | } 170 | } 171 | } 172 | } 173 | 174 | // reti pops a 16-bit address from the stack and jumps to it, then enables 175 | // interrupts. 176 | func reti(state *State) instruction { 177 | // M-Cycle 0: Fetch instruction 178 | 179 | return func(state *State) instruction { 180 | // M-Cycle 1: Read lower byte of target address 181 | 182 | addressLower := state.popFromStack() 183 | 184 | return func(state *State) instruction { 185 | // M-Cycle 2: Read upper byte of target address 186 | 187 | addressUpper := state.popFromStack() 188 | 189 | return func(state *State) instruction { 190 | // M-Cycle 3: Set the PC to the target address and turn on 191 | // interrupts 192 | 193 | state.regPC.setLower(addressLower) 194 | state.regPC.setUpper(addressUpper) 195 | 196 | state.interruptsEnabled = true 197 | 198 | return nil 199 | } 200 | } 201 | } 202 | } 203 | 204 | // makeRST creates an instruction that pushes the current program counter to 205 | // the stack and jumps to the given address. 206 | func makeRST(address uint16) instruction { 207 | return func(state *State) instruction { 208 | // M-Cycle 0: Fetch instruction 209 | 210 | return func(state *State) instruction { 211 | // M-Cycle 1: Some unknown internal behavior 212 | 213 | return func(state *State) instruction { 214 | // M-Cycle 2: Push the upper byte of the PC to the stack 215 | 216 | _, upper := split16(state.regPC.get()) 217 | state.pushToStack(upper) 218 | 219 | return func(state *State) instruction { 220 | // M-Cycle 3: Push the lower byte of the PC to the stack 221 | // and set PC to target address 222 | 223 | lower, _ := split16(state.regPC.get()) 224 | state.pushToStack(lower) 225 | 226 | // TODO(velovix): Is this where the PC is actually set? 227 | state.regPC.set(address) 228 | 229 | return nil 230 | } 231 | } 232 | } 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /gameboy/cpu_control_ops.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | import "fmt" 4 | 5 | // nop does nothing. 6 | func nop(state *State) instruction { 7 | // M-Cycle 0: Fetch instruction 8 | 9 | return nil 10 | } 11 | 12 | // di sets the master interrupt flag to false, disabling all interrupt 13 | // handling. 14 | func di(state *State) instruction { 15 | // M-Cycle 0: Fetch instruction and do operation 16 | 17 | state.interruptsEnabled = false 18 | // Cancel a delayed interrupt enable request if any 19 | state.enableInterruptsTimer = 0 20 | 21 | return nil 22 | } 23 | 24 | // ei sets the master interrupt flag to true, but not until the instruction 25 | // after EI has been executed. Interrupts may still be disabled using the 26 | // interrupt flags memory register, however. 27 | func ei(state *State) instruction { 28 | // M-Cycle 0: Fetch instruction and do operation 29 | 30 | state.enableInterruptsTimer = 2 31 | 32 | return nil 33 | } 34 | 35 | // halt stops running instructions until an interrupt is triggered. 36 | func halt(state *State) instruction { 37 | // M-Cycle 0: Fetch instruction and do operation 38 | 39 | state.halted = true 40 | 41 | return nil 42 | } 43 | 44 | // cpl inverts the value of register A. 45 | func cpl(state *State) instruction { 46 | // M-Cycle 0: Fetch instruction and do operation 47 | 48 | invertedA := ^state.regA.get() 49 | state.regA.set(invertedA) 50 | 51 | state.setHalfCarryFlag(true) 52 | state.setSubtractFlag(true) 53 | 54 | return nil 55 | } 56 | 57 | // ccf flips the carry flag. 58 | func ccf(state *State) instruction { 59 | // M-Cycle 0: Fetch instruction and do operation 60 | 61 | state.setCarryFlag(!state.getCarryFlag()) 62 | 63 | state.setHalfCarryFlag(false) 64 | state.setSubtractFlag(false) 65 | 66 | return nil 67 | } 68 | 69 | // scf sets the carry flag to true. 70 | func scf(state *State) instruction { 71 | // M-Cycle 0: Fetch instruction and do operation 72 | 73 | state.setCarryFlag(true) 74 | state.setHalfCarryFlag(false) 75 | state.setSubtractFlag(false) 76 | 77 | return nil 78 | } 79 | 80 | // stop puts the Game Boy in stop mode. In this mode, the screen is blank and 81 | // the CPU stops. Stop mode is exited when a button is pressed. 82 | func stop(state *State) instruction { 83 | // M-Cycle 0: Fetch instruction and do operation 84 | 85 | // For whatever reason, this instruction is two bytes in length 86 | state.incrementPC() 87 | 88 | fmt.Println("Switch to STOP mode") 89 | 90 | state.stopped = true 91 | 92 | return nil 93 | } 94 | -------------------------------------------------------------------------------- /gameboy/cpu_registers.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | // register8 represents an 8-bit register. Registers A, B, C, D, E, H, L, and F 4 | // are all of this type. 5 | type register8 interface { 6 | set(val uint8) uint8 7 | get() uint8 8 | } 9 | 10 | // normalRegister8 is a regular 8-bit register. It stores 8-bit values with no 11 | // other special behavior. 12 | type normalRegister8 struct { 13 | val uint8 14 | } 15 | 16 | func (reg *normalRegister8) set(val uint8) uint8 { 17 | reg.val = val 18 | 19 | return reg.get() 20 | } 21 | 22 | func (reg *normalRegister8) get() uint8 { 23 | return reg.val 24 | } 25 | 26 | // flagRegister8 is the 8-bit flag register. It is distinct from a normal 8-bit 27 | // register because bits 3 through 0 are always zero. Any attempts to write to 28 | // these bits will be ignored 29 | type flagRegister8 struct { 30 | val uint8 31 | } 32 | 33 | func (reg *flagRegister8) set(val uint8) uint8 { 34 | // Mask away unused bits 35 | val &= 0xF0 36 | reg.val = val 37 | return reg.get() 38 | } 39 | 40 | func (reg *flagRegister8) get() uint8 { 41 | return reg.val 42 | } 43 | 44 | // register16 represents a CPU register of 16-bit size. 45 | type register16 interface { 46 | // set sets the register's value. 47 | set(val uint16) uint16 48 | // setLower sets the least significant byte's value. 49 | setLower(val uint8) uint8 50 | // setUpper sets the most significant byte's value. 51 | setUpper(val uint8) uint8 52 | // get returns the register's value. 53 | get() uint16 54 | } 55 | 56 | // normalRegister16 represents a normal 16-bit register. Registers SP and PC 57 | // are both of this type. 58 | type normalRegister16 struct { 59 | val uint16 60 | } 61 | 62 | func (reg *normalRegister16) set(val uint16) uint16 { 63 | reg.val = val 64 | return reg.get() 65 | } 66 | 67 | func (reg *normalRegister16) setLower(val uint8) uint8 { 68 | reg.val = (reg.val & 0xFF00) | uint16(val) 69 | return val 70 | } 71 | 72 | func (reg *normalRegister16) setUpper(val uint8) uint8 { 73 | reg.val = (uint16(val) << 8) | (reg.val & 0x00FF) 74 | return val 75 | } 76 | 77 | func (reg *normalRegister16) get() uint16 { 78 | return reg.val 79 | } 80 | 81 | // registerCombined represents a 16-bit register backed by two 8-bit registers. 82 | // Writes to this register are reflected in the two registers it is backed by. 83 | type registerCombined struct { 84 | upper, lower register8 85 | } 86 | 87 | func (reg *registerCombined) set(val uint16) uint16 { 88 | reg.upper.set(uint8(val >> 8)) 89 | reg.lower.set(uint8(val)) 90 | 91 | return reg.get() 92 | } 93 | 94 | func (reg *registerCombined) setLower(val uint8) uint8 { 95 | reg.lower.set(val) 96 | return val 97 | } 98 | 99 | func (reg *registerCombined) setUpper(val uint8) uint8 { 100 | reg.upper.set(val) 101 | return val 102 | } 103 | 104 | func (reg *registerCombined) get() uint16 { 105 | return (uint16(reg.upper.get()) << 8) | uint16(reg.lower.get()) 106 | } 107 | 108 | func (reg *registerCombined) size() int { 109 | return 16 110 | } 111 | -------------------------------------------------------------------------------- /gameboy/debugger.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | import ( 4 | "bufio" 5 | "fmt" 6 | "os" 7 | "runtime/debug" 8 | "strconv" 9 | "strings" 10 | ) 11 | 12 | var printWarnings = false 13 | 14 | type debugger struct { 15 | state *State 16 | 17 | breakOnPC *uint16 18 | breakOnOpcode *uint8 19 | breakOnAddrRead *uint16 20 | breakOnAddrWrite *uint16 21 | stepping bool 22 | } 23 | 24 | func (db *debugger) pcHook(pc uint16) { 25 | if db.breakOnPC != nil && pc == *db.breakOnPC { 26 | fmt.Printf("PC BREAK: %#04x\n", pc) 27 | db.stepping = true 28 | } 29 | } 30 | 31 | func (db *debugger) opcodeHook(opcode uint8) { 32 | if (db.breakOnOpcode != nil && opcode == *db.breakOnOpcode) || db.stepping { 33 | fmt.Printf("Opcode BREAK: %#02x\n", opcode) 34 | db.printState() 35 | db.readCommand() 36 | } 37 | } 38 | 39 | func (db *debugger) memReadHook(addr uint16) { 40 | if db.breakOnAddrRead != nil && addr == *db.breakOnAddrRead { 41 | fmt.Printf("Address read BREAK: %#04x\n", addr) 42 | db.printState() 43 | db.readCommand() 44 | } 45 | } 46 | 47 | func (db *debugger) memWriteHook(addr uint16, val uint8) { 48 | if db.breakOnAddrWrite != nil && addr == *db.breakOnAddrWrite { 49 | fmt.Printf("Address write BREAK: %#04x with value %#04x\n", addr, val) 50 | db.printState() 51 | db.readCommand() 52 | } 53 | } 54 | 55 | func (db *debugger) printState() { 56 | fmt.Printf(" AF: %#04x\n", db.state.regAF.get()) 57 | fmt.Printf(" BC: %#04x\n", db.state.regBC.get()) 58 | fmt.Printf(" DE: %#04x\n", db.state.regDE.get()) 59 | fmt.Printf(" HL: %#04x\n", db.state.regHL.get()) 60 | fmt.Printf(" SP: %#04x\n", db.state.regSP.get()) 61 | fmt.Printf(" PC: %#04x\n", db.state.instructionStart) 62 | fmt.Printf(" DV: %#02x\n", db.state.mmu.at(dividerAddr)) 63 | } 64 | 65 | func (db *debugger) readCommand() { 66 | reader := bufio.NewReader(os.Stdin) 67 | for { 68 | fmt.Print("Now what? ") 69 | command, _ := reader.ReadString('\n') 70 | if len(command) < 2 { 71 | fmt.Println("\nNo command received") 72 | continue 73 | } 74 | // Remove trailing newline 75 | command = command[:len(command)-1] 76 | 77 | if command == "c" { 78 | fmt.Println("Continuing") 79 | db.stepping = false 80 | break 81 | } else if command == "n" { 82 | fmt.Println("Stepping") 83 | db.stepping = true 84 | break 85 | } else if strings.HasPrefix(command, "m") { 86 | // Parse the command 87 | splitted := strings.Split(command, " ") 88 | if len(splitted) != 2 { 89 | fmt.Println("Format: m [address in hex]") 90 | continue 91 | } 92 | addr, err := strconv.ParseUint(splitted[1], 16, 16) 93 | if err != nil { 94 | fmt.Println("Error parsing address:", err) 95 | continue 96 | } 97 | 98 | // Get the address, temporarily turning off break on read to avoid 99 | // a break loop 100 | oldBreakOnAddrRead := db.breakOnAddrRead 101 | db.breakOnAddrRead = nil 102 | fmt.Printf("Value at address %#04x: %#x\n", 103 | uint16(addr), 104 | db.state.mmu.at(uint16(addr))) 105 | db.breakOnAddrRead = oldBreakOnAddrRead 106 | } else if command == "trace" { 107 | stacktrace := debug.Stack() 108 | fmt.Println(string(stacktrace)) 109 | } else { 110 | fmt.Printf("Unknown command '%v'\n", command) 111 | continue 112 | } 113 | } 114 | } 115 | -------------------------------------------------------------------------------- /gameboy/device.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | import ( 4 | "fmt" 5 | "time" 6 | 7 | "golang.org/x/xerrors" 8 | ) 9 | 10 | // Device represents the Game Boy hardware. 11 | type Device struct { 12 | state *State 13 | header romHeader 14 | debugger *debugger 15 | timers *timers 16 | videoController *videoController 17 | joypad *joypad 18 | serial *serial 19 | interruptManager *interruptManager 20 | SoundController *SoundController 21 | opcodeMapper *opcodeMapper 22 | 23 | saveGames SaveGameDriver 24 | } 25 | 26 | type DebugConfiguration struct { 27 | Debugging bool 28 | 29 | BreakOnPC *uint16 30 | BreakOnOpcode *uint8 31 | BreakOnAddrRead *uint16 32 | BreakOnAddrWrite *uint16 33 | } 34 | 35 | func NewDevice( 36 | bootROM []byte, 37 | cartridgeData []byte, 38 | video VideoDriver, 39 | input InputDriver, 40 | saveGames SaveGameDriver, 41 | dbConfig DebugConfiguration) (*Device, error) { 42 | 43 | var device Device 44 | 45 | device.saveGames = saveGames 46 | 47 | device.header = loadROMHeader(cartridgeData) 48 | fmt.Printf("%+v\n", device.header) 49 | 50 | batteryBacked := false 51 | 52 | // Create a memory bank controller for this ROM 53 | var mbc mbc 54 | switch device.header.cartridgeType { 55 | case 0x00: 56 | // ROM ONLY 57 | mbc = newROMOnlyMBC(device.header, cartridgeData) 58 | case 0x01: 59 | // MBC1 60 | mbc = newMBC1(device.header, cartridgeData) 61 | case 0x02: 62 | // MBC1+RAM 63 | mbc = newMBC1(device.header, cartridgeData) 64 | case 0x03: 65 | // MBC1+RAM+BATTERY 66 | mbc = newMBC1(device.header, cartridgeData) 67 | batteryBacked = true 68 | // case 0x05: 69 | // MBC2 70 | // TODO(velovix): Support this 71 | // case 0x06: 72 | // MBC2+BATTERY 73 | // TODO(velovix): Support this 74 | // case 0x08: 75 | // ROM+RAM 76 | // TODO(velovix): Support this 77 | // case 0x09: 78 | // ROM+RAM+BATTERY 79 | // TODO(velovix): Support this 80 | // case 0x0B: 81 | // MMM01 82 | // TODO(velovix): Support this 83 | // case 0x0C: 84 | // MMM01+RAM 85 | // TODO(velovix): Support this 86 | // case 0x0D: 87 | // MMM01+RAM+BATTERY 88 | // TODO(velovix): Support this 89 | // case 0x0F: 90 | // MBC3+RTC+BATTERY 91 | // TODO(velovix): Support this 92 | // batteryBacked = true 93 | // case 0x10: 94 | // MBC3+RTC+RAM+BATTERY 95 | // TODO(velovix): Support this 96 | batteryBacked = true 97 | case 0x11: 98 | // MBC3 99 | mbc = newMBC3(device.header, cartridgeData, false) 100 | case 0x12: 101 | // MBC3+RAM 102 | mbc = newMBC3(device.header, cartridgeData, false) 103 | case 0x13: 104 | // MBC3+RAM+BATTERY 105 | mbc = newMBC3(device.header, cartridgeData, false) 106 | batteryBacked = true 107 | case 0x19: 108 | // MBC5 109 | mbc = newMBC5(device.header, cartridgeData, false) 110 | case 0x1A: 111 | // MBC5+RAM 112 | mbc = newMBC5(device.header, cartridgeData, false) 113 | case 0x1B: 114 | // MBC5+RAM+BATTERY 115 | mbc = newMBC5(device.header, cartridgeData, false) 116 | batteryBacked = true 117 | case 0x1C: 118 | // MBC5+RUMBLE 119 | mbc = newMBC5(device.header, cartridgeData, true) 120 | case 0x1D: 121 | // MBC5+RUMBLE+RAM 122 | mbc = newMBC5(device.header, cartridgeData, true) 123 | case 0x1E: 124 | // MBC5+RUMBLE+RAM+BATTERY 125 | mbc = newMBC5(device.header, cartridgeData, true) 126 | batteryBacked = true 127 | // case 0x20: 128 | // MBC6 129 | // TODO(velovix): Support this 130 | // case 0x22: 131 | // MBC7+SENSOR+RUMBLE+RAM+BATTERY 132 | // TODO(velovix): Support this 133 | // case 0xFC: 134 | // POCKET CAMERA 135 | // TODO(velovix): Support this 136 | // case 0xFD: 137 | // BANDAI TAMA5 138 | // TODO(velovix): Support this 139 | // case 0xFE: 140 | // HuC3 141 | // TODO(velovix): Support this 142 | // case 0xFF: 143 | // HuC1+RAM+BATTERY 144 | // TODO(velovix): Support this 145 | // batteryBacked = true 146 | default: 147 | return nil, xerrors.Errorf("unknown cartridge type %#x", device.header.cartridgeType) 148 | } 149 | 150 | // Load up a save game if we're using a battery backed cartridge 151 | if batteryBacked { 152 | batteryMBC := mbc.(batteryBackedMBC) 153 | hasSave, err := device.saveGames.Has(device.header.title) 154 | if err != nil { 155 | return nil, xerrors.Errorf("checking for saves: %w", err) 156 | } 157 | if hasSave { 158 | fmt.Println("Loading battery-backed game save...") 159 | data, err := device.saveGames.Load(device.header.title) 160 | if err != nil { 161 | return nil, xerrors.Errorf("loading game save: %w", err) 162 | } 163 | batteryMBC.loadBatteryBackedRAM(data) 164 | } 165 | } 166 | 167 | mmu := newMMU(bootROM, cartridgeData, mbc) 168 | device.state = NewState(mmu) 169 | 170 | if dbConfig.Debugging { 171 | device.debugger = &debugger{state: device.state} 172 | 173 | device.debugger.breakOnPC = dbConfig.BreakOnPC 174 | device.debugger.breakOnOpcode = dbConfig.BreakOnOpcode 175 | device.debugger.breakOnAddrRead = dbConfig.BreakOnAddrRead 176 | device.debugger.breakOnAddrWrite = dbConfig.BreakOnAddrWrite 177 | 178 | device.state.mmu.db = device.debugger 179 | } 180 | 181 | device.timers = newTimers(device.state) 182 | mmu.timers = device.timers 183 | 184 | device.videoController = newVideoController( 185 | device.state, video) 186 | mmu.videoController = device.videoController 187 | 188 | device.joypad = newJoypad(device.state, input) 189 | 190 | device.serial = newSerial(device.state) 191 | 192 | device.interruptManager = newInterruptManager(device.state, device.timers) 193 | device.joypad.interruptManager = device.interruptManager 194 | device.videoController.interruptManager = device.interruptManager 195 | device.timers.interruptManager = device.interruptManager 196 | mmu.interruptManager = device.interruptManager 197 | 198 | device.SoundController = newSoundController(device.state) 199 | 200 | device.opcodeMapper = newOpcodeMapper(device.state) 201 | 202 | return &device, nil 203 | } 204 | 205 | // BenchmarkComponents prints out performance information on each component of 206 | // the device. Drivers are temporarily mocked out. This will leave the device 207 | // in a strange state that will likely not play nicely with games. 208 | // 209 | // Once I'm more confident of the performance of this emulator, this could 210 | // likely be removed. 211 | func (device *Device) BenchmarkComponents() { 212 | secondCycles := 10 213 | 214 | start := time.Now() 215 | for i := 0; i < secondCycles; i++ { 216 | for j := 0; j < cpuClockRate; j++ { 217 | device.timers.tick() 218 | } 219 | } 220 | fmt.Println("Timer performance:", float64(secondCycles)/time.Since(start).Seconds()) 221 | 222 | start = time.Now() 223 | oldVideoDriver := device.videoController.driver 224 | device.videoController.driver = &noopVideoDriver{} 225 | for i := 0; i < secondCycles; i++ { 226 | for j := 0; j < cpuClockRate; j++ { 227 | device.videoController.tick() 228 | } 229 | } 230 | fmt.Println("Video controller performance:", float64(secondCycles)/time.Since(start).Seconds()) 231 | device.videoController.driver = oldVideoDriver 232 | 233 | start = time.Now() 234 | oldInputDriver := device.joypad.driver 235 | device.joypad.driver = &noopInputDriver{} 236 | for i := 0; i < secondCycles; i++ { 237 | for j := 0; j < cpuClockRate; j++ { 238 | device.joypad.tick() 239 | } 240 | } 241 | device.joypad.driver = oldInputDriver 242 | fmt.Println("Joypad performance:", float64(secondCycles)/time.Since(start).Seconds()) 243 | 244 | start = time.Now() 245 | for i := 0; i < secondCycles; i++ { 246 | for j := 0; j < cpuClockRate; j++ { 247 | device.interruptManager.check() 248 | } 249 | } 250 | fmt.Println("Interrupt manager performance:", float64(secondCycles)/time.Since(start).Seconds()) 251 | 252 | start = time.Now() 253 | for i := 0; i < secondCycles; i++ { 254 | for j := 0; j < cpuClockRate; j++ { 255 | device.SoundController.tick() 256 | } 257 | } 258 | fmt.Println("Sound controller performance:", float64(secondCycles)/time.Since(start).Seconds()) 259 | 260 | start = time.Now() 261 | for i := 0; i < secondCycles; i++ { 262 | for j := 0; j < cpuClockRate; j++ { 263 | //device.opcodeMapper.run(0x00) 264 | } 265 | } 266 | fmt.Println("Opcode mapper performance:", float64(secondCycles)/time.Since(start).Seconds()) 267 | } 268 | 269 | // Start starts the main processing loop of the Gameboy. 270 | func (device *Device) Start(onExit chan bool) error { 271 | var currentInstruction instruction 272 | var err error 273 | 274 | for { 275 | // Check periodically (but not every tick for performance reasons) if 276 | // the main loop should be exited 277 | if device.timers.cpuClock == 0 { 278 | select { 279 | case <-onExit: 280 | // Save the game, if necessary 281 | if mbc, ok := device.state.mmu.mbc.(batteryBackedMBC); ok { 282 | fmt.Println("Saving battery-backed game state...") 283 | data := mbc.dumpBatteryBackedRAM() 284 | err := device.saveGames.Save(device.header.title, data) 285 | if err != nil { 286 | return xerrors.Errorf("saving game: %w", err) 287 | } 288 | } 289 | return nil 290 | default: 291 | } 292 | } 293 | 294 | device.joypad.tick() 295 | if device.state.stopped { 296 | // We're in stop mode, don't do anything 297 | time.Sleep(time.Millisecond) 298 | continue 299 | } 300 | 301 | device.timers.tick() 302 | 303 | if device.state.halted { 304 | // The device is halted. Process no new instructions, but check for 305 | // interrupts. 306 | device.interruptManager.check() 307 | } else if !device.state.halted { 308 | // Notify the debugger that we're at this PC value 309 | /*if device.debugger != nil { 310 | device.debugger.pcHook(device.state.regPC.get()) 311 | }*/ 312 | 313 | if currentInstruction == nil { 314 | // Process interrupts before fetching a new instruction. Note 315 | // that this means interrupt processing does not happen while 316 | // an instruction is being executed 317 | // TODO(velovix): Is this the right behavior? 318 | device.interruptManager.check() 319 | 320 | // Fetch a new operation 321 | opcode := device.state.incrementPC() 322 | 323 | if device.debugger != nil { 324 | device.debugger.opcodeHook(opcode) 325 | } 326 | 327 | currentInstruction, err = device.opcodeMapper.getInstruction(opcode) 328 | if err != nil { 329 | return err 330 | } 331 | } 332 | 333 | // Get the next step in the instruction 334 | currentInstruction = currentInstruction(device.state) 335 | } 336 | 337 | device.state.mmu.tick() 338 | device.videoController.tick() 339 | device.SoundController.tick() 340 | 341 | // Process any delayed requests to toggle the master interrupt switch. 342 | // These are created by the EI and DI instructions. 343 | if device.state.enableInterruptsTimer > 0 { 344 | device.state.enableInterruptsTimer-- 345 | if device.state.enableInterruptsTimer == 0 { 346 | device.state.interruptsEnabled = true 347 | } 348 | } 349 | } 350 | } 351 | 352 | const ( 353 | // interruptDispatchMCycles is the number of M-Cycles consumed while an 354 | // interrupt is being dispatched. 355 | interruptDispatchMCycles = 5 356 | ) 357 | -------------------------------------------------------------------------------- /gameboy/driver_interfaces.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | // VideoDriver describes an object can display RGBA frames. 4 | type VideoDriver interface { 5 | // Render displays the given frame data on-screen. 6 | // 7 | // Each element of the frame data is 8-bit R, G, B, and A values laid out 8 | // in that order. 9 | Render(frameData []uint8) error 10 | // Close de-initializes the driver. 11 | Close() 12 | } 13 | 14 | // noopVideoDriver is a mock video driver that does nothing. 15 | type noopVideoDriver struct{} 16 | 17 | func (driver *noopVideoDriver) Render(frameData []uint8) error { 18 | return nil 19 | } 20 | 21 | func (driver *noopVideoDriver) Close() { 22 | } 23 | 24 | type Button int 25 | 26 | const ( 27 | _ Button = iota 28 | ButtonStart 29 | ButtonSelect 30 | ButtonB 31 | ButtonA 32 | ButtonDown 33 | ButtonUp 34 | ButtonLeft 35 | ButtonRight 36 | ) 37 | 38 | // InputDriver describes an object that can take in and report user input. 39 | type InputDriver interface { 40 | // State returns the state of the given button since the last call to 41 | // Update. A true value indicates the button is pressed. 42 | State(Button) bool 43 | // Update updates the internal button state and returns true if a button 44 | // has been pressed since the last call. 45 | Update() bool 46 | } 47 | 48 | // noopInputDriver is a mock input driver that does nothing. 49 | type noopInputDriver struct{} 50 | 51 | func (driver *noopInputDriver) State(button Button) bool { 52 | return false 53 | } 54 | 55 | func (driver *noopInputDriver) Update() bool { 56 | return false 57 | } 58 | 59 | // SaveGameDriver describes an object that can save and load game saves. 60 | type SaveGameDriver interface { 61 | // Save puts the game save in some persistent storage under a name. 62 | Save(name string, data []uint8) error 63 | // Load returns a game save from some persistent storage under the given 64 | // name. 65 | Load(name string) ([]uint8, error) 66 | // Has returns true if a game save exists under the given name. 67 | Has(name string) (bool, error) 68 | } 69 | -------------------------------------------------------------------------------- /gameboy/interrupt_manager.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | // interruptManager receives interrupts and moves the program counter to 4 | // interrupt handlers where appropriate. 5 | type interruptManager struct { 6 | state *State 7 | timers *timers 8 | 9 | // The value of the interrupt flag register, whose value indicates whether 10 | // or not certain interrupts are scheduled to happen. 11 | interruptFlags uint8 12 | // The value of the interrupt enable register, whose value indicates 13 | // whether or not certain interrupts are allowed to happen. 14 | interruptEnable uint8 15 | } 16 | 17 | func newInterruptManager(state *State, timers *timers) *interruptManager { 18 | mgr := &interruptManager{ 19 | state: state, 20 | timers: timers, 21 | } 22 | 23 | mgr.state.mmu.subscribeTo(ifAddr, mgr.onIFWrite) 24 | mgr.state.mmu.subscribeTo(ieAddr, mgr.onIEWrite) 25 | 26 | return mgr 27 | } 28 | 29 | // check checks for any interrupts that have happened, and triggers an 30 | // interrupt handler by moving the program counter where appropriate. 31 | func (mgr *interruptManager) check() { 32 | clearedInterruptFlags := mgr.interruptFlags 33 | 34 | // The address of the handler to jump to 35 | var target uint16 36 | 37 | // Check each bit of the interrupt flag to see if an interrupt 38 | // happened, and each bit of the interrupt enable flag to check if 39 | // we should process it. Then, the flag is cleared. 40 | // Order here is important. Interrupts with lower target addresses 41 | // have priority over higher ones. 42 | if mgr.interruptEnable&mgr.interruptFlags&0x01 == 0x01 { 43 | // VBlank interrupt 44 | target = vblankInterruptTarget 45 | clearedInterruptFlags &= ^uint8(0x01) 46 | } else if mgr.interruptEnable&mgr.interruptFlags&0x02 == 0x02 { 47 | // LCDC interrupt 48 | target = lcdcInterruptTarget 49 | clearedInterruptFlags &= ^uint8(0x02) 50 | } else if mgr.interruptEnable&mgr.interruptFlags&0x04 == 0x04 { 51 | // TIMA overflow interrupt 52 | target = timaOverflowInterruptTarget 53 | clearedInterruptFlags &= ^uint8(0x04) 54 | } else if mgr.interruptEnable&mgr.interruptFlags&0x08 == 0x08 { 55 | // Serial interrupt 56 | target = serialInterruptTarget 57 | clearedInterruptFlags &= ^uint8(0x08) 58 | } else if mgr.interruptEnable&mgr.interruptFlags&0x10 == 0x10 { 59 | // P10-P13 interrupt 60 | target = p1Thru4InterruptTarget 61 | clearedInterruptFlags &= ^uint8(0x10) 62 | } 63 | 64 | if target != 0 { 65 | if mgr.state.interruptsEnabled { 66 | // Dispatch the interrupt 67 | mgr.state.interruptsEnabled = false 68 | // Clear the interrupt flag 69 | mgr.interruptFlags = clearedInterruptFlags 70 | // Push the current program counter to the stack for later use 71 | mgr.state.pushToStack16(mgr.state.regPC.get()) 72 | mgr.state.regPC.set(target) 73 | // Dispatching an interrupt takes clock cycles 74 | for i := 0; i < interruptDispatchMCycles; i++ { 75 | mgr.timers.tick() 76 | } 77 | } 78 | 79 | if mgr.state.halted { 80 | // Take the Game Boy of halt mode. Note that this will happen even 81 | // if the master interrupt switch is disabled 82 | mgr.state.halted = false 83 | // Taking the Game Boy off halt mode takes one M-Cycle 84 | mgr.timers.tick() 85 | } 86 | } 87 | } 88 | 89 | // vblankEnabled returns true if the VBlank interrupt is enabled. 90 | func (mgr *interruptManager) vblankEnabled() bool { 91 | return mgr.interruptEnable&0x1 == 0x1 92 | } 93 | 94 | // flagVBlank sets the VBlank interrupt flag, signaling that the interrupt 95 | // should be run the next time interrupts are checked. 96 | func (mgr *interruptManager) flagVBlank() { 97 | mgr.interruptFlags |= 0x1 98 | } 99 | 100 | // lcdcEnabled returns true if the LCDC interrupt is enabled. 101 | func (mgr *interruptManager) lcdcEnabled() bool { 102 | return mgr.interruptEnable&0x2 == 0x2 103 | } 104 | 105 | // flagLCDC sets the LCDC interrupt flag, signaling that the interrupt should 106 | // be run the next time interrupts are checked. 107 | func (mgr *interruptManager) flagLCDC() { 108 | mgr.interruptFlags |= 0x2 109 | } 110 | 111 | // timaEnabled returns true if the TIMA interrupt is enabled. 112 | func (mgr *interruptManager) timaEnabled() bool { 113 | return mgr.interruptEnable&0x4 == 0x4 114 | } 115 | 116 | // flagTIMA sets the TIMA interrupt flag, signaling that the interrupt should 117 | // be run the next time interrupts are checked. 118 | func (mgr *interruptManager) flagTIMA() { 119 | mgr.interruptFlags |= 0x4 120 | } 121 | 122 | // serialIOEnabled returns true if the serial IO interrupt is enabled. 123 | func (mgr *interruptManager) serialIOEnabled() bool { 124 | return mgr.interruptEnable&0x8 == 0x8 125 | } 126 | 127 | // flagSerialIO sets the serial IO interrupt flag, signaling that the interrupt 128 | // should be run the next time interrupts are checked. 129 | func (mgr *interruptManager) flagSerialIO() { 130 | mgr.interruptFlags |= 0x8 131 | } 132 | 133 | // p10ToP13Enabled returns true if the P10-P13 interrupt is enabled. 134 | func (mgr *interruptManager) p10ToP13Enabled() bool { 135 | return mgr.interruptEnable&0x10 == 0x10 136 | } 137 | 138 | // flagP10ToP13 sets the P10-P13 interrupt flag, signaling that the interrupt 139 | // should be run the next time interrupts are checked. 140 | // TODO(velovix): What a lame name. I need to find a better one. 141 | func (mgr *interruptManager) flagP10ToP13() { 142 | mgr.interruptFlags |= 0x10 143 | } 144 | 145 | // onIFWrite is triggered when the Interrupt Flag register is written to. No 146 | // interrupt handling action is taken here, but the unused bits in this 147 | // register are set to 1. 148 | func (mgr *interruptManager) onIFWrite(addr uint16, value uint8) uint8 { 149 | // The first three bits of the register are unused 150 | value |= 0xE3 151 | 152 | mgr.interruptFlags = value 153 | 154 | return value 155 | } 156 | 157 | // onIEWrite is triggered when the Interrupt Enable register is written to. It 158 | // simply updates the internal value stored in this component. 159 | func (mgr *interruptManager) onIEWrite(addr uint16, value uint8) uint8 { 160 | mgr.interruptEnable = value 161 | 162 | return value 163 | } 164 | -------------------------------------------------------------------------------- /gameboy/joypad.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | // eventProcessPeriod is the delay between input processes. This doesn't have 4 | // any meaning to the Game Boy hardware, but limiting the input processing 5 | // provides better performance. 6 | const eventProcessPeriod = cpuClockRate / 60 7 | 8 | // joypad handles user input events and exposes them to the ROM. 9 | type joypad struct { 10 | state *State 11 | interruptManager *interruptManager 12 | 13 | driver InputDriver 14 | 15 | // The number of cycles since the last event processing. 16 | lastEventProcess int 17 | } 18 | 19 | // newJoypad creates a new joypad manager object. 20 | func newJoypad(state *State, driver InputDriver) *joypad { 21 | j := &joypad{ 22 | state: state, 23 | driver: driver, 24 | } 25 | 26 | // Subscribe to P1 address writes 27 | j.state.mmu.subscribeTo(p1Addr, j.onP1Write) 28 | 29 | return j 30 | } 31 | 32 | // tick updates the P1 register value based on the current state of the user's 33 | // input device and the game's configuration of this register. May also 34 | // generate a P10-P13 interrupt if a button is pressed and this interrupt is 35 | // enabled. 36 | func (j *joypad) tick() { 37 | j.lastEventProcess += ticksPerMCycle 38 | if j.lastEventProcess < eventProcessPeriod { 39 | return 40 | } 41 | 42 | j.lastEventProcess -= eventProcessPeriod 43 | 44 | buttonPressed := j.driver.Update() 45 | 46 | if j.state.stopped && buttonPressed { 47 | // Exit stop mode on button press 48 | j.state.stopped = false 49 | } 50 | 51 | // Generate an interrupt if any new buttons have been pressed 52 | if buttonPressed && j.state.interruptsEnabled && j.interruptManager.p10ToP13Enabled() { 53 | j.interruptManager.flagP10ToP13() 54 | } 55 | } 56 | 57 | // onP1Write is called when the P1 register is written to. This triggers the 58 | // joypad to update the memory register according to the requested data. This 59 | // data is either the button states or the d-pad states. 60 | func (j *joypad) onP1Write(addr uint16, writeVal uint8) uint8 { 61 | // Use bits 5 and 4 to decide what joypad input should be exposed. Note 62 | // that 0 means "select this" in this case. 63 | getDPadState := writeVal&0x10 == 0x00 64 | getButtonState := writeVal&0x20 == 0x00 65 | 66 | newP1 := writeVal 67 | 68 | // The unused first two bits of P1 are always high 69 | newP1 |= 0xC0 70 | 71 | // Set the state of the d-pad/buttons. Note again that 0 means the button 72 | // is pressed, not 1 73 | if getDPadState { 74 | // Check Down, Up, Left, and Right buttons 75 | newP1 |= buttonStatesToNibble( 76 | j.driver.State(ButtonDown), 77 | j.driver.State(ButtonUp), 78 | j.driver.State(ButtonLeft), 79 | j.driver.State(ButtonRight)) 80 | } else if getButtonState { 81 | // Check Start, Select, B, and A buttons 82 | newP1 |= buttonStatesToNibble( 83 | j.driver.State(ButtonStart), 84 | j.driver.State(ButtonSelect), 85 | j.driver.State(ButtonB), 86 | j.driver.State(ButtonA)) 87 | } 88 | // Otherwise, provide nothing 89 | 90 | return newP1 91 | } 92 | 93 | // buttonStatesToNibble converts the given 4 button states into a nibble where 94 | // a true value maps to a 0 and a false maps to 1. 95 | func buttonStatesToNibble(states ...bool) uint8 { 96 | output := uint8(0x0) 97 | 98 | for i, state := range states { 99 | if !state { 100 | output |= 0x1 << uint(3-i) 101 | } 102 | } 103 | 104 | return output 105 | } 106 | -------------------------------------------------------------------------------- /gameboy/jump_ops.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | // jr loads a signed offset value, then jumps to the operation at address PC + 4 | // offset. In other words, it's a jump relative to the current position. 5 | func jr(state *State) instruction { 6 | // M-Cycle 0: Fetch instruction 7 | 8 | return func(state *State) instruction { 9 | // M-Cycle 1: Read immediate value 10 | 11 | offset := int8(state.incrementPC()) 12 | 13 | return func(state *State) instruction { 14 | // M-Cycle 2: Do operation 15 | 16 | relativeJump(state, offset) 17 | 18 | return nil 19 | } 20 | } 21 | } 22 | 23 | // makeJRIfFlag creates an instruction that loads an offset value, then jumps 24 | // to the operation at address PC + offset if the given flag is at the expected 25 | // setting. 26 | func makeJRIfFlag(flagMask uint8, isSet bool) instruction { 27 | // M-Cycle 0: Fetch instruction 28 | 29 | return func(state *State) instruction { 30 | // M-Cycle 1: Read immediate value and check condition 31 | offset := int8(state.incrementPC()) 32 | 33 | flagState := state.regF.get()&flagMask == flagMask 34 | 35 | if flagState != isSet { 36 | // Condition evaluated to false, don't jump 37 | return nil 38 | } 39 | 40 | return func(state *State) instruction { 41 | // M-Cycle 2: Do operation 42 | 43 | relativeJump(state, offset) 44 | 45 | return nil 46 | } 47 | } 48 | } 49 | 50 | // jp loads a 16-bit address and jumps to it. 51 | func jp(state *State) instruction { 52 | // M-Cycle 0: This instruction doesn't do anything 53 | 54 | return func(state *State) instruction { 55 | // M-Cycle 1: Load the least significant bit of the target address 56 | 57 | addressLower := state.incrementPC() 58 | 59 | return func(state *State) instruction { 60 | // M-Cycle 2: Load the most significant bit of the target address 61 | // and set the program counter 62 | 63 | addressUpper := state.incrementPC() 64 | 65 | state.regPC.setLower(addressLower) 66 | state.regPC.setUpper(addressUpper) 67 | 68 | return func(state *State) instruction { 69 | // M-Cycle 3: Internal delay 70 | 71 | return nil 72 | } 73 | } 74 | } 75 | } 76 | 77 | // makeJPIfFlag creates an instruction that loads a 16-bit address and jumps to 78 | // it if the given flag is at the expected setting. 79 | func makeJPIfFlag(flagMask uint8, isSet bool) instruction { 80 | return func(state *State) instruction { 81 | // M-Cycle 0: Fetch instruction 82 | 83 | return func(state *State) instruction { 84 | // M-Cycle 1: Read lower byte of target address 85 | 86 | addressLower := state.incrementPC() 87 | 88 | return func(state *State) instruction { 89 | // M-Cycle 2: Read upper byte of target address and check 90 | // condition 91 | 92 | addressUpper := state.incrementPC() 93 | 94 | flagState := state.regF.get()&flagMask == flagMask 95 | if flagState != isSet { 96 | // Condition evaluated to false, don't jump 97 | return nil 98 | } 99 | 100 | return func(state *State) instruction { 101 | // M-Cycle 3: Set the PC 102 | 103 | state.regPC.setLower(addressLower) 104 | state.regPC.setUpper(addressUpper) 105 | 106 | return nil 107 | } 108 | } 109 | } 110 | } 111 | } 112 | 113 | // jpToHL jumps to the address specified by register HL. 114 | func jpToHL(state *State) instruction { 115 | // M-Cycle 0: Fetch instruction and do operation 116 | 117 | hlVal := state.regHL.get() 118 | 119 | state.regPC.set(hlVal) 120 | 121 | return nil 122 | } 123 | 124 | // relativeJump moves the program counter by the given signed value. 125 | func relativeJump(state *State, offset int8) { 126 | pc := uint16(int(state.regPC.get()) + int(offset)) 127 | state.regPC.set(pc) 128 | } 129 | -------------------------------------------------------------------------------- /gameboy/loading_ops.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | // makeLD creates an instruction that loads the value of reg2 into reg1. 4 | func makeLD(reg1, reg2 register8) instruction { 5 | return func(state *State) instruction { 6 | // M-Cycle 0: Do operation 7 | 8 | reg1.set(reg2.get()) 9 | 10 | return nil 11 | } 12 | } 13 | 14 | // ldHLToSP puts the value of register HL into register SP. 15 | func ldHLToSP(state *State) instruction { 16 | // M-Cycle 0: Fetch instruction 17 | 18 | return func(state *State) instruction { 19 | // M-Cycle 1: Do operation 20 | 21 | hlVal := state.regHL.get() 22 | 23 | state.regSP.set(hlVal) 24 | 25 | return nil 26 | } 27 | } 28 | 29 | // makeLDToMem creates an instruction that loads the value of reg2 into the 30 | // memory address specified by reg1. 31 | func makeLDToMem(reg1 register16, reg2 register8) instruction { 32 | return func(state *State) instruction { 33 | // M-Cycle 0: Fetch instruction 34 | 35 | return func(state *State) instruction { 36 | // M-Cycle 1: Write to memory 37 | 38 | state.mmu.set(reg1.get(), reg2.get()) 39 | 40 | return nil 41 | } 42 | } 43 | } 44 | 45 | // makeLDFromMem creates an instruction that loads the value in the memory 46 | // address specified by reg2 into reg1. 47 | func makeLDFromMem(reg1 register8, reg2 register16) instruction { 48 | return func(state *State) instruction { 49 | // M-Cycle 0: Fetch instruction 50 | 51 | return func(state *State) instruction { 52 | // M-Cycle 1: Read from memory into register 53 | 54 | val := state.mmu.at(reg2.get()) 55 | reg1.set(val) 56 | 57 | return nil 58 | } 59 | } 60 | } 61 | 62 | // makeLD8BitImm creates an instruction that loads an 8-bit immediate value 63 | // into the given register. 64 | func makeLD8BitImm(reg register8) instruction { 65 | return func(state *State) instruction { 66 | // M-Cycle 0: Fetch instruction 67 | 68 | return func(state *State) instruction { 69 | // M-Cycle 1: Read from PC position and set register 70 | 71 | imm := state.incrementPC() 72 | 73 | reg.set(imm) 74 | 75 | return nil 76 | } 77 | } 78 | } 79 | 80 | // makeLD16BitImm creates an instruction that loads a 16-bit immediate value 81 | // into the specified 16-bit register. 82 | func makeLD16BitImm(reg register16) func(*State) instruction { 83 | return func(state *State) instruction { 84 | // M-Cycle 0: Fetch instruction 85 | 86 | return func(state *State) instruction { 87 | // M-Cycle 1: Read least significant byte from PC position 88 | 89 | immLower := state.incrementPC() 90 | 91 | return func(state *State) instruction { 92 | // M-Cycle 2: Read most significant byte from PC position and 93 | // set the register 94 | 95 | immUpper := state.incrementPC() 96 | 97 | imm := combine16(immLower, immUpper) 98 | reg.set(imm) 99 | 100 | return nil 101 | } 102 | } 103 | } 104 | } 105 | 106 | // ldTo16BitImmMem saves the value of register A to an address in memory 107 | // specified by a 16-bit immediate value. 108 | func ldTo16BitImmMem(state *State) instruction { 109 | // M-Cycle 0: Fetch instruction 110 | 111 | return func(state *State) instruction { 112 | // M-Cycle 1: Read least significant byte from PC position 113 | 114 | immLower := state.incrementPC() 115 | 116 | return func(state *State) instruction { 117 | // M-Cycle 2: Read most significant byte from PC position 118 | 119 | immUpper := state.incrementPC() 120 | 121 | return func(state *State) instruction { 122 | // M-Cycle 3: Write register to memory 123 | 124 | imm := combine16(immLower, immUpper) 125 | aVal := state.regA.get() 126 | 127 | state.mmu.set(imm, aVal) 128 | 129 | return nil 130 | } 131 | } 132 | } 133 | } 134 | 135 | // ldFrom16BitImmMem loads the value in memory at the address specified by a 136 | // 16-bit immediate value into register A. 137 | func ldFrom16BitImmMem(state *State) instruction { 138 | // M-Cycle 0: Fetch instruction 139 | 140 | return func(state *State) instruction { 141 | // M-Cycle 1: Read least significant byte from PC position 142 | 143 | immLower := state.incrementPC() 144 | 145 | return func(state *State) instruction { 146 | // M-Cycle 2: Read most significant byte from PC position 147 | 148 | immUpper := state.incrementPC() 149 | 150 | return func(state *State) instruction { 151 | // M-Cycle 3: Write register to memory 152 | 153 | imm := combine16(immLower, immUpper) 154 | memVal := state.mmu.at(imm) 155 | 156 | state.regA.set(memVal) 157 | 158 | return nil 159 | } 160 | } 161 | } 162 | } 163 | 164 | // ld8BitImmToMemHL loads an 8-bit immediate value into the memory address 165 | // specified by the HL register. 166 | func ld8BitImmToMemHL(state *State) instruction { 167 | // M-Cycle 0: Fetch instruction 168 | 169 | return func(state *State) instruction { 170 | // M-Cycle 1: Read immediate value 171 | 172 | imm := state.incrementPC() 173 | 174 | return func(state *State) instruction { 175 | // M-Cycle 2: Write to memory 176 | 177 | state.mmu.set(state.regHL.get(), imm) 178 | 179 | return nil 180 | } 181 | } 182 | } 183 | 184 | // ldSPToMem loads a 16-bit address and saves the stack pointer at that 185 | // address. 186 | func ldSPToMem(state *State) instruction { 187 | // M-Cycle 0: Fetch instruction 188 | 189 | return func(state *State) instruction { 190 | // M-Cycle 1: Read least significant byte at PC position 191 | 192 | immLower := state.incrementPC() 193 | 194 | return func(state *State) instruction { 195 | // M-Cycle 2: Read most significant byte at PC position 196 | 197 | immUpper := state.incrementPC() 198 | 199 | return func(state *State) instruction { 200 | // M-Cycle 3: Save lower byte of stack pointer to memory 201 | 202 | imm := combine16(immLower, immUpper) 203 | 204 | lowerSP, _ := split16(state.regSP.get()) 205 | state.mmu.set(imm, lowerSP) 206 | 207 | return func(state *State) instruction { 208 | // M-Cycle 4: Save upper byte of stack pointer to memory 209 | 210 | _, upperSP := split16(state.regSP.get()) 211 | state.mmu.set(imm+1, upperSP) 212 | 213 | return nil 214 | } 215 | } 216 | } 217 | } 218 | } 219 | 220 | // ldToMemC saves the value of register A at the memory address 221 | // 0xFF00+register C. 222 | func ldToMemC(state *State) instruction { 223 | // M-Cycle 0: Fetch instruction 224 | 225 | return func(state *State) instruction { 226 | // M-Cycle 1: Write to memory 227 | 228 | aVal := state.regA.get() 229 | addr := uint16(state.regC.get()) + 0xFF00 230 | 231 | state.mmu.set(addr, aVal) 232 | 233 | return nil 234 | } 235 | } 236 | 237 | // ldFromMemC loads the value at memory address 0xFF00 + register C into 238 | // register A. 239 | func ldFromMemC(state *State) instruction { 240 | // M-Cycle 0: Fetch instruction 241 | 242 | return func(state *State) instruction { 243 | // M-Cycle 1: Read from memory 244 | 245 | addr := uint16(state.regC.get()) + 0xFF00 246 | memVal := state.mmu.at(addr) 247 | 248 | state.regA.set(memVal) 249 | 250 | return nil 251 | } 252 | } 253 | 254 | // ldiToMem loads register A into the memory address specified by register HL, 255 | // then increments register HL. 256 | func ldiToMem(state *State) instruction { 257 | // M-Cycle 0: Fetch instruction 258 | 259 | return func(state *State) instruction { 260 | // M-Cycle 1: Write to memory 261 | 262 | state.mmu.set(state.regHL.get(), state.regA.get()) 263 | 264 | state.regHL.set(state.regHL.get() + 1) 265 | 266 | return nil 267 | } 268 | } 269 | 270 | // lddToMem loads register A into the memory address specified by register HL, 271 | // then decrements register HL. 272 | func lddToMem(state *State) instruction { 273 | // M-Cycle 0: Fetch instruction 274 | 275 | return func(state *State) instruction { 276 | // M-Cycle 1: Write to memory 277 | 278 | state.mmu.set(state.regHL.get(), state.regA.get()) 279 | 280 | state.regHL.set(state.regHL.get() - 1) 281 | 282 | return nil 283 | } 284 | } 285 | 286 | // ldiFromMem puts the value stored in the memory address specified by register 287 | // HL into register A, then increments register HL. 288 | func ldiFromMem(state *State) instruction { 289 | // M-Cycle 0: Fetch instruction 290 | 291 | return func(state *State) instruction { 292 | // M-Cycle 1: Read from memory 293 | 294 | memVal := state.mmu.at(state.regHL.get()) 295 | state.regA.set(memVal) 296 | 297 | state.regHL.set(state.regHL.get() + 1) 298 | 299 | return nil 300 | } 301 | } 302 | 303 | // lddFromMem puts the value stored in the memory address specified by register 304 | // HL into register A, then decrements register HL. 305 | func lddFromMem(state *State) instruction { 306 | // M-Cycle 0: Fetch instruction 307 | 308 | return func(state *State) instruction { 309 | // M-Cycle 1: Read from memory 310 | 311 | memVal := state.mmu.at(state.regHL.get()) 312 | state.regA.set(memVal) 313 | 314 | state.regHL.set(state.regHL.get() - 1) 315 | 316 | return nil 317 | } 318 | } 319 | 320 | // ldhToMem loads an offset value, then saves the value of register A into the 321 | // memory address 0xFF00 + offset. 322 | func ldhToMem(state *State) instruction { 323 | // M-Cycle 0: Fetch instruction 324 | 325 | return func(state *State) instruction { 326 | // M-Cycle 1: Read offset from PC location 327 | 328 | offset := state.incrementPC() 329 | 330 | return func(state *State) instruction { 331 | // M-Cycle 2: Write to memory 332 | 333 | state.mmu.set(0xFF00+uint16(offset), state.regA.get()) 334 | 335 | return nil 336 | } 337 | } 338 | } 339 | 340 | // ldhFromMem loads an offset value, then loads the value at memory address 341 | // 0xFF00 + offset into register A. 342 | func ldhFromMem(state *State) instruction { 343 | // M-Cycle 0: Fetch instruction 344 | 345 | return func(state *State) instruction { 346 | // M-Cycle 1: Read offset from PC location 347 | 348 | offset := state.incrementPC() 349 | 350 | return func(state *State) instruction { 351 | // M-Cycle 2: Read from memory 352 | 353 | fromMem := state.mmu.at(0xFF00 + uint16(offset)) 354 | state.regA.set(fromMem) 355 | 356 | return nil 357 | } 358 | } 359 | } 360 | 361 | // ldhl loads an 8-bit immediate value and adds it to the stack pointer. The 362 | // result is stored in register HL. 363 | func ldhl(state *State) instruction { 364 | // M-Cycle 0: Fetch instruction 365 | 366 | return func(state *State) instruction { 367 | // M-Cycle 1: Read immediate value and do operation 368 | 369 | immUnsigned := state.incrementPC() 370 | imm := int8(immUnsigned) 371 | 372 | spVal := state.regSP.get() 373 | 374 | // This instruction's behavior for the carry and half carry flags is very 375 | // weird. 376 | // 377 | // When checking for a carry and half carry, the immediate value is treated 378 | // as _unsigned_ for some reason and only the lowest 8 bits of the stack 379 | // pointer are considered. HL does not play into this calculation at all. 380 | lowerSP, _ := split16(spVal) 381 | state.setHalfCarryFlag(isHalfCarry(lowerSP, immUnsigned)) 382 | state.setCarryFlag(isCarry(lowerSP, immUnsigned)) 383 | 384 | state.regHL.set(uint16(int(imm) + int(spVal))) 385 | 386 | state.setZeroFlag(false) 387 | state.setSubtractFlag(false) 388 | 389 | return func(state *State) instruction { 390 | // M-Cycle 2: Internal delay 391 | 392 | return nil 393 | } 394 | } 395 | } 396 | 397 | // makePUSH creates an instruction that decrements the stack pointer by 2, then 398 | // puts the value of the given register at its position. 399 | func makePUSH(reg register16) instruction { 400 | return func(state *State) instruction { 401 | // M-Cycle 0: Fetch operation 402 | 403 | return func(state *State) instruction { 404 | // M-Cycle 1: Internal delay 405 | 406 | return func(state *State) instruction { 407 | // M-Cycle 2: Write upper byte to stack 408 | 409 | _, upper := split16(reg.get()) 410 | state.pushToStack(upper) 411 | 412 | return func(state *State) instruction { 413 | // M-Cycle 3: Write lower byte to stack 414 | 415 | lower, _ := split16(reg.get()) 416 | state.pushToStack(lower) 417 | 418 | return nil 419 | } 420 | } 421 | } 422 | } 423 | } 424 | 425 | // makePOP creates an instruction that loads the two bytes at the top of the 426 | // stack in the given register and increments the stack pointer by 2. 427 | func makePOP(reg register16) instruction { 428 | return func(state *State) instruction { 429 | // M-Cycle 0: Fetch instruction 430 | 431 | return func(state *State) instruction { 432 | // M-Cycle 1: Pop lower byte from stack 433 | 434 | reg.setLower(state.popFromStack()) 435 | 436 | return func(state *State) instruction { 437 | // M-Cycle 2: Pop upper byte from stack 438 | 439 | reg.setUpper(state.popFromStack()) 440 | 441 | return nil 442 | } 443 | } 444 | } 445 | } 446 | -------------------------------------------------------------------------------- /gameboy/mbc1.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | import "fmt" 4 | 5 | // mbc1 implements an MBC1 memory bank controller. An MBC1 can support up to 6 | // 125 16K ROM banks, up to 4 8K RAM banks, and potentially a battery backup. 7 | type mbc1 struct { 8 | // romBanks contains cartridge ROM banks, indexed by their bank number. 9 | romBanks [][]uint8 10 | // ramBanks contains all extra RAM banks, indexed by their bank number. 11 | // These extra RAM banks are supplied by the cartridge. 12 | ramBanks [][]uint8 13 | 14 | // bankReg1 is set by writing to 0x2000-0x3FFF. It is a 5-bit value. It is 15 | // used to specify the lower 5 bits of the desired ROM bank. 16 | bankReg1 uint8 17 | // bankReg2 is set by writing to 0x6000-0x7FFF. It is a 2-bit value. In 18 | // bank selection mode 0, it is used to specify the upper 2 bits of the 19 | // desired ROM bank in 0x4000-0x7FFF. In bank selection mode 1, it is used 20 | // to specify the upper 2 bits of the desired ROM bank in 0x0000-0x3FFF and 21 | // the lower 2 bits of the desired RAM bank. 22 | bankReg2 uint8 23 | 24 | // True if RAM turned on. 25 | ramEnabled bool 26 | // Controls how any bits written to 0x4000-0x6000 are interpreted. If this 27 | // value is 0, they are interpreted as the upper bits of the ROM bank 28 | // selection, where the lower bits are whatever is written to 29 | // 0x4000-0x8000. If this value is 1, they are interpreted as the RAM bank 30 | // selection. 31 | bankSelectionMode uint8 32 | } 33 | 34 | func newMBC1(header romHeader, cartridgeData []uint8) *mbc1 { 35 | var m mbc1 36 | 37 | m.romBanks = makeROMBanks(header.romSizeType, cartridgeData) 38 | m.ramBanks = makeRAMBanks(header.ramSizeType) 39 | 40 | // The default bank values 41 | m.bankReg1 = 0x1 42 | m.bankReg2 = 0x0 43 | 44 | return &m 45 | } 46 | 47 | // at provides access to the MBC1 banked ROM and RAM. 48 | func (m *mbc1) at(addr uint16) uint8 { 49 | switch { 50 | case inBank0ROMArea(addr): 51 | switch m.bankSelectionMode { 52 | case 0: 53 | // This area is always bank 0 in this mode 54 | return m.romBanks[0][addr] 55 | case 1: 56 | // The bank here is specified by the value written to 57 | // 0x4000-0x5FFF, shifted 5 bits over. 58 | bank := int(m.bankReg2 << 5) 59 | // If an out-of-bounds ROM bank is selected, the value will "wrap 60 | // around" 61 | bank %= len(m.romBanks) 62 | return m.romBanks[bank][addr] 63 | default: 64 | panic(fmt.Sprintf("invalid bank selection mode %#x", m.bankSelectionMode)) 65 | } 66 | case inBankedROMArea(addr): 67 | // The current bank is calculated by combining bank registers 1 and 2 68 | bank := int(m.bankReg1 | (m.bankReg2 << 5)) 69 | // If an out-of-bounds ROM bank is selected, the value will "wrap 70 | // around" 71 | bank %= len(m.romBanks) 72 | 73 | return m.romBanks[bank][addr-bankedROMAddr] 74 | case inBankedRAMArea(addr): 75 | if m.ramEnabled && len(m.ramBanks) > 0 { 76 | switch m.bankSelectionMode { 77 | case 0: 78 | // In this mode, bank 0 is always used 79 | return m.ramBanks[0][addr-bankedRAMAddr] 80 | case 1: 81 | // The current bank is equal to the value of bank register 2 82 | bank := int(m.bankReg2) 83 | // If an out-of-bounds RAM bank is selected, the value will "wrap 84 | // around" 85 | bank %= len(m.ramBanks) 86 | 87 | return m.ramBanks[bank][addr-bankedRAMAddr] 88 | default: 89 | panic(fmt.Sprintf("invalid bank selection mode %v", m.bankSelectionMode)) 90 | } 91 | } else { 92 | // The default value for disabled RAM 93 | return 0xFF 94 | } 95 | default: 96 | panic(fmt.Sprintf("MBC1 is unable to handle reads to address %#x", addr)) 97 | } 98 | } 99 | 100 | // set can do many things with the MBC1. 101 | // 102 | // If the target address is within ROM, it will control some aspect of the MBC1 103 | // (like switching banks), depending on the address itself and the given value. 104 | // 105 | // If the target address is within the RAM bank area, the selected RAM bank 106 | // will be written to. 107 | func (m *mbc1) set(addr uint16, val uint8) { 108 | if addr < 0x2000 { 109 | // The RAM enable/disable area. Used to turn on and off access to 110 | // banked RAM. 111 | lower, _ := split(val) 112 | // 0x0A is the magic number to turn this device on 113 | m.ramEnabled = lower == 0x0A 114 | } else if addr < 0x4000 { 115 | // ROM Bank 01-7F register 116 | // This area controls the value of what I call "bank register 1". For 117 | // more information on what this does, see the field's documentation on 118 | // the mbc1 type. 119 | m.bankReg1 = val & 0x1F 120 | 121 | // This register cannot have 0x0 written to it. A write of 0x0 will be 122 | // interpreted as 0x1. This means that banks 0x0, 0x20, 0x40, and 0x60 123 | // are inaccessible 124 | if m.bankReg1 == 0x00 { 125 | m.bankReg1 = 0x01 126 | } 127 | } else if addr < 0x6000 { 128 | // RAM Bank Number or Upper Bits of ROM Bank Number "register" 129 | // This area controls the value of what I call "bank register 2". For 130 | // more information on what this does, see the field's documentation on 131 | // the mbc1 type. 132 | // This "register" is only 2 bits in size, get those 2 bits 133 | m.bankReg2 = val & 0x03 134 | } else if addr < 0x8000 { 135 | // The ROM/RAM Mode Select "register" 136 | // This changes which mode the RAM Bank Number or Upper Bits of ROM 137 | // Bank Number "register" uses. 138 | m.bankSelectionMode = val & 0x01 139 | } else if addr >= ramAddr && addr < ramMirrorAddr { 140 | // Bank 0 RAM area 141 | m.ramBanks[0][addr-ramAddr] = val 142 | } else if addr >= bankedRAMAddr && addr < ramAddr { 143 | // Banked RAM 144 | if len(m.ramBanks) > 0 && m.ramEnabled { 145 | switch m.bankSelectionMode { 146 | case 0: 147 | // In this mode, bank 0 is always used 148 | m.ramBanks[0][addr-bankedRAMAddr] = val 149 | case 1: 150 | // The current RAM bank is controlled by bank register 2 151 | bank := int(m.bankReg2) 152 | // If an out-of-bounds RAM bank is selected, the value will "wrap 153 | // around" 154 | bank %= len(m.ramBanks) 155 | m.ramBanks[bank][addr-bankedRAMAddr] = val 156 | default: 157 | panic(fmt.Sprintf("invalid bank selection mode %v", m.bankSelectionMode)) 158 | } 159 | } 160 | } else { 161 | panic(fmt.Sprintf("It isn't the MBC1's job to handle writes to address %#x", addr)) 162 | } 163 | } 164 | 165 | func (m *mbc1) dumpBatteryBackedRAM() []uint8 { 166 | var dump []uint8 167 | 168 | for _, bank := range m.ramBanks { 169 | for _, val := range bank { 170 | dump = append(dump, val) 171 | } 172 | } 173 | 174 | return dump 175 | } 176 | 177 | func (m *mbc1) loadBatteryBackedRAM(dump []uint8) { 178 | for bankNum, bank := range m.ramBanks { 179 | for i := range bank { 180 | dumpIndex := len(bank)*bankNum + i 181 | if i >= len(dump) { 182 | panic(fmt.Sprintf("RAM dump is too small for this MBC: %v", i)) 183 | } 184 | bank[i] = dump[dumpIndex] 185 | } 186 | } 187 | } 188 | -------------------------------------------------------------------------------- /gameboy/mbc3.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | import "fmt" 4 | 5 | // mbc3 implements the MBC3 memory bank controller. An MBC3 can support up to 6 | // 128 16K ROM banks, up to up to 8 8K RAM banks, potentially a real time 7 | // clock (RTC), and potentially a battery backup. 8 | type mbc3 struct { 9 | // romBanks contains cartridge ROM banks, indexed by their bank number. 10 | romBanks [][]uint8 11 | // ramBanks contains all extra RAM banks, indexed by their bank number. 12 | // These extra RAM banks are supplied by the cartridge. 13 | ramBanks [][]uint8 14 | 15 | // True if this MBC has a real time clock in it. 16 | hasRTC bool 17 | 18 | // The currently selected ROM bank. 19 | currROMBank uint8 20 | // The currently selected RAM bank. 21 | currRAMBank uint8 22 | // True if RAM turned on. 23 | ramAndRTCEnabled bool 24 | // True if the RTC is "latched", meaning that its value should be frozen in 25 | // memory. 26 | rtcLatched bool 27 | } 28 | 29 | func newMBC3( 30 | header romHeader, 31 | cartridgeData []uint8, 32 | hasRTC bool) *mbc3 { 33 | 34 | var m mbc3 35 | 36 | if hasRTC { 37 | panic("MBC3 devices with an RTC are not supported") 38 | } 39 | 40 | m.hasRTC = hasRTC 41 | 42 | m.romBanks = makeROMBanks(header.romSizeType, cartridgeData) 43 | m.ramBanks = makeRAMBanks(header.ramSizeType) 44 | 45 | m.currROMBank = 1 46 | m.currRAMBank = 0 47 | 48 | return &m 49 | } 50 | 51 | // at provides access to the MBC3 banked ROM, banked RAM, and real time clock. 52 | func (m *mbc3) at(addr uint16) uint8 { 53 | switch { 54 | case inBank0ROMArea(addr): 55 | return m.romBanks[0][addr] 56 | case inBankedROMArea(addr): 57 | bank := m.currROMBank 58 | if bank == 0 { 59 | // Bank 0 is not directly selectable, map to bank 1 instead 60 | bank = 1 61 | } 62 | // If an out-of-bounds ROM bank is selected, the value will "wrap 63 | // around" 64 | bank %= uint8(len(m.romBanks)) 65 | return m.romBanks[bank][addr-bankedROMAddr] 66 | case inBankedRAMArea(addr): 67 | // Banked RAM or Real Time Clock register area 68 | if m.ramAndRTCEnabled && len(m.ramBanks) > 0 { 69 | bank := m.currRAMBank 70 | // If an out-of-bounds RAM bank is selected, the value will "wrap 71 | // around" 72 | bank %= uint8(len(m.ramBanks)) 73 | 74 | return m.ramBanks[bank][addr-bankedRAMAddr] 75 | } else { 76 | // The default value for disabled or unavailable RAM 77 | return 0xFF 78 | } 79 | default: 80 | panic(fmt.Sprintf("MBC3 is unable to handle reads to address %#x", addr)) 81 | } 82 | } 83 | 84 | // set can do many things with the MBC3. 85 | // 86 | // If the target address is within ROM, it will control some aspect of the MBC3 87 | // (like switching banks or controlling the RTC), depending on the address 88 | // itself and the given value. 89 | // 90 | // If the target address is within the RAM bank area, the selected RAM bank 91 | // will be written to. 92 | func (m *mbc3) set(addr uint16, val uint8) { 93 | if addr < 0x2000 { 94 | // The RAM and RTC enable/disable area. Used to turn on and off access 95 | // to banked RAM and the RTC. These two devices are positioned in the 96 | // same memory location, so another option (below) chooses which one is 97 | // actually exposed. 98 | lower, _ := split(val) 99 | // 0x0A is the magic number to turn these devices on 100 | m.ramAndRTCEnabled = lower == 0x0A 101 | } else if addr < 0x4000 { 102 | // ROM Bank Number "register" 103 | // This area is used to specify all 7 bits of the desired ROM bank 104 | // number, which the MBC will switch to. 105 | 106 | // This "register" is only 7 bits in size, get those 7 bits 107 | bank := val & 0x7F 108 | // This register cannot have 0x0 written to it. A write of 0x0 will be 109 | // interpreted as 0x1. This means that bank 0x0 is not inaccessible 110 | if bank == 0x00 { 111 | bank = 0x01 112 | } 113 | m.currROMBank = bank 114 | } else if addr < 0x6000 { 115 | // RAM Bank Number or RTC Register Select 116 | // Writing a value of 0x00 to 0x07 in this register will switch to the 117 | // RAM bank of that number. Writing a value of 0x08 to 0x0C will map 118 | // that corresponding RTC register to the RAM bank address space. 119 | if val <= 0x07 { 120 | m.currRAMBank = val 121 | } else if val <= 0x0C && m.hasRTC { 122 | panic("Attempt to control the RTC register, but RTC is not supported") 123 | } else { 124 | panic(fmt.Sprintf("Unexpected value in RAM Bank/RTC Register Select %#x", val)) 125 | } 126 | } else if addr < 0x8000 { 127 | // Latch Clock Data Register 128 | // Writing a 0x01 to this area will lock the RTC at its current value 129 | // until a 0x00 is written. Note that the RTC itself continues to tick 130 | // even while this latch is set. Only the in-memory value remains 131 | // unchanged. 132 | if m.hasRTC { 133 | m.rtcLatched = val == 0x01 134 | fmt.Println("Warning: Attempt to latch the RTC register, but RTC is not supported") 135 | } 136 | } else if inBankedRAMArea(addr) { 137 | if m.ramAndRTCEnabled { 138 | m.ramBanks[m.currRAMBank][addr-bankedRAMAddr] = val 139 | } else { 140 | fmt.Printf("Attempt to write to banked RAM when RAM is disabled: At address %#x\n", addr) 141 | } 142 | } else { 143 | panic(fmt.Sprintf("The MBC3 should not have been notified of a write "+ 144 | "to address %#x", addr)) 145 | } 146 | } 147 | 148 | func (m *mbc3) dumpBatteryBackedRAM() []uint8 { 149 | var dump []uint8 150 | 151 | for _, bank := range m.ramBanks { 152 | for _, val := range bank { 153 | dump = append(dump, val) 154 | } 155 | } 156 | 157 | return dump 158 | } 159 | 160 | func (m *mbc3) loadBatteryBackedRAM(dump []uint8) { 161 | for bankNum, bank := range m.ramBanks { 162 | for i := range bank { 163 | dumpIndex := len(bank)*bankNum + i 164 | if i >= len(dump) { 165 | panic(fmt.Sprintf("RAM dump is too small for this MBC: %v", i)) 166 | } 167 | bank[i] = dump[dumpIndex] 168 | } 169 | } 170 | } 171 | -------------------------------------------------------------------------------- /gameboy/mbc5.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | import "fmt" 4 | 5 | type mbc5 struct { 6 | // romBanks contains cartridge ROM banks, indexed by their bank number. 7 | romBanks [][]uint8 8 | // ramBanks contains all extra RAM banks, indexed by their bank number. 9 | // These extra RAM banks are supplied by the cartridge. 10 | ramBanks [][]uint8 11 | 12 | // The currently selected ROM bank. 13 | currROMBank uint16 14 | // The currently selected RAM bank. 15 | currRAMBank uint8 16 | // True if RAM turned on. 17 | ramEnabled bool 18 | 19 | // If true, then this cartridge is equipped with rumble. 20 | hasRumble bool 21 | // If true, the rumble motor is currently running. 22 | rumbling bool 23 | } 24 | 25 | func newMBC5( 26 | header romHeader, 27 | cartridgeData []uint8, 28 | hasRumble bool) *mbc5 { 29 | 30 | var m mbc5 31 | 32 | m.romBanks = makeROMBanks(header.romSizeType, cartridgeData) 33 | m.ramBanks = makeRAMBanks(header.ramSizeType) 34 | 35 | m.currROMBank = 1 36 | m.currRAMBank = 0 37 | 38 | m.hasRumble = hasRumble 39 | 40 | return &m 41 | } 42 | 43 | // at provides access to the MBC5 banked ROM and RAM. 44 | func (m *mbc5) at(addr uint16) uint8 { 45 | switch { 46 | case inBank0ROMArea(addr): 47 | return m.romBanks[0][addr] 48 | case inBankedROMArea(addr): 49 | bank := m.currROMBank 50 | // If an out-of-bounds ROM bank is selected, the value will "wrap 51 | // around" 52 | bank %= uint16(len(m.romBanks)) 53 | return m.romBanks[bank][addr-bankedROMAddr] 54 | case inBankedRAMArea(addr): 55 | if m.ramEnabled && len(m.ramBanks) > 0 { 56 | bank := m.currRAMBank 57 | // If an out-of-bounds RAM bank is selected, the value will "wrap 58 | // around" 59 | bank %= uint8(len(m.ramBanks)) 60 | 61 | return m.ramBanks[bank][addr-bankedRAMAddr] 62 | } else { 63 | // The default value for disabled or unavailable RAM 64 | return 0xFF 65 | } 66 | default: 67 | panic(fmt.Sprintf("MBC5 is unable to handle reads to address %#x", addr)) 68 | } 69 | } 70 | 71 | // set can do many things with the MBC5. 72 | // 73 | // Writing to special areas in ROM can turn on and off cartridge RAM, specify 74 | // the current ROM bank, specify the current RAM bank, and turn on and off 75 | // rumble. 76 | // 77 | // If the target address is within the RAM bank area, the selected RAM bank 78 | // will be written to. 79 | func (m *mbc5) set(addr uint16, val uint8) { 80 | if addr < 0x2000 { 81 | // The RAM enable/disable area. Used to turn on and off access to 82 | // banked RAM. 83 | lower, _ := split(val) 84 | // 0x0A is the magic number to turn this device on 85 | m.ramEnabled = lower == 0x0A 86 | } else if addr < 0x3000 { 87 | // ROM Bank (Low bits) 88 | // This are is used to specify the lower 8 bits of the desired ROM bank 89 | // number, which the MBC will switch to. 90 | _, upper := split16(m.currROMBank) 91 | m.currROMBank = combine16(val, upper) 92 | } else if addr < 0x4000 { 93 | // ROM Bank (High bits) 94 | // This are is used to specify the upper 8 bits of the desired ROM bank 95 | // number, which the MBC will switch to. 96 | // The Cycle-Accurate Game Boy Docs claim that this area is a mirror to 97 | // ROM Bank (Low bits) when fewer than 256 banks are used. I'm doubtful 98 | // but who am I to say. 99 | if len(m.romBanks) < 256 { 100 | // This area is just a mirror for ROM Bank (Low bits) 101 | _, upper := split16(m.currROMBank) 102 | m.currROMBank = combine16(val, upper) 103 | } else { 104 | // Actually set the high bits of the ROM bank 105 | lower, _ := split16(m.currROMBank) 106 | m.currROMBank = combine16(lower, val) 107 | } 108 | } else if addr < 0x6000 { 109 | // RAM Bank and Rumble Enable 110 | if m.hasRumble { 111 | // Rumble on/off is mapped to bit 4 on this register. As a result, 112 | // only bits 0-3 are mapped to RAM bank switching, limiting the 113 | // maximum number of RAM banks these cartridges can have. 114 | m.currRAMBank = val & 0x0F 115 | m.rumbling = val&0x10 == 0x10 116 | } else { 117 | m.currRAMBank = val 118 | } 119 | } else if addr < 0x8000 { 120 | // This area of ROM doesn't do anything when written to 121 | } else if inBankedRAMArea(addr) { 122 | if m.ramEnabled { 123 | m.ramBanks[m.currRAMBank][addr-bankedRAMAddr] = val 124 | } else { 125 | fmt.Printf("Attempt to write to banked RAM when RAM is disabled: At address %#x\n", addr) 126 | } 127 | } else { 128 | panic(fmt.Sprintf("MBC5 is unable to handle writes to address %#x", addr)) 129 | } 130 | } 131 | 132 | func (m *mbc5) dumpBatteryBackedRAM() []uint8 { 133 | var dump []uint8 134 | 135 | for _, bank := range m.ramBanks { 136 | for _, val := range bank { 137 | dump = append(dump, val) 138 | } 139 | } 140 | 141 | return dump 142 | } 143 | 144 | func (m *mbc5) loadBatteryBackedRAM(dump []uint8) { 145 | for bankNum, bank := range m.ramBanks { 146 | for i := range bank { 147 | dumpIndex := len(bank)*bankNum + i 148 | if i >= len(dump) { 149 | panic(fmt.Sprintf("RAM dump is too small for this MBC: %v", i)) 150 | } 151 | bank[i] = dump[dumpIndex] 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /gameboy/mmu.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | import ( 4 | "fmt" 5 | ) 6 | 7 | // dmaCycleLength is the number of cycles a DMA transfer takes. 8 | const dmaCycleLength = 671 9 | 10 | // mmu is the memory management unit. It handles all operations that are common 11 | // to all Game Boy games and defers to the cartridge's memory bank controller 12 | // in cartridge-specific cases. 13 | type mmu struct { 14 | memory *[0x10000]uint8 15 | 16 | // bootROM refers to the first 256 bytes of ROM, which is where the Game 17 | // Boy boot sequence is stored. 18 | bootROM []uint8 19 | // ram is the built-in RAM on the device. 20 | ram []uint8 21 | // videoRAM is where sprite and tile data is stored for the video 22 | // controller to access. 23 | videoRAM []uint8 24 | // oamRAM is where sprite attribute data is stored for the video controller 25 | // to access. 26 | oamRAM []uint8 27 | // ioRAM is where memory registers are mapped to for controlling and 28 | // reading various aspects of the hardware. 29 | ioRAM []uint8 30 | // hram is a special general-purpose RAM area 31 | hram []uint8 32 | 33 | // bootROMEnabled is true if the boot ROM is available at 0x0000 to 0x0100. 34 | // The boot ROM itself turns this off before the game starts. If this is 35 | // turned off, 0x0000 to 0x0100 maps to ROM bank 0. 36 | bootROMEnabled bool 37 | // dmaActive is true if a DMA transfer is happening. 38 | dmaActive bool 39 | // dmaCursor is the next memory address to be transferred. 40 | dmaCursor uint16 41 | // dmaCycleCount is the number of a cycles a DMA transfer takes. 42 | dmaCycleCount int 43 | 44 | // subscribers maps a memory address to a function that should be called 45 | // when that memory address is written to. The value written to memory may 46 | // be modified by this function. 47 | subscribers map[uint16]onWriteFunc 48 | 49 | // mbc is the memory bank controller that this MMU will use. 50 | mbc mbc 51 | 52 | // Components that will be consulted for their internal values when certain 53 | // addresses are read from. 54 | timers *timers 55 | videoController *videoController 56 | interruptManager *interruptManager 57 | 58 | db *debugger 59 | } 60 | 61 | // mbc describes a memory bank controller. 62 | type mbc interface { 63 | set(addr uint16, val uint8) 64 | at(addr uint16) uint8 65 | } 66 | 67 | // batteryBackedMBC is a memory bank controller with RAM that is 68 | // battery-backed, meaning that it can be saved after the device 69 | // is powered off. 70 | type batteryBackedMBC interface { 71 | mbc 72 | // dumpBatteryBackedRAM returns a dump of all RAM that is battery-backed in 73 | // the MBC. 74 | dumpBatteryBackedRAM() []uint8 75 | // loadBatteryBackedRAM loads the given data into all battery-backed RAM in 76 | // the MBC. 77 | loadBatteryBackedRAM(dump []uint8) 78 | } 79 | 80 | type onWriteFunc func(addr uint16, val uint8) uint8 81 | 82 | func newMMU(bootROM []byte, cartridgeData []uint8, mbc mbc) *mmu { 83 | if len(bootROM) != bootROMEndAddr { 84 | panic(fmt.Sprintf("invalid boot ROM size %#x", len(bootROM))) 85 | } 86 | 87 | m := &mmu{ 88 | memory: &[0x10000]uint8{}, 89 | bootROM: bootROM, 90 | ram: make([]uint8, ramMirrorAddr-ramAddr), 91 | bootROMEnabled: true, 92 | videoRAM: make([]uint8, bankedRAMAddr-videoRAMAddr), 93 | oamRAM: make([]uint8, invalidArea2Addr-oamRAMAddr), 94 | ioRAM: make([]uint8, hramAddr-ioAddr), 95 | hram: make([]uint8, lastAddr-hramAddr+1), 96 | subscribers: make(map[uint16]onWriteFunc), 97 | mbc: mbc, 98 | } 99 | 100 | m.subscribeTo(bootROMDisableAddr, m.onBootROMDisableWrite) 101 | m.subscribeTo(dmaAddr, m.onDMAWrite) 102 | 103 | return m 104 | } 105 | 106 | // at returns the value in the given address. 107 | func (m *mmu) at(addr uint16) uint8 { 108 | if m.db != nil { 109 | m.db.memReadHook(addr) 110 | } 111 | 112 | switch { 113 | case isUnmappedAddress[addr]: 114 | // Unmapped areas of memory always read 0xFF 115 | return 0xFF 116 | case inBootROMArea(addr): 117 | // Either the boot ROM if it's enabled, or a ROM access 118 | if m.bootROMEnabled { 119 | return m.bootROM[addr-bootROMAddr] 120 | } else { 121 | return m.mbc.at(addr) 122 | } 123 | case inBank0ROMArea(addr): 124 | return m.mbc.at(addr) 125 | case inBankedROMArea(addr): 126 | // Some additional ROM bank, controlled by the MBC 127 | return m.mbc.at(addr) 128 | case inBankedRAMArea(addr): 129 | // The MBC handles RAM banking and availability 130 | return m.mbc.at(addr) 131 | case inRAMMirrorArea(addr): 132 | // A bank 0 RAM mirror 133 | return m.memory[addr-(ramMirrorAddr-ramAddr)] 134 | case inInvalidArea(addr): 135 | // Invalid area, which always returns 0xFF since it's the MMU's default 136 | // value 137 | if printWarnings { 138 | fmt.Printf("Warning: Read from invalid memory address %#x\n", addr) 139 | } 140 | return 0xFF 141 | case addr == ifAddr: 142 | return m.interruptManager.interruptFlags 143 | case addr == ieAddr: 144 | return m.interruptManager.interruptEnable 145 | case addr == dividerAddr: 146 | return m.timers.divider 147 | case addr == timaAddr: 148 | return m.timers.tima 149 | case addr == tacAddr: 150 | return m.timers.tac 151 | case addr == tmaAddr: 152 | return m.timers.tma 153 | case addr == lyAddr: 154 | return m.videoController.ly 155 | case addr == lycAddr: 156 | return m.videoController.lyc 157 | default: 158 | return m.memory[addr] 159 | } 160 | } 161 | 162 | // tick progresses the MMU by one m-cycle. 163 | func (m *mmu) tick() { 164 | for i := 0; i < ticksPerMCycle; i++ { 165 | if m.dmaActive { 166 | // Work on a DMA transfer 167 | lower, _ := split16(m.dmaCursor) 168 | if lower <= 0x9F { 169 | // Transfer a byte 170 | m.setNoNotify(oamRAMAddr+uint16(lower), m.at(m.dmaCursor)) 171 | 172 | m.dmaCursor++ 173 | } 174 | // Wait for the DMA transfer to finish 175 | if m.dmaCycleCount >= dmaCycleLength { 176 | m.dmaActive = false 177 | } else { 178 | m.dmaCycleCount++ 179 | } 180 | } 181 | } 182 | } 183 | 184 | // set requests the MMU to set the value at the given address to the given 185 | // value. This method notifies any subscribed devices about this write, meaning 186 | // that side effects may occur. 187 | func (m *mmu) set(addr uint16, val uint8) { 188 | // Unmapped addresses cannot be written to 189 | if isUnmappedAddress[addr] { 190 | return 191 | } 192 | 193 | // Notify any subscribers of this event 194 | if onWrite, ok := m.subscribers[addr]; ok { 195 | val = onWrite(addr, val) 196 | } 197 | 198 | m.setNoNotify(addr, val) 199 | } 200 | 201 | // setNoNotify requests the MMU to set the value at the given address to the 202 | // given value. Subscribed devices are not notified. This is useful for devices 203 | // that might incorrectly trigger themselves when writing to a place in memory. 204 | func (m *mmu) setNoNotify(addr uint16, val uint8) { 205 | if m.db != nil { 206 | m.db.memWriteHook(addr, val) 207 | } 208 | 209 | switch { 210 | case inBank0ROMArea(addr) || inBankedROMArea(addr): 211 | // "Writes" to ROM areas are used to control MBCs 212 | m.mbc.set(addr, val) 213 | case inBankedRAMArea(addr): 214 | // The MBC handles RAM banking and availability 215 | m.mbc.set(addr, val) 216 | case inInvalidArea(addr): 217 | if printWarnings { 218 | fmt.Printf("Warning: Write to invalid area %#x\n", addr) 219 | } 220 | default: 221 | m.memory[addr] = val 222 | } 223 | } 224 | 225 | // subscribeTo sets up the given function to be called when a value is written 226 | // to the given address. 227 | func (m *mmu) subscribeTo(addr uint16, onWrite onWriteFunc) { 228 | if _, ok := m.subscribers[addr]; ok { 229 | panic(fmt.Sprintf("attempt to have multiple subscribers to one address %#x", addr)) 230 | } else { 231 | m.subscribers[addr] = onWrite 232 | } 233 | } 234 | 235 | // onDMAWrite triggers when the special DMA address is written to. This 236 | // triggers a DMA transfer, where data is copied into OAM RAM. 237 | func (m *mmu) onDMAWrite(addr uint16, val uint8) uint8 { 238 | // Nothing happens if a DMA transfer is already happening 239 | if !m.dmaActive { 240 | // TODO(velovix): Lock all memory except HRAM? 241 | // Start a DMA transfer 242 | m.dmaActive = true 243 | // Use the value as the higher byte in the source address 244 | m.dmaCursor = uint16(val) << 8 245 | m.dmaCycleCount = 0 246 | } 247 | 248 | return val 249 | } 250 | 251 | // onBootROMDisableWrite triggers when the boot ROM disable register is written 252 | // to. It disables the boot ROM. 253 | func (m *mmu) onBootROMDisableWrite(addr uint16, val uint8) uint8 { 254 | if m.bootROMEnabled { 255 | fmt.Println("Disabled boot ROM") 256 | m.bootROMEnabled = false 257 | } 258 | 259 | // This register always reads 0xFF 260 | return 0xFF 261 | } 262 | -------------------------------------------------------------------------------- /gameboy/mmu_utils.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | import "fmt" 4 | 5 | // makeROMBanks creates the necessary amount of ROM banks as specified by the 6 | // given ROM size type, then returns it as a map whose key is a ROM bank number 7 | // and whose value is the corresponding ROM bank. 8 | func makeROMBanks(romSizeType uint8, cartridgeData []uint8) [][]uint8 { 9 | var romBankCount int 10 | 11 | switch romSizeType { 12 | case 0x00: 13 | // A single ROM bank, no switching going on 14 | romBankCount = 2 15 | case 0x01: 16 | // Four ROM banks, 16 KB in size 17 | romBankCount = 4 18 | case 0x02: 19 | // Eight ROM banks, 16 KB in size 20 | romBankCount = 8 21 | case 0x03: 22 | // Sixteen ROM banks, 16 KB in size 23 | romBankCount = 16 24 | case 0x04: 25 | // Thirty-two ROM banks, 16 KB in size 26 | romBankCount = 32 27 | case 0x05: 28 | // Sixty-four ROM banks, 16 KB in size 29 | romBankCount = 64 30 | case 0x06: 31 | // 128 ROM banks, 16 KB in size 32 | romBankCount = 128 33 | case 0x07: 34 | // 256 ROM banks, 16 KB in size 35 | romBankCount = 256 36 | case 0x08: 37 | // 512 ROM banks, 16 KB in size 38 | romBankCount = 512 39 | default: 40 | panic(fmt.Sprintf("Unsupported ROM size type %v", romSizeType)) 41 | } 42 | 43 | romBanks := make([][]uint8, romBankCount) 44 | 45 | // Create the ROM banks 46 | for i := 0; i < romBankCount; i++ { 47 | romBanks[i] = make([]uint8, 0x4000) 48 | } 49 | 50 | // Put cartridge data into each bank 51 | for bank, data := range romBanks { 52 | startAddr := 0x4000 * int(bank) 53 | for i := startAddr; i < startAddr+0x4000; i++ { 54 | data[i-startAddr] = cartridgeData[i] 55 | } 56 | } 57 | 58 | return romBanks 59 | } 60 | 61 | // makeRAMBanks creates the necessary amount of external RAM banks as specified 62 | // by the given RAM size type, then returns it as a map whose key is a RAM bank 63 | // number and whose value is the corresponding RAM bank. 64 | func makeRAMBanks(ramSizeType uint8) (ramBanks [][]uint8) { 65 | switch ramSizeType { 66 | case 0x00: 67 | // No bank 68 | case 0x01: 69 | // One 2 KB bank 70 | ramBanks = append(ramBanks, make([]uint8, 2000)) 71 | case 0x02: 72 | // One 8 KB bank 73 | ramBanks = append(ramBanks, make([]uint8, 0x2000)) 74 | case 0x03: 75 | // Four 8 KB banks 76 | for i := 0; i < 4; i++ { 77 | ramBanks = append(ramBanks, make([]uint8, 0x2000)) 78 | } 79 | case 0x04: 80 | // Sixteen 8 KB banks 81 | for i := 0; i < 16; i++ { 82 | ramBanks = append(ramBanks, make([]uint8, 0x2000)) 83 | } 84 | case 0x05: 85 | // Eight 8 KB banks 86 | for i := 0; i < 8; i++ { 87 | ramBanks = append(ramBanks, make([]uint8, 0x2000)) 88 | } 89 | default: 90 | panic(fmt.Sprintf("Unknown RAM size type %v", ramSizeType)) 91 | } 92 | 93 | return ramBanks 94 | } 95 | 96 | func inBootROMArea(addr uint16) bool { 97 | return addr < bootROMEndAddr 98 | } 99 | 100 | func inBank0ROMArea(addr uint16) bool { 101 | return addr < bankedROMAddr 102 | } 103 | 104 | func inBankedROMArea(addr uint16) bool { 105 | return addr >= bankedROMAddr && addr < videoRAMAddr 106 | } 107 | 108 | func inVideoRAMArea(addr uint16) bool { 109 | return addr >= videoRAMAddr && addr < bankedRAMAddr 110 | } 111 | 112 | func inBankedRAMArea(addr uint16) bool { 113 | return addr >= bankedRAMAddr && addr < ramAddr 114 | } 115 | 116 | func inRAMArea(addr uint16) bool { 117 | return addr >= ramAddr && addr < ramMirrorAddr 118 | } 119 | 120 | func inRAMMirrorArea(addr uint16) bool { 121 | return addr >= ramMirrorAddr && addr < oamRAMAddr 122 | } 123 | 124 | func inOAMArea(addr uint16) bool { 125 | return addr >= oamRAMAddr && addr < invalidArea2Addr 126 | } 127 | 128 | func inInvalidArea(addr uint16) bool { 129 | return addr >= invalidArea2Addr && addr < ioAddr 130 | } 131 | 132 | func inIOArea(addr uint16) bool { 133 | return addr >= ioAddr && addr < hramAddr 134 | } 135 | 136 | func inHRAMArea(addr uint16) bool { 137 | return addr >= hramAddr 138 | } 139 | 140 | // The start of each section of the memory map. 141 | const ( 142 | bootROMAddr = 0x0000 143 | bootROMEndAddr = 0x0100 144 | bank0ROMAddr = 0x0000 145 | bankedROMAddr = 0x4000 146 | videoRAMAddr = 0x8000 147 | bankedRAMAddr = 0xA000 148 | ramAddr = 0xC000 149 | ramMirrorAddr = 0xE000 150 | oamRAMAddr = 0xFE00 151 | // TODO(velovix): Rename this since there's only one invalid area 152 | invalidArea2Addr = 0xFEA0 153 | ioAddr = 0xFF00 154 | hramAddr = 0xFF80 155 | lastAddr = 0xFFFF 156 | ) 157 | -------------------------------------------------------------------------------- /gameboy/rom_header.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | import ( 4 | "bytes" 5 | "fmt" 6 | "strings" 7 | ) 8 | 9 | type romHeader struct { 10 | // The title of the game. 11 | title string 12 | // Some four character code with an unknown meaning. 13 | manufacturerCode string 14 | // Tells us if the game requires the CGB or can run on the original DMG 15 | // Game Boy. 16 | cgbFlag uint8 17 | // A two character code indicating the game's publisher. 18 | licenseeCode string 19 | // Tells us if the game is SGB-compatible. 20 | sgbFlag uint8 21 | // This describes what hardware is available in the cartridge, like a memory 22 | // bank switcher, battery, etc. 23 | cartridgeType uint8 24 | // This tells us how big the ROM is and consequently how many banks are 25 | // being used. This number is not the actual size of the ROM, just a 26 | // sentinel value. 27 | romSizeType uint8 28 | // This tells us how much additional RAM is included in the cartridge. This 29 | // number is not the actual RAM size, just a sentinel value. 30 | ramSizeType uint8 31 | // This tells us if the cartridge is for the Japanese market or not. 32 | destinationCode uint8 33 | // This used to be the way that cartridges would tell us who published the 34 | // game, but future games used the new licensee code instead. 35 | oldLicenseeCode uint8 36 | // This is supposed to tell us the "version" of the game, but is pretty 37 | // much always 0 apparently. 38 | maskROMVersionNumber uint8 39 | // The checksum of all previous header info 40 | headerChecksum uint8 41 | } 42 | 43 | func loadROMHeader(cartridgeData []byte) romHeader { 44 | return romHeader{ 45 | title: noNullTerms(string(cartridgeData[0x0134:0x0140])), 46 | manufacturerCode: noNullTerms(string(cartridgeData[0x013F:0x0143])), 47 | cgbFlag: cartridgeData[0x0143], 48 | licenseeCode: noNullTerms(string(cartridgeData[0x0144:0x0146])), 49 | sgbFlag: cartridgeData[0x0146], 50 | cartridgeType: cartridgeData[0x0147], 51 | romSizeType: cartridgeData[0x0148], 52 | ramSizeType: cartridgeData[0x0149], 53 | destinationCode: cartridgeData[0x014A], 54 | oldLicenseeCode: cartridgeData[0x014B], 55 | maskROMVersionNumber: cartridgeData[0x014C], 56 | headerChecksum: cartridgeData[0x014D], 57 | } 58 | 59 | } 60 | 61 | // noNullTerms removes null terminators from the given string. 62 | func noNullTerms(str string) string { 63 | return strings.Trim(str, "\000") 64 | } 65 | 66 | func (h romHeader) String() string { 67 | str := bytes.NewBufferString("") 68 | 69 | fmt.Fprintln(str, "Cartridge Header:") 70 | fmt.Fprintln(str, " Title:", h.title) 71 | fmt.Fprintln(str, " Manufacturer Code:", h.manufacturerCode) 72 | fmt.Fprintln(str, " CGB Flag:", h.cgbFlag) 73 | fmt.Fprintln(str, " Licensee Code:", h.licenseeCode) 74 | fmt.Fprintln(str, " SGB Flag:", h.sgbFlag) 75 | fmt.Fprintln(str, " Cartridge Type:", h.cartridgeType) 76 | fmt.Fprintln(str, " ROM Size Type:", h.romSizeType) 77 | fmt.Fprintln(str, " RAM Size Type:", h.ramSizeType) 78 | fmt.Fprintln(str, " Destination Code:", h.destinationCode) 79 | fmt.Fprintln(str, " Old Licensee Code:", h.oldLicenseeCode) 80 | fmt.Fprintln(str, " Mask ROM Version Number:", h.maskROMVersionNumber) 81 | fmt.Fprintln(str, " Header Checksum:", h.headerChecksum) 82 | 83 | return str.String() 84 | } 85 | -------------------------------------------------------------------------------- /gameboy/rom_only_mbc.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | import "fmt" 4 | 5 | // romOnlyMBC is the basic memory bank controller. It can scarcely be called a 6 | // memory bank controller at all since there's no switching. This MBC provides 7 | // a single 16K ROM bank. 8 | type romOnlyMBC struct { 9 | // romBanks are the two available ROM banks in this basic controller. 10 | romBanks [][]uint8 11 | } 12 | 13 | func newROMOnlyMBC(header romHeader, cartridgeData []uint8) *romOnlyMBC { 14 | m := &romOnlyMBC{ 15 | romBanks: makeROMBanks(header.romSizeType, cartridgeData), 16 | } 17 | 18 | return m 19 | } 20 | 21 | // at provides access to the bank 1 ROM. 22 | func (m *romOnlyMBC) at(addr uint16) uint8 { 23 | switch { 24 | case inBank0ROMArea(addr): 25 | return m.romBanks[0][addr-bank0ROMAddr] 26 | case inBankedROMArea(addr): 27 | return m.romBanks[1][addr-bankedROMAddr] 28 | case inBankedRAMArea(addr): 29 | if printWarnings { 30 | fmt.Printf("Warning: Read from banked RAM section at address %#x, "+ 31 | "but the ROM-only MBC does not support banked RAM\n", 32 | addr) 33 | } 34 | return 0xFF 35 | default: 36 | panic(fmt.Sprintf("The ROM-only MBC should not have been "+ 37 | "notified of a read to address %#x\n", addr)) 38 | } 39 | } 40 | 41 | // set can update bank 0 RAM, but otherwise does not support any special 42 | // operations like real MBCs do. 43 | func (m *romOnlyMBC) set(addr uint16, val uint8) { 44 | switch { 45 | case inBank0ROMArea(addr) || inBankedROMArea(addr): 46 | if printWarnings { 47 | fmt.Printf("Warning: Ignoring write to ROM space "+ 48 | "at %#x with ROM-only MBC\n", addr) 49 | } 50 | case inBankedRAMArea(addr): 51 | if printWarnings { 52 | fmt.Printf("Warning: Ignoring write to banked RAM space "+ 53 | "at %#x with ROM-only MBC\n", addr) 54 | } 55 | default: 56 | panic(fmt.Sprintf("The ROM-only MBC should not have been "+ 57 | "notified of a write to address %#x\n", addr)) 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /gameboy/rotate_shift_ops.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | import ( 4 | "math/bits" 5 | ) 6 | 7 | // rlca bit rotates register A left by one, which is equivalent to a left bit 8 | // shift where the most significant bit is carried over to the least 9 | // significant bit. This bit is also stored in the carry flag. 10 | func rlca(state *State) instruction { 11 | // M-Cycle 0: Fetch instruction and do operation 12 | 13 | rotated := bits.RotateLeft8(state.regA.get(), 1) 14 | state.regA.set(rotated) 15 | 16 | state.setZeroFlag(false) 17 | state.setSubtractFlag(false) 18 | state.setHalfCarryFlag(false) 19 | 20 | carryBit := state.regA.get() & 0x01 21 | state.setCarryFlag(carryBit == 1) 22 | 23 | return nil 24 | } 25 | 26 | // rla rotates register A left by one, but uses the carry flag as a "bit 8" of 27 | // sorts during this operation. This means that we're essentially rotating 28 | // "(carry flag << 1) | register A". 29 | func rla(state *State) instruction { 30 | // M-Cycle 0: Fetch instruction and do operation 31 | 32 | oldVal := state.regA.get() 33 | // Get the current most significant bit, which will be put in the carry 34 | // flag 35 | var msb uint8 36 | if oldVal&0x80 == 0x80 { 37 | msb = 1 38 | } else { 39 | msb = 0 40 | } 41 | 42 | // Get the current carry bit, which will be put in the least significant 43 | // bit of register A 44 | var oldCarryVal uint8 45 | if state.getCarryFlag() { 46 | oldCarryVal = 1 47 | } else { 48 | oldCarryVal = 0 49 | } 50 | 51 | newVal := oldVal << 1 52 | newVal |= oldCarryVal 53 | state.setCarryFlag(msb == 1) 54 | 55 | state.setZeroFlag(false) 56 | state.setSubtractFlag(false) 57 | state.setHalfCarryFlag(false) 58 | 59 | state.regA.set(newVal) 60 | 61 | return nil 62 | } 63 | 64 | // rrca bit rotates register A right by one, which is equivalent to a right bit 65 | // shift where the least significant bit is carried over to the most 66 | // significant bit. This bit is also stored in the carry flag. 67 | func rrca(state *State) instruction { 68 | // M-Cycle 0: Fetch instruction and do operation 69 | 70 | carryBit := state.regA.get() & 0x01 71 | state.setCarryFlag(carryBit == 1) 72 | 73 | rotated := bits.RotateLeft8(state.regA.get(), -1) 74 | state.regA.set(rotated) 75 | 76 | state.setZeroFlag(false) 77 | state.setSubtractFlag(false) 78 | state.setHalfCarryFlag(false) 79 | 80 | return nil 81 | } 82 | 83 | // rra rotates register A right by one, but uses the carry flag as a "bit -1" 84 | // of sorts during this operation. This means that we're essentially rotating 85 | // "carry flag | (register A << 1)". 86 | func rra(state *State) instruction { 87 | // M-Cycle 0: Fetch instruction and do operation 88 | 89 | oldVal := state.regA.get() 90 | // Get the current least significant bit, which will be put in the carry 91 | // flag 92 | var lsb uint8 93 | if oldVal&0x01 == 0x01 { 94 | lsb = 1 95 | } else { 96 | lsb = 0 97 | } 98 | 99 | // Get the current carry bit, which will be put in the most significant bit 100 | // of register A 101 | var oldCarryVal uint8 102 | if state.getCarryFlag() { 103 | oldCarryVal = 1 104 | } else { 105 | oldCarryVal = 0 106 | } 107 | 108 | newVal := oldVal >> 1 109 | newVal |= (oldCarryVal << 7) 110 | state.setCarryFlag(lsb == 1) 111 | 112 | state.setZeroFlag(false) 113 | state.setSubtractFlag(false) 114 | state.setHalfCarryFlag(false) 115 | 116 | state.regA.set(newVal) 117 | 118 | return nil 119 | } 120 | 121 | // makeSRL creates an instruction that shifts the contents of the given 122 | // register to the right. Bit 0 is shifted to the carry register. Bit 7 is set 123 | // to 0. 124 | func makeSRL(reg register8) instruction { 125 | return func(state *State) instruction { 126 | // M-Cycle 1: Do operation 127 | 128 | regVal := reg.get() 129 | 130 | // Put the least significant bit in the carry register 131 | lsb := regVal & 0x01 132 | state.setCarryFlag(lsb == 1) 133 | 134 | regVal = reg.set(regVal >> 1) 135 | 136 | state.setZeroFlag(regVal == 0) 137 | state.setSubtractFlag(false) 138 | state.setHalfCarryFlag(false) 139 | 140 | return nil 141 | } 142 | } 143 | 144 | // srlMemHL shifts the value at the address in memory specified by register 145 | // HL to the right. Bit 0 is shifted to the carry register. Bit 7 is set to 0. 146 | func srlMemHL(state *State) instruction { 147 | // M-Cycle 1: Fetch CB instruction 148 | 149 | return func(state *State) instruction { 150 | // M-Cycle 2: Read from memory at HL 151 | 152 | hlVal := state.regHL.get() 153 | memVal := state.mmu.at(hlVal) 154 | 155 | return func(state *State) instruction { 156 | // M-Cycle 3: Do operation and write to memory at HL 157 | 158 | // Put the least significant bit in the carry register 159 | lsb := memVal & 0x01 160 | state.setCarryFlag(lsb == 1) 161 | 162 | state.mmu.set(hlVal, memVal>>1) 163 | 164 | state.setZeroFlag(memVal>>1 == 0) 165 | state.setSubtractFlag(false) 166 | state.setHalfCarryFlag(false) 167 | 168 | return nil 169 | } 170 | } 171 | } 172 | 173 | // makeRR creates an instruction that rotates the contents of the given 174 | // register right by one, but uses the carry flag as a "bit -1" of sorts during 175 | // this operation. This means we're essentially rotating "(register << 1) | 176 | // carry flag". 177 | func makeRR(reg register8) instruction { 178 | return func(state *State) instruction { 179 | // M-Cycle 1: Do operation 180 | 181 | oldVal := reg.get() 182 | // Get the current least significant bit, which will be put in the carry 183 | // flag 184 | var lsb uint8 185 | if oldVal&0x01 == 0x01 { 186 | lsb = 1 187 | } else { 188 | lsb = 0 189 | } 190 | 191 | // Get the current carry bit, which will be put in the most significant bit 192 | // of register A 193 | var oldCarryVal uint8 194 | if state.getCarryFlag() { 195 | oldCarryVal = 1 196 | } else { 197 | oldCarryVal = 0 198 | } 199 | 200 | newVal := oldVal >> 1 201 | newVal |= (oldCarryVal << 7) 202 | state.setCarryFlag(lsb == 1) 203 | 204 | state.setZeroFlag(newVal == 0) 205 | state.setSubtractFlag(false) 206 | state.setHalfCarryFlag(false) 207 | 208 | reg.set(newVal) 209 | 210 | return nil 211 | } 212 | } 213 | 214 | // rrMemHL rotates the value stored in memory at the address specified by 215 | // register HL by 1. The carry flag is used as a "bit -1" of sorts during this 216 | // operation. This means we're essentially rotating 217 | // "(mem[regHL] << 1) | carryFlag". 218 | func rrMemHL(state *State) instruction { 219 | // M-Cycle 1: Fetch CB instruction 220 | 221 | return func(state *State) instruction { 222 | // M-Cycle 2: Read from memory at HL 223 | 224 | hlVal := state.regHL.get() 225 | oldVal := state.mmu.at(hlVal) 226 | 227 | return func(state *State) instruction { 228 | // M-Cycle 3: Do operation and write to memory at HL 229 | 230 | // Get the current least significant bit, which will be put in the carry 231 | // flag 232 | var lsb uint8 233 | if oldVal&0x01 == 0x01 { 234 | lsb = 1 235 | } else { 236 | lsb = 0 237 | } 238 | 239 | // Get the current carry bit, which will be put in the most significant bit 240 | // of register A 241 | var oldCarryVal uint8 242 | if state.getCarryFlag() { 243 | oldCarryVal = 1 244 | } else { 245 | oldCarryVal = 0 246 | } 247 | 248 | newVal := oldVal >> 1 249 | newVal |= (oldCarryVal << 7) 250 | state.setCarryFlag(lsb == 1) 251 | 252 | state.setZeroFlag(newVal == 0) 253 | state.setSubtractFlag(false) 254 | state.setHalfCarryFlag(false) 255 | 256 | state.mmu.set(hlVal, newVal) 257 | 258 | return nil 259 | } 260 | } 261 | } 262 | 263 | // makeRLC creates an instruction that bit rotates the given register left by 264 | // one, which is equivalent to a left bit shift where the most significant bit 265 | // is carried over to the least significant bit. This bit is also stored in the 266 | // carry flag. 267 | func makeRLC(reg register8) instruction { 268 | return func(state *State) instruction { 269 | // M-Cycle 1: Do operation 270 | 271 | rotated := bits.RotateLeft8(reg.get(), 1) 272 | reg.set(rotated) 273 | 274 | state.setZeroFlag(rotated == 0) 275 | state.setSubtractFlag(false) 276 | state.setHalfCarryFlag(false) 277 | 278 | carryBit := reg.get() & 0x01 279 | state.setCarryFlag(carryBit == 1) 280 | 281 | return nil 282 | } 283 | } 284 | 285 | // rlcMemHL bit rotates the value found in memory at the address specified by 286 | // HL left by one, which is equivalent to a left bit shift where the most 287 | // significant bit is carried over to the least significant bit. This bit is 288 | // also stored in the carry flag. 289 | func rlcMemHL(state *State) instruction { 290 | // M-Cycle 1: Fetch CB instruction 291 | 292 | return func(state *State) instruction { 293 | // M-Cycle 2: Read from HL location in memory 294 | 295 | memVal := state.mmu.at(state.regHL.get()) 296 | 297 | return func(state *State) instruction { 298 | // M-Cycle 2: Write to HL location in memory 299 | 300 | memVal = bits.RotateLeft8(memVal, 1) 301 | state.mmu.set(state.regHL.get(), memVal) 302 | 303 | state.setZeroFlag(memVal == 0) 304 | state.setSubtractFlag(false) 305 | state.setHalfCarryFlag(false) 306 | 307 | carryBit := memVal & 0x01 308 | state.setCarryFlag(carryBit == 1) 309 | 310 | return nil 311 | } 312 | } 313 | } 314 | 315 | // makeRRC creates an instruction that bit rotates the given register right by 316 | // one, which is equivalent to a right bit shift where the least significant 317 | // bit is carried over to the most significant bit. This bit is also stored in 318 | // the carry flag. 319 | func makeRRC(reg register8) instruction { 320 | return func(state *State) instruction { 321 | // M-Cycle 1: Do operation 322 | 323 | carryBit := reg.get() & 0x01 324 | state.setCarryFlag(carryBit == 1) 325 | 326 | rotated := bits.RotateLeft8(reg.get(), -1) 327 | reg.set(rotated) 328 | 329 | state.setZeroFlag(rotated == 0) 330 | state.setSubtractFlag(false) 331 | state.setHalfCarryFlag(false) 332 | 333 | return nil 334 | } 335 | } 336 | 337 | // rrcMemHL bit rotates the value found in memory at the address specified by 338 | // HL right by one, which is equivalent to a right bit shift where the least 339 | // significant bit is carried over to the most significant bit. This bit is 340 | // also stored in the carry flag. 341 | func rrcMemHL(state *State) instruction { 342 | // M-Cycle 1: Fetch CB instruction 343 | 344 | return func(state *State) instruction { 345 | // M-Cycle 2: Read from memory at HL 346 | 347 | memVal := state.mmu.at(state.regHL.get()) 348 | 349 | return func(state *State) instruction { 350 | // M-Cycle 3: Do operation and write to memory at HL 351 | 352 | carryBit := memVal & 0x01 353 | 354 | memVal = bits.RotateLeft8(memVal, -1) 355 | state.mmu.set(state.regHL.get(), memVal) 356 | 357 | state.setZeroFlag(memVal == 0) 358 | state.setSubtractFlag(false) 359 | state.setHalfCarryFlag(false) 360 | 361 | state.setCarryFlag(carryBit == 1) 362 | 363 | return nil 364 | } 365 | 366 | } 367 | } 368 | 369 | // makeRL creates an instruction that rotates the given register value left by 370 | // one, but uses the carry flag as a "bit 8" of sorts during this operation. 371 | // This means that we're essentially rotating "(carry flag << 1) | register A". 372 | func makeRL(reg register8) instruction { 373 | return func(state *State) instruction { 374 | // M-Cycle 1: Do operation 375 | 376 | oldVal := reg.get() 377 | // Get the current most significant bit, which will be put in the carry 378 | // flag 379 | var msb uint8 380 | if oldVal&0x80 == 0x80 { 381 | msb = 1 382 | } else { 383 | msb = 0 384 | } 385 | 386 | // Get the current carry bit, which will be put in the least significant 387 | // bit of the register 388 | var oldCarryVal uint8 389 | if state.getCarryFlag() { 390 | oldCarryVal = 1 391 | } else { 392 | oldCarryVal = 0 393 | } 394 | 395 | newVal := oldVal << 1 396 | newVal |= oldCarryVal 397 | state.setCarryFlag(msb == 1) 398 | 399 | state.setSubtractFlag(false) 400 | state.setHalfCarryFlag(false) 401 | 402 | reg.set(newVal) 403 | 404 | state.setZeroFlag(newVal == 0) 405 | 406 | return nil 407 | } 408 | } 409 | 410 | // rlMemHL rotates the value in memory at the address specified by register HL 411 | // left by one, but uses the carry flag as a "bit 8" of sorts during this 412 | // operation. This means that we're essentially rotating 413 | // "(carry flag << 1) | mem(regHL)". 414 | func rlMemHL(state *State) instruction { 415 | // M-Cycle 1: Fetch CB instruction 416 | 417 | return func(state *State) instruction { 418 | // M-Cycle 2: Read from memory at HL 419 | 420 | oldVal := state.mmu.at(state.regHL.get()) 421 | 422 | return func(state *State) instruction { 423 | // M-Cycle 3: Write to memory at HL 424 | 425 | // Get the current most significant bit, which will be put in the carry 426 | // flag 427 | var msb uint8 428 | if oldVal&0x80 == 0x80 { 429 | msb = 1 430 | } else { 431 | msb = 0 432 | } 433 | 434 | // Get the current carry bit, which will be put in the least significant 435 | // bit of the register 436 | var oldCarryVal uint8 437 | if state.getCarryFlag() { 438 | oldCarryVal = 1 439 | } else { 440 | oldCarryVal = 0 441 | } 442 | 443 | newVal := oldVal << 1 444 | newVal |= oldCarryVal 445 | state.setCarryFlag(msb == 1) 446 | 447 | state.setSubtractFlag(false) 448 | state.setHalfCarryFlag(false) 449 | 450 | state.mmu.set(state.regHL.get(), newVal) 451 | 452 | state.setZeroFlag(newVal == 0) 453 | 454 | return nil 455 | } 456 | } 457 | } 458 | 459 | // makeSLA creates an instruction that shifts the contents of the given 460 | // register to the left. Bit 7 is shifted to the carry register. Bit 0 is set 461 | // to 0. 462 | func makeSLA(reg register8) instruction { 463 | return func(state *State) instruction { 464 | // M-Cycle 1: Do operation 465 | 466 | regVal := reg.get() 467 | 468 | // Put the most significant bit in the carry register 469 | msb := regVal&0x80 == 0x80 470 | state.setCarryFlag(msb) 471 | 472 | regVal = reg.set(regVal << 1) 473 | 474 | state.setZeroFlag(regVal == 0) 475 | state.setSubtractFlag(false) 476 | state.setHalfCarryFlag(false) 477 | 478 | return nil 479 | } 480 | } 481 | 482 | // slaMemHL shifts the value at the address in memory specified by register 483 | // HL to the left. Bit 7 is shifted to the carry register. Bit 0 is set to 0. 484 | func slaMemHL(state *State) instruction { 485 | // M-Cycle 1: Fetch CB instruction 486 | 487 | return func(state *State) instruction { 488 | // M-Cycle 2: Read from memory at HL 489 | 490 | hlVal := state.regHL.get() 491 | memVal := state.mmu.at(hlVal) 492 | 493 | return func(state *State) instruction { 494 | // M-Cycle 3: Write to memory at HL 495 | 496 | // Put the most significant bit in the carry register 497 | state.setCarryFlag(memVal&0x80 == 0x80) 498 | 499 | memVal <<= 1 500 | state.mmu.set(hlVal, memVal) 501 | 502 | state.setZeroFlag(memVal == 0) 503 | state.setSubtractFlag(false) 504 | state.setHalfCarryFlag(false) 505 | 506 | return nil 507 | } 508 | } 509 | } 510 | 511 | // makeSRA creates an instruction that shifts the contents of the given 512 | // register to the right. Bit 0 is shifted to the carry register. Bit 7 is left 513 | // unchanged. 514 | func makeSRA(reg register8) instruction { 515 | return func(state *State) instruction { 516 | // M-Cycle 1: Do operation 517 | 518 | regVal := reg.get() 519 | 520 | // Put the least significant bit in the carry register 521 | lsb := regVal & 0x01 522 | state.setCarryFlag(lsb == 1) 523 | 524 | msb := regVal & 0x80 525 | 526 | regVal >>= 1 527 | 528 | // Put the previous most significant bit back in bit 7 529 | regVal |= msb 530 | regVal = reg.set(regVal) 531 | 532 | state.setZeroFlag(regVal == 0) 533 | state.setSubtractFlag(false) 534 | state.setHalfCarryFlag(false) 535 | 536 | return nil 537 | } 538 | } 539 | 540 | // sraMemHL shifts the value at the address in memory specified by register HL 541 | // to the right. Bit 0 is shifted to the carry register. Bit 7 is unchanged. 542 | func sraMemHL(state *State) instruction { 543 | // M-Cycle 1: Fetch CB instruction 544 | 545 | return func(state *State) instruction { 546 | // M-Cycle 2: Read from memory at HL 547 | 548 | hlVal := state.regHL.get() 549 | memVal := state.mmu.at(hlVal) 550 | 551 | return func(state *State) instruction { 552 | // M-Cycle 3: Do operation and write to memory at HL 553 | 554 | // Put the least significant bit in the carry register 555 | lsb := memVal & 0x01 556 | state.setCarryFlag(lsb == 1) 557 | 558 | memVal = memVal >> 1 559 | 560 | // Put the previous most significant bit back in bit 7 561 | memVal |= (memVal & 0x40) << 1 562 | 563 | state.mmu.set(hlVal, memVal) 564 | 565 | state.setZeroFlag(memVal == 0) 566 | state.setSubtractFlag(false) 567 | state.setHalfCarryFlag(false) 568 | 569 | return nil 570 | } 571 | } 572 | } 573 | -------------------------------------------------------------------------------- /gameboy/serial.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | // serial controls transfer operations to and from the Game Boy through the 4 | // link cable. 5 | type serial struct{} 6 | 7 | func newSerial(state *State) *serial { 8 | s := &serial{} 9 | 10 | state.mmu.subscribeTo(scAddr, s.onSCWrite) 11 | 12 | return s 13 | } 14 | 15 | // onSCWrite triggers when the SIO Control register is written to. It 16 | // configures or starts a serial transfer. 17 | func (s *serial) onSCWrite(addr uint16, val uint8) uint8 { 18 | // TODO(velovix): Actually trigger some behavior 19 | 20 | // Unused bits 6-1 are always 1 21 | return val | 0x7E 22 | } 23 | -------------------------------------------------------------------------------- /gameboy/sound_controller.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | ) 7 | 8 | const ( 9 | // frameSequencerClockRate is the clock rate of the frame sequencer, a 10 | // clock used to time sound operations. 11 | frameSequencerClockRate = 512 12 | // durationClockRate is the clock rate of the duration clock. When this 13 | // ticks, it decreases the duration counter of a voice by 1. This is 14 | // clocked by the frame sequencer. 15 | durationClockRate = 256 16 | // volumeClockRate is the clock rate of the volume clock. When this ticks, 17 | // it decreases or increases the volume of a voice by 1. This is clocked by 18 | // the frame sequencer. 19 | volumeClockRate = 64 20 | // frequencyClockRate is the clock rate of the frequency clock. When this 21 | // ticks, it decreases or increases the frequency of Pulse A by some 22 | // amount. This is clocked by the frame sequencer. 23 | frequencyClockRate = 128 24 | ) 25 | 26 | // SoundController emulates the Game Boy's sound chip. It produces audio data 27 | // that may be played. 28 | type SoundController struct { 29 | state *State 30 | 31 | // A clock that runs at 512 Hz. It is used to time sound operations. 32 | frameSequencer int 33 | // A clock that increments with every CPU cycle. Used by the frame 34 | // sequencer. 35 | tClock int 36 | 37 | // If false, the whole controller goes to sleep and now sound is emitted 38 | Enabled bool 39 | // Volume for the left and right audio channels. 40 | leftVolume int 41 | rightVolume int 42 | 43 | PulseA *PulseA 44 | PulseB *PulseB 45 | Wave *Wave 46 | Noise *Noise 47 | } 48 | 49 | type PulseA struct { 50 | On bool 51 | RightEnabled bool 52 | LeftEnabled bool 53 | 54 | volume int 55 | frequency int 56 | 57 | duration int 58 | useDuration bool 59 | 60 | volumePeriod int 61 | amplify bool 62 | 63 | dutyCycle int 64 | 65 | lastFrequency int 66 | frequencyPeriod int 67 | attenuate bool 68 | sweepShift uint 69 | useFrequencySweep bool 70 | } 71 | 72 | func (voice *PulseA) tick(frameSequencer int) { 73 | if !voice.On { 74 | return 75 | } 76 | 77 | // Decrease the duration if the duration clock ticked 78 | if voice.useDuration && frameSequencer%(frameSequencerClockRate/durationClockRate) == 0 { 79 | voice.duration-- 80 | if voice.duration == 0 { 81 | voice.On = false 82 | } 83 | } 84 | 85 | // Sweep the volume up or down 86 | if voice.volumePeriod != 0 { 87 | // Calculate the resulting volume clock rate from the base clock rate 88 | // and the configurable period value 89 | clockRate := volumeClockRate / voice.volumePeriod 90 | // Increment or decrement the volume on the volume clock's tick if the 91 | // volume isn't already at max or min 92 | if frameSequencer%(frameSequencerClockRate/clockRate) == 0 { 93 | if voice.amplify && voice.volume < 15 { 94 | voice.volume++ 95 | } else if voice.volume > 0 { 96 | voice.volume-- 97 | } 98 | } 99 | } 100 | 101 | if voice.useFrequencySweep && voice.frequencyPeriod != 0 { 102 | // Calculate the resulting frequency clock rate from the base clock 103 | // rate and the configurable period value 104 | clockRate := frequencyClockRate / voice.frequencyPeriod 105 | // Increment or decrement the frequency on the frequency clock's tick 106 | // if the frequency isn't already at max or min 107 | if frameSequencer%(frameSequencerClockRate/clockRate) == 0 { 108 | // Calculate the frequency step by shifting the initial frequency of 109 | // the voice by the configured amount 110 | step := voice.lastFrequency >> voice.sweepShift 111 | 112 | newFrequency := voice.lastFrequency 113 | if voice.attenuate { 114 | newFrequency -= step 115 | } else { 116 | newFrequency += step 117 | } 118 | 119 | // Check for an overflow. The frequency is an 11-bit value 120 | if newFrequency > 2047 || newFrequency < 0 { 121 | voice.useFrequencySweep = false 122 | } else { 123 | voice.lastFrequency = newFrequency 124 | voice.frequency = newFrequency 125 | 126 | // Check for a future overflow. I know this seems weird but 127 | // this is apparently how the hardware does it 128 | if voice.attenuate { 129 | newFrequency -= step 130 | } else { 131 | newFrequency += step 132 | } 133 | if newFrequency > 2047 || newFrequency < 0 { 134 | voice.useFrequencySweep = false 135 | } 136 | } 137 | } 138 | } 139 | } 140 | 141 | func (voice *PulseA) Volume() float64 { 142 | return float64(voice.volume) / 15.0 143 | } 144 | 145 | func (voice *PulseA) Frequency() float64 { 146 | return 131072.0 / (2048.0 - float64(voice.frequency)) 147 | } 148 | 149 | func (voice *PulseA) DutyCycle() float64 { 150 | switch voice.dutyCycle { 151 | case 0: 152 | return 0.125 153 | case 1: 154 | return 0.25 155 | case 2: 156 | return 0.50 157 | case 3: 158 | return 0.75 159 | default: 160 | panic(fmt.Sprintf("invalid duty cycle value %v", voice.dutyCycle)) 161 | } 162 | } 163 | 164 | type PulseB struct { 165 | On bool 166 | RightEnabled bool 167 | LeftEnabled bool 168 | 169 | volume int 170 | frequency int 171 | 172 | duration int 173 | useDuration bool 174 | 175 | volumePeriod int 176 | amplify bool 177 | 178 | dutyCycle int 179 | } 180 | 181 | func (voice *PulseB) tick(frameSequencer int) { 182 | if !voice.On { 183 | return 184 | } 185 | 186 | // Decrease the duration if the duration clock ticked 187 | if voice.useDuration && frameSequencer%(frameSequencerClockRate/durationClockRate) == 0 { 188 | voice.duration-- 189 | if voice.duration == 0 { 190 | voice.On = false 191 | } 192 | } 193 | 194 | // Sweep the volume up or down 195 | if voice.volumePeriod != 0 { 196 | // Calculate the resulting volume clock rate from the base clock rate 197 | // and the configurable period value 198 | clockRate := volumeClockRate / voice.volumePeriod 199 | // Increment or decrement the volume on the volume clock's tick if the 200 | // volume isn't already at max or min 201 | if frameSequencer%(frameSequencerClockRate/clockRate) == 0 { 202 | if voice.amplify && voice.volume < 15 { 203 | voice.volume++ 204 | } else if voice.volume > 0 { 205 | voice.volume-- 206 | } 207 | } 208 | } 209 | } 210 | 211 | func (voice *PulseB) Volume() float64 { 212 | return float64(voice.volume) / 15.0 213 | } 214 | 215 | func (voice *PulseB) Frequency() float64 { 216 | return 131072.0 / (2048.0 - float64(voice.frequency)) 217 | } 218 | 219 | func (voice *PulseB) DutyCycle() float64 { 220 | switch voice.dutyCycle { 221 | case 0: 222 | return 0.125 223 | case 1: 224 | return 0.25 225 | case 2: 226 | return 0.50 227 | case 3: 228 | return 0.75 229 | default: 230 | panic(fmt.Sprintf("invalid duty cycle value %v", voice.dutyCycle)) 231 | } 232 | } 233 | 234 | type Wave struct { 235 | On bool 236 | RightEnabled bool 237 | LeftEnabled bool 238 | 239 | volume int 240 | frequency int 241 | pattern []uint8 242 | 243 | duration int 244 | useDuration bool 245 | 246 | rightShiftCode int 247 | } 248 | 249 | func (voice *Wave) tick(frameSequencer int) { 250 | if !voice.On { 251 | return 252 | } 253 | 254 | // Decrease the duration if the duration clock ticked 255 | if voice.useDuration && frameSequencer%(frameSequencerClockRate/durationClockRate) == 0 { 256 | voice.duration-- 257 | if voice.duration == 0 { 258 | voice.On = false 259 | } 260 | } 261 | } 262 | 263 | func (voice *Wave) Volume() float64 { 264 | return float64(voice.volume) 265 | } 266 | 267 | func (voice *Wave) Frequency() float64 { 268 | return 65536 / (2048 - float64(voice.frequency)) 269 | } 270 | 271 | func (voice *Wave) Pattern() [32]float64 { 272 | var pattern [32]float64 273 | 274 | var rightShift uint 275 | switch voice.rightShiftCode { 276 | case 0: 277 | rightShift = 4 278 | case 1: 279 | rightShift = 0 280 | case 2: 281 | rightShift = 1 282 | case 3: 283 | rightShift = 2 284 | } 285 | 286 | for i := 0; i < len(voice.pattern); i++ { 287 | value := voice.pattern[i] >> rightShift 288 | pattern[i] = float64(value) / 15.0 289 | } 290 | 291 | return pattern 292 | } 293 | 294 | type Noise struct { 295 | On bool 296 | LeftEnabled bool 297 | RightEnabled bool 298 | 299 | duration int 300 | useDuration bool 301 | 302 | volume int 303 | volumePeriod int 304 | amplify bool 305 | 306 | shiftClockFrequency int 307 | dividingRatio int 308 | lfsr uint16 309 | widthMode LFSRWidthMode 310 | } 311 | 312 | func (voice *Noise) tick(frameSequencer int) { 313 | if !voice.On { 314 | return 315 | } 316 | 317 | // Decrease the duration if the duration clock ticked 318 | if voice.useDuration && frameSequencer%(frameSequencerClockRate/durationClockRate) == 0 { 319 | voice.duration-- 320 | if voice.duration == 0 { 321 | voice.On = false 322 | } 323 | } 324 | 325 | // Sweep the volume up or down 326 | if voice.volumePeriod != 0 { 327 | // Calculate the resulting volume clock rate from the base clock rate 328 | // and the configurable period value 329 | clockRate := volumeClockRate / voice.volumePeriod 330 | // Increment or decrement the volume on the volume clock's tick if the 331 | // volume isn't already at max or min 332 | if frameSequencer%(frameSequencerClockRate/clockRate) == 0 { 333 | if voice.amplify && voice.volume < 15 { 334 | voice.volume++ 335 | } else if voice.volume > 0 { 336 | voice.volume-- 337 | } 338 | } 339 | } 340 | } 341 | 342 | func (voice *Noise) ShiftFrequency() float64 { 343 | var dividingRatio float64 344 | if voice.dividingRatio == 0 { 345 | dividingRatio = 0.5 346 | } else { 347 | dividingRatio = float64(voice.dividingRatio) 348 | } 349 | 350 | return 524288.0 / 351 | dividingRatio / 352 | math.Pow(2, float64(voice.shiftClockFrequency+1)) 353 | } 354 | 355 | func (voice *Noise) WidthMode() LFSRWidthMode { 356 | return voice.widthMode 357 | } 358 | 359 | func (voice *Noise) Volume() float64 { 360 | return float64(voice.volume) / 15 361 | } 362 | 363 | type LFSRWidthMode int 364 | 365 | const ( 366 | WidthMode7Bit LFSRWidthMode = 7 367 | WidthMode15Bit LFSRWidthMode = 15 368 | ) 369 | 370 | func newSoundController(state *State) *SoundController { 371 | sc := &SoundController{ 372 | state: state, 373 | PulseA: &PulseA{}, 374 | PulseB: &PulseB{}, 375 | Wave: &Wave{}, 376 | Noise: &Noise{}, 377 | } 378 | 379 | sc.state.mmu.subscribeTo(nr10Addr, sc.onNR10Write) 380 | sc.state.mmu.subscribeTo(nr14Addr, sc.onNR14Write) 381 | sc.state.mmu.subscribeTo(nr24Addr, sc.onNR24Write) 382 | sc.state.mmu.subscribeTo(nr30Addr, sc.onNR30Write) 383 | sc.state.mmu.subscribeTo(nr32Addr, sc.onNR32Write) 384 | sc.state.mmu.subscribeTo(nr34Addr, sc.onNR34Write) 385 | sc.state.mmu.subscribeTo(nr41Addr, sc.onNR41Write) 386 | sc.state.mmu.subscribeTo(nr44Addr, sc.onNR44Write) 387 | sc.state.mmu.subscribeTo(nr50Addr, sc.onNR50Write) 388 | sc.state.mmu.subscribeTo(nr51Addr, sc.onNR51Write) 389 | sc.state.mmu.subscribeTo(nr52Addr, sc.onNR52Write) 390 | 391 | return sc 392 | } 393 | 394 | // LeftVolume returns the global volume control of the left channel, from 0 to 395 | // 1. 396 | func (sc *SoundController) LeftVolume() float64 { 397 | return float64(sc.leftVolume) / 7 398 | } 399 | 400 | // RightVolume returns the global volume control of the right channel, from 0 401 | // to 1. 402 | func (sc *SoundController) RightVolume() float64 { 403 | return float64(sc.rightVolume) / 7 404 | } 405 | 406 | // tick runs the sound controller for one m-cycle. 407 | func (sc *SoundController) tick() { 408 | for i := 0; i < ticksPerMCycle; i++ { 409 | // Update the frame sequencer 410 | sc.tClock++ 411 | if sc.tClock%(cpuClockRate/frameSequencerClockRate) == 0 { 412 | sc.frameSequencer++ 413 | sc.PulseA.tick(sc.frameSequencer) 414 | sc.PulseB.tick(sc.frameSequencer) 415 | sc.Wave.tick(sc.frameSequencer) 416 | sc.Noise.tick(sc.frameSequencer) 417 | } 418 | if sc.tClock == cpuClockRate { 419 | sc.tClock = 0 420 | } 421 | 422 | } 423 | } 424 | 425 | // onNR10Write is called when the Sound Mode 1 Sweep register is written to. 426 | func (sc *SoundController) onNR10Write(addr uint16, val uint8) uint8 { 427 | // Bit 7 is unused and always 1 428 | return val | 0x80 429 | } 430 | 431 | // onNR14Write is called when the NR14 memory register is written to. When a 1 432 | // is written to bit 7 of this register, the Pulse A voice is restarted with 433 | // the configuration found in this register and others. 434 | func (sc *SoundController) onNR14Write(addr uint16, val uint8) uint8 { 435 | if val&0x80 == 0x80 { 436 | sc.PulseA.On = true 437 | 438 | // Load duration information 439 | duration := sc.state.mmu.memory[nr11Addr] & 0x3F 440 | sc.PulseA.duration = 64 - int(duration) 441 | sc.PulseA.useDuration = val&0x40 == 0x40 442 | 443 | // Load volume and volume sweep information 444 | nr12 := sc.state.mmu.memory[nr12Addr] 445 | volume := (nr12 & 0xF0) >> 4 446 | volumePeriod := nr12 & 0x7 447 | amplify := nr12&0x8 == 0x8 448 | 449 | dutyCycle := (sc.state.mmu.memory[nr11Addr] & 0xC0) >> 6 450 | 451 | // Load frequency and frequency sweep information 452 | frequency := uint16(sc.state.mmu.memory[nr13Addr]) 453 | frequency |= uint16(val&0x7) << 8 454 | nr10 := sc.state.mmu.memory[nr10Addr] 455 | frequencyPeriod := (nr10 & 0x70) >> 4 456 | attenuate := nr10&0x08 == 0x08 457 | sweepShift := nr10 & 0x07 458 | 459 | sc.PulseA.volume = int(volume) 460 | sc.PulseA.volumePeriod = int(volumePeriod) 461 | sc.PulseA.amplify = amplify 462 | 463 | sc.PulseA.dutyCycle = int(dutyCycle) 464 | 465 | sc.PulseA.frequency = int(frequency) 466 | sc.PulseA.lastFrequency = int(frequency) 467 | sc.PulseA.frequencyPeriod = int(frequencyPeriod) 468 | sc.PulseA.attenuate = attenuate 469 | sc.PulseA.sweepShift = uint(sweepShift) 470 | sc.PulseA.useFrequencySweep = sweepShift != 0 || frequencyPeriod != 0 471 | } 472 | 473 | return val 474 | } 475 | 476 | // onNR24Write is called when the NR24 memory register is written to. When a 1 477 | // is written to bit 7 of this register, the Pulse B voice is restarted with 478 | // the configuration found in this register and others. 479 | func (sc *SoundController) onNR24Write(addr uint16, val uint8) uint8 { 480 | if val&0x80 == 0x80 { 481 | sc.PulseB.On = true 482 | 483 | // Load duration information 484 | duration := sc.state.mmu.memory[nr21Addr] & 0x3F 485 | sc.PulseB.duration = 64 - int(duration) 486 | sc.PulseB.useDuration = val&0x40 == 0x40 487 | 488 | // Load frequency information 489 | frequency := uint16(sc.state.mmu.memory[nr23Addr]) 490 | frequency |= uint16(val&0x7) << 8 491 | 492 | // Load volume and volume sweep information 493 | nr22 := sc.state.mmu.memory[nr22Addr] 494 | volume := (nr22 & 0xF0) >> 4 495 | amplify := nr22&0x8 == 0x8 496 | volumePeriod := nr22 & 0x7 497 | 498 | dutyCycle := (sc.state.mmu.memory[nr21Addr] & 0xC0) >> 6 499 | 500 | sc.PulseB.volume = int(volume) 501 | sc.PulseB.volumePeriod = int(volumePeriod) 502 | sc.PulseB.amplify = amplify 503 | 504 | sc.PulseB.frequency = int(frequency) 505 | sc.PulseB.dutyCycle = int(dutyCycle) 506 | } 507 | 508 | return val 509 | } 510 | 511 | // onNR30Write is called when the Sound Mode 3 On/Off register is written to. 512 | func (sc *SoundController) onNR30Write(addr uint16, val uint8) uint8 { 513 | // Bits 6-0 are unused and always 1 514 | return val | 0x7F 515 | } 516 | 517 | // onNR32Write is called when the Sound Mode 3 Select Output Level register is 518 | // written to. 519 | func (sc *SoundController) onNR32Write(addr uint16, val uint8) uint8 { 520 | // Bits 7 and bits 4-0 are unused and always 1 521 | return val | 0x9F 522 | } 523 | 524 | // onNR34Write is called when the NR34 memory register is written to. When a 1 525 | // is written to bit 7 of this register, the wave voice is restarted with 526 | // the configuration found in this register and others. 527 | func (sc *SoundController) onNR34Write(addr uint16, val uint8) uint8 { 528 | if val&0x80 == 0x80 { 529 | sc.Wave.On = true 530 | 531 | // Wave volume is one bit in size 532 | sc.Wave.volume = int(sc.state.mmu.memory[nr30Addr] & 0x80 >> 7) 533 | 534 | // Load duration information 535 | duration := sc.state.mmu.memory[nr31Addr] 536 | sc.Wave.duration = 256 - int(duration) 537 | sc.Wave.useDuration = val&0x40 == 0x40 538 | 539 | frequency := uint16(sc.state.mmu.memory[nr33Addr]) 540 | frequency |= uint16(val&0x7) << 8 541 | 542 | rightShiftCode := (sc.state.mmu.memory[nr32Addr] & 0x60) >> 5 543 | 544 | sc.Wave.rightShiftCode = int(rightShiftCode) 545 | sc.Wave.frequency = int(frequency) 546 | sc.Wave.pattern = sc.readWaveTable() 547 | } 548 | 549 | return val 550 | } 551 | 552 | // onNR41Write is called when the Sound Mode 4 Sound Length register is written 553 | // to. 554 | func (sc *SoundController) onNR41Write(addr uint16, val uint8) uint8 { 555 | // Bits 7 and 6 are unused and always 1 556 | return val | 0xC0 557 | } 558 | 559 | // onNR44Write is called when the NR44 memory register is written to. When a 1 560 | // is written to bit 7 of this register, the noise voice is restarted with 561 | // the configuration found in this register and others. 562 | func (sc *SoundController) onNR44Write(addr uint16, val uint8) uint8 { 563 | if val&0x80 == 0x80 { 564 | sc.Noise.On = true 565 | 566 | duration := sc.state.mmu.memory[nr41Addr] & 0x3F 567 | sc.Noise.duration = 64 - int(duration) 568 | sc.Noise.useDuration = val&0x40 == 0x40 569 | 570 | nr42 := sc.state.mmu.memory[nr42Addr] 571 | volume := (nr42 & 0xF0) >> 4 572 | amplify := nr42&0x8 == 0x8 573 | volumePeriod := nr42 & 0x7 574 | 575 | nr43 := sc.state.mmu.memory[nr43Addr] 576 | shiftClockFrequency := (nr43 & 0xF0) >> 4 577 | dividingRatio := nr43 & 0x7 578 | 579 | var widthMode LFSRWidthMode 580 | if nr43&0x8 == 0x8 { 581 | widthMode = WidthMode7Bit 582 | } else { 583 | widthMode = WidthMode15Bit 584 | } 585 | 586 | sc.Noise.volume = int(volume) 587 | sc.Noise.volumePeriod = int(volumePeriod) 588 | sc.Noise.amplify = amplify 589 | 590 | sc.Noise.shiftClockFrequency = int(shiftClockFrequency) 591 | sc.Noise.dividingRatio = int(dividingRatio) 592 | sc.Noise.widthMode = widthMode 593 | } 594 | 595 | // Bits 5-0 are unused and always 1 596 | return val | 0x3F 597 | } 598 | 599 | // onNR50Write is called when the Cartridge Channel Control and Volume Register 600 | // is written to. This register controls left and right channel audio volume. 601 | func (sc *SoundController) onNR50Write(addr uint16, val uint8) uint8 { 602 | sc.leftVolume = int((val & 0x70) >> 4) 603 | sc.rightVolume = int(val & 0x07) 604 | 605 | return val 606 | } 607 | 608 | // onNR51Write is called when the Selection of Sound Output Terminal register 609 | // is written to. This register enables or disables each voice on either the 610 | // right or the left audio channel. This allows for stereo sound. 611 | func (sc *SoundController) onNR51Write(addr uint16, val uint8) uint8 { 612 | sc.Noise.LeftEnabled = val&0x80 == 0x80 613 | sc.Wave.LeftEnabled = val&0x40 == 0x40 614 | sc.PulseB.LeftEnabled = val&0x20 == 0x20 615 | sc.PulseA.LeftEnabled = val&0x10 == 0x10 616 | sc.Noise.RightEnabled = val&0x08 == 0x08 617 | sc.Wave.RightEnabled = val&0x04 == 0x04 618 | sc.PulseB.RightEnabled = val&0x02 == 0x02 619 | sc.PulseA.RightEnabled = val&0x01 == 0x01 620 | 621 | return val 622 | } 623 | 624 | // onNR52Write is called when the Sound On/Off register is written to. On 625 | // write, it can enable or disable the sound. 626 | func (sc *SoundController) onNR52Write(addr uint16, val uint8) uint8 { 627 | sc.Enabled = val&0x80 == 0x80 628 | 629 | // TODO(velovix): Zero out all registers except length and stop receiving 630 | // writes 631 | // TODO(velovix): Make the "on" values for channels available here 632 | 633 | // Bits 6-4 are unused and always 1 634 | return val | 0x70 635 | } 636 | 637 | func (sc *SoundController) readWaveTable() []uint8 { 638 | wavePattern := make([]uint8, 0, 32) 639 | 640 | for addr := uint16(wavePatternRAMStart); addr < wavePatternRAMEnd; addr++ { 641 | val := sc.state.mmu.memory[addr] 642 | lower, upper := split(val) 643 | wavePattern = append(wavePattern, upper, lower) 644 | } 645 | 646 | return wavePattern 647 | } 648 | -------------------------------------------------------------------------------- /gameboy/state.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | // State holds the entire state of the Game Boy. 4 | type State struct { 5 | regA *normalRegister8 6 | regB *normalRegister8 7 | regC *normalRegister8 8 | regD *normalRegister8 9 | regE *normalRegister8 10 | regF *flagRegister8 11 | regH *normalRegister8 12 | regL *normalRegister8 13 | 14 | regAF *registerCombined 15 | regBC *registerCombined 16 | regDE *registerCombined 17 | regHL *registerCombined 18 | regSP *normalRegister16 19 | regPC *normalRegister16 20 | 21 | // The active memory management unit. 22 | mmu *mmu 23 | // If this value is >0, it is decremented after every operation. When this 24 | // timer decrements to 0, interrupts are enabled. This is used to emulate 25 | // the EI instruction's delayed effects. 26 | enableInterruptsTimer int 27 | // The master interrupt switch. If this is false, no interrupts will be 28 | // processed. 29 | interruptsEnabled bool 30 | // If true, the CPU is halted and no instructions will be run until an 31 | // interrupt occurs, which will set this value to false. 32 | halted bool 33 | // If true, the Game Boy is in "stop mode". This means that the CPU is 34 | // halted and the screen is turned white. This mode is exited when a button 35 | // is pressed. 36 | stopped bool 37 | 38 | // A program counter value pointing to the start of the current 39 | // instruction. 40 | instructionStart uint16 41 | } 42 | 43 | // NewState creates a new Game Boy state object with special memory addresses 44 | // initialized in accordance with the Game Boy's start up sequence. 45 | func NewState(mmu *mmu) *State { 46 | state := &State{ 47 | mmu: mmu, 48 | } 49 | state.regA = &normalRegister8{0} 50 | state.regB = &normalRegister8{0} 51 | state.regC = &normalRegister8{0} 52 | state.regD = &normalRegister8{0} 53 | state.regE = &normalRegister8{0} 54 | state.regH = &normalRegister8{0} 55 | state.regL = &normalRegister8{0} 56 | state.regF = &flagRegister8{0} 57 | state.regAF = ®isterCombined{ 58 | upper: state.regA, 59 | lower: state.regF} 60 | state.regBC = ®isterCombined{ 61 | upper: state.regB, 62 | lower: state.regC} 63 | state.regDE = ®isterCombined{ 64 | upper: state.regD, 65 | lower: state.regE} 66 | state.regHL = ®isterCombined{ 67 | upper: state.regH, 68 | lower: state.regL} 69 | state.regSP = &normalRegister16{0} 70 | state.regPC = &normalRegister16{0} 71 | 72 | state.instructionStart = state.regPC.get() 73 | 74 | return state 75 | } 76 | 77 | // incrementPC increments the program counter by 1 and returns the value that 78 | // was at its previous location. 79 | func (state *State) incrementPC() uint8 { 80 | poppedVal := state.mmu.at(state.regPC.get()) 81 | state.regPC.set(state.regPC.get() + 1) 82 | 83 | return poppedVal 84 | } 85 | 86 | // instructionDone signals to the state object that an instruction has 87 | // finished. This moves the instructionStart value to the current position of 88 | // the program counter. 89 | func (state *State) instructionDone() { 90 | state.instructionStart = state.regPC.get() 91 | } 92 | 93 | // popFromStack reads a value from the current stack position and increments 94 | // the stack pointer. 95 | func (state *State) popFromStack() uint8 { 96 | val := state.mmu.at(state.regSP.get()) 97 | 98 | state.regSP.set(state.regSP.get() + 1) 99 | 100 | return val 101 | } 102 | 103 | // popFromStack16 reads a 16-bit value from the current stack position and 104 | // decrements the stack pointer twice. 105 | func (state *State) popFromStack16() uint16 { 106 | lower := state.popFromStack() 107 | upper := state.popFromStack() 108 | 109 | return combine16(lower, upper) 110 | } 111 | 112 | // pushToStack decrements the stack pointer and writes the given value. 113 | func (state *State) pushToStack(val uint8) { 114 | state.regSP.set(state.regSP.get() - 1) 115 | 116 | state.mmu.set(state.regSP.get(), val) 117 | } 118 | 119 | // pushToStack16 pushes a 16-bit value to the stack, decrementing the stack 120 | // pointer twice. 121 | func (state *State) pushToStack16(val uint16) { 122 | lower, upper := split16(val) 123 | state.pushToStack(upper) 124 | state.pushToStack(lower) 125 | } 126 | 127 | // getZeroFlag returns the state of the zero bit in the flag register. 128 | func (state *State) getZeroFlag() bool { 129 | mask := uint8(0x80) 130 | return state.regF.get()&mask == mask 131 | } 132 | 133 | // setZeroFlag sets the zero bit in the flag register to the given value. 134 | func (state *State) setZeroFlag(on bool) { 135 | mask := uint8(0x80) 136 | if on { 137 | state.regF.set(state.regF.get() | mask) 138 | } else { 139 | state.regF.set(state.regF.get() & ^mask) 140 | } 141 | } 142 | 143 | // getSubtractFlag returns the state of the subtract bit in the flag register. 144 | func (state *State) getSubtractFlag() bool { 145 | mask := uint8(0x40) 146 | return state.regF.get()&mask == mask 147 | } 148 | 149 | // setSubtractFlag sets the subtract bit in the flag register to the given 150 | // value. 151 | func (state *State) setSubtractFlag(on bool) { 152 | mask := uint8(0x40) 153 | if on { 154 | state.regF.set(state.regF.get() | mask) 155 | } else { 156 | state.regF.set(state.regF.get() & ^mask) 157 | } 158 | } 159 | 160 | // getHalfCarryFlag returns the state of the half carry bit in the flag 161 | // register. 162 | func (state *State) getHalfCarryFlag() bool { 163 | mask := uint8(0x20) 164 | return state.regF.get()&mask == mask 165 | } 166 | 167 | // setHalfCarryFlag sets the half carry bit in the flag register to the given 168 | // value. 169 | func (state *State) setHalfCarryFlag(on bool) { 170 | mask := uint8(0x20) 171 | if on { 172 | state.regF.set(state.regF.get() | mask) 173 | } else { 174 | state.regF.set(state.regF.get() & ^mask) 175 | } 176 | } 177 | 178 | // getCarryFlag returns the state of the carry bit in the flag register. 179 | func (state *State) getCarryFlag() bool { 180 | mask := uint8(0x10) 181 | return state.regF.get()&mask == mask 182 | } 183 | 184 | // setCarryFlag sets the carry bit in the flag register to the given value. 185 | func (state *State) setCarryFlag(on bool) { 186 | mask := uint8(0x10) 187 | if on { 188 | state.regF.set(state.regF.get() | mask) 189 | } else { 190 | state.regF.set(state.regF.get() & ^mask) 191 | } 192 | } 193 | 194 | // flagMask is a bit mask that can be applied to the flag register to check the 195 | // flag's value. 196 | const ( 197 | _ uint8 = 0x00 198 | // zeroFlag is set when the result of the previous math operation was zero. 199 | zeroFlag = 0x80 200 | // subtractFlag is set when the previous math operation was a subtraction. 201 | subtractFlag = 0x40 202 | // halfCarryFlag is set when the previous math operation results in a carry 203 | // to the 4th bit. 204 | halfCarryFlag = 0x20 205 | // carryFlag is set when the previous math operation results in a carry 206 | // from the most significant bit. 207 | carryFlag = 0x10 208 | ) 209 | -------------------------------------------------------------------------------- /gameboy/timers.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | import "fmt" 4 | 5 | const ( 6 | // The Game Boy processor clock speed 7 | cpuClockRate = 4194304 8 | 9 | // The number of CPU clock cycles in one machine cycle, or m-cycle. 10 | ticksPerMCycle = 4 11 | ) 12 | 13 | // timers keeps track of all timers in the Gameboy, including the TIMA. 14 | type timers struct { 15 | // A clock that increments every M-Cycle 16 | cpuClock uint16 17 | 18 | // Conceptually, the TIMA is a clock that runs at some frequency. In 19 | // reality, however, the TIMA is a variable that increments when a falling 20 | // edge detector detects a falling edge. The input to this falling edge 21 | // detector is (timaRunning & timaBit). 22 | // 23 | // The "delay" value of the falling edge detector used to increment the 24 | // TIMA. This holds the value previously fed to the falling edge detector. 25 | // If this value is 1 and the new value is 0, we know there's a falling 26 | // edge. 27 | // 28 | // The term "delay" wouldn't be my first choice to describe this concept, 29 | // but it's what they call it in most diagrams for falling edge detectors. 30 | fallingEdgeDetectorDelay uint8 31 | 32 | // True if the TIMA is overflowing to zero during this M-Cycle. 33 | timaOverflowing bool 34 | // True if the TMA is being transferred to the TIMA this M-Cycle. Note that 35 | // this happens one instruction after the TIMA overflows. 36 | tmaToTIMATransferring bool 37 | 38 | // The divider is a one-byte timer that is incremented every 64 clocks. It 39 | // is, in effect, the upper byte of the CPU clock, if we think of the 40 | // system clock as a two-byte value. 41 | divider uint8 42 | // The TIMA is a one-byte that can be configured to increment at various 43 | // rates. It is accessed as a memory register. 44 | tima uint8 45 | // The TAC is a one-byte area of one-bit flags that configure the TIMA. 46 | // TODO(velovix): Add some documentation here about what each bit does 47 | tac uint8 48 | // The TMA is a one-byte integer that configures the value the TIMA gets 49 | // set to when it overflows. 50 | tma uint8 51 | 52 | state *State 53 | interruptManager *interruptManager 54 | } 55 | 56 | func newTimers(state *State) *timers { 57 | t := &timers{state: state} 58 | 59 | t.state.mmu.subscribeTo(dividerAddr, t.onDividerWrite) 60 | t.state.mmu.subscribeTo(tacAddr, t.onTACWrite) 61 | t.state.mmu.subscribeTo(timaAddr, t.onTIMAWrite) 62 | t.state.mmu.subscribeTo(tmaAddr, t.onTMAWrite) 63 | 64 | // The CPU is busy for 2 M-Cycles before running the boot ROM. The first 65 | // apparently sets up something related to the CPU's reset functionality. 66 | // The second pre-fetches the first instruction of the boot ROM. These 67 | // specifics are all internal details though so it's sufficient to simply 68 | // increment the timers. 69 | t.tick() 70 | t.tick() 71 | 72 | return t 73 | } 74 | 75 | // tick increments the timers by one m-cycle. 76 | func (t *timers) tick() { 77 | // Parse the TAC bits for TIMA configuration information 78 | timaRunning := t.tac&0x4 == 0x4 79 | 80 | t.cpuClock++ 81 | t.divider = uint8(t.cpuClock >> 6) 82 | 83 | if t.tmaToTIMATransferring { 84 | // This process was finished last M-Cycle 85 | t.tmaToTIMATransferring = false 86 | } 87 | 88 | // Check the countdown to see if it's time to process a delayed TIMA 89 | // interrupt 90 | if t.timaOverflowing { 91 | // Transfer the TMA value to the TIMA 92 | t.tima = t.tma 93 | t.timaOverflowing = false 94 | t.tmaToTIMATransferring = true 95 | 96 | if t.state.interruptsEnabled && t.interruptManager.timaEnabled() { 97 | // Flag a TIMA overflow interrupt 98 | t.interruptManager.flagTIMA() 99 | } 100 | } 101 | 102 | // Check for a falling edge and increment the TIMA if there was one 103 | timaBit := t.timaBit() 104 | fallingEdgeDetectorInput := uint8(0) 105 | if timaRunning && timaBit == 1 { 106 | fallingEdgeDetectorInput = 1 107 | } 108 | if fallingEdgeDetectorInput == 0 && t.fallingEdgeDetectorDelay == 1 { 109 | t.incrementTIMA() 110 | } 111 | 112 | // Update the delay value 113 | t.fallingEdgeDetectorDelay = fallingEdgeDetectorInput 114 | } 115 | 116 | // onDividerWrite is called when the divider register is written to. This 117 | // triggers the divider timer to reset to zero. 118 | func (t *timers) onDividerWrite(addr uint16, writeVal uint8) uint8 { 119 | t.cpuClock = 0 120 | 121 | return 0 122 | } 123 | 124 | // onTACWrite is called when the TAC register is written to. This controls 125 | // various aspects of the TIMA timer. 126 | func (t *timers) onTACWrite(addr uint16, writeVal uint8) uint8 { 127 | // This register is only 3 bits in size, get those bits 128 | writeVal = writeVal & 0x07 129 | 130 | // Update the TAC value 131 | t.tac = writeVal 132 | 133 | timaRunningAfter := t.tac&0x4 == 0x4 134 | newTIMABit := t.timaBit() 135 | 136 | // Setting the TAC can bring the falling edge detector's input value low by 137 | // either disabling the TIMA or moving the TIMA bit to a bit that is low. 138 | // If the "delay" value of the falling edge detector was high, this will 139 | // trigger a falling edge and increment the TIMA. 140 | if t.fallingEdgeDetectorDelay == 1 && 141 | (!timaRunningAfter || newTIMABit == 0) { 142 | 143 | t.incrementTIMA() 144 | } 145 | 146 | // All unused bits are high 147 | return 0xF8 | writeVal 148 | } 149 | 150 | // onTIMAWrite is called when the TIMA register is written to. This protects 151 | // the TIMA from being written to if it was recently updated to the TMA 152 | // register's value. 153 | func (t *timers) onTIMAWrite(addr uint16, writeVal uint8) uint8 { 154 | if t.timaOverflowing { 155 | // This write cancels the TIMA overflow detection, so the TMA->TIMA 156 | // transfer and the overflow interrupt will not happen next M-Cycle 157 | t.timaOverflowing = false 158 | t.tima = writeVal 159 | 160 | return writeVal 161 | } else if t.tmaToTIMATransferring { 162 | // The TMA overwrites the value set here 163 | return t.tima 164 | } else { 165 | // Set the TIMA value normally 166 | t.tima = writeVal 167 | return writeVal 168 | } 169 | } 170 | 171 | // onTMAWrite is called when the TMA register is written to. This emulates a 172 | // special behavior with the TMA->TIMA loading process. If instructions write 173 | // to this address while the TMA->TIMA transfer is happening, the TIMA will 174 | // take on this new value. 175 | func (t *timers) onTMAWrite(addr uint16, writeVal uint8) uint8 { 176 | t.tma = writeVal 177 | 178 | if t.tmaToTIMATransferring { 179 | // During this time where the TIMA is being set to the TMA, any changes 180 | // to the TMA will also be reflected in the TIMA 181 | t.tima = writeVal 182 | } 183 | 184 | return writeVal 185 | } 186 | 187 | // timaBit returns the bit in the CPU clock that is used by the falling edge 188 | // detector to decide if the TIMA needs to be incremented. 189 | func (t *timers) timaBit() uint8 { 190 | timaRateBits := t.tac & 0x3 191 | 192 | // Pull the bit of interest from the CPU clock 193 | switch timaRateBits { 194 | case 0x0: 195 | // TIMA configured at 4096 Hz 196 | return uint8((t.cpuClock >> 7) & 0x1) 197 | case 0x3: 198 | // TIMA configured at 16384 Hz 199 | return uint8((t.cpuClock >> 5) & 0x1) 200 | case 0x2: 201 | // TIMA configured at 65536 Hz 202 | return uint8((t.cpuClock >> 3) & 0x1) 203 | case 0x1: 204 | // TIMA configured at 262144 Hz 205 | return uint8((t.cpuClock >> 1) & 0x1) 206 | default: 207 | panic(fmt.Sprintf("invalid TIMA rate %v", timaRateBits)) 208 | } 209 | } 210 | 211 | func (t *timers) incrementTIMA() { 212 | t.tima++ 213 | 214 | if t.tima == 0 { 215 | // There is a 1 M-Cycle delay between the TIMA overflow and the 216 | // interrupt and reset to TMA 217 | t.timaOverflowing = true 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /gameboy/unmapped_addresses.go: -------------------------------------------------------------------------------- 1 | package gameboy 2 | 3 | // isUnmappedAddress is a slice whose index is a memory address and whose value 4 | // is true if the address is unmapped. This array is filled in using the 5 | // unmappedAddresses slice at program initialization. 6 | var isUnmappedAddress [0x10000]bool 7 | 8 | func init() { 9 | for _, addr := range unmappedAddresses { 10 | isUnmappedAddress[addr] = true 11 | } 12 | } 13 | 14 | // unmappedAddresses is a list of all unmapped addresses in the DMG. 15 | var unmappedAddresses = []uint16{ 16 | // Unused CGB registers 17 | key1Addr, 18 | vbkAddr, 19 | hdma1Addr, 20 | hdma2Addr, 21 | hdma3Addr, 22 | hdma4Addr, 23 | hdma5Addr, 24 | rpAddr, 25 | bcpsAddr, 26 | bcpdAddr, 27 | ocpsAddr, 28 | ocpdAddr, 29 | svbkAddr, 30 | pcm12Ch2Addr, 31 | pcm34Ch4Addr, 32 | // Misc unused addresses 33 | 0xFF03, 34 | 0xFF08, 35 | 0xFF09, 36 | 0xFF0A, 37 | 0xFF0B, 38 | 0xFF0C, 39 | 0xFF0D, 40 | 0xFF0E, 41 | 0xFF15, 42 | 0xFF1F, 43 | 0xFF27, 44 | 0xFF28, 45 | 0xFF29, 46 | 0xFF2A, 47 | 0xFF2B, 48 | 0xFF2C, 49 | 0xFF2D, 50 | 0xFF2E, 51 | 0xFF2F, 52 | 0xFF4C, 53 | 0xFF4E, 54 | 0xFF57, 55 | 0xFF58, 56 | 0xFF59, 57 | 0xFF5A, 58 | 0xFF5B, 59 | 0xFF5C, 60 | 0xFF5D, 61 | 0xFF5E, 62 | 0xFF5F, 63 | 0xFF60, 64 | 0xFF61, 65 | 0xFF62, 66 | 0xFF63, 67 | 0xFF64, 68 | 0xFF65, 69 | 0xFF66, 70 | 0xFF67, 71 | 0xFF6C, 72 | 0xFF6D, 73 | 0xFF6E, 74 | 0xFF6F, 75 | 0xFF71, 76 | 0xFF72, 77 | 0xFF73, 78 | 0xFF74, 79 | 0xFF75, 80 | 0xFF78, 81 | 0xFF79, 82 | 0xFF7A, 83 | 0xFF7B, 84 | 0xFF7C, 85 | 0xFF7D, 86 | 0xFF7E, 87 | 0xFF7F, 88 | } 89 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/velovix/gopherboy 2 | 3 | go 1.15 4 | 5 | require ( 6 | github.com/pkg/profile v1.5.0 7 | github.com/veandco/go-sdl2 v0.4.5 8 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 9 | ) 10 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/pkg/profile v1.5.0 h1:042Buzk+NhDI+DeSAA62RwJL8VAuZUMQZUjCsRz1Mug= 2 | github.com/pkg/profile v1.5.0/go.mod h1:qBsxPvzyUincmltOk6iyRVxHYg4adc0OFOv72ZdLa18= 3 | github.com/veandco/go-sdl2 v0.4.5 h1:GFIjMabK7y2XWpr9sGvN7RDKHt7vrA7XPTUW60eOw+Y= 4 | github.com/veandco/go-sdl2 v0.4.5/go.mod h1:OROqMhHD43nT4/i9crJukyVecjPNYYuCofep6SNiAjY= 5 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= 6 | golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= 7 | --------------------------------------------------------------------------------