diff --git a/chip8/chip8.go b/chip8/chip8.go index ac0ea4e..72adb7a 100644 --- a/chip8/chip8.go +++ b/chip8/chip8.go @@ -8,14 +8,11 @@ import ( const ( MemorySize = 4096 // 4KB of memory - StackSize = 16 InstructionSize = 2 ) const ( - ProgramAddress = uint16(0x0200) - StackAddress = uint16(0x0EA0) - VideoBufferAddress = uint16(0x0F00) + ProgramAddress = uint16(0x0200) ) const ( @@ -27,17 +24,19 @@ const ( type Emulator struct { V [16]uint8 // general registers I uint16 // address register - SP uint16 // stack pointer PC uint16 // program counter DT uint8 // delay timer ST uint8 // sound timer + Stack *Stack // very simple stack Memory [MemorySize]uint8 // 4KB of system RAM ROM []uint8 // game rom - Display *image.Gray + Display *image.Gray // display buffer } func NewEmulator() *Emulator { emulator := new(Emulator) + emulator.Stack = NewStack() + emulator.Display = image.NewGray(image.Rect(0, 0, Width, Height)) emulator.Reset() return emulator } @@ -45,17 +44,22 @@ func NewEmulator() *Emulator { func (emulator *Emulator) Reset() { emulator.V = [16]uint8{} emulator.I = 0 - emulator.SP = StackAddress emulator.PC = ProgramAddress emulator.DT = 0 emulator.ST = 0 emulator.Memory = [MemorySize]uint8{} + + // clean stack + emulator.Stack.Clear() + + // clean display + emulator.ClearScreen() + + // load rom on memory copy(emulator.Memory[ProgramAddress:], emulator.ROM) - emulator.Display = image.NewGray(image.Rect(0, 0, Width, Height)) } func (emulator *Emulator) Cycle() { - pc := emulator.PC emulator.DT = uint8(math.Max(0, float64(emulator.DT)-1)) @@ -82,11 +86,11 @@ func (emulator *Emulator) Cycle() { case 0x2: // 2nnn - CALL addr emulator.Call(nnn) case 0x3: // 3xkk - SE Vx, byte - emulator.SkipEqual(x, kk) + emulator.SkipEqualByte(x, kk) case 0x4: // 4xkk - SNE Vx, byte - emulator.SkipNotEqual(x, kk) + emulator.SkipNotEqualByte(x, kk) case 0x5: // 5xy0 - SE Vx, Vy - emulator.SkipRegistersEqual(x, y) + emulator.SkipEqual(x, y) case 0x6: // 6xkk - LD Vx, byte emulator.LoadByte(x, kk) case 0x7: // 7xkk - ADD Vx, byte @@ -113,6 +117,7 @@ func (emulator *Emulator) Cycle() { emulator.ShiftLeft(x) } case 0x9: // 9xy0 - SNE Vx, Vy + emulator.SkipNotEqual(x, y) case 0xA: // Annn - LD I, addr case 0xB: // Bnnn - JP V0, addr case 0xC: // Cxkk - RND Vx, byte diff --git a/chip8/instructions.go b/chip8/instructions.go index a8fc463..fef38e1 100644 --- a/chip8/instructions.go +++ b/chip8/instructions.go @@ -13,10 +13,9 @@ func (emulator *Emulator) ClearScreen() { // Return from a subroutine. // -// The interpreter sets the program counter to the address at the top of the stack, -// then subtracts 1 from the stack pointer. +// The interpreter sets the program counter to the address at the top of the stack. func (emulator *Emulator) Return() { - emulator.StackPop() + emulator.PC = emulator.Stack.Pop() } // Jump to location nnn. @@ -28,10 +27,10 @@ func (emulator *Emulator) Jump(nnn uint16) { // Call subroutine at nnn. // -// The interpreter increments the stack pointer, then puts the current PC -// on the top of the stack. The PC is then set to nnn. +// Puts the current PC on the top of the stack. +// The PC is then set to nnn. func (emulator *Emulator) Call(nnn uint16) { - emulator.StackPush() + emulator.Stack.Push(emulator.PC) emulator.PC = nnn } @@ -39,7 +38,7 @@ func (emulator *Emulator) Call(nnn uint16) { // // The interpreter compares register Vx to kk, and if they are equal, // increments the program counter by 2. -func (emulator *Emulator) SkipEqual(x uint8, kk uint8) { +func (emulator *Emulator) SkipEqualByte(x uint8, kk uint8) { if emulator.V[x] == kk { emulator.PC += InstructionSize } @@ -49,7 +48,7 @@ func (emulator *Emulator) SkipEqual(x uint8, kk uint8) { // // The interpreter compares register Vx to kk, and if they are not equal, // increments the program counter by 2. -func (emulator *Emulator) SkipNotEqual(x uint8, kk uint8) { +func (emulator *Emulator) SkipNotEqualByte(x uint8, kk uint8) { if emulator.V[x] != kk { emulator.PC += InstructionSize } @@ -59,7 +58,7 @@ func (emulator *Emulator) SkipNotEqual(x uint8, kk uint8) { // // The interpreter compares register Vx to register Vy, and if they are equal, // increments the program counter by 2. -func (emulator *Emulator) SkipRegistersEqual(x uint8, y uint8) { +func (emulator *Emulator) SkipEqual(x uint8, y uint8) { if emulator.V[x] == emulator.V[y] { emulator.PC += InstructionSize } @@ -161,6 +160,16 @@ func (emulator *Emulator) ShiftLeft(x uint8) { emulator.V[x] <<= 1 } +// Skip next instruction if Vx != Vy. + +// The values of Vx and Vy are compared, and if they are not equal, +// the program counter is increased by 2. +func (emulator *Emulator) SkipNotEqual(x uint8, y uint8) { + if emulator.V[x] != emulator.V[y] { + emulator.PC += InstructionSize + } +} + // Display n-byte sprite starting at memory location I at (Vx, Vy), set VF = collision. // // The interpreter reads n bytes from memory, starting at the address stored in I. diff --git a/chip8/stack.go b/chip8/stack.go index 4767ffd..d0bfa03 100644 --- a/chip8/stack.go +++ b/chip8/stack.go @@ -1,20 +1,36 @@ package chip8 -import "encoding/binary" +const StackSize = 16 -func (emulator *Emulator) StackPush() { - if emulator.SP == StackAddress+StackSize*2 { +type Stack struct { + Values []uint16 +} + +func NewStack() *Stack { + stack := new(Stack) + stack.Clear() + return stack +} + +func (stack *Stack) Clear() { + stack.Values = make([]uint16, 0, StackSize) +} + +func (stack *Stack) Push(value uint16) { + if len(stack.Values) == cap(stack.Values) { panic("chip8: stack overflow") } - binary.BigEndian.PutUint16(emulator.Memory[emulator.SP:], emulator.PC) - emulator.SP += 2 + stack.Values = append(stack.Values, value) } -func (emulator *Emulator) StackPop() { - if emulator.SP == StackAddress { +func (stack *Stack) Pop() uint16 { + if len(stack.Values) == 0 { panic("chip8: nothing to pop from stack") } - emulator.SP -= 2 - emulator.PC = binary.BigEndian.Uint16(emulator.Memory[emulator.SP:]) - binary.BigEndian.PutUint16(emulator.Memory[emulator.SP:], 0x00) // clean the stack position + n := len(stack.Values) - 1 + defer func() { + stack.Values[n] = 0x00 + stack.Values = stack.Values[:n] + }() + return stack.Values[n] } diff --git a/chip8/stack_test.go b/chip8/stack_test.go index 77313de..dcc145d 100644 --- a/chip8/stack_test.go +++ b/chip8/stack_test.go @@ -7,46 +7,43 @@ import ( "github.com/tangzero/chip8-emulator/chip8" ) -func TestEmulator_StackPush(t *testing.T) { - emulator := chip8.NewEmulator() +func TestStack_Push(t *testing.T) { + stack := chip8.NewStack() - emulator.PC = 0xABCD - emulator.StackPush() + stack.Push(0xABCD) + stack.Push(0xCDEF) + stack.Push(0x9999) - assert.Equal(t, uint8(0xAB), emulator.Memory[chip8.StackAddress]) - assert.Equal(t, uint8(0xCD), emulator.Memory[chip8.StackAddress+1]) - assert.Equal(t, chip8.StackAddress+2, emulator.SP) + assert.Equal(t, []uint16{0xABCD, 0xCDEF, 0x9999}, stack.Values) } -func TestEmulator_StackPush_Overflow(t *testing.T) { - emulator := chip8.NewEmulator() +func TestStack_Push_Overflow(t *testing.T) { + stack := chip8.NewStack() defer func() { assert.Equal(t, "chip8: stack overflow", recover()) }() - emulator.SP = chip8.StackAddress + chip8.StackSize*2 - emulator.StackPush() + for { + stack.Push(0xCAFE) + } } func TestEmulator_StackPop(t *testing.T) { - emulator := chip8.NewEmulator() + stack := chip8.NewStack() - emulator.SP = chip8.StackAddress + 32 - emulator.Memory[chip8.StackAddress+30] = 0xEE - emulator.Memory[chip8.StackAddress+31] = 0xFF - emulator.StackPop() + stack.Values = append(stack.Values, 0xCAFE) - assert.Equal(t, uint16(0xEEFF), emulator.PC) - assert.Equal(t, chip8.StackAddress+30, emulator.SP) + assert.Equal(t, uint16(0xCAFE), stack.Pop()) + assert.Equal(t, []uint16{}, stack.Values) } func TestEmulator_StackPop_Empty(t *testing.T) { - emulator := chip8.NewEmulator() + stack := chip8.NewStack() defer func() { assert.Equal(t, "chip8: nothing to pop from stack", recover()) }() - emulator.StackPop() + stack.Pop() }