Compare commits
6 Commits
1bae615b39
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b860999dc8 | ||
|
|
7678fda9e7 | ||
|
|
7c494acc7e | ||
|
|
772ff893af | ||
|
|
c18615c629 | ||
|
|
82d3898216 |
53
gb/bus.go
53
gb/bus.go
@@ -4,12 +4,27 @@ import (
|
||||
"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 {
|
||||
Cart *Cartridge
|
||||
RAM *RAM
|
||||
}
|
||||
|
||||
func NewBus(cart *Cartridge) *Bus {
|
||||
bus := Bus{Cart: cart}
|
||||
bus := Bus{Cart: cart, RAM: NewRAM()}
|
||||
|
||||
return &bus
|
||||
}
|
||||
@@ -19,16 +34,29 @@ func (bus *Bus) Read(address uint16) byte {
|
||||
// ROM data
|
||||
value, err := bus.Cart.Read(address)
|
||||
if err != nil {
|
||||
fmt.Printf("Error reading from bus address %X: %s", address, err)
|
||||
fmt.Printf("Error reading from bus address %X: %s\n", address, err)
|
||||
return 0
|
||||
}
|
||||
return value
|
||||
} else if address < 0xE000 {
|
||||
//WRAM (Working RAM)
|
||||
return bus.RAM.WRAMRead(address)
|
||||
} else if address < 0xFF80 {
|
||||
//IO Registers
|
||||
return IORead(address)
|
||||
} else {
|
||||
fmt.Printf("Reading from bus address %X not implemented!\n", address)
|
||||
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 {
|
||||
if address < 0x8000 {
|
||||
// ROM data
|
||||
@@ -37,7 +65,28 @@ func (bus *Bus) Write(address uint16, value byte) error {
|
||||
return fmt.Errorf("Error writing to bus address %X: %s", address, err)
|
||||
}
|
||||
return nil
|
||||
} else if address < 0xE000 {
|
||||
//WRAM (Working RAM)
|
||||
bus.RAM.WRAMWrite(address, value)
|
||||
return nil
|
||||
} else if address < 0xFF80 {
|
||||
//IO Registers
|
||||
IOWrite(address, value)
|
||||
return nil
|
||||
} else {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -15,6 +15,7 @@ type Console struct {
|
||||
Bus *Bus
|
||||
CPU *CPU
|
||||
front *image.RGBA
|
||||
Ticks uint64
|
||||
}
|
||||
|
||||
func NewConsole(path string) (*Console, error) {
|
||||
@@ -47,15 +48,25 @@ func NewConsole(path string) (*Console, error) {
|
||||
|
||||
func (console *Console) StepMilliSeconds(ms uint64) {
|
||||
speed := int(ms / 3)
|
||||
for y := 0; y < ConsoleHeight; y++ {
|
||||
for x := 0; x < ConsoleWidth; x++ {
|
||||
for y := range ConsoleHeight {
|
||||
for x := range ConsoleWidth {
|
||||
console.front.Set(x, y, color.RGBA{0, uint8(y + speed), uint8(x + speed), 255})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (console *Console) Buffer() *image.RGBA {
|
||||
|
||||
return console.front
|
||||
}
|
||||
|
||||
func (console *Console) Cycle(cycles int) {
|
||||
for range cycles {
|
||||
// Cycles are given in M-cycles (machine cycles) instead of T-states (system clock ticks)
|
||||
// One machine cycle takes 4 system clock ticks to complete
|
||||
for range 4 {
|
||||
console.Ticks++
|
||||
timer.Tick()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
288
gb/cpu.go
288
gb/cpu.go
@@ -30,16 +30,21 @@ type Registers struct {
|
||||
}
|
||||
|
||||
type CPU struct {
|
||||
Bus *Bus
|
||||
Regs Registers
|
||||
Halted bool
|
||||
Stepping bool
|
||||
Bus *Bus
|
||||
Regs Registers
|
||||
Halted bool
|
||||
Stepping bool
|
||||
InterruptMasterEnable bool
|
||||
InterruptFlags byte
|
||||
}
|
||||
|
||||
func NewCPU(bus *Bus) *CPU {
|
||||
cpu := CPU{}
|
||||
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{SP: 0xDFFF}
|
||||
cpu.Stepping = true
|
||||
|
||||
return &cpu
|
||||
@@ -48,15 +53,153 @@ func NewCPU(bus *Bus) *CPU {
|
||||
func (cpu *CPU) Step() {
|
||||
if !cpu.Halted {
|
||||
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)
|
||||
fmt.Printf("%04X: (%02X %02X %02X) A: %02X B: %02X C: %02X D: %02X E: %02X H: %02X L: %02X\n", cpu.Regs.PC,
|
||||
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.D, cpu.Regs.E, cpu.Regs.H, cpu.Regs.L)
|
||||
|
||||
cpu.Regs.PC++
|
||||
|
||||
switch opcode {
|
||||
|
||||
case 0x00:
|
||||
// NOP
|
||||
|
||||
case 0x0D:
|
||||
// DEC C
|
||||
cpu.Regs.C--
|
||||
|
||||
// Set appropriate flags
|
||||
if cpu.Regs.C == 0 {
|
||||
cpu.SetFlag(Z)
|
||||
} else {
|
||||
cpu.ClearFlag(Z)
|
||||
}
|
||||
|
||||
cpu.SetFlag(N)
|
||||
|
||||
if (cpu.Regs.C & 0x0F) == 0x0F {
|
||||
cpu.SetFlag(H)
|
||||
} else {
|
||||
cpu.ClearFlag(H)
|
||||
}
|
||||
|
||||
case 0x0E:
|
||||
// LD C, n8
|
||||
val := cpu.Bus.Read(cpu.Regs.PC)
|
||||
// emu_cycles(1);
|
||||
cpu.Regs.C = val
|
||||
cpu.Regs.PC++
|
||||
|
||||
case 0x10:
|
||||
// STOP n8
|
||||
cpu.Halted = true
|
||||
|
||||
case 0x11:
|
||||
// LD DE, n16
|
||||
cpu.Regs.E = cpu.Bus.Read(cpu.Regs.PC)
|
||||
// emu_cycles(1);
|
||||
cpu.Regs.D = cpu.Bus.Read(cpu.Regs.PC + 1)
|
||||
// emu_cycles(1);
|
||||
cpu.Regs.PC += 2
|
||||
|
||||
case 0x12:
|
||||
// LD [DE], A
|
||||
// Get 16-bit address from DE
|
||||
address := uint16(cpu.Regs.D)<<8 | uint16(cpu.Regs.E)
|
||||
cpu.Bus.Write(address, cpu.Regs.A)
|
||||
|
||||
case 0x14:
|
||||
// INC D
|
||||
cpu.Regs.D++
|
||||
|
||||
// Set appropriate flags
|
||||
if cpu.Regs.D == 0 {
|
||||
cpu.SetFlag(Z)
|
||||
} else {
|
||||
cpu.ClearFlag(Z)
|
||||
}
|
||||
|
||||
cpu.ClearFlag(N)
|
||||
|
||||
if (cpu.Regs.D & 0x0F) == 0 {
|
||||
cpu.SetFlag(H)
|
||||
} else {
|
||||
cpu.ClearFlag(H)
|
||||
}
|
||||
|
||||
case 0x18:
|
||||
// JR e8
|
||||
// Jump relative to 8-bit signed offset
|
||||
// emu_cycles(3);
|
||||
offset := int8(cpu.Bus.Read(cpu.Regs.PC))
|
||||
cpu.Regs.PC++
|
||||
cpu.Regs.PC = uint16(int(cpu.Regs.PC) + int(offset))
|
||||
|
||||
case 0x1C:
|
||||
// INC E
|
||||
cpu.Regs.E++
|
||||
|
||||
// Set appropriate flags
|
||||
if cpu.Regs.E == 0 {
|
||||
cpu.SetFlag(Z)
|
||||
} else {
|
||||
cpu.ClearFlag(Z)
|
||||
}
|
||||
|
||||
cpu.ClearFlag(N)
|
||||
|
||||
if (cpu.Regs.E & 0x0F) == 0 {
|
||||
cpu.SetFlag(H)
|
||||
} else {
|
||||
cpu.ClearFlag(H)
|
||||
}
|
||||
|
||||
case 0x20:
|
||||
// JR NZ, e8
|
||||
if cpu.IsFlagSet(Z) {
|
||||
// Z is set, don't execute
|
||||
// emu_cycles(2);
|
||||
cpu.Regs.PC++
|
||||
} else {
|
||||
// Z is not set, execute
|
||||
// Jump relative to 8-bit signed offset
|
||||
// emu_cycles(3);
|
||||
offset := int8(cpu.Bus.Read(cpu.Regs.PC))
|
||||
cpu.Regs.PC++
|
||||
cpu.Regs.PC = uint16(int(cpu.Regs.PC) + int(offset))
|
||||
}
|
||||
|
||||
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 0x2A:
|
||||
// LD A, [HL+] or LD A, [HLI]
|
||||
// Get 16-bit address from HL
|
||||
address := uint16(cpu.Regs.H)<<8 | uint16(cpu.Regs.L)
|
||||
// Read byte at address and assign value to A register
|
||||
cpu.Regs.A = cpu.Bus.Read(address)
|
||||
// emu_cycles(1);
|
||||
// Increment HL
|
||||
address++
|
||||
cpu.Regs.H = byte(address >> 8)
|
||||
cpu.Regs.L = byte(address)
|
||||
// emu_cycles(1);
|
||||
|
||||
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:
|
||||
// INC A
|
||||
cpu.Regs.A++
|
||||
@@ -75,19 +218,114 @@ func (cpu *CPU) Step() {
|
||||
} else {
|
||||
cpu.ClearFlag(H)
|
||||
}
|
||||
|
||||
case 0x3E:
|
||||
// LD A, n8
|
||||
val := cpu.Bus.Read(cpu.Regs.PC)
|
||||
// emu_cycles(1);
|
||||
cpu.Regs.A = val
|
||||
cpu.Regs.PC++
|
||||
|
||||
case 0x47:
|
||||
// LD B, A
|
||||
cpu.Regs.B = cpu.Regs.A
|
||||
|
||||
case 0x78:
|
||||
// LD A, B
|
||||
cpu.Regs.A = cpu.Regs.B
|
||||
|
||||
case 0x7C:
|
||||
// LD A, H
|
||||
cpu.Regs.A = cpu.Regs.H
|
||||
|
||||
case 0x7D:
|
||||
// LD A, L
|
||||
cpu.Regs.A = cpu.Regs.L
|
||||
|
||||
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 {
|
||||
// case 0x7E:
|
||||
// // BIT 7, [HL]
|
||||
// // Read byte pointed to by address HL
|
||||
// address := uint16(cpu.Regs.H)<<8 | uint16(cpu.Regs.L)
|
||||
// val := cpu.Bus.Read(address)
|
||||
|
||||
// // Check if bit 7 is set
|
||||
// if (val & 0x80) == 0 {
|
||||
// // Set zero flag if bit is not set
|
||||
// cpu.SetFlag(Z)
|
||||
// }
|
||||
// cpu.ClearFlag(N)
|
||||
// cpu.SetFlag(H)
|
||||
|
||||
default:
|
||||
fmt.Printf("\nINVALID INSTRUCTION! Unknown CB opcode: %02X\n", cbOpcode)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
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)
|
||||
cpu.Regs.PC = uint16(lo) | uint16(hi)<<8
|
||||
|
||||
case 0xC9:
|
||||
// RET
|
||||
// emu_cycles(4);
|
||||
cpu.Regs.PC = cpu.StackPop16()
|
||||
|
||||
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 0xE0:
|
||||
// LDH [a8], A
|
||||
offset := cpu.Bus.Read(cpu.Regs.PC)
|
||||
address := 0xFF00 | uint16(offset)
|
||||
cpu.Bus.Write(address, cpu.Regs.A)
|
||||
cpu.Regs.PC++
|
||||
|
||||
case 0xE5:
|
||||
// PUSH HL
|
||||
// emu_cycles(4);
|
||||
cpu.StackPush(cpu.Regs.H)
|
||||
cpu.StackPush(cpu.Regs.L)
|
||||
|
||||
case 0xE9:
|
||||
// JP HL
|
||||
val := uint16(cpu.Regs.H)<<8 | uint16(cpu.Regs.L)
|
||||
cpu.Regs.PC = val
|
||||
|
||||
case 0xEA:
|
||||
// LD [a16], A
|
||||
lo := cpu.Bus.Read(cpu.Regs.PC)
|
||||
cpu.Regs.PC++
|
||||
hi := cpu.Bus.Read(cpu.Regs.PC)
|
||||
cpu.Regs.PC++
|
||||
|
||||
address := uint16(lo) | uint16(hi)<<8
|
||||
cpu.Bus.Write(address, cpu.Regs.A)
|
||||
|
||||
case 0xF3:
|
||||
// DI
|
||||
cpu.InterruptMasterEnable = false
|
||||
|
||||
default:
|
||||
fmt.Printf("\nINVALID INSTRUCTION! Unknown opcode: %02X\n", opcode)
|
||||
os.Exit(1)
|
||||
@@ -110,3 +348,35 @@ func (cpu *CPU) ToggleFlag(flag CPUFlags) {
|
||||
func (cpu *CPU) IsFlagSet(flag CPUFlags) bool {
|
||||
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)
|
||||
}
|
||||
|
||||
func (cpu *CPU) GetInterruptFlags() byte {
|
||||
return cpu.InterruptFlags
|
||||
}
|
||||
|
||||
func (cpu *CPU) SetInterruptFlags(value byte) {
|
||||
cpu.InterruptFlags = value
|
||||
}
|
||||
|
||||
412
gb/cpu_test.go
412
gb/cpu_test.go
@@ -54,16 +54,15 @@ func TestInstruction00(t *testing.T) {
|
||||
|
||||
cpu.Step()
|
||||
|
||||
assert.Equal(t, cpu.Regs.PC, uint16(0x01))
|
||||
assert.Equal(t, uint16(0x01), cpu.Regs.PC)
|
||||
}
|
||||
|
||||
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))
|
||||
assert.Equal(t, byte(0x01), cpu.Regs.A)
|
||||
|
||||
// Should clear Zero Flag
|
||||
cpu = createCPU([]byte{0x3C, 0x00, 0x00})
|
||||
@@ -76,7 +75,6 @@ func TestInstruction3C(t *testing.T) {
|
||||
cpu = createCPU([]byte{0x3C, 0x00, 0x00})
|
||||
cpu.Regs.A = 0xFF
|
||||
|
||||
assert.False(t, cpu.IsFlagSet(Z))
|
||||
cpu.Step()
|
||||
assert.True(t, cpu.IsFlagSet(Z))
|
||||
|
||||
@@ -91,7 +89,6 @@ func TestInstruction3C(t *testing.T) {
|
||||
cpu = createCPU([]byte{0x3C, 0x00, 0x00})
|
||||
cpu.Regs.A = 0x0F
|
||||
|
||||
assert.False(t, cpu.IsFlagSet(H))
|
||||
cpu.Step()
|
||||
assert.True(t, cpu.IsFlagSet(H))
|
||||
|
||||
@@ -108,7 +105,7 @@ func TestInstructionC3(t *testing.T) {
|
||||
|
||||
cpu.Step()
|
||||
|
||||
assert.Equal(t, cpu.Regs.PC, uint16(0x150))
|
||||
assert.Equal(t, uint16(0x150), cpu.Regs.PC)
|
||||
}
|
||||
|
||||
func TestInstructionE9(t *testing.T) {
|
||||
@@ -118,5 +115,406 @@ func TestInstructionE9(t *testing.T) {
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
// func TestInstructionCB7E(t *testing.T) {
|
||||
// cpu := createCPU([]byte{0xCB, 0x7E, 0x00, 0x00})
|
||||
|
||||
// cpu.Regs.H = 0xFF
|
||||
// cpu.Regs.L = 0x40
|
||||
// err := cpu.Bus.Write(0xFF40, 0xFF)
|
||||
// assert.Equal(t, err, nil)
|
||||
|
||||
// // FIXME(m): This needs bus access to IO, in particular the LCD
|
||||
// // at 0xFF40.
|
||||
|
||||
// // assert.Equal(t, 0xFF, cpu.Bus.Read(0xFF40))
|
||||
|
||||
// // cpu.Step()
|
||||
|
||||
// // // Should set the zero flag
|
||||
// // assert.True(t, cpu.IsFlagSet(Z))
|
||||
// }
|
||||
|
||||
func TestInstruction47(t *testing.T) {
|
||||
cpu := createCPU([]byte{0x47, 0x00, 0x00})
|
||||
|
||||
cpu.Regs.A = 0xDE
|
||||
|
||||
cpu.Step()
|
||||
|
||||
// Should load value in register A into register B
|
||||
assert.Equal(t, byte(0xDE), cpu.Regs.B)
|
||||
|
||||
// Should increase the stack pointer
|
||||
assert.Equal(t, uint16(0x1), cpu.Regs.PC)
|
||||
}
|
||||
|
||||
func TestInstruction11(t *testing.T) {
|
||||
cpu := createCPU([]byte{0x11, 0x34, 0x12})
|
||||
|
||||
cpu.Step()
|
||||
|
||||
// Should load the 16-bit immediate value into the combined DE register
|
||||
assert.Equal(t, byte(0x12), cpu.Regs.D)
|
||||
assert.Equal(t, byte(0x34), cpu.Regs.E)
|
||||
|
||||
// Should increase the stack pointer
|
||||
assert.Equal(t, uint16(0x3), cpu.Regs.PC)
|
||||
}
|
||||
|
||||
func TestInstruction0E(t *testing.T) {
|
||||
cpu := createCPU([]byte{0x0E, 0xDE, 0x00})
|
||||
|
||||
cpu.Step()
|
||||
|
||||
// Should load the 8-bit immediate value into register C
|
||||
assert.Equal(t, byte(0xDE), cpu.Regs.C)
|
||||
|
||||
// Should increase the stack pointer
|
||||
assert.Equal(t, uint16(0x2), cpu.Regs.PC)
|
||||
}
|
||||
|
||||
func TestInstruction2A(t *testing.T) {
|
||||
cpu := createCPU([]byte{0x2A, 0x00, 0x00})
|
||||
cpu.Regs.H = 0xD1
|
||||
cpu.Regs.L = 0x23
|
||||
cpu.Bus.Write(0xD123, 0xDE)
|
||||
|
||||
cpu.Step()
|
||||
|
||||
// Should load the byte pointed to by HL into register A
|
||||
assert.Equal(t, byte(0xDE), cpu.Regs.A)
|
||||
|
||||
// Should increment HL
|
||||
hl := uint16(cpu.Regs.H)<<8 | uint16(cpu.Regs.L)
|
||||
assert.Equal(t, uint16(0xD124), hl)
|
||||
|
||||
// Should increase the stack pointer
|
||||
assert.Equal(t, uint16(0x1), cpu.Regs.PC)
|
||||
}
|
||||
|
||||
func TestInstruction12(t *testing.T) {
|
||||
cpu := createCPU([]byte{0x12, 0x00, 0x00})
|
||||
cpu.Regs.D = 0xD1
|
||||
cpu.Regs.E = 0x23
|
||||
cpu.Regs.A = 0xDE
|
||||
|
||||
cpu.Step()
|
||||
|
||||
// Should copy the value in register A into the byte pointed to by DE
|
||||
address := uint16(cpu.Regs.D)<<8 | uint16(cpu.Regs.E)
|
||||
assert.Equal(t, byte(0xDE), cpu.Bus.Read(address))
|
||||
|
||||
// Should increase the stack pointer
|
||||
assert.Equal(t, uint16(0x1), cpu.Regs.PC)
|
||||
}
|
||||
|
||||
func TestInstruction1C(t *testing.T) {
|
||||
// Should increment E register
|
||||
cpu := createCPU([]byte{0x1C, 0x00, 0x00})
|
||||
|
||||
cpu.Step()
|
||||
assert.Equal(t, byte(0x01), cpu.Regs.E)
|
||||
|
||||
// Should clear Zero Flag
|
||||
cpu = createCPU([]byte{0x1C, 0x00, 0x00})
|
||||
|
||||
cpu.SetFlag(Z)
|
||||
cpu.Step()
|
||||
assert.False(t, cpu.IsFlagSet(Z))
|
||||
|
||||
// Should set Zero Flag
|
||||
cpu = createCPU([]byte{0x1C, 0x00, 0x00})
|
||||
cpu.Regs.E = 0xFF
|
||||
|
||||
cpu.Step()
|
||||
assert.True(t, cpu.IsFlagSet(Z))
|
||||
|
||||
// Should clear Subtract Flag
|
||||
cpu = createCPU([]byte{0x1C, 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{0x1C, 0x00, 0x00})
|
||||
cpu.Regs.E = 0x0F
|
||||
|
||||
cpu.Step()
|
||||
assert.True(t, cpu.IsFlagSet(H))
|
||||
|
||||
// Should clear Half Carry Flag if we don't overflow from bit 3
|
||||
cpu = createCPU([]byte{0x1C, 0x00, 0x00})
|
||||
|
||||
cpu.SetFlag(H)
|
||||
cpu.Step()
|
||||
assert.False(t, cpu.IsFlagSet(H))
|
||||
}
|
||||
|
||||
func TestInstruction20(t *testing.T) {
|
||||
// Should not jump if Z is set
|
||||
cpu := createCPU([]byte{0x20, 0xFB, 0x00})
|
||||
cpu.SetFlag(Z)
|
||||
|
||||
cpu.Step()
|
||||
|
||||
assert.Equal(t, uint16(0x02), cpu.Regs.PC)
|
||||
|
||||
// Should jump to positive offset if Z is not set
|
||||
cpu = createCPU([]byte{0x20, 0x0A, 0x00})
|
||||
|
||||
cpu.Step()
|
||||
|
||||
assert.Equal(t, uint16(0x0C), cpu.Regs.PC)
|
||||
|
||||
// Should jump to negative offset if Z is not set
|
||||
cpu = createCPU([]byte{0x20, 0xFB, 0x00})
|
||||
|
||||
cpu.Step()
|
||||
|
||||
assert.Equal(t, uint16(0xFFFD), cpu.Regs.PC)
|
||||
}
|
||||
|
||||
func TestInstruction10(t *testing.T) {
|
||||
cpu := createCPU([]byte{0x10, 0x2A, 0x12})
|
||||
|
||||
cpu.Step()
|
||||
|
||||
assert.True(t, cpu.Halted)
|
||||
}
|
||||
|
||||
func TestInstruction14(t *testing.T) {
|
||||
// Should increment D register
|
||||
cpu := createCPU([]byte{0x14, 0x00, 0x00})
|
||||
|
||||
cpu.Step()
|
||||
assert.Equal(t, byte(0x01), cpu.Regs.D)
|
||||
|
||||
// Should clear Zero Flag
|
||||
cpu = createCPU([]byte{0x14, 0x00, 0x00})
|
||||
|
||||
cpu.SetFlag(Z)
|
||||
cpu.Step()
|
||||
assert.False(t, cpu.IsFlagSet(Z))
|
||||
|
||||
// Should set Zero Flag
|
||||
cpu = createCPU([]byte{0x14, 0x00, 0x00})
|
||||
cpu.Regs.D = 0xFF
|
||||
|
||||
cpu.Step()
|
||||
assert.True(t, cpu.IsFlagSet(Z))
|
||||
|
||||
// Should clear Subtract Flag
|
||||
cpu = createCPU([]byte{0x14, 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{0x14, 0x00, 0x00})
|
||||
cpu.Regs.D = 0x0F
|
||||
|
||||
cpu.Step()
|
||||
assert.True(t, cpu.IsFlagSet(H))
|
||||
|
||||
// Should clear Half Carry Flag if we don't overflow from bit 3
|
||||
cpu = createCPU([]byte{0x14, 0x00, 0x00})
|
||||
|
||||
cpu.SetFlag(H)
|
||||
cpu.Step()
|
||||
assert.False(t, cpu.IsFlagSet(H))
|
||||
}
|
||||
|
||||
func TestInstruction0D(t *testing.T) {
|
||||
// Should decrement C register
|
||||
cpu := createCPU([]byte{0x0D, 0x00, 0x00})
|
||||
cpu.Regs.C = 0x01
|
||||
|
||||
cpu.Step()
|
||||
assert.Equal(t, byte(0x00), cpu.Regs.C)
|
||||
|
||||
// Should clear Zero Flag
|
||||
cpu = createCPU([]byte{0x0D, 0x00, 0x00})
|
||||
|
||||
cpu.SetFlag(Z)
|
||||
cpu.Step()
|
||||
assert.False(t, cpu.IsFlagSet(Z))
|
||||
|
||||
// Should set Zero Flag
|
||||
cpu = createCPU([]byte{0x0D, 0x00, 0x00})
|
||||
cpu.Regs.C = 0x01
|
||||
|
||||
cpu.Step()
|
||||
assert.True(t, cpu.IsFlagSet(Z))
|
||||
|
||||
// Should set Subtract Flag
|
||||
cpu = createCPU([]byte{0x0D, 0x00, 0x00})
|
||||
|
||||
cpu.SetFlag(N)
|
||||
cpu.Step()
|
||||
assert.True(t, cpu.IsFlagSet(N))
|
||||
|
||||
// Should set Half Carry Flag if we overflow from bit 3
|
||||
cpu = createCPU([]byte{0x0D, 0x00, 0x00})
|
||||
cpu.Regs.C = 0x10
|
||||
|
||||
cpu.Step()
|
||||
assert.True(t, cpu.IsFlagSet(H))
|
||||
|
||||
// Should clear Half Carry Flag if we don't overflow from bit 3
|
||||
cpu = createCPU([]byte{0x0D, 0x00, 0x00})
|
||||
cpu.Regs.C = 0x01
|
||||
|
||||
cpu.SetFlag(H)
|
||||
cpu.Step()
|
||||
assert.False(t, cpu.IsFlagSet(H))
|
||||
}
|
||||
|
||||
func TestInstruction78(t *testing.T) {
|
||||
cpu := createCPU([]byte{0x78, 0x00, 0x00})
|
||||
|
||||
cpu.Regs.B = 0xDE
|
||||
|
||||
cpu.Step()
|
||||
|
||||
// Should load value in register B into register A
|
||||
assert.Equal(t, byte(0xDE), cpu.Regs.A)
|
||||
|
||||
// Should increase the stack pointer
|
||||
assert.Equal(t, uint16(0x1), cpu.Regs.PC)
|
||||
}
|
||||
|
||||
func TestInstructionEA(t *testing.T) {
|
||||
cpu := createCPU([]byte{0xEA, 0x23, 0xD1})
|
||||
cpu.Regs.A = 0x42
|
||||
|
||||
cpu.Step()
|
||||
|
||||
// Should load byte in register A into the memory location pointed to by the 16-bit immediate value
|
||||
val := cpu.Bus.Read(0xD123)
|
||||
assert.Equal(t, byte(0x42), val)
|
||||
|
||||
// Should increase the stack pointer
|
||||
assert.Equal(t, uint16(0x3), cpu.Regs.PC)
|
||||
}
|
||||
|
||||
func TestInstruction3E(t *testing.T) {
|
||||
cpu := createCPU([]byte{0x3E, 0xDE, 0x00})
|
||||
|
||||
cpu.Step()
|
||||
|
||||
// Should load the 8-bit immediate value into register A
|
||||
assert.Equal(t, byte(0xDE), cpu.Regs.A)
|
||||
|
||||
// Should increase the stack pointer
|
||||
assert.Equal(t, uint16(0x2), cpu.Regs.PC)
|
||||
}
|
||||
|
||||
func TestInstructionE0(t *testing.T) {
|
||||
cpu := createCPU([]byte{0xE0, 0x07, 0x00})
|
||||
cpu.Regs.A = 0x42
|
||||
|
||||
cpu.Step()
|
||||
|
||||
// Should copy the value in register A into the byte at address 0xFF00 + 0x07
|
||||
val := cpu.Bus.Read(0xFF07)
|
||||
assert.Equal(t, byte(0x42), val)
|
||||
|
||||
// Should increase the stack pointer
|
||||
assert.Equal(t, uint16(0x2), cpu.Regs.PC)
|
||||
}
|
||||
|
||||
func TestInstruction7D(t *testing.T) {
|
||||
cpu := createCPU([]byte{0x7D, 0x00, 0x00})
|
||||
|
||||
cpu.Regs.L = 0xDE
|
||||
|
||||
cpu.Step()
|
||||
|
||||
// Should load into register A the value in register L
|
||||
assert.Equal(t, byte(0xDE), cpu.Regs.A)
|
||||
|
||||
// Should increase the stack pointer
|
||||
assert.Equal(t, uint16(0x1), cpu.Regs.PC)
|
||||
}
|
||||
|
||||
func TestInstruction7C(t *testing.T) {
|
||||
cpu := createCPU([]byte{0x7C, 0x00, 0x00})
|
||||
|
||||
cpu.Regs.H = 0xDE
|
||||
|
||||
cpu.Step()
|
||||
|
||||
// Should load into register A the value in register H
|
||||
assert.Equal(t, byte(0xDE), cpu.Regs.A)
|
||||
|
||||
// Should increase the stack pointer
|
||||
assert.Equal(t, uint16(0x1), cpu.Regs.PC)
|
||||
}
|
||||
|
||||
func TestInstruction18(t *testing.T) {
|
||||
// Should jump to positive offset
|
||||
cpu := createCPU([]byte{0x18, 0x0A, 0x00})
|
||||
|
||||
cpu.Step()
|
||||
|
||||
assert.Equal(t, uint16(0x0C), cpu.Regs.PC)
|
||||
|
||||
// Should jump to negative offset
|
||||
cpu = createCPU([]byte{0x18, 0xFB, 0x00})
|
||||
|
||||
cpu.Step()
|
||||
|
||||
assert.Equal(t, uint16(0xFFFD), cpu.Regs.PC)
|
||||
}
|
||||
|
||||
45
gb/io.go
Normal file
45
gb/io.go
Normal file
@@ -0,0 +1,45 @@
|
||||
package gb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var SerialData [2]byte
|
||||
var InterruptFlags byte
|
||||
|
||||
func IORead(address uint16) byte {
|
||||
if address == 0xFF01 {
|
||||
return SerialData[0]
|
||||
} else if address == 0xFF02 {
|
||||
return SerialData[1]
|
||||
} else if (address >= 0xFF04) && (address <= 0xFF07) {
|
||||
return timer.Read(address)
|
||||
} else if address == 0xFF0F {
|
||||
return InterruptFlags
|
||||
} else if (address >= 0xFF10) && (address <= 0xFF3F) {
|
||||
// Ignore sound
|
||||
return 0
|
||||
} else {
|
||||
fmt.Printf("Reading from IO: invalid address %X\n", address)
|
||||
os.Exit(1)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func IOWrite(address uint16, value byte) {
|
||||
if address == 0xFF01 {
|
||||
SerialData[0] = value
|
||||
} else if address == 0xFF02 {
|
||||
SerialData[1] = value
|
||||
} else if (address >= 0xFF04) && (address <= 0xFF07) {
|
||||
timer.Write(address, value)
|
||||
} else if address == 0xFF0F {
|
||||
InterruptFlags = value
|
||||
} else if (address >= 0xFF10) && (address <= 0xFF3F) {
|
||||
// Ignore sound
|
||||
} else {
|
||||
fmt.Printf("Writing to IO: invalid address %X\n", address)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
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\n", 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\n", 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))
|
||||
}
|
||||
125
gb/timer.go
Normal file
125
gb/timer.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package gb
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
)
|
||||
|
||||
var timer = Timer{DIV: 0xAC00}
|
||||
|
||||
type Timer struct {
|
||||
DIV uint16
|
||||
TIMA byte
|
||||
TMA byte
|
||||
TAC byte
|
||||
}
|
||||
|
||||
func (timer *Timer) Tick() {
|
||||
previousDIV := timer.DIV
|
||||
timer.DIV++
|
||||
|
||||
timerNeedsUpdate := false
|
||||
|
||||
// Determine clock mode
|
||||
// TAC (Timer control register)
|
||||
// Bits 0-1 determines clock frequency
|
||||
// 00: 4096 Hz
|
||||
// 01: 262144 Hz
|
||||
// 10: 65536 Hz
|
||||
// 11: 16384 Hz
|
||||
// Bit 2 determines whether timer is enabled or not
|
||||
|
||||
clockMode := timer.TAC & (0b11)
|
||||
switch clockMode {
|
||||
case 0b00:
|
||||
// 4096 Hz
|
||||
// Detect a falling edge by comparing bit 9 in the previous DIV value
|
||||
// with bit 9 in the current DIV value
|
||||
previouslyEnabled := previousDIV&(1<<9) != 0
|
||||
currentlyDisabled := timer.DIV&(1<<9) == 0
|
||||
timerNeedsUpdate = previouslyEnabled && currentlyDisabled
|
||||
|
||||
case 0b01:
|
||||
// 262144 Hz
|
||||
// Detect a falling edge by comparing bit 3 in the previous DIV value
|
||||
// with bit 3 in the current DIV value
|
||||
previouslyEnabled := previousDIV&(1<<3) != 0
|
||||
currentlyDisabled := timer.DIV&(1<<3) == 0
|
||||
timerNeedsUpdate = previouslyEnabled && currentlyDisabled
|
||||
|
||||
case 0b10:
|
||||
// 65536 Hz
|
||||
// Detect a falling edge by comparing bit 5 in the previous DIV value
|
||||
// with bit 5 in the current DIV value
|
||||
previouslyEnabled := previousDIV&(1<<5) != 0
|
||||
currentlyDisabled := timer.DIV&(1<<5) == 0
|
||||
timerNeedsUpdate = previouslyEnabled && currentlyDisabled
|
||||
|
||||
case 0b11:
|
||||
// 16384 Hz
|
||||
// Detect a falling edge by comparing bit 7 in the previous DIV value
|
||||
// with bit 7 in the current DIV value
|
||||
previouslyEnabled := previousDIV&(1<<7) != 0
|
||||
currentlyDisabled := timer.DIV&(1<<7) == 0
|
||||
timerNeedsUpdate = previouslyEnabled && currentlyDisabled
|
||||
}
|
||||
|
||||
timerIsEnabled := timer.TAC&(1<<2) != 0
|
||||
// If the timer needs to be updated based on the determined clock mode and the timer is enabled, increment the timer
|
||||
if timerNeedsUpdate && timerIsEnabled {
|
||||
timer.TIMA++
|
||||
|
||||
// Check if TIMA is going to wrap and trigger an interrupt if necessary
|
||||
if timer.TIMA == 0xFF {
|
||||
timer.TIMA = timer.TMA
|
||||
|
||||
fmt.Println("TODO: cpu_request_interrupt(IT_TIMER")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (timer *Timer) Read(address uint16) byte {
|
||||
switch address {
|
||||
case 0xFF04:
|
||||
return byte(timer.DIV >> 8)
|
||||
|
||||
case 0xFF05:
|
||||
return timer.TIMA
|
||||
|
||||
case 0xFF06:
|
||||
return timer.TMA
|
||||
|
||||
case 0xFF07:
|
||||
return timer.TAC
|
||||
|
||||
default:
|
||||
fmt.Printf("Reading from Timer: invalid address %X\n", address)
|
||||
os.Exit(1)
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (timer *Timer) Write(address uint16, value byte) {
|
||||
switch address {
|
||||
case 0xFF04:
|
||||
//DIV
|
||||
timer.DIV = 0
|
||||
|
||||
case 0xFF05:
|
||||
//TIMA
|
||||
timer.TIMA = value
|
||||
|
||||
case 0xFF06:
|
||||
//TMA
|
||||
timer.TMA = value
|
||||
|
||||
case 0xFF07:
|
||||
//TAC
|
||||
timer.TAC = value
|
||||
|
||||
default:
|
||||
fmt.Printf("Writing to Timer: invalid address %X\n", address)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
7
main.go
7
main.go
@@ -9,10 +9,7 @@ import (
|
||||
"gb-player/gb"
|
||||
)
|
||||
|
||||
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")
|
||||
}
|
||||
@@ -26,8 +23,8 @@ func main() {
|
||||
}
|
||||
|
||||
fmt.Println("Executing instructions")
|
||||
running := true
|
||||
for running {
|
||||
|
||||
for !console.CPU.Halted {
|
||||
console.CPU.Step()
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user