Just trying to trigger release #1
53
.drone.yml
Normal file
53
.drone.yml
Normal file
@ -0,0 +1,53 @@
|
||||
kind: pipeline
|
||||
type: docker
|
||||
name: default
|
||||
|
||||
steps:
|
||||
- name : test
|
||||
image: golang:latest
|
||||
commands:
|
||||
- go test -v ./chip8
|
||||
- name : build windows
|
||||
image: golang:latest
|
||||
commands:
|
||||
- go build -o windows_test ./cmd/test_prog
|
||||
enviroment:
|
||||
GOOS: windows
|
||||
GOARCH: amd64
|
||||
- name : build linux
|
||||
image: golang:latest
|
||||
commands:
|
||||
- go build -o linux_test ./cmd/test_prog
|
||||
enviroment:
|
||||
GOOS: linux
|
||||
GOARCH: amd64
|
||||
- name : build mac
|
||||
image: golang:latest
|
||||
commands:
|
||||
- go build -o mac_test ./cmd/test_prog
|
||||
enviroment:
|
||||
GOOS: darwin
|
||||
GOARCH: amd64
|
||||
|
||||
|
||||
- name: publish
|
||||
image: plugins/gitea-release
|
||||
depends_on:
|
||||
- test
|
||||
- build windows
|
||||
- build linux
|
||||
- build mac
|
||||
# This step is only run when a branch is tagged in Gitea.
|
||||
when:
|
||||
event:
|
||||
- tag
|
||||
settings:
|
||||
base_url: https://git.jacknet.io
|
||||
api_key:
|
||||
from_secret: gitea_token
|
||||
files:
|
||||
- mac_test
|
||||
- linux_test
|
||||
- windows_test
|
||||
checksum:
|
||||
- sha1
|
1
README.md
Normal file
1
README.md
Normal file
@ -0,0 +1 @@
|
||||
[](https://drone.jacknet.io/S.D/Chip-8_Go)
|
305
chip8/chip8.go
Normal file
305
chip8/chip8.go
Normal file
@ -0,0 +1,305 @@
|
||||
package chip8
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
)
|
||||
|
||||
const graphicsBufferSize = 64 * 32
|
||||
|
||||
type Chip8 struct {
|
||||
addressRegister uint16
|
||||
beepTimer byte
|
||||
drawRequired bool
|
||||
delayTimer byte
|
||||
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
|
||||
}
|
||||
|
||||
// we can't have const arrays in go
|
||||
// This is a good a solution as any
|
||||
func getLetters() []byte {
|
||||
return []byte{
|
||||
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
|
||||
}
|
||||
|
||||
func NewCHIP8(prog []byte) *Chip8 {
|
||||
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
|
||||
}
|
||||
|
||||
func (cpu *Chip8) GetGraphicsBuffer() [graphicsBufferSize]byte {
|
||||
return cpu.graphics
|
||||
}
|
||||
|
||||
func (cpu *Chip8) clearDisplay() {
|
||||
// fuck it the gc can do the hard work for us
|
||||
cpu.graphics = [64 * 32]byte{}
|
||||
cpu.drawRequired = true
|
||||
}
|
||||
|
||||
// what if there's nothing in the stack?
|
||||
// return something sensible
|
||||
func (cpu *Chip8) leaveFunction() {
|
||||
cpu.pc = cpu.stack[len(cpu.stack)-1]
|
||||
cpu.stack[len(cpu.stack)-1] = 0
|
||||
cpu.stack = cpu.stack[:len(cpu.stack)-1]
|
||||
}
|
||||
|
||||
func (cpu *Chip8) goTo() {
|
||||
cpu.pc = cpu.opcode & 0x0FFF
|
||||
}
|
||||
|
||||
func (cpu *Chip8) callSubroutine() {
|
||||
cpu.stack = append(cpu.stack, cpu.pc)
|
||||
cpu.pc = cpu.opcode & 0x0FFF
|
||||
}
|
||||
|
||||
func (cpu *Chip8) skipIfRegisterEqual() {
|
||||
r := (cpu.opcode) >> 8 & 0x0F
|
||||
if cpu.registers[r] == byte(cpu.opcode&0xFF) {
|
||||
cpu.pc += 2
|
||||
}
|
||||
}
|
||||
|
||||
func (cpu *Chip8) skipIfRegisterNotEqual() {
|
||||
r := (cpu.opcode) >> 8 & 0x0F
|
||||
if cpu.registers[r] != byte(cpu.opcode&0xFF) {
|
||||
cpu.pc += 2
|
||||
}
|
||||
}
|
||||
|
||||
func (cpu *Chip8) skipIfRegistersEqual() {
|
||||
x, y := (cpu.opcode>>8)&0x0F, (cpu.opcode>>4)&0x0F
|
||||
if cpu.registers[x] == cpu.registers[y] {
|
||||
cpu.pc += 2
|
||||
}
|
||||
}
|
||||
|
||||
func (cpu *Chip8) setRegisterTo() {
|
||||
r := (cpu.opcode >> 8) & 0x0F
|
||||
cpu.registers[r] = byte(cpu.opcode & 0xFF)
|
||||
}
|
||||
|
||||
func (cpu *Chip8) registerPlusEqualsNN() {} // QUESTION HERE - WHAT DO IF IT WOULD WRAP ROUND?
|
||||
|
||||
func (cpu *Chip8) bitOpsAndMath() {
|
||||
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
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
func (cpu *Chip8) skipIfRegistersNotEqual() {
|
||||
x, y := (cpu.opcode>>8)&0x0F, (cpu.opcode>>4)&0x0F
|
||||
if cpu.registers[x] != cpu.registers[y] {
|
||||
cpu.pc += 2
|
||||
}
|
||||
}
|
||||
|
||||
func (cpu *Chip8) setAddressRegister() {
|
||||
// ANNN
|
||||
// Sets the address register to NNN
|
||||
cpu.addressRegister = cpu.opcode & 0x0FFF
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
func (cpu *Chip8) SkipOnKeyOpcodes() {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (cpu *Chip8) FifteenIndexOpcodes() {
|
||||
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
|
||||
for i, val := range cpu.keys {
|
||||
if val != 0 {
|
||||
cpu.registers[reg] = byte(i)
|
||||
}
|
||||
}
|
||||
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]
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
fmt.Printf("Hello world!\n")
|
||||
prog := []byte{1, 2, 3, 4}
|
||||
new_cpu := NewCHIP8(prog)
|
||||
fmt.Printf("%d\n", new_cpu.opcode)
|
||||
}
|
149
chip8/chip8_test.go
Normal file
149
chip8/chip8_test.go
Normal file
@ -0,0 +1,149 @@
|
||||
package chip8
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func slicesEqual(x, y []byte) bool {
|
||||
if len(x) != len(y) {
|
||||
return false
|
||||
}
|
||||
for i, xi := range x {
|
||||
if xi != y[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// This test is kinda shit, there's so much stuff we ain't testing
|
||||
// Maybe fix
|
||||
func TestCreateCPU(t *testing.T) {
|
||||
prog := []byte{1, 2, 3, 4}
|
||||
newCPU := NewCHIP8(prog)
|
||||
if !slicesEqual(newCPU.memory[200:204], prog) {
|
||||
t.Errorf("CPU not initalized properly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestClearDisplay(t *testing.T) {
|
||||
cpu := Chip8{}
|
||||
for i := range cpu.graphics {
|
||||
cpu.graphics[i] = byte(i % 255)
|
||||
}
|
||||
cpu.clearDisplay()
|
||||
graphicsArray := cpu.GetGraphicsBuffer()
|
||||
graphicsSlice := graphicsArray[:]
|
||||
emptySlice := make([]byte, len(cpu.graphics))
|
||||
if !slicesEqual(graphicsSlice, emptySlice) {
|
||||
t.Errorf("Graphics buffer not cleared properly")
|
||||
}
|
||||
}
|
||||
|
||||
func TestLeaveFunction(t *testing.T) {
|
||||
cpu := Chip8{pc: 50}
|
||||
cpu.stack = append(cpu.stack, 1, 2, 3, 4, 5)
|
||||
cpu.leaveFunction()
|
||||
if cpu.pc != 4 && len(cpu.stack) != 4 {
|
||||
t.Errorf("TestLeaveFunction not in expected state")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoTo(t *testing.T) {
|
||||
cpu := Chip8{opcode: 0x3420}
|
||||
cpu.goTo()
|
||||
if cpu.pc != 0x420 {
|
||||
t.Errorf("Test GoTo not working as expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestCallSubroutine(t *testing.T) {
|
||||
cpu := Chip8{opcode: 0x3420, pc: 43}
|
||||
cpu.callSubroutine()
|
||||
if (cpu.pc != 420) && (cpu.stack[0] != 43) && (len(cpu.stack) != 1) {
|
||||
t.Errorf("CallSubroutine not working as expected")
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkipIfRegisterEqual(t *testing.T) {
|
||||
var tests = []struct {
|
||||
register, regValue byte
|
||||
compValue, want uint16
|
||||
}{
|
||||
{0, 2, 4, 0},
|
||||
{0, 2, 2, 2},
|
||||
{15, 2, 2, 2},
|
||||
{15, 1, 12, 0},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testname := fmt.Sprintf("Reg:%d,RegVal:%d,Comp:%d", tt.register, tt.regValue, tt.compValue)
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
cpu := Chip8{opcode: tt.compValue + uint16(tt.register)<<8}
|
||||
cpu.registers[tt.register] = tt.regValue
|
||||
cpu.skipIfRegisterEqual()
|
||||
if tt.want != cpu.pc {
|
||||
t.Errorf("PC is %d, Wanted %d", cpu.pc, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkipIfRegisterNotEqual(t *testing.T) {
|
||||
var tests = []struct {
|
||||
register, regValue byte
|
||||
compValue, want uint16
|
||||
}{
|
||||
{0, 2, 4, 2},
|
||||
{0, 2, 2, 0},
|
||||
{15, 2, 2, 0},
|
||||
{15, 1, 12, 2},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testname := fmt.Sprintf("Reg:%d,RegVal:%d,Comp:%d", tt.register, tt.regValue, tt.compValue)
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
cpu := Chip8{opcode: tt.compValue + uint16(tt.register)<<8}
|
||||
cpu.registers[tt.register] = tt.regValue
|
||||
cpu.skipIfRegisterNotEqual()
|
||||
if tt.want != cpu.pc {
|
||||
t.Errorf("PC is %d, Wanted %d", cpu.pc, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSkipIfRegistersEqual(t *testing.T) {
|
||||
var tests = []struct {
|
||||
reg1, reg2, val1, val2 byte
|
||||
}{
|
||||
{0, 2, 4, 2},
|
||||
{0, 2, 2, 2},
|
||||
{15, 1, 2, 0},
|
||||
{15, 4, 12, 12},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
testname := fmt.Sprintf("Reg1:%d,Reg2:%d,val1:%d,val2:%d", tt.reg1, tt.reg2, tt.val1, tt.val2)
|
||||
t.Run(testname, func(t *testing.T) {
|
||||
cpu := Chip8{opcode: uint16(tt.reg1)<<8 + uint16(tt.reg2)<<4}
|
||||
cpu.registers[tt.reg1] = tt.val1
|
||||
cpu.registers[tt.reg2] = tt.val2
|
||||
cpu.skipIfRegistersEqual()
|
||||
if (2 != cpu.pc) && (tt.val1 == tt.val2) {
|
||||
t.Errorf("PC is %d, Wanted %d", cpu.pc, 2)
|
||||
} else if (0 != cpu.pc) && (tt.val1 != tt.val2) {
|
||||
t.Errorf("PC is %d, Wanted %d", cpu.pc, 2)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestSetRegisterTo(t *testing.T) {
|
||||
cpu := Chip8{opcode: 0x0824}
|
||||
cpu.setRegisterTo()
|
||||
if cpu.registers[8] != 0x24 {
|
||||
t.Errorf("Register 8 is %d wanted %d", cpu.registers[8], 0x24)
|
||||
}
|
||||
}
|
12
cmd/test_prog/main.go
Normal file
12
cmd/test_prog/main.go
Normal file
@ -0,0 +1,12 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"git.jacknet.io/S.D/Chip-8_Go/chip8"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
func main() {
|
||||
prog := make([]byte, 6)
|
||||
cpu := chip8.NewCHIP8(prog)
|
||||
fmt.Printf("This should print out zero: %d!\n", cpu.GetGraphicsBuffer()[0])
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user