better stack implementation

This commit is contained in:
Jairinho 2022-02-24 17:50:44 -03:00
parent a6c9060e8c
commit d73b642f7b
No known key found for this signature in database
GPG Key ID: 954589B18A21D5B6
4 changed files with 78 additions and 51 deletions

View File

@ -8,14 +8,11 @@ import (
const ( const (
MemorySize = 4096 // 4KB of memory MemorySize = 4096 // 4KB of memory
StackSize = 16
InstructionSize = 2 InstructionSize = 2
) )
const ( const (
ProgramAddress = uint16(0x0200) ProgramAddress = uint16(0x0200)
StackAddress = uint16(0x0EA0)
VideoBufferAddress = uint16(0x0F00)
) )
const ( const (
@ -27,17 +24,19 @@ const (
type Emulator struct { type Emulator struct {
V [16]uint8 // general registers V [16]uint8 // general registers
I uint16 // address register I uint16 // address register
SP uint16 // stack pointer
PC uint16 // program counter PC uint16 // program counter
DT uint8 // delay timer DT uint8 // delay timer
ST uint8 // sound timer ST uint8 // sound timer
Stack *Stack // very simple stack
Memory [MemorySize]uint8 // 4KB of system RAM Memory [MemorySize]uint8 // 4KB of system RAM
ROM []uint8 // game rom ROM []uint8 // game rom
Display *image.Gray Display *image.Gray // display buffer
} }
func NewEmulator() *Emulator { func NewEmulator() *Emulator {
emulator := new(Emulator) emulator := new(Emulator)
emulator.Stack = NewStack()
emulator.Display = image.NewGray(image.Rect(0, 0, Width, Height))
emulator.Reset() emulator.Reset()
return emulator return emulator
} }
@ -45,17 +44,22 @@ func NewEmulator() *Emulator {
func (emulator *Emulator) Reset() { func (emulator *Emulator) Reset() {
emulator.V = [16]uint8{} emulator.V = [16]uint8{}
emulator.I = 0 emulator.I = 0
emulator.SP = StackAddress
emulator.PC = ProgramAddress emulator.PC = ProgramAddress
emulator.DT = 0 emulator.DT = 0
emulator.ST = 0 emulator.ST = 0
emulator.Memory = [MemorySize]uint8{} emulator.Memory = [MemorySize]uint8{}
// clean stack
emulator.Stack.Clear()
// clean display
emulator.ClearScreen()
// load rom on memory
copy(emulator.Memory[ProgramAddress:], emulator.ROM) copy(emulator.Memory[ProgramAddress:], emulator.ROM)
emulator.Display = image.NewGray(image.Rect(0, 0, Width, Height))
} }
func (emulator *Emulator) Cycle() { func (emulator *Emulator) Cycle() {
pc := emulator.PC pc := emulator.PC
emulator.DT = uint8(math.Max(0, float64(emulator.DT)-1)) emulator.DT = uint8(math.Max(0, float64(emulator.DT)-1))
@ -82,11 +86,11 @@ func (emulator *Emulator) Cycle() {
case 0x2: // 2nnn - CALL addr case 0x2: // 2nnn - CALL addr
emulator.Call(nnn) emulator.Call(nnn)
case 0x3: // 3xkk - SE Vx, byte case 0x3: // 3xkk - SE Vx, byte
emulator.SkipEqual(x, kk) emulator.SkipEqualByte(x, kk)
case 0x4: // 4xkk - SNE Vx, byte case 0x4: // 4xkk - SNE Vx, byte
emulator.SkipNotEqual(x, kk) emulator.SkipNotEqualByte(x, kk)
case 0x5: // 5xy0 - SE Vx, Vy case 0x5: // 5xy0 - SE Vx, Vy
emulator.SkipRegistersEqual(x, y) emulator.SkipEqual(x, y)
case 0x6: // 6xkk - LD Vx, byte case 0x6: // 6xkk - LD Vx, byte
emulator.LoadByte(x, kk) emulator.LoadByte(x, kk)
case 0x7: // 7xkk - ADD Vx, byte case 0x7: // 7xkk - ADD Vx, byte
@ -113,6 +117,7 @@ func (emulator *Emulator) Cycle() {
emulator.ShiftLeft(x) emulator.ShiftLeft(x)
} }
case 0x9: // 9xy0 - SNE Vx, Vy case 0x9: // 9xy0 - SNE Vx, Vy
emulator.SkipNotEqual(x, y)
case 0xA: // Annn - LD I, addr case 0xA: // Annn - LD I, addr
case 0xB: // Bnnn - JP V0, addr case 0xB: // Bnnn - JP V0, addr
case 0xC: // Cxkk - RND Vx, byte case 0xC: // Cxkk - RND Vx, byte

View File

@ -13,10 +13,9 @@ func (emulator *Emulator) ClearScreen() {
// Return from a subroutine. // Return from a subroutine.
// //
// The interpreter sets the program counter to the address at the top of the stack, // The interpreter sets the program counter to the address at the top of the stack.
// then subtracts 1 from the stack pointer.
func (emulator *Emulator) Return() { func (emulator *Emulator) Return() {
emulator.StackPop() emulator.PC = emulator.Stack.Pop()
} }
// Jump to location nnn. // Jump to location nnn.
@ -28,10 +27,10 @@ func (emulator *Emulator) Jump(nnn uint16) {
// Call subroutine at nnn. // Call subroutine at nnn.
// //
// The interpreter increments the stack pointer, then puts the current PC // Puts the current PC on the top of the stack.
// on the top of the stack. The PC is then set to nnn. // The PC is then set to nnn.
func (emulator *Emulator) Call(nnn uint16) { func (emulator *Emulator) Call(nnn uint16) {
emulator.StackPush() emulator.Stack.Push(emulator.PC)
emulator.PC = nnn 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, // The interpreter compares register Vx to kk, and if they are equal,
// increments the program counter by 2. // 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 { if emulator.V[x] == kk {
emulator.PC += InstructionSize 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, // The interpreter compares register Vx to kk, and if they are not equal,
// increments the program counter by 2. // 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 { if emulator.V[x] != kk {
emulator.PC += InstructionSize 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, // The interpreter compares register Vx to register Vy, and if they are equal,
// increments the program counter by 2. // 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] { if emulator.V[x] == emulator.V[y] {
emulator.PC += InstructionSize emulator.PC += InstructionSize
} }
@ -161,6 +160,16 @@ func (emulator *Emulator) ShiftLeft(x uint8) {
emulator.V[x] <<= 1 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. // 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. // The interpreter reads n bytes from memory, starting at the address stored in I.

View File

@ -1,20 +1,36 @@
package chip8 package chip8
import "encoding/binary" const StackSize = 16
func (emulator *Emulator) StackPush() { type Stack struct {
if emulator.SP == StackAddress+StackSize*2 { 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") panic("chip8: stack overflow")
} }
binary.BigEndian.PutUint16(emulator.Memory[emulator.SP:], emulator.PC) stack.Values = append(stack.Values, value)
emulator.SP += 2
} }
func (emulator *Emulator) StackPop() { func (stack *Stack) Pop() uint16 {
if emulator.SP == StackAddress { if len(stack.Values) == 0 {
panic("chip8: nothing to pop from stack") panic("chip8: nothing to pop from stack")
} }
emulator.SP -= 2 n := len(stack.Values) - 1
emulator.PC = binary.BigEndian.Uint16(emulator.Memory[emulator.SP:]) defer func() {
binary.BigEndian.PutUint16(emulator.Memory[emulator.SP:], 0x00) // clean the stack position stack.Values[n] = 0x00
stack.Values = stack.Values[:n]
}()
return stack.Values[n]
} }

View File

@ -7,46 +7,43 @@ import (
"github.com/tangzero/chip8-emulator/chip8" "github.com/tangzero/chip8-emulator/chip8"
) )
func TestEmulator_StackPush(t *testing.T) { func TestStack_Push(t *testing.T) {
emulator := chip8.NewEmulator() stack := chip8.NewStack()
emulator.PC = 0xABCD stack.Push(0xABCD)
emulator.StackPush() stack.Push(0xCDEF)
stack.Push(0x9999)
assert.Equal(t, uint8(0xAB), emulator.Memory[chip8.StackAddress]) assert.Equal(t, []uint16{0xABCD, 0xCDEF, 0x9999}, stack.Values)
assert.Equal(t, uint8(0xCD), emulator.Memory[chip8.StackAddress+1])
assert.Equal(t, chip8.StackAddress+2, emulator.SP)
} }
func TestEmulator_StackPush_Overflow(t *testing.T) { func TestStack_Push_Overflow(t *testing.T) {
emulator := chip8.NewEmulator() stack := chip8.NewStack()
defer func() { defer func() {
assert.Equal(t, "chip8: stack overflow", recover()) assert.Equal(t, "chip8: stack overflow", recover())
}() }()
emulator.SP = chip8.StackAddress + chip8.StackSize*2 for {
emulator.StackPush() stack.Push(0xCAFE)
}
} }
func TestEmulator_StackPop(t *testing.T) { func TestEmulator_StackPop(t *testing.T) {
emulator := chip8.NewEmulator() stack := chip8.NewStack()
emulator.SP = chip8.StackAddress + 32 stack.Values = append(stack.Values, 0xCAFE)
emulator.Memory[chip8.StackAddress+30] = 0xEE
emulator.Memory[chip8.StackAddress+31] = 0xFF
emulator.StackPop()
assert.Equal(t, uint16(0xEEFF), emulator.PC) assert.Equal(t, uint16(0xCAFE), stack.Pop())
assert.Equal(t, chip8.StackAddress+30, emulator.SP) assert.Equal(t, []uint16{}, stack.Values)
} }
func TestEmulator_StackPop_Empty(t *testing.T) { func TestEmulator_StackPop_Empty(t *testing.T) {
emulator := chip8.NewEmulator() stack := chip8.NewStack()
defer func() { defer func() {
assert.Equal(t, "chip8: nothing to pop from stack", recover()) assert.Equal(t, "chip8: nothing to pop from stack", recover())
}() }()
emulator.StackPop() stack.Pop()
} }