Implement timer, some IO operations and more CPU instructions
This commit is contained in:
parent
7678fda9e7
commit
b860999dc8
@ -34,7 +34,7 @@ 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
|
||||
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
39
gb/cpu.go
39
gb/cpu.go
@ -35,6 +35,7 @@ type CPU struct {
|
||||
Halted bool
|
||||
Stepping bool
|
||||
InterruptMasterEnable bool
|
||||
InterruptFlags byte
|
||||
}
|
||||
|
||||
func NewCPU(bus *Bus) *CPU {
|
||||
@ -127,6 +128,14 @@ func (cpu *CPU) Step() {
|
||||
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++
|
||||
@ -225,6 +234,14 @@ func (cpu *CPU) Step() {
|
||||
// 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)
|
||||
@ -262,10 +279,10 @@ func (cpu *CPU) Step() {
|
||||
// emu_cycles(1);
|
||||
cpu.Regs.PC = uint16(lo) | uint16(hi)<<8
|
||||
|
||||
// case 0xC9:
|
||||
// // RET
|
||||
// // emu_cycles(4);
|
||||
// cpu.Regs.PC = cpu.StackPop16()
|
||||
case 0xC9:
|
||||
// RET
|
||||
// emu_cycles(4);
|
||||
cpu.Regs.PC = cpu.StackPop16()
|
||||
|
||||
case 0xCD:
|
||||
// CALL a16
|
||||
@ -284,6 +301,12 @@ func (cpu *CPU) Step() {
|
||||
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)
|
||||
@ -349,3 +372,11 @@ func (cpu *CPU) StackPop16() uint16 {
|
||||
|
||||
return uint16(hi)<<8 | uint16(lo)
|
||||
}
|
||||
|
||||
func (cpu *CPU) GetInterruptFlags() byte {
|
||||
return cpu.InterruptFlags
|
||||
}
|
||||
|
||||
func (cpu *CPU) SetInterruptFlags(value byte) {
|
||||
cpu.InterruptFlags = value
|
||||
}
|
||||
|
||||
@ -474,3 +474,47 @@ func TestInstructionE0(t *testing.T) {
|
||||
// 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)
|
||||
}
|
||||
|
||||
41
gb/io.go
41
gb/io.go
@ -6,41 +6,40 @@ import (
|
||||
)
|
||||
|
||||
var SerialData [2]byte
|
||||
var InterruptFlags byte
|
||||
|
||||
func IORead(address uint16) byte {
|
||||
if address == 0xFF01 {
|
||||
return SerialData[0]
|
||||
}
|
||||
|
||||
if address == 0xFF02 {
|
||||
} else if address == 0xFF02 {
|
||||
return SerialData[1]
|
||||
}
|
||||
|
||||
if (address >= 0xFF04) && (address <= 0xFF07) {
|
||||
fmt.Printf("Reading from IO: invalid address %X. Timer not yet implemented!", address)
|
||||
} 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)
|
||||
}
|
||||
|
||||
fmt.Printf("Reading from IO: invalid address %X", address)
|
||||
os.Exit(1)
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
func IOWrite(address uint16, value byte) {
|
||||
if address == 0xFF01 {
|
||||
SerialData[0] = value
|
||||
}
|
||||
|
||||
if address == 0xFF02 {
|
||||
} else if address == 0xFF02 {
|
||||
SerialData[1] = value
|
||||
}
|
||||
|
||||
if (address >= 0xFF04) && (address <= 0xFF07) {
|
||||
fmt.Printf("Writing to IO: invalid address %X. Timer not yet implemented!", address)
|
||||
} 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)
|
||||
}
|
||||
|
||||
fmt.Printf("Writing to IO: invalid address %X", address)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@ -21,7 +21,7 @@ func (ram *RAM) WRAMRead(address uint16) byte {
|
||||
address -= 0xC000
|
||||
|
||||
if address >= 0x2000 {
|
||||
fmt.Printf("Reading from WRAM: invalid address %X", address+0xC000)
|
||||
fmt.Printf("Reading from WRAM: invalid address %X\n", address+0xC000)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@ -33,7 +33,7 @@ func (ram *RAM) WRAMWrite(address uint16, value byte) {
|
||||
address -= 0xC000
|
||||
|
||||
if address >= 0x2000 {
|
||||
fmt.Printf("Writing to WRAM: invalid address %X", address)
|
||||
fmt.Printf("Writing to WRAM: invalid address %X\n", address)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
8
main.go
8
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,9 +23,8 @@ func main() {
|
||||
}
|
||||
|
||||
fmt.Println("Executing instructions")
|
||||
running := true
|
||||
for running {
|
||||
|
||||
for !console.CPU.Halted {
|
||||
console.CPU.Step()
|
||||
running = !console.CPU.Halted
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user