From 6776939cde506c6e377b7616c86b2125209a2dd1 Mon Sep 17 00:00:00 2001 From: "S.D" Date: Sat, 10 Oct 2020 21:29:53 +0100 Subject: [PATCH 01/13] first few functions and unit tests --- .drone.yml | 13 +++++ pkg/chip8/chip8.go | 116 ++++++++++++++++++++++++++++++++++++++++ pkg/chip8/chip8_test.go | 56 +++++++++++++++++++ 3 files changed, 185 insertions(+) create mode 100644 .drone.yml create mode 100644 pkg/chip8/chip8.go create mode 100644 pkg/chip8/chip8_test.go diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..ca55ce9 --- /dev/null +++ b/.drone.yml @@ -0,0 +1,13 @@ +kind: pipeline +type: docker +name: default + +steps: +- name : test + image: golang:latest + commands: + - go test +- name : build + image: golang:latest + commands + - go build \ No newline at end of file diff --git a/pkg/chip8/chip8.go b/pkg/chip8/chip8.go new file mode 100644 index 0000000..cf9b546 --- /dev/null +++ b/pkg/chip8/chip8.go @@ -0,0 +1,116 @@ +package chip8 + +import "fmt" + +const graphicsBufferSize = 64 * 32 + +type Chip8 struct { + address_register uint16 + beep_timer uint16 + draw_required bool + delay_timer uint16 + graphics [graphicsBufferSize]uint8 + keys [16]uint8 + memory [4096]uint8 + opcode uint16 + pc uint16 + registers [16]uint8 // 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() []uint8 { + return []uint8{ + 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 []uint8) *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]uint8 { + return cpu.graphics +} + +func (cpu *Chip8) clearDisplay() { + // fuck it the gc can do the hard work for us + cpu.graphics = [64 * 32]uint8{} + cpu.draw_required = 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] == uint8(cpu.opcode&0xFF) { + cpu.pc += 2 + } +} + +func (cpu *Chip8) skipIfRegisterNotEqual() { + r := (cpu.opcode) >> 8 & 0x0F + if cpu.registers[r] != uint8(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) setRegisterToNN() { + r := (cpu.opcode >> 8) & 0x0F + cpu.registers[r] = uint8(cpu.opcode & 0xFF) +} + +func (cpu *Chip8) RegisterPlusEqualsNN() {} // QUESTION HERE - WHAT DO IF IT WOULD WRAP ROUND? + +func main() { + fmt.Printf("Hello world!\n") + prog := []uint8{1, 2, 3, 4} + new_cpu := NewCHIP8(prog) + fmt.Printf("%d\n", new_cpu.opcode) +} diff --git a/pkg/chip8/chip8_test.go b/pkg/chip8/chip8_test.go new file mode 100644 index 0000000..ea377aa --- /dev/null +++ b/pkg/chip8/chip8_test.go @@ -0,0 +1,56 @@ +package chip8 + +import ( + "testing" +) + +func slicesEqual(x, y []uint8) 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 := []uint8{1, 2, 3, 4} + new_cpu := NewCHIP8(prog) + if !slicesEqual(new_cpu.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] = uint8(i % 255) + } + cpu.clearDisplay() + graphics_array := cpu.GetGraphicsBuffer() + graphics_slice := graphics_array[:] + empty_slice := make([]uint8, len(cpu.graphics)) + if !slicesEqual(graphics_slice, empty_slice) { + 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 TestPasses(t *testing.T) { + if 1 != 1 { + t.Errorf("We should never get this!") + } +} -- 2.47.2 From 09f1091c3f545ba64a5b76da250e751c991dcfa1 Mon Sep 17 00:00:00 2001 From: "S.D" Date: Sat, 10 Oct 2020 21:38:11 +0100 Subject: [PATCH 02/13] init readme --- README.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..88178bd --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +[![Build Status](https://drone.jacknet.io/api/badges/S.D/Chip-8_Go/status.svg)](https://drone.jacknet.io/S.D/Chip-8_Go) \ No newline at end of file -- 2.47.2 From 0d02d2f3c7d2e7c6be12346771c0d15030d3b784 Mon Sep 17 00:00:00 2001 From: "S.D" Date: Sat, 10 Oct 2020 21:38:56 +0100 Subject: [PATCH 03/13] Fix missing colon in drone --- .drone.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index ca55ce9..15e7af2 100644 --- a/.drone.yml +++ b/.drone.yml @@ -9,5 +9,5 @@ steps: - go test - name : build image: golang:latest - commands + commands: - go build \ No newline at end of file -- 2.47.2 From 80124aa3a5721147c9e41f9248ab375612758b1f Mon Sep 17 00:00:00 2001 From: "S.D" Date: Sat, 10 Oct 2020 20:43:11 +0000 Subject: [PATCH 04/13] maybe build now? --- .drone.yml | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/.drone.yml b/.drone.yml index 15e7af2..af634fd 100644 --- a/.drone.yml +++ b/.drone.yml @@ -6,8 +6,4 @@ steps: - name : test image: golang:latest commands: - - go test -- name : build - image: golang:latest - commands: - - go build \ No newline at end of file + - go test -v ./pkg/... -- 2.47.2 From 75ab3648a1041c2a6faca579d602227f9fe0bbf2 Mon Sep 17 00:00:00 2001 From: "S.D" Date: Sat, 10 Oct 2020 22:29:35 +0100 Subject: [PATCH 05/13] add unit tests for existing methods --- pkg/chip8/chip8.go | 2 +- pkg/chip8/chip8_test.go | 99 +++++++++++++++++++++++++++++++++++++++-- 2 files changed, 97 insertions(+), 4 deletions(-) diff --git a/pkg/chip8/chip8.go b/pkg/chip8/chip8.go index cf9b546..59b6982 100644 --- a/pkg/chip8/chip8.go +++ b/pkg/chip8/chip8.go @@ -101,7 +101,7 @@ func (cpu *Chip8) skipIfRegistersEqual() { } } -func (cpu *Chip8) setRegisterToNN() { +func (cpu *Chip8) setRegisterTo() { r := (cpu.opcode >> 8) & 0x0F cpu.registers[r] = uint8(cpu.opcode & 0xFF) } diff --git a/pkg/chip8/chip8_test.go b/pkg/chip8/chip8_test.go index ea377aa..38b98ed 100644 --- a/pkg/chip8/chip8_test.go +++ b/pkg/chip8/chip8_test.go @@ -1,6 +1,7 @@ package chip8 import ( + "fmt" "testing" ) @@ -49,8 +50,100 @@ func TestLeaveFunction(t *testing.T) { } } -func TestPasses(t *testing.T) { - if 1 != 1 { - t.Errorf("We should never get this!") +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 uint8 + 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 uint8 + 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 uint8 + }{ + {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] != 24 { + t.Errorf("Register 8 is %d wanted %d", cpu.registers[8], 24) } } -- 2.47.2 From f89e28ab6d134a982991151774a11240ffb8ec39 Mon Sep 17 00:00:00 2001 From: "S.D" Date: Sat, 10 Oct 2020 22:30:16 +0100 Subject: [PATCH 06/13] hurr durr hex/int --- pkg/chip8/chip8_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/chip8/chip8_test.go b/pkg/chip8/chip8_test.go index 38b98ed..2af7c7f 100644 --- a/pkg/chip8/chip8_test.go +++ b/pkg/chip8/chip8_test.go @@ -143,7 +143,7 @@ func TestSkipIfRegistersEqual(t *testing.T) { func TestSetRegisterTo(t *testing.T) { cpu := Chip8{opcode: 0x0824} cpu.setRegisterTo() - if cpu.registers[8] != 24 { - t.Errorf("Register 8 is %d wanted %d", cpu.registers[8], 24) + if cpu.registers[8] != 0x24 { + t.Errorf("Register 8 is %d wanted %d", cpu.registers[8], 0x24) } } -- 2.47.2 From cd5ad596439974a3b32b3e4526cb1484c4aaba0f Mon Sep 17 00:00:00 2001 From: "S.D" Date: Sat, 10 Oct 2020 22:58:18 +0100 Subject: [PATCH 07/13] change all uint8's > byte they're the same thing! just more clear in this case --- pkg/chip8/chip8.go | 76 +++++++++++++++++++++++++++++++++-------- pkg/chip8/chip8_test.go | 14 ++++---- 2 files changed, 69 insertions(+), 21 deletions(-) diff --git a/pkg/chip8/chip8.go b/pkg/chip8/chip8.go index 59b6982..d7ef11b 100644 --- a/pkg/chip8/chip8.go +++ b/pkg/chip8/chip8.go @@ -9,19 +9,19 @@ type Chip8 struct { beep_timer uint16 draw_required bool delay_timer uint16 - graphics [graphicsBufferSize]uint8 - keys [16]uint8 - memory [4096]uint8 + graphics [graphicsBufferSize]byte + keys [16]byte + memory [4096]byte opcode uint16 pc uint16 - registers [16]uint8 // an array - has a fixed length - stack []uint16 // a slice - basically a c++ vector + 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() []uint8 { - return []uint8{ +func getLetters() []byte { + return []byte{ 0xF0, 0x90, 0x90, 0x90, 0xF0, // 0 0x20, 0x60, 0x20, 0x20, 0x70, // 1 0xF0, 0x10, 0xF0, 0x80, 0xF0, // 2 @@ -40,7 +40,7 @@ func getLetters() []uint8 { 0xF0, 0x80, 0xF0, 0x80, 0x80} // F } -func NewCHIP8(prog []uint8) *Chip8 { +func NewCHIP8(prog []byte) *Chip8 { cpu := Chip8{pc: 0x200} memory_slice := cpu.memory[:80] copy(memory_slice, getLetters()) @@ -53,13 +53,13 @@ func NewCHIP8(prog []uint8) *Chip8 { return &cpu } -func (cpu *Chip8) GetGraphicsBuffer() [graphicsBufferSize]uint8 { +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]uint8{} + cpu.graphics = [64 * 32]byte{} cpu.draw_required = true } @@ -82,14 +82,14 @@ func (cpu *Chip8) callSubroutine() { func (cpu *Chip8) skipIfRegisterEqual() { r := (cpu.opcode) >> 8 & 0x0F - if cpu.registers[r] == uint8(cpu.opcode&0xFF) { + if cpu.registers[r] == byte(cpu.opcode&0xFF) { cpu.pc += 2 } } func (cpu *Chip8) skipIfRegisterNotEqual() { r := (cpu.opcode) >> 8 & 0x0F - if cpu.registers[r] != uint8(cpu.opcode&0xFF) { + if cpu.registers[r] != byte(cpu.opcode&0xFF) { cpu.pc += 2 } } @@ -103,14 +103,62 @@ func (cpu *Chip8) skipIfRegistersEqual() { func (cpu *Chip8) setRegisterTo() { r := (cpu.opcode >> 8) & 0x0F - cpu.registers[r] = uint8(cpu.opcode & 0xFF) + 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 + } +} + func main() { fmt.Printf("Hello world!\n") - prog := []uint8{1, 2, 3, 4} + prog := []byte{1, 2, 3, 4} new_cpu := NewCHIP8(prog) fmt.Printf("%d\n", new_cpu.opcode) } diff --git a/pkg/chip8/chip8_test.go b/pkg/chip8/chip8_test.go index 2af7c7f..623caef 100644 --- a/pkg/chip8/chip8_test.go +++ b/pkg/chip8/chip8_test.go @@ -5,7 +5,7 @@ import ( "testing" ) -func slicesEqual(x, y []uint8) bool { +func slicesEqual(x, y []byte) bool { if len(x) != len(y) { return false } @@ -20,7 +20,7 @@ func slicesEqual(x, y []uint8) bool { // This test is kinda shit, there's so much stuff we ain't testing // Maybe fix func TestCreateCPU(t *testing.T) { - prog := []uint8{1, 2, 3, 4} + prog := []byte{1, 2, 3, 4} new_cpu := NewCHIP8(prog) if !slicesEqual(new_cpu.memory[200:204], prog) { t.Errorf("CPU not initalized properly") @@ -30,12 +30,12 @@ func TestCreateCPU(t *testing.T) { func TestClearDisplay(t *testing.T) { cpu := Chip8{} for i := range cpu.graphics { - cpu.graphics[i] = uint8(i % 255) + cpu.graphics[i] = byte(i % 255) } cpu.clearDisplay() graphics_array := cpu.GetGraphicsBuffer() graphics_slice := graphics_array[:] - empty_slice := make([]uint8, len(cpu.graphics)) + empty_slice := make([]byte, len(cpu.graphics)) if !slicesEqual(graphics_slice, empty_slice) { t.Errorf("Graphics buffer not cleared properly") } @@ -68,7 +68,7 @@ func TestCallSubroutine(t *testing.T) { func TestSkipIfRegisterEqual(t *testing.T) { var tests = []struct { - register, regValue uint8 + register, regValue byte compValue, want uint16 }{ {0, 2, 4, 0}, @@ -92,7 +92,7 @@ func TestSkipIfRegisterEqual(t *testing.T) { func TestSkipIfRegisterNotEqual(t *testing.T) { var tests = []struct { - register, regValue uint8 + register, regValue byte compValue, want uint16 }{ {0, 2, 4, 2}, @@ -116,7 +116,7 @@ func TestSkipIfRegisterNotEqual(t *testing.T) { func TestSkipIfRegistersEqual(t *testing.T) { var tests = []struct { - reg1, reg2, val1, val2 uint8 + reg1, reg2, val1, val2 byte }{ {0, 2, 4, 2}, {0, 2, 2, 2}, -- 2.47.2 From 98741356a6c0984b5705d3f7f7e95ac4776355a3 Mon Sep 17 00:00:00 2001 From: "S.D" Date: Sat, 10 Oct 2020 23:08:57 +0100 Subject: [PATCH 08/13] get rid of underscores -> camelCase --- pkg/chip8/chip8.go | 47 ++++++++++++++++++++++++++++++----------- pkg/chip8/chip8_test.go | 12 +++++------ 2 files changed, 41 insertions(+), 18 deletions(-) diff --git a/pkg/chip8/chip8.go b/pkg/chip8/chip8.go index d7ef11b..bd9ae11 100644 --- a/pkg/chip8/chip8.go +++ b/pkg/chip8/chip8.go @@ -5,17 +5,17 @@ import "fmt" const graphicsBufferSize = 64 * 32 type Chip8 struct { - address_register uint16 - beep_timer uint16 - draw_required bool - delay_timer uint16 - 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 + addressRegister uint16 + beepTimer uint16 + drawRequired bool + delayTimer uint16 + 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 @@ -60,7 +60,7 @@ func (cpu *Chip8) GetGraphicsBuffer() [graphicsBufferSize]byte { func (cpu *Chip8) clearDisplay() { // fuck it the gc can do the hard work for us cpu.graphics = [64 * 32]byte{} - cpu.draw_required = true + cpu.drawRequired = true } // what if there's nothing in the stack? @@ -153,9 +153,32 @@ func (cpu *Chip8) BitOpsAndMath() { // 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 main() { fmt.Printf("Hello world!\n") prog := []byte{1, 2, 3, 4} diff --git a/pkg/chip8/chip8_test.go b/pkg/chip8/chip8_test.go index 623caef..68329d4 100644 --- a/pkg/chip8/chip8_test.go +++ b/pkg/chip8/chip8_test.go @@ -21,8 +21,8 @@ func slicesEqual(x, y []byte) bool { // Maybe fix func TestCreateCPU(t *testing.T) { prog := []byte{1, 2, 3, 4} - new_cpu := NewCHIP8(prog) - if !slicesEqual(new_cpu.memory[200:204], prog) { + newCPU := NewCHIP8(prog) + if !slicesEqual(newCPU.memory[200:204], prog) { t.Errorf("CPU not initalized properly") } } @@ -33,10 +33,10 @@ func TestClearDisplay(t *testing.T) { cpu.graphics[i] = byte(i % 255) } cpu.clearDisplay() - graphics_array := cpu.GetGraphicsBuffer() - graphics_slice := graphics_array[:] - empty_slice := make([]byte, len(cpu.graphics)) - if !slicesEqual(graphics_slice, empty_slice) { + graphicsArray := cpu.GetGraphicsBuffer() + graphicsSlice := graphicsArray[:] + emptySlice := make([]byte, len(cpu.graphics)) + if !slicesEqual(graphicsSlice, emptySlice) { t.Errorf("Graphics buffer not cleared properly") } } -- 2.47.2 From cda3236557f9f92f53d60b3072f60d671c3a0fe9 Mon Sep 17 00:00:00 2001 From: "S.D" Date: Sat, 10 Oct 2020 23:54:09 +0100 Subject: [PATCH 09/13] hopefully implemented all the opcodes - need to test tomorrow unsure if over/underflows will work as intended --- pkg/chip8/chip8.go | 132 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 125 insertions(+), 7 deletions(-) diff --git a/pkg/chip8/chip8.go b/pkg/chip8/chip8.go index bd9ae11..02e513d 100644 --- a/pkg/chip8/chip8.go +++ b/pkg/chip8/chip8.go @@ -1,14 +1,17 @@ package chip8 -import "fmt" +import ( + "fmt" + "math/rand" +) const graphicsBufferSize = 64 * 32 type Chip8 struct { addressRegister uint16 - beepTimer uint16 + beepTimer byte drawRequired bool - delayTimer uint16 + delayTimer byte graphics [graphicsBufferSize]byte keys [16]byte memory [4096]byte @@ -106,9 +109,9 @@ func (cpu *Chip8) setRegisterTo() { cpu.registers[r] = byte(cpu.opcode & 0xFF) } -func (cpu *Chip8) RegisterPlusEqualsNN() {} // QUESTION HERE - WHAT DO IF IT WOULD WRAP ROUND? +func (cpu *Chip8) registerPlusEqualsNN() {} // QUESTION HERE - WHAT DO IF IT WOULD WRAP ROUND? -func (cpu *Chip8) BitOpsAndMath() { +func (cpu *Chip8) bitOpsAndMath() { instruction := cpu.opcode & 0x0F regX, regY := cpu.opcode>>8&0x0F, cpu.opcode>>4&0x0F switch instruction { @@ -166,19 +169,134 @@ func (cpu *Chip8) BitOpsAndMath() { } } -func (cpu *Chip8) SkipIfRegistersNotEqual() { +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() { +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} -- 2.47.2 From 0578f1aa93794c725352771d8707258ea6814656 Mon Sep 17 00:00:00 2001 From: "S.D" Date: Sun, 11 Oct 2020 19:18:08 +0100 Subject: [PATCH 10/13] testing a different package layout --- .drone.yml | 7 ++++++- {pkg/chip8 => chip8}/chip8.go | 0 {pkg/chip8 => chip8}/chip8_test.go | 0 cmd/test_prog/main.go | 12 ++++++++++++ go.mod | 3 +++ 5 files changed, 21 insertions(+), 1 deletion(-) rename {pkg/chip8 => chip8}/chip8.go (100%) rename {pkg/chip8 => chip8}/chip8_test.go (100%) create mode 100644 cmd/test_prog/main.go create mode 100644 go.mod diff --git a/.drone.yml b/.drone.yml index af634fd..ef937fb 100644 --- a/.drone.yml +++ b/.drone.yml @@ -6,4 +6,9 @@ steps: - name : test image: golang:latest commands: - - go test -v ./pkg/... + - go test -v ./chip8 +- name : build + image: golang:latest + commands: + - go build ./cmd/test_prog + diff --git a/pkg/chip8/chip8.go b/chip8/chip8.go similarity index 100% rename from pkg/chip8/chip8.go rename to chip8/chip8.go diff --git a/pkg/chip8/chip8_test.go b/chip8/chip8_test.go similarity index 100% rename from pkg/chip8/chip8_test.go rename to chip8/chip8_test.go diff --git a/cmd/test_prog/main.go b/cmd/test_prog/main.go new file mode 100644 index 0000000..f3088e2 --- /dev/null +++ b/cmd/test_prog/main.go @@ -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("%d\n", cpu.GetGraphicsBuffer()[0]) +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..a927f9a --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module git.jacknet.io/S.D/Chip-8_Go + +go 1.15 -- 2.47.2 From 409a939f9af708838d5975b9243ba554305fe0bc Mon Sep 17 00:00:00 2001 From: "S.D" Date: Sun, 11 Oct 2020 19:26:53 +0100 Subject: [PATCH 11/13] all sorts of wacky build stuff --- .drone.yml | 41 +++++++++++++++++++++++++++++++++++++++-- cmd/test_prog/main.go | 2 +- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/.drone.yml b/.drone.yml index ef937fb..4ca5642 100644 --- a/.drone.yml +++ b/.drone.yml @@ -7,8 +7,45 @@ steps: image: golang:latest commands: - go test -v ./chip8 -- name : build +- name : build windows image: golang:latest commands: - - go build ./cmd/test_prog + - 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 + # 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 \ No newline at end of file diff --git a/cmd/test_prog/main.go b/cmd/test_prog/main.go index f3088e2..e61a557 100644 --- a/cmd/test_prog/main.go +++ b/cmd/test_prog/main.go @@ -8,5 +8,5 @@ import ( func main() { prog := make([]byte, 6) cpu := chip8.NewCHIP8(prog) - fmt.Printf("%d\n", cpu.GetGraphicsBuffer()[0]) + fmt.Printf("This should print out zero: %d\n", cpu.GetGraphicsBuffer()[0]) } \ No newline at end of file -- 2.47.2 From 5c53e6111e2b1037b9b6a2b50e42f80099bb90fe Mon Sep 17 00:00:00 2001 From: "S.D" Date: Sun, 11 Oct 2020 19:28:44 +0100 Subject: [PATCH 12/13] fix build step? --- .drone.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.drone.yml b/.drone.yml index 4ca5642..6d8c856 100644 --- a/.drone.yml +++ b/.drone.yml @@ -34,7 +34,9 @@ steps: image: plugins/gitea-release depends_on: - test - - build + - build windows + - build linux + - build mac # This step is only run when a branch is tagged in Gitea. when: event: -- 2.47.2 From 552e21fcfc3acbdf09851dca4fca5d1ed3258771 Mon Sep 17 00:00:00 2001 From: "S.D" Date: Sun, 11 Oct 2020 19:30:38 +0100 Subject: [PATCH 13/13] create commit for tag --- cmd/test_prog/main.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/test_prog/main.go b/cmd/test_prog/main.go index e61a557..21e8d6d 100644 --- a/cmd/test_prog/main.go +++ b/cmd/test_prog/main.go @@ -8,5 +8,5 @@ import ( func main() { prog := make([]byte, 6) cpu := chip8.NewCHIP8(prog) - fmt.Printf("This should print out zero: %d\n", cpu.GetGraphicsBuffer()[0]) + fmt.Printf("This should print out zero: %d!\n", cpu.GetGraphicsBuffer()[0]) } \ No newline at end of file -- 2.47.2