Compare commits
2 Commits
1bae615b39
...
c18615c629
| Author | SHA1 | Date | |
|---|---|---|---|
| c18615c629 | |||
| 82d3898216 |
42
gb/bus.go
42
gb/bus.go
@ -4,12 +4,27 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 0x0000 - 0x3FFF : ROM Bank 0
|
||||||
|
// 0x4000 - 0x7FFF : ROM Bank 1 - Switchable
|
||||||
|
// 0x8000 - 0x97FF : CHR RAM
|
||||||
|
// 0x9800 - 0x9BFF : BG Map 1
|
||||||
|
// 0x9C00 - 0x9FFF : BG Map 2
|
||||||
|
// 0xA000 - 0xBFFF : Cartridge RAM
|
||||||
|
// 0xC000 - 0xCFFF : RAM Bank 0
|
||||||
|
// 0xD000 - 0xDFFF : RAM Bank 1-7 - switchable - Color only
|
||||||
|
// 0xE000 - 0xFDFF : Reserved - Echo RAM
|
||||||
|
// 0xFE00 - 0xFE9F : Object Attribute Memory
|
||||||
|
// 0xFEA0 - 0xFEFF : Reserved - Unusable
|
||||||
|
// 0xFF00 - 0xFF7F : I/O Registers
|
||||||
|
// 0xFF80 - 0xFFFE : Zero Page
|
||||||
|
|
||||||
type Bus struct {
|
type Bus struct {
|
||||||
Cart *Cartridge
|
Cart *Cartridge
|
||||||
|
RAM *RAM
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewBus(cart *Cartridge) *Bus {
|
func NewBus(cart *Cartridge) *Bus {
|
||||||
bus := Bus{Cart: cart}
|
bus := Bus{Cart: cart, RAM: NewRAM()}
|
||||||
|
|
||||||
return &bus
|
return &bus
|
||||||
}
|
}
|
||||||
@ -23,12 +38,21 @@ func (bus *Bus) Read(address uint16) byte {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return value
|
return value
|
||||||
|
} else if address < 0xE000 {
|
||||||
|
return bus.RAM.WRAMRead(address)
|
||||||
} else {
|
} else {
|
||||||
fmt.Printf("Reading from bus address %X not implemented!\n", address)
|
fmt.Printf("Reading from bus address %X not implemented!\n", address)
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bus *Bus) Read16(address uint16) uint16 {
|
||||||
|
lo := bus.Read(address)
|
||||||
|
hi := bus.Read(address + 1)
|
||||||
|
|
||||||
|
return uint16(lo) | uint16(hi)<<8
|
||||||
|
}
|
||||||
|
|
||||||
func (bus *Bus) Write(address uint16, value byte) error {
|
func (bus *Bus) Write(address uint16, value byte) error {
|
||||||
if address < 0x8000 {
|
if address < 0x8000 {
|
||||||
// ROM data
|
// ROM data
|
||||||
@ -37,7 +61,23 @@ func (bus *Bus) Write(address uint16, value byte) error {
|
|||||||
return fmt.Errorf("Error writing to bus address %X: %s", address, err)
|
return fmt.Errorf("Error writing to bus address %X: %s", address, err)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
} else if address < 0xE000 {
|
||||||
|
bus.RAM.WRAMWrite(address, value)
|
||||||
|
return nil
|
||||||
} else {
|
} else {
|
||||||
return fmt.Errorf("Writing to bus address %X not implemented!", address)
|
return fmt.Errorf("Writing to bus address %X not implemented!", address)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (bus *Bus) Write16(address uint16, value uint16) error {
|
||||||
|
err := bus.Write(address+1, byte((value>>8)&0xFF))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = bus.Write(address, byte(value&0xFF))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
93
gb/cpu.go
93
gb/cpu.go
@ -30,16 +30,20 @@ type Registers struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type CPU struct {
|
type CPU struct {
|
||||||
Bus *Bus
|
Bus *Bus
|
||||||
Regs Registers
|
Regs Registers
|
||||||
Halted bool
|
Halted bool
|
||||||
Stepping bool
|
Stepping bool
|
||||||
|
InterruptMasterEnable bool
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewCPU(bus *Bus) *CPU {
|
func NewCPU(bus *Bus) *CPU {
|
||||||
cpu := CPU{}
|
cpu := CPU{}
|
||||||
cpu.Bus = bus
|
cpu.Bus = bus
|
||||||
cpu.Regs = Registers{}
|
// NOTE(m): PC is usually set to 0x100 by the boot rom
|
||||||
|
// TODO(m): SP is usually set programmatically by the cartridge code.
|
||||||
|
// Remove this hardcoded value later!
|
||||||
|
cpu.Regs = Registers{PC: 0x100, SP: 0xDFFF}
|
||||||
cpu.Stepping = true
|
cpu.Stepping = true
|
||||||
|
|
||||||
return &cpu
|
return &cpu
|
||||||
@ -48,15 +52,34 @@ func NewCPU(bus *Bus) *CPU {
|
|||||||
func (cpu *CPU) Step() {
|
func (cpu *CPU) Step() {
|
||||||
if !cpu.Halted {
|
if !cpu.Halted {
|
||||||
opcode := cpu.Bus.Read(cpu.Regs.PC)
|
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,
|
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)
|
opcode, cpu.Bus.Read(cpu.Regs.PC+1), cpu.Bus.Read(cpu.Regs.PC+2), cpu.Regs.A, cpu.Regs.B, cpu.Regs.C)
|
||||||
|
|
||||||
|
cpu.Regs.PC++
|
||||||
|
|
||||||
switch opcode {
|
switch opcode {
|
||||||
|
|
||||||
case 0x00:
|
case 0x00:
|
||||||
// NOP
|
// NOP
|
||||||
|
|
||||||
|
case 0x21:
|
||||||
|
// LD HL, n16
|
||||||
|
cpu.Regs.L = cpu.Bus.Read(cpu.Regs.PC)
|
||||||
|
// emu_cycles(1);
|
||||||
|
cpu.Regs.H = cpu.Bus.Read(cpu.Regs.PC + 1)
|
||||||
|
// emu_cycles(1);
|
||||||
|
cpu.Regs.PC += 2
|
||||||
|
|
||||||
|
case 0x31:
|
||||||
|
// LD SP, n16
|
||||||
|
lo := cpu.Bus.Read(cpu.Regs.PC)
|
||||||
|
// emu_cycles(1);
|
||||||
|
hi := cpu.Bus.Read(cpu.Regs.PC + 1)
|
||||||
|
// emu_cycles(1);
|
||||||
|
cpu.Regs.SP = uint16(lo) | uint16(hi)<<8
|
||||||
|
cpu.Regs.PC += 2
|
||||||
|
|
||||||
case 0x3C:
|
case 0x3C:
|
||||||
// INC A
|
// INC A
|
||||||
cpu.Regs.A++
|
cpu.Regs.A++
|
||||||
@ -75,19 +98,49 @@ func (cpu *CPU) Step() {
|
|||||||
} else {
|
} else {
|
||||||
cpu.ClearFlag(H)
|
cpu.ClearFlag(H)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case 0xCB:
|
||||||
|
// Prefix byte instructions
|
||||||
|
cbOpcode := cpu.Bus.Read(cpu.Regs.PC)
|
||||||
|
|
||||||
|
fmt.Printf("%04X: (%02X %02X %02X) A: %02X B: %02X C: %02X\n", cpu.Regs.PC,
|
||||||
|
cbOpcode, cpu.Bus.Read(cpu.Regs.PC+1), cpu.Bus.Read(cpu.Regs.PC+2), cpu.Regs.A, cpu.Regs.B, cpu.Regs.C)
|
||||||
|
|
||||||
|
cpu.Regs.PC++
|
||||||
|
|
||||||
|
switch cbOpcode {
|
||||||
|
default:
|
||||||
|
fmt.Printf("\nINVALID INSTRUCTION! Unknown CB opcode: %02X\n", cbOpcode)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
case 0xC3:
|
case 0xC3:
|
||||||
// JP a16
|
// JP a16
|
||||||
lo := cpu.Bus.Read(cpu.Regs.PC)
|
lo := cpu.Bus.Read(cpu.Regs.PC)
|
||||||
// emu_cycles(1);
|
// emu_cycles(1);
|
||||||
hi := cpu.Bus.Read(cpu.Regs.PC + 1)
|
hi := cpu.Bus.Read(cpu.Regs.PC + 1)
|
||||||
// emu_cycles(1);
|
// emu_cycles(1);
|
||||||
cpu.Regs.PC = uint16(hi)<<8 | uint16(lo)
|
cpu.Regs.PC = uint16(lo) | uint16(hi)<<8
|
||||||
|
|
||||||
|
case 0xCD:
|
||||||
|
// CALL a16
|
||||||
|
cpu.StackPush16(cpu.Regs.PC + 2)
|
||||||
|
|
||||||
|
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(lo) | uint16(hi)<<8
|
||||||
|
|
||||||
case 0xE9:
|
case 0xE9:
|
||||||
// JP HL
|
// JP HL
|
||||||
val := uint16(cpu.Regs.H)<<8 | uint16(cpu.Regs.L)
|
val := uint16(cpu.Regs.H)<<8 | uint16(cpu.Regs.L)
|
||||||
cpu.Regs.PC = val
|
cpu.Regs.PC = val
|
||||||
|
|
||||||
|
case 0xF3:
|
||||||
|
// DI
|
||||||
|
cpu.InterruptMasterEnable = false
|
||||||
|
|
||||||
default:
|
default:
|
||||||
fmt.Printf("\nINVALID INSTRUCTION! Unknown opcode: %02X\n", opcode)
|
fmt.Printf("\nINVALID INSTRUCTION! Unknown opcode: %02X\n", opcode)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@ -110,3 +163,27 @@ func (cpu *CPU) ToggleFlag(flag CPUFlags) {
|
|||||||
func (cpu *CPU) IsFlagSet(flag CPUFlags) bool {
|
func (cpu *CPU) IsFlagSet(flag CPUFlags) bool {
|
||||||
return cpu.Regs.F&flag != 0
|
return cpu.Regs.F&flag != 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (cpu *CPU) StackPush(data byte) {
|
||||||
|
cpu.Regs.SP--
|
||||||
|
cpu.Bus.Write(cpu.Regs.SP, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cpu *CPU) StackPush16(data uint16) {
|
||||||
|
cpu.StackPush(byte((data >> 8) & 0xFF))
|
||||||
|
cpu.StackPush(byte(data & 0xFF))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cpu *CPU) StackPop() byte {
|
||||||
|
val := cpu.Bus.Read(cpu.Regs.SP)
|
||||||
|
cpu.Regs.SP++
|
||||||
|
|
||||||
|
return val
|
||||||
|
}
|
||||||
|
|
||||||
|
func (cpu *CPU) StackPop16() uint16 {
|
||||||
|
lo := cpu.StackPop()
|
||||||
|
hi := cpu.StackPop()
|
||||||
|
|
||||||
|
return uint16(hi)<<8 | uint16(lo)
|
||||||
|
}
|
||||||
|
|||||||
@ -54,16 +54,16 @@ func TestInstruction00(t *testing.T) {
|
|||||||
|
|
||||||
cpu.Step()
|
cpu.Step()
|
||||||
|
|
||||||
assert.Equal(t, cpu.Regs.PC, uint16(0x01))
|
assert.Equal(t, uint16(0x01), cpu.Regs.PC)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInstruction3C(t *testing.T) {
|
func TestInstruction3C(t *testing.T) {
|
||||||
// Should increment A register
|
// Should increment A register
|
||||||
cpu := createCPU([]byte{0x3C, 0x00, 0x00})
|
cpu := createCPU([]byte{0x3C, 0x00, 0x00})
|
||||||
|
|
||||||
assert.Equal(t, cpu.Regs.A, byte(0x0))
|
assert.Equal(t, byte(0x0), cpu.Regs.A)
|
||||||
cpu.Step()
|
cpu.Step()
|
||||||
assert.Equal(t, cpu.Regs.A, byte(0x01))
|
assert.Equal(t, byte(0x01), cpu.Regs.A)
|
||||||
|
|
||||||
// Should clear Zero Flag
|
// Should clear Zero Flag
|
||||||
cpu = createCPU([]byte{0x3C, 0x00, 0x00})
|
cpu = createCPU([]byte{0x3C, 0x00, 0x00})
|
||||||
@ -108,7 +108,7 @@ func TestInstructionC3(t *testing.T) {
|
|||||||
|
|
||||||
cpu.Step()
|
cpu.Step()
|
||||||
|
|
||||||
assert.Equal(t, cpu.Regs.PC, uint16(0x150))
|
assert.Equal(t, uint16(0x150), cpu.Regs.PC)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestInstructionE9(t *testing.T) {
|
func TestInstructionE9(t *testing.T) {
|
||||||
@ -118,5 +118,52 @@ func TestInstructionE9(t *testing.T) {
|
|||||||
|
|
||||||
cpu.Step()
|
cpu.Step()
|
||||||
|
|
||||||
assert.Equal(t, cpu.Regs.PC, uint16(0x1234))
|
assert.Equal(t, uint16(0x1234), cpu.Regs.PC)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInstructionF3(t *testing.T) {
|
||||||
|
cpu := createCPU([]byte{0xF3, 0x00, 0x00})
|
||||||
|
|
||||||
|
cpu.InterruptMasterEnable = true
|
||||||
|
cpu.Step()
|
||||||
|
|
||||||
|
assert.False(t, cpu.InterruptMasterEnable)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInstruction31(t *testing.T) {
|
||||||
|
cpu := createCPU([]byte{0x31, 0xFF, 0xCF})
|
||||||
|
|
||||||
|
cpu.Step()
|
||||||
|
|
||||||
|
// Should load SP with n16 value
|
||||||
|
assert.Equal(t, uint16(0xCFFF), cpu.Regs.SP)
|
||||||
|
|
||||||
|
// Should step over the 16 bit value onto the next instruction, i.e. increase the program counter with 3.
|
||||||
|
assert.Equal(t, uint16(0x0003), cpu.Regs.PC)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInstructionCD(t *testing.T) {
|
||||||
|
cpu := createCPU([]byte{0xCD, 0x4C, 0x48, 0x41})
|
||||||
|
|
||||||
|
cpu.Step()
|
||||||
|
|
||||||
|
// Should push the address of the next instruction onto the stack
|
||||||
|
// In this case 0x41 which is at address 0x03 (i.e. the 4th instruction in the row above creating the CPU instance)
|
||||||
|
assert.Equal(t, uint16(0x03), cpu.Bus.Read16(cpu.Regs.SP))
|
||||||
|
|
||||||
|
// Should jump to n16 value
|
||||||
|
assert.Equal(t, uint16(0x484C), cpu.Regs.PC)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInstruction21(t *testing.T) {
|
||||||
|
cpu := createCPU([]byte{0x21, 0x34, 0x12})
|
||||||
|
|
||||||
|
cpu.Step()
|
||||||
|
|
||||||
|
// Should load the 16-bit immediate value into the combined HL register
|
||||||
|
assert.Equal(t, byte(0x12), cpu.Regs.H)
|
||||||
|
assert.Equal(t, byte(0x34), cpu.Regs.L)
|
||||||
|
|
||||||
|
// Should increase the stack pointer
|
||||||
|
assert.Equal(t, uint16(0x3), cpu.Regs.PC)
|
||||||
}
|
}
|
||||||
|
|||||||
55
gb/ram.go
Normal file
55
gb/ram.go
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package gb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
type RAM struct {
|
||||||
|
WRAM [0x2000]byte
|
||||||
|
HRAM [0x80]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRAM() *RAM {
|
||||||
|
ram := RAM{}
|
||||||
|
|
||||||
|
return &ram
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ram *RAM) WRAMRead(address uint16) byte {
|
||||||
|
// TODO(m): Understand this line
|
||||||
|
address -= 0xC000
|
||||||
|
|
||||||
|
if address >= 0x2000 {
|
||||||
|
fmt.Printf("Reading from WRAM: invalid address %X", address+0xC000)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return ram.WRAM[address]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ram *RAM) WRAMWrite(address uint16, value byte) {
|
||||||
|
// TODO(m): Understand this line
|
||||||
|
address -= 0xC000
|
||||||
|
|
||||||
|
if address >= 0x2000 {
|
||||||
|
fmt.Printf("Writing to WRAM: invalid address %X", address)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ram.WRAM[address] = value
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ram *RAM) HRAMRead(address uint16) byte {
|
||||||
|
// TODO(m): Understand this line
|
||||||
|
address -= 0xFF80
|
||||||
|
|
||||||
|
return ram.HRAM[address]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ram *RAM) HRAMWrite(address uint16, value byte) {
|
||||||
|
// TODO(m): Understand this line
|
||||||
|
address -= 0xFF80
|
||||||
|
|
||||||
|
ram.HRAM[address] = value
|
||||||
|
}
|
||||||
59
gb/stack_test.go
Normal file
59
gb/stack_test.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package gb
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestStackPush(t *testing.T) {
|
||||||
|
cpu := createCPU([]byte{0x00, 0x00, 0x00})
|
||||||
|
|
||||||
|
cpu.StackPush(0xDE)
|
||||||
|
|
||||||
|
// Should decrement the stack pointer
|
||||||
|
assert.Equal(t, cpu.Regs.SP, uint16(0xDFFE))
|
||||||
|
|
||||||
|
// Should write the value to the stack
|
||||||
|
assert.Equal(t, cpu.Bus.Read(cpu.Regs.SP), byte(0xDE))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStackPush16(t *testing.T) {
|
||||||
|
cpu := createCPU([]byte{0x00, 0x00, 0x00})
|
||||||
|
|
||||||
|
cpu.StackPush16(0xDEAD)
|
||||||
|
|
||||||
|
// Should decrement the stack pointer twice
|
||||||
|
assert.Equal(t, cpu.Regs.SP, uint16(0xDFFD))
|
||||||
|
|
||||||
|
// Should write the value to the stack
|
||||||
|
assert.Equal(t, cpu.Bus.Read16(cpu.Regs.SP), uint16(0xDEAD))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStackPop(t *testing.T) {
|
||||||
|
cpu := createCPU([]byte{0x00, 0x00, 0x00})
|
||||||
|
|
||||||
|
cpu.StackPush(0xDE)
|
||||||
|
|
||||||
|
val := cpu.StackPop()
|
||||||
|
|
||||||
|
// Should increment the stack pointer
|
||||||
|
assert.Equal(t, cpu.Regs.SP, uint16(0xDFFF))
|
||||||
|
|
||||||
|
// Should return the byte value
|
||||||
|
assert.Equal(t, val, byte(0xDE))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStackPop16(t *testing.T) {
|
||||||
|
cpu := createCPU([]byte{0x00, 0x00, 0x00})
|
||||||
|
|
||||||
|
cpu.StackPush16(0xBEEF)
|
||||||
|
|
||||||
|
val := cpu.StackPop16()
|
||||||
|
|
||||||
|
// Should increment the stack pointer
|
||||||
|
assert.Equal(t, cpu.Regs.SP, uint16(0xDFFF))
|
||||||
|
|
||||||
|
// Should return the 16 bit value
|
||||||
|
assert.Equal(t, val, uint16(0xBEEF))
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user