diff --git a/gb/bus.go b/gb/bus.go index 87abdf9..eeea0d6 100644 --- a/gb/bus.go +++ b/gb/bus.go @@ -34,7 +34,7 @@ func (bus *Bus) Read(address uint16) byte { // ROM data value, err := bus.Cart.Read(address) if err != nil { - fmt.Printf("Error reading from bus address %X: %s", address, err) + fmt.Printf("Error reading from bus address %X: %s\n", address, err) return 0 } return value diff --git a/gb/console.go b/gb/console.go index a1e119a..29e7c47 100644 --- a/gb/console.go +++ b/gb/console.go @@ -15,6 +15,7 @@ type Console struct { Bus *Bus CPU *CPU front *image.RGBA + Ticks uint64 } func NewConsole(path string) (*Console, error) { @@ -47,15 +48,25 @@ func NewConsole(path string) (*Console, error) { func (console *Console) StepMilliSeconds(ms uint64) { speed := int(ms / 3) - for y := 0; y < ConsoleHeight; y++ { - for x := 0; x < ConsoleWidth; x++ { + for y := range ConsoleHeight { + for x := range ConsoleWidth { console.front.Set(x, y, color.RGBA{0, uint8(y + speed), uint8(x + speed), 255}) } } - } func (console *Console) Buffer() *image.RGBA { return console.front } + +func (console *Console) Cycle(cycles int) { + for range cycles { + // Cycles are given in M-cycles (machine cycles) instead of T-states (system clock ticks) + // One machine cycle takes 4 system clock ticks to complete + for range 4 { + console.Ticks++ + timer.Tick() + } + } +} diff --git a/gb/cpu.go b/gb/cpu.go index c95d946..ffe5eab 100644 --- a/gb/cpu.go +++ b/gb/cpu.go @@ -35,6 +35,7 @@ type CPU struct { Halted bool Stepping bool InterruptMasterEnable bool + InterruptFlags byte } func NewCPU(bus *Bus) *CPU { @@ -127,6 +128,14 @@ func (cpu *CPU) Step() { cpu.ClearFlag(H) } + case 0x18: + // JR e8 + // Jump relative to 8-bit signed offset + // emu_cycles(3); + offset := int8(cpu.Bus.Read(cpu.Regs.PC)) + cpu.Regs.PC++ + cpu.Regs.PC = uint16(int(cpu.Regs.PC) + int(offset)) + case 0x1C: // INC E cpu.Regs.E++ @@ -225,6 +234,14 @@ func (cpu *CPU) Step() { // LD A, B cpu.Regs.A = cpu.Regs.B + case 0x7C: + // LD A, H + cpu.Regs.A = cpu.Regs.H + + case 0x7D: + // LD A, L + cpu.Regs.A = cpu.Regs.L + case 0xCB: // Prefix byte instructions cbOpcode := cpu.Bus.Read(cpu.Regs.PC) @@ -262,10 +279,10 @@ func (cpu *CPU) Step() { // emu_cycles(1); cpu.Regs.PC = uint16(lo) | uint16(hi)<<8 - // case 0xC9: - // // RET - // // emu_cycles(4); - // cpu.Regs.PC = cpu.StackPop16() + case 0xC9: + // RET + // emu_cycles(4); + cpu.Regs.PC = cpu.StackPop16() case 0xCD: // CALL a16 @@ -284,6 +301,12 @@ func (cpu *CPU) Step() { cpu.Bus.Write(address, cpu.Regs.A) cpu.Regs.PC++ + case 0xE5: + // PUSH HL + // emu_cycles(4); + cpu.StackPush(cpu.Regs.H) + cpu.StackPush(cpu.Regs.L) + case 0xE9: // JP HL val := uint16(cpu.Regs.H)<<8 | uint16(cpu.Regs.L) @@ -349,3 +372,11 @@ func (cpu *CPU) StackPop16() uint16 { return uint16(hi)<<8 | uint16(lo) } + +func (cpu *CPU) GetInterruptFlags() byte { + return cpu.InterruptFlags +} + +func (cpu *CPU) SetInterruptFlags(value byte) { + cpu.InterruptFlags = value +} diff --git a/gb/cpu_test.go b/gb/cpu_test.go index 71aa3c4..58e92ed 100644 --- a/gb/cpu_test.go +++ b/gb/cpu_test.go @@ -474,3 +474,47 @@ func TestInstructionE0(t *testing.T) { // Should increase the stack pointer assert.Equal(t, uint16(0x2), cpu.Regs.PC) } + +func TestInstruction7D(t *testing.T) { + cpu := createCPU([]byte{0x7D, 0x00, 0x00}) + + cpu.Regs.L = 0xDE + + cpu.Step() + + // Should load into register A the value in register L + assert.Equal(t, byte(0xDE), cpu.Regs.A) + + // Should increase the stack pointer + assert.Equal(t, uint16(0x1), cpu.Regs.PC) +} + +func TestInstruction7C(t *testing.T) { + cpu := createCPU([]byte{0x7C, 0x00, 0x00}) + + cpu.Regs.H = 0xDE + + cpu.Step() + + // Should load into register A the value in register H + assert.Equal(t, byte(0xDE), cpu.Regs.A) + + // Should increase the stack pointer + assert.Equal(t, uint16(0x1), cpu.Regs.PC) +} + +func TestInstruction18(t *testing.T) { + // Should jump to positive offset + cpu := createCPU([]byte{0x18, 0x0A, 0x00}) + + cpu.Step() + + assert.Equal(t, uint16(0x0C), cpu.Regs.PC) + + // Should jump to negative offset + cpu = createCPU([]byte{0x18, 0xFB, 0x00}) + + cpu.Step() + + assert.Equal(t, uint16(0xFFFD), cpu.Regs.PC) +} diff --git a/gb/io.go b/gb/io.go index 7803936..afadb67 100644 --- a/gb/io.go +++ b/gb/io.go @@ -6,41 +6,40 @@ import ( ) var SerialData [2]byte +var InterruptFlags byte func IORead(address uint16) byte { if address == 0xFF01 { return SerialData[0] - } - - if address == 0xFF02 { + } else if address == 0xFF02 { return SerialData[1] - } - - if (address >= 0xFF04) && (address <= 0xFF07) { - fmt.Printf("Reading from IO: invalid address %X. Timer not yet implemented!", address) + } else if (address >= 0xFF04) && (address <= 0xFF07) { + return timer.Read(address) + } else if address == 0xFF0F { + return InterruptFlags + } else if (address >= 0xFF10) && (address <= 0xFF3F) { + // Ignore sound + return 0 + } else { + fmt.Printf("Reading from IO: invalid address %X\n", address) os.Exit(1) } - - fmt.Printf("Reading from IO: invalid address %X", address) - os.Exit(1) - return 0 } func IOWrite(address uint16, value byte) { if address == 0xFF01 { SerialData[0] = value - } - - if address == 0xFF02 { + } else if address == 0xFF02 { SerialData[1] = value - } - - if (address >= 0xFF04) && (address <= 0xFF07) { - fmt.Printf("Writing to IO: invalid address %X. Timer not yet implemented!", address) + } else if (address >= 0xFF04) && (address <= 0xFF07) { + timer.Write(address, value) + } else if address == 0xFF0F { + InterruptFlags = value + } else if (address >= 0xFF10) && (address <= 0xFF3F) { + // Ignore sound + } else { + fmt.Printf("Writing to IO: invalid address %X\n", address) os.Exit(1) } - - fmt.Printf("Writing to IO: invalid address %X", address) - os.Exit(1) } diff --git a/gb/ram.go b/gb/ram.go index f9d8e90..b4245c9 100644 --- a/gb/ram.go +++ b/gb/ram.go @@ -21,7 +21,7 @@ func (ram *RAM) WRAMRead(address uint16) byte { address -= 0xC000 if address >= 0x2000 { - fmt.Printf("Reading from WRAM: invalid address %X", address+0xC000) + fmt.Printf("Reading from WRAM: invalid address %X\n", address+0xC000) os.Exit(1) } @@ -33,7 +33,7 @@ func (ram *RAM) WRAMWrite(address uint16, value byte) { address -= 0xC000 if address >= 0x2000 { - fmt.Printf("Writing to WRAM: invalid address %X", address) + fmt.Printf("Writing to WRAM: invalid address %X\n", address) os.Exit(1) } diff --git a/gb/timer.go b/gb/timer.go new file mode 100644 index 0000000..4a2e357 --- /dev/null +++ b/gb/timer.go @@ -0,0 +1,125 @@ +package gb + +import ( + "fmt" + "os" +) + +var timer = Timer{DIV: 0xAC00} + +type Timer struct { + DIV uint16 + TIMA byte + TMA byte + TAC byte +} + +func (timer *Timer) Tick() { + previousDIV := timer.DIV + timer.DIV++ + + timerNeedsUpdate := false + + // Determine clock mode + // TAC (Timer control register) + // Bits 0-1 determines clock frequency + // 00: 4096 Hz + // 01: 262144 Hz + // 10: 65536 Hz + // 11: 16384 Hz + // Bit 2 determines whether timer is enabled or not + + clockMode := timer.TAC & (0b11) + switch clockMode { + case 0b00: + // 4096 Hz + // Detect a falling edge by comparing bit 9 in the previous DIV value + // with bit 9 in the current DIV value + previouslyEnabled := previousDIV&(1<<9) != 0 + currentlyDisabled := timer.DIV&(1<<9) == 0 + timerNeedsUpdate = previouslyEnabled && currentlyDisabled + + case 0b01: + // 262144 Hz + // Detect a falling edge by comparing bit 3 in the previous DIV value + // with bit 3 in the current DIV value + previouslyEnabled := previousDIV&(1<<3) != 0 + currentlyDisabled := timer.DIV&(1<<3) == 0 + timerNeedsUpdate = previouslyEnabled && currentlyDisabled + + case 0b10: + // 65536 Hz + // Detect a falling edge by comparing bit 5 in the previous DIV value + // with bit 5 in the current DIV value + previouslyEnabled := previousDIV&(1<<5) != 0 + currentlyDisabled := timer.DIV&(1<<5) == 0 + timerNeedsUpdate = previouslyEnabled && currentlyDisabled + + case 0b11: + // 16384 Hz + // Detect a falling edge by comparing bit 7 in the previous DIV value + // with bit 7 in the current DIV value + previouslyEnabled := previousDIV&(1<<7) != 0 + currentlyDisabled := timer.DIV&(1<<7) == 0 + timerNeedsUpdate = previouslyEnabled && currentlyDisabled + } + + timerIsEnabled := timer.TAC&(1<<2) != 0 + // If the timer needs to be updated based on the determined clock mode and the timer is enabled, increment the timer + if timerNeedsUpdate && timerIsEnabled { + timer.TIMA++ + + // Check if TIMA is going to wrap and trigger an interrupt if necessary + if timer.TIMA == 0xFF { + timer.TIMA = timer.TMA + + fmt.Println("TODO: cpu_request_interrupt(IT_TIMER") + } + } + +} + +func (timer *Timer) Read(address uint16) byte { + switch address { + case 0xFF04: + return byte(timer.DIV >> 8) + + case 0xFF05: + return timer.TIMA + + case 0xFF06: + return timer.TMA + + case 0xFF07: + return timer.TAC + + default: + fmt.Printf("Reading from Timer: invalid address %X\n", address) + os.Exit(1) + } + return 0 +} + +func (timer *Timer) Write(address uint16, value byte) { + switch address { + case 0xFF04: + //DIV + timer.DIV = 0 + + case 0xFF05: + //TIMA + timer.TIMA = value + + case 0xFF06: + //TMA + timer.TMA = value + + case 0xFF07: + //TAC + timer.TAC = value + + default: + fmt.Printf("Writing to Timer: invalid address %X\n", address) + os.Exit(1) + } +} diff --git a/main.go b/main.go index 006bea9..164fc15 100644 --- a/main.go +++ b/main.go @@ -9,10 +9,7 @@ import ( "gb-player/gb" ) -var running = true - func main() { - // FIXME(m): Allow specifying rom file on command line if len(os.Args) != 2 { log.Fatalln("No rom file specified") } @@ -26,9 +23,8 @@ func main() { } fmt.Println("Executing instructions") - running := true - for running { + + for !console.CPU.Halted { console.CPU.Step() - running = !console.CPU.Halted } }