More work on CPU

This commit is contained in:
Michael Smith 2025-08-21 19:53:41 +02:00
parent b72667947f
commit 6e3149d093
8 changed files with 226 additions and 102 deletions

21
cpu.go
View File

@ -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++
}

View File

@ -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
}
}

View File

@ -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"))

View File

@ -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

111
gb/cpu.go
View File

@ -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
}

122
gb/cpu_test.go Normal file
View File

@ -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))
}

View File

@ -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
}

20
main.go
View File

@ -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()
}
}