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