383 lines
7.3 KiB
Go
383 lines
7.3 KiB
Go
package gb
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
)
|
|
|
|
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 {
|
|
A byte
|
|
F CPUFlags
|
|
B byte
|
|
C byte
|
|
D byte
|
|
E byte
|
|
H byte
|
|
L byte
|
|
PC uint16
|
|
SP uint16
|
|
}
|
|
|
|
type CPU struct {
|
|
Bus *Bus
|
|
Regs Registers
|
|
Halted bool
|
|
Stepping bool
|
|
InterruptMasterEnable bool
|
|
InterruptFlags byte
|
|
}
|
|
|
|
func NewCPU(bus *Bus) *CPU {
|
|
cpu := CPU{}
|
|
cpu.Bus = bus
|
|
// 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
|
|
}
|
|
|
|
func (cpu *CPU) Step() {
|
|
if !cpu.Halted {
|
|
opcode := cpu.Bus.Read(cpu.Regs.PC)
|
|
|
|
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++
|
|
|
|
// 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 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(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)
|
|
}
|
|
}
|
|
}
|
|
|
|
func (cpu *CPU) SetFlag(flag CPUFlags) {
|
|
cpu.Regs.F |= flag
|
|
}
|
|
|
|
func (cpu *CPU) ClearFlag(flag CPUFlags) {
|
|
cpu.Regs.F &^= flag
|
|
}
|
|
|
|
func (cpu *CPU) ToggleFlag(flag CPUFlags) {
|
|
cpu.Regs.F ^= flag
|
|
}
|
|
|
|
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
|
|
}
|