better stack implementation
This commit is contained in:
parent
a6c9060e8c
commit
d73b642f7b
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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]
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user