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
|
return &bus
|
||||||
}
|
}
|
||||||
|
|
||||||
func (bus *Bus) Read(address uint16) (byte, error) {
|
func (bus *Bus) Read(address uint16) byte {
|
||||||
if address < 0x8000 {
|
if address < 0x8000 {
|
||||||
// ROM data
|
// ROM data
|
||||||
value, err := bus.Cart.Read(address)
|
value, err := bus.Cart.Read(address)
|
||||||
if err != nil {
|
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 {
|
} 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 {
|
type Cartridge struct {
|
||||||
|
Header *ROMHeader
|
||||||
Data []byte
|
Data []byte
|
||||||
Filename string
|
Filename string
|
||||||
Mapper string
|
Mapper string
|
||||||
@ -252,6 +253,7 @@ func InsertCartridge(filename string) (*Cartridge, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return &cart, nil
|
return &cart, nil
|
||||||
}
|
}
|
||||||
|
cart.Header = &header
|
||||||
|
|
||||||
// Convert some header values
|
// Convert some header values
|
||||||
cart.Title = string(bytes.Trim(header.Title[:], "\x00"))
|
cart.Title = string(bytes.Trim(header.Title[:], "\x00"))
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
package gb
|
package gb
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"image"
|
"image"
|
||||||
"image/color"
|
"image/color"
|
||||||
)
|
)
|
||||||
@ -17,13 +18,28 @@ type Console struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewConsole(path string) (*Console, error) {
|
func NewConsole(path string) (*Console, error) {
|
||||||
cartridge, err := InsertCartridge(path)
|
cart, err := InsertCartridge(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &Console{}, err
|
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))
|
buffer := image.NewRGBA(image.Rect(0, 0, ConsoleWidth, ConsoleHeight))
|
||||||
|
|
||||||
bus := NewBus(cartridge)
|
bus := NewBus(cart)
|
||||||
console := Console{Bus: bus, CPU: NewCPU(bus), front: buffer}
|
console := Console{Bus: bus, CPU: NewCPU(bus), front: buffer}
|
||||||
|
|
||||||
return &console, nil
|
return &console, nil
|
||||||
|
|||||||
111
gb/cpu.go
111
gb/cpu.go
@ -2,13 +2,23 @@ package gb
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
const CPUFrequency = 4194304
|
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 {
|
type Registers struct {
|
||||||
A byte
|
A byte
|
||||||
F byte
|
F CPUFlags
|
||||||
B byte
|
B byte
|
||||||
C byte
|
C byte
|
||||||
D byte
|
D byte
|
||||||
@ -20,62 +30,83 @@ type Registers struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CPU struct {
|
type CPU struct {
|
||||||
Bus *Bus
|
Bus *Bus
|
||||||
Regs Registers
|
Regs Registers
|
||||||
FetchedData uint16
|
Halted bool
|
||||||
MemoryDestination uint16
|
Stepping bool
|
||||||
DestinationIsMemory bool
|
|
||||||
CurrentOpcode byte
|
|
||||||
Halted bool
|
|
||||||
Stepping bool
|
|
||||||
InterruptMasterEnabled bool
|
|
||||||
CurrentInstruction string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCPU(bus *Bus) *CPU {
|
func NewCPU(bus *Bus) *CPU {
|
||||||
cpu := CPU{}
|
cpu := CPU{}
|
||||||
cpu.Bus = bus
|
cpu.Bus = bus
|
||||||
cpu.Regs = Registers{PC: 0x100}
|
cpu.Regs = Registers{}
|
||||||
cpu.Stepping = true
|
cpu.Stepping = true
|
||||||
|
|
||||||
return &cpu
|
return &cpu
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cpu *CPU) Step() error {
|
func (cpu *CPU) Step() {
|
||||||
if !cpu.Halted {
|
if !cpu.Halted {
|
||||||
err := cpu.fetchInstruction()
|
opcode := cpu.Bus.Read(cpu.Regs.PC)
|
||||||
if err != nil {
|
cpu.Regs.PC++
|
||||||
return fmt.Errorf("Error fetching instruction: %s", err)
|
|
||||||
|
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 {
|
func (cpu *CPU) SetFlag(flag CPUFlags) {
|
||||||
opcode, err := cpu.Bus.Read(cpu.Regs.PC)
|
cpu.Regs.F |= flag
|
||||||
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) ClearFlag(flag CPUFlags) {
|
||||||
|
cpu.Regs.F &^= flag
|
||||||
}
|
}
|
||||||
|
|
||||||
func (cpu *CPU) execute() {
|
func (cpu *CPU) ToggleFlag(flag CPUFlags) {
|
||||||
fmt.Println("Not executing yet...")
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
// "gb-player/ui"
|
// "gb-player/ui"
|
||||||
"gb-player/gb"
|
"gb-player/gb"
|
||||||
@ -12,11 +13,10 @@ var running = true
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
// FIXME(m): Allow specifying rom file on command line
|
// FIXME(m): Allow specifying rom file on command line
|
||||||
// if len(os.Args) != 2 {
|
if len(os.Args) != 2 {
|
||||||
// log.Fatalln("No rom file specified")
|
log.Fatalln("No rom file specified")
|
||||||
// }
|
}
|
||||||
// romPath := os.Args[1]
|
romPath := os.Args[1]
|
||||||
romPath := "./roms/dmg-acid2.gb"
|
|
||||||
|
|
||||||
// ui.Run(romPath)
|
// ui.Run(romPath)
|
||||||
|
|
||||||
@ -25,15 +25,9 @@ func main() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fmt.Println("Executing instructions")
|
||||||
running := true
|
running := true
|
||||||
for running {
|
for running {
|
||||||
err := console.CPU.Step()
|
console.CPU.Step()
|
||||||
if err != nil {
|
|
||||||
fmt.Println(err)
|
|
||||||
running = false
|
|
||||||
fmt.Println("CPU stopped")
|
|
||||||
} else {
|
|
||||||
fmt.Println("CPU step")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user