From 6e3149d093847dfbe8dde28c70bd3ac0c12f4b6b Mon Sep 17 00:00:00 2001 From: Michael Smith Date: Thu, 21 Aug 2025 19:53:41 +0200 Subject: [PATCH] More work on CPU --- cpu.go | 21 -------- gb/bus.go | 10 ++-- gb/cartridge.go | 2 + gb/console.go | 20 +++++++- gb/cpu.go | 111 ++++++++++++++++++++++++++--------------- gb/cpu_test.go | 122 +++++++++++++++++++++++++++++++++++++++++++++ gb/instructions.go | 22 -------- main.go | 20 +++----- 8 files changed, 226 insertions(+), 102 deletions(-) delete mode 100644 cpu.go create mode 100644 gb/cpu_test.go delete mode 100644 gb/instructions.go diff --git a/cpu.go b/cpu.go deleted file mode 100644 index f70fd35..0000000 --- a/cpu.go +++ /dev/null @@ -1,21 +0,0 @@ -package main - -type Cpu struct { - A uint8 - Flags uint8 - BC uint16 - DE uint16 - HL uint16 - SP uint16 - PC uint16 -} - -func Reset() Cpu { - cpu := Cpu{} - - return cpu -} - -func Tick(cpu *Cpu) { - cpu.PC++ -} diff --git a/gb/bus.go b/gb/bus.go index 3b846db..b7f7aa4 100644 --- a/gb/bus.go +++ b/gb/bus.go @@ -14,16 +14,18 @@ func NewBus(cart *Cartridge) *Bus { return &bus } -func (bus *Bus) Read(address uint16) (byte, error) { +func (bus *Bus) Read(address uint16) byte { if address < 0x8000 { // ROM data value, err := bus.Cart.Read(address) if err != nil { - return 0, fmt.Errorf("Error reading from bus address %X: %s", address, err) + fmt.Printf("Error reading from bus address %X: %s", address, err) + return 0 } - return value, nil + return value } else { - return 0, fmt.Errorf("Reading from bus address %X not implemented!", address) + fmt.Printf("Reading from bus address %X not implemented!\n", address) + return 0 } } diff --git a/gb/cartridge.go b/gb/cartridge.go index 57839a8..67369c1 100644 --- a/gb/cartridge.go +++ b/gb/cartridge.go @@ -223,6 +223,7 @@ type ROMHeader struct { } type Cartridge struct { + Header *ROMHeader Data []byte Filename string Mapper string @@ -252,6 +253,7 @@ func InsertCartridge(filename string) (*Cartridge, error) { if err != nil { return &cart, nil } + cart.Header = &header // Convert some header values cart.Title = string(bytes.Trim(header.Title[:], "\x00")) diff --git a/gb/console.go b/gb/console.go index 453d45a..a1e119a 100644 --- a/gb/console.go +++ b/gb/console.go @@ -1,6 +1,7 @@ package gb import ( + "fmt" "image" "image/color" ) @@ -17,13 +18,28 @@ type Console struct { } func NewConsole(path string) (*Console, error) { - cartridge, err := InsertCartridge(path) + cart, err := InsertCartridge(path) if err != nil { return &Console{}, err } + + fmt.Println("Cartridge loaded:") + fmt.Printf("\t Title : %s\n", cart.Title) + fmt.Printf("\t Type : %02X (%s)\n", cart.Header.CartridgeType, cart.Mapper) + fmt.Printf("\t ROM Size : %d KB\n", cart.ROMSize) + fmt.Printf("\t RAM Size : %02X (%s)\n", cart.Header.RAMSize, cart.RAMSize) + fmt.Printf("\t LIC Code : %02X (%s)\n", cart.Header.OldLicenseeCode, cart.Licensee) + fmt.Printf("\t ROM Vers : %02X\n", cart.Header.MaskROMVersionNumber) + fmt.Printf("\t Checksum : %02X ", cart.Checksum) + if cart.Checksum == cart.Header.Checksum { + fmt.Printf("(PASSED)\n") + } else { + fmt.Printf("(FAILED)\n") + } + buffer := image.NewRGBA(image.Rect(0, 0, ConsoleWidth, ConsoleHeight)) - bus := NewBus(cartridge) + bus := NewBus(cart) console := Console{Bus: bus, CPU: NewCPU(bus), front: buffer} return &console, nil diff --git a/gb/cpu.go b/gb/cpu.go index f529b2c..a7ce5fd 100644 --- a/gb/cpu.go +++ b/gb/cpu.go @@ -2,13 +2,23 @@ package gb import ( "fmt" + "os" ) const CPUFrequency = 4194304 +type CPUFlags byte + +const ( + C CPUFlags = 1 << 4 // Carry Flag + H CPUFlags = 1 << 5 // Half Carry Flag + N CPUFlags = 1 << 6 // Subtract Flag + Z CPUFlags = 1 << 7 // Zero Flag +) + type Registers struct { A byte - F byte + F CPUFlags B byte C byte D byte @@ -20,62 +30,83 @@ type Registers struct { } type CPU struct { - Bus *Bus - Regs Registers - FetchedData uint16 - MemoryDestination uint16 - DestinationIsMemory bool - CurrentOpcode byte - Halted bool - Stepping bool - InterruptMasterEnabled bool - CurrentInstruction string + Bus *Bus + Regs Registers + Halted bool + Stepping bool } func NewCPU(bus *Bus) *CPU { cpu := CPU{} cpu.Bus = bus - cpu.Regs = Registers{PC: 0x100} + cpu.Regs = Registers{} cpu.Stepping = true return &cpu } -func (cpu *CPU) Step() error { +func (cpu *CPU) Step() { if !cpu.Halted { - err := cpu.fetchInstruction() - if err != nil { - return fmt.Errorf("Error fetching instruction: %s", err) + opcode := cpu.Bus.Read(cpu.Regs.PC) + cpu.Regs.PC++ + + fmt.Printf("%04X: (%02X %02X %02X) A: %02X B: %02X C: %02X\n", cpu.Regs.PC, + opcode, cpu.Bus.Read(cpu.Regs.PC), cpu.Bus.Read(cpu.Regs.PC+1), cpu.Regs.A, cpu.Regs.B, cpu.Regs.C) + + switch opcode { + + case 0x00: + // NOP + case 0x3C: + // INC A + cpu.Regs.A++ + + // Set appropriate flags + if cpu.Regs.A == 0 { + cpu.SetFlag(Z) + } else { + cpu.ClearFlag(Z) + } + + cpu.ClearFlag(N) + + if (cpu.Regs.A & 0x0F) == 0 { + cpu.SetFlag(H) + } else { + cpu.ClearFlag(H) + } + case 0xC3: + // JP a16 + lo := cpu.Bus.Read(cpu.Regs.PC) + // emu_cycles(1); + hi := cpu.Bus.Read(cpu.Regs.PC + 1) + // emu_cycles(1); + cpu.Regs.PC = uint16(hi)<<8 | uint16(lo) + + case 0xE9: + // JP HL + val := uint16(cpu.Regs.H)<<8 | uint16(cpu.Regs.L) + cpu.Regs.PC = val + + default: + fmt.Printf("\nINVALID INSTRUCTION! Unknown opcode: %02X\n", opcode) + os.Exit(1) } - cpu.fetchData() - cpu.execute() } - - return nil } -func (cpu *CPU) fetchInstruction() error { - opcode, err := cpu.Bus.Read(cpu.Regs.PC) - if err != nil { - return fmt.Errorf("Error fetching instruction at address %X", cpu.Regs.PC) - } - cpu.CurrentOpcode = opcode - cpu.CurrentInstruction, err = InstructionByOpcode(cpu.CurrentOpcode) - if err != nil { - return fmt.Errorf("Error translating opcode %02X: %s at PC: %04X", cpu.CurrentOpcode, err, cpu.Regs.PC) - } - - fmt.Printf("Executing instruction: %02X PC: %04X\n", cpu.CurrentOpcode, cpu.Regs.PC) - - cpu.Regs.PC++ - - return nil +func (cpu *CPU) SetFlag(flag CPUFlags) { + cpu.Regs.F |= flag } -func (cpu *CPU) fetchData() { - +func (cpu *CPU) ClearFlag(flag CPUFlags) { + cpu.Regs.F &^= flag } -func (cpu *CPU) execute() { - fmt.Println("Not executing yet...") +func (cpu *CPU) ToggleFlag(flag CPUFlags) { + cpu.Regs.F ^= flag +} + +func (cpu *CPU) IsFlagSet(flag CPUFlags) bool { + return cpu.Regs.F&flag != 0 } diff --git a/gb/cpu_test.go b/gb/cpu_test.go new file mode 100644 index 0000000..637d0a4 --- /dev/null +++ b/gb/cpu_test.go @@ -0,0 +1,122 @@ +package gb + +import ( + "testing" + + "github.com/stretchr/testify/assert" +) + +func createCPU(data []byte) *CPU { + cart := Cartridge{Filename: "test"} + cart.Data = data + cpu := NewCPU(NewBus(&cart)) + cpu.Regs.PC = 0 + + return cpu +} + +func TestSetFlag(t *testing.T) { + cartridge, _ := InsertCartridge("../roms/dmg-acid2.gb") + bus := NewBus(cartridge) + cpu := NewCPU(bus) + + cpu.SetFlag(C) + + assert.True(t, cpu.IsFlagSet(C)) +} + +func TestClearFlag(t *testing.T) { + cartridge, _ := InsertCartridge("../roms/dmg-acid2.gb") + bus := NewBus(cartridge) + cpu := NewCPU(bus) + + cpu.SetFlag(C) + assert.True(t, cpu.IsFlagSet(C)) + + cpu.ClearFlag(C) + assert.False(t, cpu.IsFlagSet(C)) +} + +func TestToggleFlag(t *testing.T) { + cartridge, _ := InsertCartridge("../roms/dmg-acid2.gb") + bus := NewBus(cartridge) + cpu := NewCPU(bus) + + cpu.ToggleFlag(C) + assert.True(t, cpu.IsFlagSet(C)) + + cpu.ToggleFlag(C) + assert.False(t, cpu.IsFlagSet(C)) +} + +func TestInstruction00(t *testing.T) { + cpu := createCPU([]byte{0x00, 0x00, 0x00}) + + cpu.Step() + + assert.Equal(t, cpu.Regs.PC, uint16(0x01)) +} + +func TestInstruction3C(t *testing.T) { + // Should increment A register + cpu := createCPU([]byte{0x3C, 0x00, 0x00}) + + assert.Equal(t, cpu.Regs.A, byte(0x0)) + cpu.Step() + assert.Equal(t, cpu.Regs.A, byte(0x01)) + + // Should clear Zero Flag + cpu = createCPU([]byte{0x3C, 0x00, 0x00}) + + cpu.SetFlag(Z) + cpu.Step() + assert.False(t, cpu.IsFlagSet(Z)) + + // Should set Zero Flag + cpu = createCPU([]byte{0x3C, 0x00, 0x00}) + cpu.Regs.A = 0xFF + + assert.False(t, cpu.IsFlagSet(Z)) + cpu.Step() + assert.True(t, cpu.IsFlagSet(Z)) + + // Should clear Subtract Flag + cpu = createCPU([]byte{0x3C, 0x00, 0x00}) + + cpu.SetFlag(N) + cpu.Step() + assert.False(t, cpu.IsFlagSet(N)) + + // Should set Half Carry Flag if we overflow from bit 3 + cpu = createCPU([]byte{0x3C, 0x00, 0x00}) + cpu.Regs.A = 0x0F + + assert.False(t, cpu.IsFlagSet(H)) + cpu.Step() + assert.True(t, cpu.IsFlagSet(H)) + + // Should clear Half Carry Flag if we don't overflow from bit 3 + cpu = createCPU([]byte{0x3C, 0x00, 0x00}) + + cpu.SetFlag(H) + cpu.Step() + assert.False(t, cpu.IsFlagSet(H)) +} + +func TestInstructionC3(t *testing.T) { + cpu := createCPU([]byte{0xC3, 0x50, 0x01}) + + cpu.Step() + + assert.Equal(t, cpu.Regs.PC, uint16(0x150)) +} + +func TestInstructionE9(t *testing.T) { + cpu := createCPU([]byte{0xE9, 0x00, 0x00}) + cpu.Regs.H = 0x12 + cpu.Regs.L = 0x34 + + cpu.Step() + + assert.Equal(t, cpu.Regs.PC, uint16(0x1234)) +} diff --git a/gb/instructions.go b/gb/instructions.go deleted file mode 100644 index 9f56341..0000000 --- a/gb/instructions.go +++ /dev/null @@ -1,22 +0,0 @@ -package gb - -import ( - "fmt" -) - -var instructions = map[byte]string{ - 0x00: "NOP", - 0x01: "FOO", -} - -type Instruction struct { -} - -func InstructionByOpcode(opcode byte) (string, error) { - instruction, ok := instructions[opcode] - if !ok { - return "", fmt.Errorf("Unknown opcode: %02X", opcode) - } - - return instruction, nil -} diff --git a/main.go b/main.go index 3d04420..05f19b7 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "fmt" "log" + "os" // "gb-player/ui" "gb-player/gb" @@ -12,11 +13,10 @@ 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") - // } - // romPath := os.Args[1] - romPath := "./roms/dmg-acid2.gb" + if len(os.Args) != 2 { + log.Fatalln("No rom file specified") + } + romPath := os.Args[1] // ui.Run(romPath) @@ -25,15 +25,9 @@ func main() { log.Fatal(err) } + fmt.Println("Executing instructions") running := true for running { - err := console.CPU.Step() - if err != nil { - fmt.Println(err) - running = false - fmt.Println("CPU stopped") - } else { - fmt.Println("CPU step") - } + console.CPU.Step() } }