2020-10-10 21:29:53 +01:00
|
|
|
package chip8
|
|
|
|
|
2020-10-10 23:54:09 +01:00
|
|
|
import (
|
|
|
|
"fmt"
|
|
|
|
"math/rand"
|
|
|
|
)
|
2020-10-10 21:29:53 +01:00
|
|
|
|
|
|
|
const graphicsBufferSize = 64 * 32
|
|
|
|
|
|
|
|
type Chip8 struct {
|
2020-10-10 23:08:57 +01:00
|
|
|
addressRegister uint16
|
2020-10-10 23:54:09 +01:00
|
|
|
beepTimer byte
|
2020-10-10 23:08:57 +01:00
|
|
|
drawRequired bool
|
2020-10-10 23:54:09 +01:00
|
|
|
delayTimer byte
|
2020-10-10 23:08:57 +01:00
|
|
|
graphics [graphicsBufferSize]byte
|
|
|
|
keys [16]byte
|
|
|
|
memory [4096]byte
|
|
|
|
opcode uint16
|
|
|
|
pc uint16
|
|
|
|
registers [16]byte // an array - has a fixed length
|
|
|
|
stack []uint16 // a slice - basically a c++ vector
|
2020-10-10 21:29:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
// we can't have const arrays in go
|
|
|
|
// This is a good a solution as any
|
2020-10-10 22:58:18 +01:00
|
|
|
func getLetters() []byte {
|
|
|
|
return []byte{
|
2020-10-10 21:29:53 +01:00
|
|
|
0xF0, 0x90, 0x90, 0x90, 0xF0, // 0
|
|
|
|
0x20, 0x60, 0x20, 0x20, 0x70, // 1
|
|
|
|
0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2
|
|
|
|
0xF0, 0x10, 0xF0, 0x10, 0xF0, // 3
|
|
|
|
0x90, 0x90, 0xF0, 0x10, 0x10, // 4
|
|
|
|
0xF0, 0x80, 0xF0, 0x10, 0xF0, // 5
|
|
|
|
0xF0, 0x80, 0xF0, 0x90, 0xF0, // 6
|
|
|
|
0xF0, 0x10, 0x20, 0x40, 0x40, // 7
|
|
|
|
0xF0, 0x90, 0xF0, 0x90, 0xF0, // 8
|
|
|
|
0xF0, 0x90, 0xF0, 0x10, 0xF0, // 9
|
|
|
|
0xF0, 0x90, 0xF0, 0x90, 0x90, // A
|
|
|
|
0xE0, 0x90, 0xE0, 0x90, 0xE0, // B
|
|
|
|
0xF0, 0x80, 0x80, 0x80, 0xF0, // C
|
|
|
|
0xE0, 0x90, 0x90, 0x90, 0xE0, // D
|
|
|
|
0xF0, 0x80, 0xF0, 0x80, 0xF0, // E
|
|
|
|
0xF0, 0x80, 0xF0, 0x80, 0x80} // F
|
|
|
|
}
|
|
|
|
|
2020-10-10 22:58:18 +01:00
|
|
|
func NewCHIP8(prog []byte) *Chip8 {
|
2020-10-10 21:29:53 +01:00
|
|
|
cpu := Chip8{pc: 0x200}
|
|
|
|
memory_slice := cpu.memory[:80]
|
|
|
|
copy(memory_slice, getLetters())
|
|
|
|
memory_slice = cpu.memory[200:]
|
|
|
|
copy(memory_slice, prog)
|
|
|
|
|
|
|
|
// Do some extra checking to ensure the right
|
|
|
|
// Stuff is copied
|
|
|
|
|
|
|
|
return &cpu
|
|
|
|
}
|
|
|
|
|
2020-10-10 22:58:18 +01:00
|
|
|
func (cpu *Chip8) GetGraphicsBuffer() [graphicsBufferSize]byte {
|
2020-10-11 20:55:31 +01:00
|
|
|
cpu.drawRequired = false
|
2020-10-10 21:29:53 +01:00
|
|
|
return cpu.graphics
|
|
|
|
}
|
|
|
|
|
2020-10-11 20:55:31 +01:00
|
|
|
func (cpu *Chip8) DrawIsNeeded() bool {
|
|
|
|
return cpu.drawRequired
|
|
|
|
}
|
|
|
|
|
2020-10-11 20:05:00 +01:00
|
|
|
func (cpu *Chip8) TickTimers() {
|
|
|
|
if cpu.delayTimer > 0 {
|
|
|
|
cpu.delayTimer--
|
|
|
|
}
|
|
|
|
if cpu.beepTimer > 0 {
|
|
|
|
cpu.beepTimer--
|
|
|
|
}
|
2020-10-10 21:29:53 +01:00
|
|
|
}
|
|
|
|
|
2020-10-11 20:05:00 +01:00
|
|
|
func (cpu *Chip8) zeroIndexOpcodes() {
|
|
|
|
if cpu.opcode == 0x00E0 {
|
|
|
|
// fuck it the gc can do the hard work for us
|
|
|
|
cpu.graphics = [64 * 32]byte{}
|
|
|
|
cpu.drawRequired = true
|
|
|
|
} else if cpu.opcode == 0x00EE {
|
|
|
|
// what if there's nothing in the stack?
|
|
|
|
// return something sensible
|
|
|
|
cpu.pc = cpu.stack[len(cpu.stack)-1]
|
|
|
|
cpu.stack[len(cpu.stack)-1] = 0
|
|
|
|
cpu.stack = cpu.stack[:len(cpu.stack)-1]
|
|
|
|
}
|
2020-10-10 21:29:53 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
func (cpu *Chip8) goTo() {
|
2020-10-11 20:05:00 +01:00
|
|
|
// GOTO 1NNN
|
2020-10-10 21:29:53 +01:00
|
|
|
cpu.pc = cpu.opcode & 0x0FFF
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cpu *Chip8) callSubroutine() {
|
2020-10-11 20:05:00 +01:00
|
|
|
// Call subroutine at 0x2NNN
|
2020-10-10 21:29:53 +01:00
|
|
|
cpu.stack = append(cpu.stack, cpu.pc)
|
|
|
|
cpu.pc = cpu.opcode & 0x0FFF
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cpu *Chip8) skipIfRegisterEqual() {
|
2020-10-11 20:05:00 +01:00
|
|
|
// 3XNN
|
|
|
|
// If register x == NN skip next instruction
|
2020-10-10 21:29:53 +01:00
|
|
|
r := (cpu.opcode) >> 8 & 0x0F
|
2020-10-10 22:58:18 +01:00
|
|
|
if cpu.registers[r] == byte(cpu.opcode&0xFF) {
|
2020-10-10 21:29:53 +01:00
|
|
|
cpu.pc += 2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cpu *Chip8) skipIfRegisterNotEqual() {
|
2020-10-11 20:05:00 +01:00
|
|
|
// 4XNN
|
|
|
|
// If register x != NN skip next instruction
|
2020-10-10 21:29:53 +01:00
|
|
|
r := (cpu.opcode) >> 8 & 0x0F
|
2020-10-10 22:58:18 +01:00
|
|
|
if cpu.registers[r] != byte(cpu.opcode&0xFF) {
|
2020-10-10 21:29:53 +01:00
|
|
|
cpu.pc += 2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cpu *Chip8) skipIfRegistersEqual() {
|
2020-10-11 20:05:00 +01:00
|
|
|
// 5XY0
|
|
|
|
// If register X == register Y skip next instruction
|
2020-10-10 21:29:53 +01:00
|
|
|
x, y := (cpu.opcode>>8)&0x0F, (cpu.opcode>>4)&0x0F
|
|
|
|
if cpu.registers[x] == cpu.registers[y] {
|
|
|
|
cpu.pc += 2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-10 22:29:35 +01:00
|
|
|
func (cpu *Chip8) setRegisterTo() {
|
2020-10-11 20:05:00 +01:00
|
|
|
// 6XNN
|
|
|
|
// Set register X to NN
|
2020-10-10 21:29:53 +01:00
|
|
|
r := (cpu.opcode >> 8) & 0x0F
|
2020-10-10 22:58:18 +01:00
|
|
|
cpu.registers[r] = byte(cpu.opcode & 0xFF)
|
2020-10-10 21:29:53 +01:00
|
|
|
}
|
|
|
|
|
2020-10-11 20:05:00 +01:00
|
|
|
func (cpu *Chip8) registerPlusEqualsNN() {
|
|
|
|
// 7XNN
|
|
|
|
// register X += NN (carry flag not set)
|
|
|
|
reg := (cpu.opcode >> 8) & 0x0F
|
|
|
|
cpu.registers[reg] += uint8(cpu.opcode & 0xFF)
|
|
|
|
}
|
2020-10-10 21:29:53 +01:00
|
|
|
|
2020-10-10 23:54:09 +01:00
|
|
|
func (cpu *Chip8) bitOpsAndMath() {
|
2020-10-10 22:58:18 +01:00
|
|
|
instruction := cpu.opcode & 0x0F
|
|
|
|
regX, regY := cpu.opcode>>8&0x0F, cpu.opcode>>4&0x0F
|
|
|
|
switch instruction {
|
|
|
|
case 0:
|
|
|
|
// 8XY0
|
|
|
|
// Assign value of register Y to register X
|
|
|
|
cpu.registers[regX] = cpu.registers[regY]
|
|
|
|
case 1:
|
|
|
|
// 8XY1
|
|
|
|
// Set register X to x|Y
|
|
|
|
cpu.registers[regX] = cpu.registers[regX] | cpu.registers[regY]
|
|
|
|
case 2:
|
|
|
|
// 8XY2
|
|
|
|
// Set register x to X&Y
|
|
|
|
cpu.registers[regX] = cpu.registers[regX] & cpu.registers[regY]
|
|
|
|
case 3:
|
|
|
|
// 8XY3
|
|
|
|
// Set register x to X^Y
|
|
|
|
cpu.registers[regX] = cpu.registers[regX] ^ cpu.registers[regY]
|
|
|
|
case 4:
|
|
|
|
// 8XY4
|
|
|
|
// Set register x to X+=Y (Set VF to 1 when there's a carry and 0 if not)
|
|
|
|
res := cpu.registers[regX] + cpu.registers[regY]
|
|
|
|
if res > 0xFF {
|
|
|
|
cpu.registers[0x0F] = 1
|
|
|
|
} else {
|
|
|
|
cpu.registers[0x0F] = 0
|
|
|
|
}
|
|
|
|
cpu.registers[regX] = res & 0xFF
|
|
|
|
case 5:
|
|
|
|
// 8XY5
|
|
|
|
// Set register x to X-=Y (Set VF to 1 when there's a carry and 0 if not)
|
|
|
|
res := cpu.registers[regX] - cpu.registers[regY]
|
|
|
|
if res > 0xFF {
|
|
|
|
cpu.registers[0x0F] = 1
|
|
|
|
} else {
|
|
|
|
cpu.registers[0x0F] = 0
|
|
|
|
}
|
|
|
|
cpu.registers[regX] = res & 0xFF
|
|
|
|
case 6:
|
|
|
|
// BXY6
|
|
|
|
// Store lsb of reg X in reg F and then shift reg X >> 1
|
|
|
|
cpu.registers[0x0F] = cpu.registers[regX] & 0x01
|
|
|
|
cpu.registers[regX] >>= 1
|
2020-10-10 23:08:57 +01:00
|
|
|
case 7:
|
|
|
|
// 8XY57
|
|
|
|
// Set register x to X=Y-X (Set VF to 1 when there's a carry and 0 if not)
|
|
|
|
res := cpu.registers[regY] - cpu.registers[regX]
|
|
|
|
if res < 0 {
|
|
|
|
cpu.registers[0x0F] = 0
|
|
|
|
} else {
|
|
|
|
cpu.registers[0x0f] = 1
|
|
|
|
}
|
|
|
|
cpu.registers[regX] = res & 0xFF
|
2020-10-10 22:58:18 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-10 23:54:09 +01:00
|
|
|
func (cpu *Chip8) skipIfRegistersNotEqual() {
|
2020-10-10 23:08:57 +01:00
|
|
|
x, y := (cpu.opcode>>8)&0x0F, (cpu.opcode>>4)&0x0F
|
|
|
|
if cpu.registers[x] != cpu.registers[y] {
|
|
|
|
cpu.pc += 2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-10 23:54:09 +01:00
|
|
|
func (cpu *Chip8) setAddressRegister() {
|
2020-10-10 23:08:57 +01:00
|
|
|
// ANNN
|
|
|
|
// Sets the address register to NNN
|
|
|
|
cpu.addressRegister = cpu.opcode & 0x0FFF
|
|
|
|
}
|
|
|
|
|
2020-10-10 23:54:09 +01:00
|
|
|
func (cpu *Chip8) jumpToV0PlusAddress() {
|
|
|
|
// BNNN
|
|
|
|
// PC=V0+NNN
|
|
|
|
cpu.pc = uint16(cpu.registers[0]) + (cpu.opcode & 0x0FFF)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cpu *Chip8) setRegisterToRand() {
|
|
|
|
// CXNN
|
|
|
|
// Vx = rand() & NN
|
|
|
|
cpu.registers[(cpu.opcode>>8)&0x0F] = byte(cpu.opcode&0xFF) & byte(rand.Intn(256))
|
|
|
|
}
|
|
|
|
|
|
|
|
func (cpu *Chip8) displaySprite() {
|
|
|
|
// DXYN
|
|
|
|
// Draws a sprite in the graphics buffer
|
|
|
|
// VF is set to 1 if any pixels are flipped and zero otherwise
|
|
|
|
cpu.registers[0x0F] = 0
|
|
|
|
x := int(cpu.registers[(cpu.opcode>>8)&0x0F])
|
|
|
|
y := int(cpu.registers[(cpu.opcode>>4)&0x0F])
|
|
|
|
|
|
|
|
for row := 0; row < int(cpu.opcode&0xF); row++ {
|
|
|
|
pixel := cpu.memory[int(cpu.addressRegister)+row]
|
|
|
|
for column := 0; column < 8; column++ {
|
|
|
|
if pixel&(0x80>>column) != 0 {
|
|
|
|
graphicsPosition := x + column + ((y + row) * 60)
|
|
|
|
if cpu.graphics[graphicsPosition] == 1 {
|
|
|
|
cpu.registers[0xF] = 1
|
|
|
|
}
|
|
|
|
cpu.graphics[graphicsPosition] ^= 1
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
cpu.drawRequired = true
|
|
|
|
}
|
|
|
|
|
2020-10-11 20:55:31 +01:00
|
|
|
func (cpu *Chip8) skipOnKeyOpcodes() {
|
2020-10-10 23:54:09 +01:00
|
|
|
opcode := cpu.opcode & 0xFF
|
|
|
|
key := cpu.registers[(cpu.opcode>>8)&0x0F]
|
|
|
|
switch opcode {
|
|
|
|
case 0x9E:
|
|
|
|
// EX9E
|
|
|
|
// skip if key X is pressed
|
|
|
|
if cpu.keys[key] != 0 {
|
|
|
|
cpu.pc += 2
|
|
|
|
}
|
|
|
|
case 0xA1:
|
|
|
|
// EXA1
|
|
|
|
// skip if kkey X is not pressed
|
|
|
|
if cpu.keys[key] == 0 {
|
|
|
|
cpu.pc += 2
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-11 20:55:31 +01:00
|
|
|
func (cpu *Chip8) fifteenIndexOpcodes() {
|
2020-10-10 23:54:09 +01:00
|
|
|
instruction := cpu.opcode & 0xFF
|
|
|
|
reg := int(cpu.opcode>>8) & 0xF
|
|
|
|
switch instruction {
|
|
|
|
// FX07
|
|
|
|
// Set VX to the value of the delay timer
|
|
|
|
case 0x07:
|
|
|
|
cpu.registers[reg] = cpu.delayTimer
|
|
|
|
case 0x0A:
|
|
|
|
// FX0A
|
|
|
|
// block + wait for key press
|
2020-10-11 20:55:31 +01:00
|
|
|
found := false
|
2020-10-10 23:54:09 +01:00
|
|
|
for i, val := range cpu.keys {
|
|
|
|
if val != 0 {
|
|
|
|
cpu.registers[reg] = byte(i)
|
2020-10-11 20:55:31 +01:00
|
|
|
found = true
|
2020-10-10 23:54:09 +01:00
|
|
|
}
|
|
|
|
}
|
2020-10-11 20:55:31 +01:00
|
|
|
if !found {
|
|
|
|
cpu.pc -= 2
|
|
|
|
}
|
2020-10-10 23:54:09 +01:00
|
|
|
case 0x15:
|
|
|
|
// FX15
|
|
|
|
// Set delay timer to VX
|
|
|
|
cpu.delayTimer = cpu.registers[reg]
|
|
|
|
case 0x18:
|
|
|
|
// FX18
|
|
|
|
// SET THE BEEP TIMER TO VX
|
|
|
|
// BEEP
|
|
|
|
cpu.beepTimer = cpu.registers[reg]
|
|
|
|
case 0x1E:
|
|
|
|
// FX1E
|
|
|
|
// Add VX to the address register
|
|
|
|
// Set VF to 1 when range overflow
|
|
|
|
if int(cpu.registers[reg])+int(cpu.addressRegister) > 0xFFF {
|
|
|
|
cpu.registers[0x0F] = 1
|
|
|
|
} else {
|
|
|
|
cpu.registers[0x0F] = 0
|
|
|
|
}
|
|
|
|
cpu.addressRegister = uint16((int(cpu.addressRegister) + int(cpu.registers[reg])) & 0xFFF)
|
|
|
|
case 0x29:
|
|
|
|
// FX29
|
|
|
|
// Sets address register to location of char stored in VX
|
|
|
|
cpu.addressRegister = uint16(cpu.registers[reg]) * 5
|
|
|
|
case 0x33:
|
|
|
|
// FX33
|
|
|
|
// Stores BCD representation of VX
|
|
|
|
cpu.memory[cpu.addressRegister] = cpu.registers[reg] / 100
|
|
|
|
cpu.memory[cpu.addressRegister+1] = (cpu.registers[reg] / 10) % 10
|
|
|
|
cpu.memory[cpu.addressRegister+2] = cpu.registers[reg] % 10
|
|
|
|
case 0x55:
|
|
|
|
// FX55
|
|
|
|
// takes values from and including reg X and stores then in memory
|
|
|
|
for i := 0; i <= reg; i++ {
|
|
|
|
cpu.memory[int(cpu.addressRegister)+i] = cpu.registers[i]
|
|
|
|
}
|
|
|
|
case 0x65:
|
|
|
|
// FX65
|
|
|
|
// takes values from memory and stores them in registers
|
|
|
|
for i := 0; i <= reg; i++ {
|
|
|
|
cpu.registers[i] = cpu.memory[int(cpu.addressRegister)+i]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
2020-10-11 20:55:31 +01:00
|
|
|
func (cpu *Chip8) PerformCycle() {
|
|
|
|
var opcode uint16 = (uint16(cpu.memory[cpu.pc]) << 8) + uint16(cpu.memory[cpu.pc])
|
|
|
|
cpu.pc += 2
|
|
|
|
switch opcode >> 12 {
|
|
|
|
case 0:
|
|
|
|
cpu.zeroIndexOpcodes()
|
|
|
|
case 1:
|
|
|
|
cpu.goTo()
|
|
|
|
case 2:
|
|
|
|
cpu.callSubroutine()
|
|
|
|
case 3:
|
|
|
|
cpu.skipIfRegisterEqual()
|
|
|
|
case 4:
|
|
|
|
cpu.skipIfRegisterNotEqual()
|
|
|
|
case 5:
|
|
|
|
cpu.skipIfRegistersEqual()
|
|
|
|
case 6:
|
|
|
|
cpu.setRegisterTo()
|
|
|
|
case 7:
|
|
|
|
cpu.registerPlusEqualsNN()
|
|
|
|
case 8:
|
|
|
|
cpu.bitOpsAndMath()
|
|
|
|
case 9:
|
|
|
|
cpu.skipIfRegistersNotEqual()
|
|
|
|
case 0xA:
|
|
|
|
cpu.setAddressRegister()
|
|
|
|
case 0xB:
|
|
|
|
cpu.jumpToV0PlusAddress()
|
|
|
|
case 0xC:
|
|
|
|
cpu.setRegisterToRand()
|
|
|
|
case 0xD:
|
|
|
|
cpu.displaySprite()
|
|
|
|
case 0xE:
|
|
|
|
cpu.skipOnKeyOpcodes()
|
|
|
|
case 0xF:
|
|
|
|
cpu.fifteenIndexOpcodes()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-10 21:29:53 +01:00
|
|
|
func main() {
|
|
|
|
fmt.Printf("Hello world!\n")
|
2020-10-10 22:58:18 +01:00
|
|
|
prog := []byte{1, 2, 3, 4}
|
2020-10-10 21:29:53 +01:00
|
|
|
new_cpu := NewCHIP8(prog)
|
|
|
|
fmt.Printf("%d\n", new_cpu.opcode)
|
|
|
|
}
|