More work on CPU
This commit is contained in:
parent
b72667947f
commit
6e3149d093
21
cpu.go
21
cpu.go
@ -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++
|
||||
}
|
||||
10
gb/bus.go
10
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
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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"))
|
||||
|
||||
@ -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
111
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
|
||||
}
|
||||
|
||||
122
gb/cpu_test.go
Normal file
122
gb/cpu_test.go
Normal 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))
|
||||
}
|
||||
@ -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
20
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()
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user