diff --git a/gb/bus.go b/gb/bus.go new file mode 100644 index 0000000..3b846db --- /dev/null +++ b/gb/bus.go @@ -0,0 +1,41 @@ +package gb + +import ( + "fmt" +) + +type Bus struct { + Cart *Cartridge +} + +func NewBus(cart *Cartridge) *Bus { + bus := Bus{Cart: cart} + + return &bus +} + +func (bus *Bus) Read(address uint16) (byte, error) { + 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) + } + return value, nil + } else { + return 0, fmt.Errorf("Reading from bus address %X not implemented!", address) + } +} + +func (bus *Bus) Write(address uint16, value byte) error { + if address < 0x8000 { + // ROM data + err := bus.Cart.Write(address, value) + if err != nil { + return fmt.Errorf("Error writing to bus address %X: %s", address, err) + } + return nil + } else { + return fmt.Errorf("Writing to bus address %X not implemented!", address) + } +} diff --git a/gb/cartridge.go b/gb/cartridge.go index 0fffbff..57839a8 100644 --- a/gb/cartridge.go +++ b/gb/cartridge.go @@ -7,17 +7,7 @@ import ( "os" ) -// See https://gbdev.io/pandocs/The_Cartridge_Header.html#0104-0133--nintendo-logo -var expectedLogo = [48]byte{ - 0xCE, 0xED, 0x66, 0x66, 0xCC, 0x0D, 0x00, 0x0B, - 0x03, 0x73, 0x00, 0x83, 0x00, 0x0C, 0x00, 0x0D, - 0x00, 0x08, 0x11, 0x1F, 0x88, 0x89, 0x00, 0x0E, - 0xDC, 0xCC, 0x6E, 0xE6, 0xDD, 0xDD, 0xD9, 0x99, - 0xBB, 0xBB, 0x67, 0x63, 0x6E, 0x0E, 0xEC, 0xCC, - 0xDD, 0xDC, 0x99, 0x9F, 0xBB, 0xB9, 0x33, 0x3E, -} - -// Cartridge types +// Cartridge types aka Mappers // See https://gbdev.io/pandocs/The_Cartridge_Header.html#0147--cartridge-type var cartridgeTypes = map[byte]string{ 0x00: "ROM ONLY", @@ -233,6 +223,7 @@ type ROMHeader struct { } type Cartridge struct { + Data []byte Filename string Mapper string Title string @@ -246,53 +237,62 @@ type Cartridge struct { } func InsertCartridge(filename string) (*Cartridge, error) { - cartridge := Cartridge{Filename: filename} + cart := Cartridge{Filename: filename} - rom, err := os.ReadFile(filename) + data, err := os.ReadFile(filename) if err != nil { - return &cartridge, err + return &cart, err } + cart.Data = data // Read header var header ROMHeader - buffer := bytes.NewReader(rom) + buffer := bytes.NewReader(cart.Data) err = binary.Read(buffer, binary.LittleEndian, &header) if err != nil { - return &cartridge, nil + return &cart, nil } // Convert some header values - cartridge.Title = string(bytes.Trim(header.Title[:], "\x00")) - cartridge.Mapper = cartridgeTypes[header.CartridgeType] + cart.Title = string(bytes.Trim(header.Title[:], "\x00")) + cart.Mapper = cartridgeTypes[header.CartridgeType] if header.OldLicenseeCode == 0x33 { // FIXME(m): Support new licensee codes - cartridge.Licensee = "Indicates that the New licensee code should be used instead." + cart.Licensee = "Indicates that the New licensee code should be used instead." } else { - cartridge.Licensee = oldLicensees[header.OldLicenseeCode] + cart.Licensee = oldLicensees[header.OldLicenseeCode] } - cartridge.SGBSupport = (header.SGBFlag == 0x03) - cartridge.ROMSize = 32 * (1 << header.ROMSize) - cartridge.RAMSize = ramSizes[header.RAMSize] + cart.SGBSupport = (header.SGBFlag == 0x03) + cart.ROMSize = 32 * (1 << header.ROMSize) + cart.RAMSize = ramSizes[header.RAMSize] switch header.DestinationCode { case 0x00: - cartridge.Destination = "Japan (and possibly overseas)" + cart.Destination = "Japan (and possibly overseas)" case 0x01: - cartridge.Destination = "Overseas only" + cart.Destination = "Overseas only" default: - cartridge.Destination = "UNKNOWN" + cart.Destination = "UNKNOWN" } - cartridge.Version = int(header.MaskROMVersionNumber) + cart.Version = int(header.MaskROMVersionNumber) // Calculate and verify checksum for address := uint16(0x0134); address <= uint16(0x014C); address++ { - cartridge.Checksum = cartridge.Checksum - rom[address] - 1 + cart.Checksum = cart.Checksum - cart.Data[address] - 1 } - if cartridge.Checksum != header.Checksum { - return &cartridge, fmt.Errorf("ROM checksum failed: %X does not equal %X", cartridge.Checksum, header.Checksum) + if cart.Checksum != header.Checksum { + return &cart, fmt.Errorf("ROM checksum failed: %X does not equal %X", cart.Checksum, header.Checksum) } // NOTE(m): Ignoring global checksum which is not used, except by one emulator. // See https://gbdev.io/pandocs/The_Cartridge_Header.html#014e-014f--global-checksum - return &cartridge, nil + return &cart, nil +} + +func (cart *Cartridge) Read(address uint16) (byte, error) { + return cart.Data[address], nil +} + +func (cart *Cartridge) Write(address uint16, value byte) error { + return fmt.Errorf("Writing to cartridge address %X not implemented!", address) } diff --git a/gb/console.go b/gb/console.go index 6397a4a..453d45a 100644 --- a/gb/console.go +++ b/gb/console.go @@ -11,15 +11,20 @@ const ( ) type Console struct { - Cartridge *Cartridge - front *image.RGBA + Bus *Bus + CPU *CPU + front *image.RGBA } func NewConsole(path string) (*Console, error) { - cartridge := InsertCartridge(path) + cartridge, err := InsertCartridge(path) + if err != nil { + return &Console{}, err + } buffer := image.NewRGBA(image.Rect(0, 0, ConsoleWidth, ConsoleHeight)) - console := Console{Cartridge: cartridge, front: buffer} + bus := NewBus(cartridge) + console := Console{Bus: bus, CPU: NewCPU(bus), front: buffer} return &console, nil } diff --git a/gb/cpu.go b/gb/cpu.go new file mode 100644 index 0000000..f529b2c --- /dev/null +++ b/gb/cpu.go @@ -0,0 +1,81 @@ +package gb + +import ( + "fmt" +) + +const CPUFrequency = 4194304 + +type Registers struct { + A byte + F byte + B byte + C byte + D byte + E byte + H byte + L byte + PC uint16 + SP uint16 +} + +type CPU struct { + Bus *Bus + Regs Registers + FetchedData uint16 + MemoryDestination uint16 + DestinationIsMemory bool + CurrentOpcode byte + Halted bool + Stepping bool + InterruptMasterEnabled bool + CurrentInstruction string +} + +func NewCPU(bus *Bus) *CPU { + cpu := CPU{} + cpu.Bus = bus + cpu.Regs = Registers{PC: 0x100} + cpu.Stepping = true + + return &cpu +} + +func (cpu *CPU) Step() error { + if !cpu.Halted { + err := cpu.fetchInstruction() + if err != nil { + return fmt.Errorf("Error fetching instruction: %s", err) + } + 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) fetchData() { + +} + +func (cpu *CPU) execute() { + fmt.Println("Not executing yet...") +} diff --git a/gb/instructions.go b/gb/instructions.go new file mode 100644 index 0000000..9f56341 --- /dev/null +++ b/gb/instructions.go @@ -0,0 +1,22 @@ +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 4f5a35e..3d04420 100644 --- a/main.go +++ b/main.go @@ -1,9 +1,15 @@ package main import ( - "gb-player/ui" + "fmt" + "log" + + // "gb-player/ui" + "gb-player/gb" ) +var running = true + func main() { // FIXME(m): Allow specifying rom file on command line // if len(os.Args) != 2 { @@ -12,5 +18,22 @@ func main() { // romPath := os.Args[1] romPath := "./roms/dmg-acid2.gb" - ui.Run(romPath) + // ui.Run(romPath) + + console, err := gb.NewConsole(romPath) + if err != nil { + log.Fatal(err) + } + + 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") + } + } } diff --git a/ui/view.go b/ui/view.go index d27d0d9..c09ab00 100644 --- a/ui/view.go +++ b/ui/view.go @@ -22,8 +22,7 @@ func (view *View) Update(dt uint64) { console := view.console renderer := view.controller.renderer - // console.StepMilliSeconds(dt) - console.StepMilliSeconds(sdl.GetTicks64()) + console.StepMilliSeconds(dt) buffer := console.Buffer()