WIP: CPU
This commit is contained in:
parent
3cb7e3a5c9
commit
b72667947f
41
gb/bus.go
Normal file
41
gb/bus.go
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -7,17 +7,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
// See https://gbdev.io/pandocs/The_Cartridge_Header.html#0104-0133--nintendo-logo
|
// Cartridge types aka Mappers
|
||||||
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
|
|
||||||
// See https://gbdev.io/pandocs/The_Cartridge_Header.html#0147--cartridge-type
|
// See https://gbdev.io/pandocs/The_Cartridge_Header.html#0147--cartridge-type
|
||||||
var cartridgeTypes = map[byte]string{
|
var cartridgeTypes = map[byte]string{
|
||||||
0x00: "ROM ONLY",
|
0x00: "ROM ONLY",
|
||||||
@ -233,6 +223,7 @@ type ROMHeader struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type Cartridge struct {
|
type Cartridge struct {
|
||||||
|
Data []byte
|
||||||
Filename string
|
Filename string
|
||||||
Mapper string
|
Mapper string
|
||||||
Title string
|
Title string
|
||||||
@ -246,53 +237,62 @@ type Cartridge struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func InsertCartridge(filename string) (*Cartridge, error) {
|
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 {
|
if err != nil {
|
||||||
return &cartridge, err
|
return &cart, err
|
||||||
}
|
}
|
||||||
|
cart.Data = data
|
||||||
|
|
||||||
// Read header
|
// Read header
|
||||||
var header ROMHeader
|
var header ROMHeader
|
||||||
buffer := bytes.NewReader(rom)
|
buffer := bytes.NewReader(cart.Data)
|
||||||
err = binary.Read(buffer, binary.LittleEndian, &header)
|
err = binary.Read(buffer, binary.LittleEndian, &header)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return &cartridge, nil
|
return &cart, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Convert some header values
|
// Convert some header values
|
||||||
cartridge.Title = string(bytes.Trim(header.Title[:], "\x00"))
|
cart.Title = string(bytes.Trim(header.Title[:], "\x00"))
|
||||||
cartridge.Mapper = cartridgeTypes[header.CartridgeType]
|
cart.Mapper = cartridgeTypes[header.CartridgeType]
|
||||||
if header.OldLicenseeCode == 0x33 {
|
if header.OldLicenseeCode == 0x33 {
|
||||||
// FIXME(m): Support new licensee codes
|
// 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 {
|
} else {
|
||||||
cartridge.Licensee = oldLicensees[header.OldLicenseeCode]
|
cart.Licensee = oldLicensees[header.OldLicenseeCode]
|
||||||
}
|
}
|
||||||
cartridge.SGBSupport = (header.SGBFlag == 0x03)
|
cart.SGBSupport = (header.SGBFlag == 0x03)
|
||||||
cartridge.ROMSize = 32 * (1 << header.ROMSize)
|
cart.ROMSize = 32 * (1 << header.ROMSize)
|
||||||
cartridge.RAMSize = ramSizes[header.RAMSize]
|
cart.RAMSize = ramSizes[header.RAMSize]
|
||||||
switch header.DestinationCode {
|
switch header.DestinationCode {
|
||||||
case 0x00:
|
case 0x00:
|
||||||
cartridge.Destination = "Japan (and possibly overseas)"
|
cart.Destination = "Japan (and possibly overseas)"
|
||||||
case 0x01:
|
case 0x01:
|
||||||
cartridge.Destination = "Overseas only"
|
cart.Destination = "Overseas only"
|
||||||
default:
|
default:
|
||||||
cartridge.Destination = "UNKNOWN"
|
cart.Destination = "UNKNOWN"
|
||||||
}
|
}
|
||||||
cartridge.Version = int(header.MaskROMVersionNumber)
|
cart.Version = int(header.MaskROMVersionNumber)
|
||||||
|
|
||||||
// Calculate and verify checksum
|
// Calculate and verify checksum
|
||||||
for address := uint16(0x0134); address <= uint16(0x014C); address++ {
|
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 {
|
if cart.Checksum != header.Checksum {
|
||||||
return &cartridge, fmt.Errorf("ROM checksum failed: %X does not equal %X", cartridge.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.
|
// 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
|
// 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)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,15 +11,20 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type Console struct {
|
type Console struct {
|
||||||
Cartridge *Cartridge
|
Bus *Bus
|
||||||
front *image.RGBA
|
CPU *CPU
|
||||||
|
front *image.RGBA
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConsole(path string) (*Console, error) {
|
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))
|
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
|
return &console, nil
|
||||||
}
|
}
|
||||||
|
|||||||
81
gb/cpu.go
Normal file
81
gb/cpu.go
Normal file
@ -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...")
|
||||||
|
}
|
||||||
22
gb/instructions.go
Normal file
22
gb/instructions.go
Normal file
@ -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
|
||||||
|
}
|
||||||
27
main.go
27
main.go
@ -1,9 +1,15 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"gb-player/ui"
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
// "gb-player/ui"
|
||||||
|
"gb-player/gb"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
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 {
|
||||||
@ -12,5 +18,22 @@ func main() {
|
|||||||
// romPath := os.Args[1]
|
// romPath := os.Args[1]
|
||||||
romPath := "./roms/dmg-acid2.gb"
|
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")
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -22,8 +22,7 @@ func (view *View) Update(dt uint64) {
|
|||||||
console := view.console
|
console := view.console
|
||||||
renderer := view.controller.renderer
|
renderer := view.controller.renderer
|
||||||
|
|
||||||
// console.StepMilliSeconds(dt)
|
console.StepMilliSeconds(dt)
|
||||||
console.StepMilliSeconds(sdl.GetTicks64())
|
|
||||||
|
|
||||||
buffer := console.Buffer()
|
buffer := console.Buffer()
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user