Extract the 6502 emulation to a separate module

This commit is contained in:
Ivan Izaguirre 2022-01-28 18:09:59 +01:00
parent 765b1fba28
commit 2ae91f8585
22 changed files with 14 additions and 27980 deletions

View File

@ -4,13 +4,13 @@ import (
"fmt"
"time"
"github.com/ivanizag/izapple2/core6502"
"github.com/ivanizag/iz6502"
)
// Apple2 represents all the components and state of the emulated machine
type Apple2 struct {
Name string
cpu *core6502.State
cpu *iz6502.State
mmu *memoryManager
io *ioC0Page
cg *CharacterGenerator

View File

@ -3,7 +3,7 @@ package izapple2
import (
"errors"
"github.com/ivanizag/izapple2/core6502"
"github.com/ivanizag/iz6502"
"github.com/ivanizag/izapple2/storage"
)
@ -30,14 +30,14 @@ func (a *Apple2) setup(clockMhz float64, fastMode bool) {
func setApple2plus(a *Apple2) {
a.Name = "Apple ][+"
a.cpu = core6502.NewNMOS6502(a.mmu)
a.cpu = iz6502.NewNMOS6502(a.mmu)
addApple2SoftSwitches(a.io)
}
func setApple2e(a *Apple2) {
a.Name = "Apple IIe"
a.isApple2e = true
a.cpu = core6502.NewNMOS6502(a.mmu)
a.cpu = iz6502.NewNMOS6502(a.mmu)
a.mmu.initExtendedRAM(1)
addApple2SoftSwitches(a.io)
addApple2ESoftSwitches(a.io)
@ -46,7 +46,7 @@ func setApple2e(a *Apple2) {
func setApple2eEnhanced(a *Apple2) {
a.Name = "Apple //e"
a.isApple2e = true
a.cpu = core6502.NewCMOS65c02(a.mmu)
a.cpu = iz6502.NewCMOS65c02(a.mmu)
a.mmu.initExtendedRAM(1)
addApple2SoftSwitches(a.io)
addApple2ESoftSwitches(a.io)

View File

@ -3,7 +3,7 @@ package izapple2
import (
"fmt"
"github.com/ivanizag/izapple2/core6502"
"github.com/ivanizag/iz6502"
"github.com/ivanizag/izapple2/storage"
)
@ -13,7 +13,7 @@ import (
func setBase64a(a *Apple2) {
a.Name = "Base 64A"
a.cpu = core6502.NewNMOS6502(a.mmu)
a.cpu = iz6502.NewNMOS6502(a.mmu)
addApple2SoftSwitches(a.io)
addBase64aSoftSwitches(a.io)
}

View File

@ -1,210 +0,0 @@
package core6502
import "fmt"
const (
modeImplicit = iota + 1
modeImplicitX
modeImplicitY
modeAccumulator
modeImmediate
modeZeroPage
modeZeroPageX
modeZeroPageY
modeRelative
modeAbsolute
modeAbsoluteX
modeAbsoluteX65c02
modeAbsoluteY
modeIndirect
modeIndexedIndirectX
modeIndirectIndexedY
// Added on the 65c02
modeIndirect65c02Fix
modeIndirectZeroPage
modeAbsoluteIndexedIndirectX
modeZeroPageAndRelative
)
func getWordInLine(line []uint8) uint16 {
return uint16(line[1]) + 0x100*uint16(line[2])
}
func resolveValue(s *State, line []uint8, opcode opcode) uint8 {
switch opcode.addressMode {
case modeAccumulator:
return s.reg.getA()
case modeImplicitX:
return s.reg.getX()
case modeImplicitY:
return s.reg.getY()
case modeImmediate:
return line[1]
}
// The value is in memory
address := resolveAddress(s, line, opcode)
return s.mem.Peek(address)
}
func resolveSetValue(s *State, line []uint8, opcode opcode, value uint8) {
switch opcode.addressMode {
case modeAccumulator:
s.reg.setA(value)
return
case modeImplicitX:
s.reg.setX(value)
return
case modeImplicitY:
s.reg.setY(value)
return
}
// The value is in memory
address := resolveAddress(s, line, opcode)
s.mem.Poke(address, value)
// On writes, the possible extra cycle crossing page boundaries is
// added and already accounted for on NMOS
if opcode.addressMode != modeAbsoluteX65c02 {
s.extraCycleCrossingBoundaries = false
}
}
func resolveAddress(s *State, line []uint8, opcode opcode) uint16 {
var address uint16
extraCycle := false
switch opcode.addressMode {
case modeZeroPage:
address = uint16(line[1])
case modeZeroPageX:
address = uint16(line[1] + s.reg.getX())
case modeZeroPageY:
address = uint16(line[1] + s.reg.getY())
case modeAbsolute:
address = getWordInLine(line)
case modeAbsoluteX65c02:
fallthrough
case modeAbsoluteX:
base := getWordInLine(line)
address, extraCycle = addOffset(base, s.reg.getX())
case modeAbsoluteY:
base := getWordInLine(line)
address, extraCycle = addOffset(base, s.reg.getY())
case modeIndexedIndirectX:
addressAddress := line[1] + s.reg.getX()
address = getZeroPageWord(s.mem, addressAddress)
case modeIndirect:
addressAddress := getWordInLine(line)
address = getWordNoCrossPage(s.mem, addressAddress)
case modeIndirect65c02Fix:
addressAddress := getWordInLine(line)
address = getWord(s.mem, addressAddress)
case modeIndirectIndexedY:
base := getZeroPageWord(s.mem, line[1])
address, extraCycle = addOffset(base, s.reg.getY())
// 65c02 additions
case modeIndirectZeroPage:
address = getZeroPageWord(s.mem, line[1])
case modeAbsoluteIndexedIndirectX:
addressAddress := getWordInLine(line) + uint16(s.reg.getX())
address = getWord(s.mem, addressAddress)
case modeRelative:
// This assumes that PC is already pointing to the next instruction
base := s.reg.getPC()
address, extraCycle = addOffsetRelative(base, line[1])
case modeZeroPageAndRelative:
// Two addressing modes combined. We refer to the second one, relative,
// placed one byte after the zeropage reference
base := s.reg.getPC()
address, _ = addOffsetRelative(base, line[2])
default:
panic("Assert failed. Missing addressing mode")
}
if extraCycle {
s.extraCycleCrossingBoundaries = true
}
return address
}
/*
Note: extra cycle on reads when crossing page boundaries.
Only for:
modeAbsoluteX
modeAbsoluteY
modeIndirectIndexedY
modeRelative
modeZeroPageAndRelative
That is when we add a 8 bit offset to a 16 bit base. The reason is
that if don't have a page crossing the CPU optimizes one cycle assuming
that the MSB addition won't change. If it does we spend this extra cycle.
Note that for writes we don't add a cycle in this case. There is no
optimization that could make a double write. The regular cycle count
is alwaus the same with no optimization.
*/
func addOffset(base uint16, offset uint8) (uint16, bool) {
dest := base + uint16(offset)
if (base & 0xff00) != (dest & 0xff00) {
return dest, true
}
return dest, false
}
func addOffsetRelative(base uint16, offset uint8) (uint16, bool) {
dest := base + uint16(int8(offset))
if (base & 0xff00) != (dest & 0xff00) {
return dest, true
}
return dest, false
}
func lineString(line []uint8, opcode opcode) string {
t := opcode.name
switch opcode.addressMode {
case modeImplicit:
case modeImplicitX:
case modeImplicitY:
//Nothing
case modeAccumulator:
t += " A"
case modeImmediate:
t += fmt.Sprintf(" #$%02x", line[1])
case modeZeroPage:
t += fmt.Sprintf(" $%02x", line[1])
case modeZeroPageX:
t += fmt.Sprintf(" $%02x,X", line[1])
case modeZeroPageY:
t += fmt.Sprintf(" $%02x,Y", line[1])
case modeRelative:
t += fmt.Sprintf(" *%+x", int8(line[1]))
case modeAbsolute:
t += fmt.Sprintf(" $%04x", getWordInLine(line))
case modeAbsoluteX65c02:
fallthrough
case modeAbsoluteX:
t += fmt.Sprintf(" $%04x,X", getWordInLine(line))
case modeAbsoluteY:
t += fmt.Sprintf(" $%04x,Y", getWordInLine(line))
case modeIndirect:
t += fmt.Sprintf(" ($%04x)", getWordInLine(line))
case modeIndexedIndirectX:
t += fmt.Sprintf(" ($%02x,X)", line[1])
case modeIndirectIndexedY:
t += fmt.Sprintf(" ($%02x),Y", line[1])
// 65c02 additions:
case modeIndirectZeroPage:
t += fmt.Sprintf(" ($%02x)", line[1])
case modeAbsoluteIndexedIndirectX:
t += fmt.Sprintf(" ($%04x,X)", getWordInLine(line))
case modeZeroPageAndRelative:
t += fmt.Sprintf(" $%02x %+x", line[1], int8(line[2]))
default:
t += "UNKNOWN MODE"
}
return t
}

View File

@ -1,173 +0,0 @@
package core6502
/*
For the diffrences with NMOS6502 see:
http://6502.org/tutorials/65c02opcodes.html
http://wilsonminesco.com/NMOS-CMOSdif/
http://www.obelisk.me.uk/65C02/reference.html
http://www.obelisk.me.uk/65C02/addressing.html
http://anyplatform.net/media/guides/cpus/65xx%20Processor%20Data.txt
*/
// NewCMOS65c02 returns an initialized 65c02
func NewCMOS65c02(m Memory) *State {
var s State
s.mem = m
var opcodes [256]opcode
for i := 0; i < 256; i++ {
opcodes[i] = opcodesNMOS6502[i]
if opcodes65c02Delta[i].cycles != 0 {
opcodes[i] = opcodes65c02Delta[i]
}
}
add65c02NOPs(&opcodes)
s.opcodes = &opcodes
return &s
}
func add65c02NOPs(opcodes *[256]opcode) {
nop11 := opcode{"NOP", 1, 1, modeImplicit, opNOP}
nop22 := opcode{"NOP", 2, 2, modeImmediate, opNOP}
nop23 := opcode{"NOP", 2, 3, modeImmediate, opNOP}
nop24 := opcode{"NOP", 2, 4, modeImmediate, opNOP}
nop34 := opcode{"NOP", 3, 4, modeAbsolute, opNOP}
opcodes[0x02] = nop22
opcodes[0x22] = nop22
opcodes[0x42] = nop22
opcodes[0x62] = nop22
opcodes[0x82] = nop22
opcodes[0xc2] = nop22
opcodes[0xe2] = nop22
opcodes[0x44] = nop23
opcodes[0x54] = nop24
opcodes[0xD4] = nop24
opcodes[0xF4] = nop24
opcodes[0x5c] = nop34
opcodes[0xdc] = nop34
opcodes[0xfc] = nop34
for i := 0; i < 0x100; i = i + 0x10 {
opcodes[i+0x03] = nop11
// RMB and SMB; opcodes[i+0x07] = nop11
opcodes[i+0x0b] = nop11
// BBR and BBS: opcodes[i+0x0f] = nop11
}
/* Changes for Rockwell65c02
nop12 := opcode{"NOP", 1, 2, modeImplicit, opNOP}
opcodes[0xcb] = nop12
opcodes[0xdb] = nop24
*/
// Detection of 65c816
opcodes[0xbf].name = "XCE"
}
var opcodes65c02Delta = [256]opcode{
// Functional difference
0x00: {"BRK", 1, 7, modeImplicit, opBRKAlt},
0x24: {"BIT", 2, 3, modeZeroPage, opBIT},
0x6C: {"JMP", 3, 6, modeIndirect65c02Fix, opJMP},
// Fixed BCD arithmetic flags
0x69: {"ADC", 2, 2, modeImmediate, opADCAlt},
0x65: {"ADC", 2, 3, modeZeroPage, opADCAlt},
0x75: {"ADC", 2, 4, modeZeroPageX, opADCAlt},
0x6D: {"ADC", 3, 4, modeAbsolute, opADCAlt},
0x7D: {"ADC", 3, 4, modeAbsoluteX, opADCAlt}, // Extra cycles
0x79: {"ADC", 3, 4, modeAbsoluteY, opADCAlt}, // Extra cycles
0x61: {"ADC", 2, 6, modeIndexedIndirectX, opADCAlt},
0x71: {"ADC", 2, 5, modeIndirectIndexedY, opADCAlt}, // Extra cycles
0xE9: {"SBC", 2, 2, modeImmediate, opSBCAlt},
0xE5: {"SBC", 2, 3, modeZeroPage, opSBCAlt},
0xF5: {"SBC", 2, 4, modeZeroPageX, opSBCAlt},
0xED: {"SBC", 3, 4, modeAbsolute, opSBCAlt},
0xFD: {"SBC", 3, 4, modeAbsoluteX, opSBCAlt}, // Extra cycles
0xF9: {"SBC", 3, 4, modeAbsoluteY, opSBCAlt}, // Extra cycles
0xE1: {"SBC", 2, 6, modeIndexedIndirectX, opSBCAlt},
0xF1: {"SBC", 2, 5, modeIndirectIndexedY, opSBCAlt}, // Extra cycles
// Different cycle count
0x1e: {"ASL", 3, 6, modeAbsoluteX65c02, buildOpShift(true, false)},
0x3e: {"ROL", 3, 6, modeAbsoluteX65c02, buildOpShift(true, true)},
0x5e: {"LSR", 3, 6, modeAbsoluteX65c02, buildOpShift(false, false)},
0x7e: {"ROR", 3, 6, modeAbsoluteX65c02, buildOpShift(false, true)},
// New indirect zero page addresssing mode
0x12: {"ORA", 2, 5, modeIndirectZeroPage, buildOpLogic(operationOr)},
0x32: {"AND", 2, 5, modeIndirectZeroPage, buildOpLogic(operationAnd)},
0x52: {"EOR", 2, 5, modeIndirectZeroPage, buildOpLogic(operationXor)},
0x72: {"ADC", 2, 5, modeIndirectZeroPage, opADCAlt},
0x92: {"STA", 2, 5, modeIndirectZeroPage, buildOpStore(regA)},
0xb2: {"LDA", 2, 5, modeIndirectZeroPage, buildOpLoad(regA)},
0xd2: {"CMP", 2, 5, modeIndirectZeroPage, buildOpCompare(regA)},
0xf2: {"SBC", 2, 5, modeIndirectZeroPage, opSBCAlt},
// New addressing options
0x89: {"BIT", 2, 2, modeImmediate, opBIT},
0x34: {"BIT", 2, 4, modeZeroPageX, opBIT},
0x3c: {"BIT", 3, 4, modeAbsoluteX, opBIT}, // Extra cycles
0x1a: {"INC", 1, 2, modeAccumulator, buildOpIncDec(true)},
0x3a: {"DEC", 1, 2, modeAccumulator, buildOpIncDec(false)},
0x7c: {"JMP", 3, 6, modeAbsoluteIndexedIndirectX, opJMP},
// Additional instructions: BRA, PHX, PHY, PLX, PLY, STZ, TRB, TSB
0xda: {"PHX", 1, 3, modeImplicit, buildOpPush(regX)},
0x5a: {"PHY", 1, 3, modeImplicit, buildOpPush(regY)},
0xfa: {"PLX", 1, 4, modeImplicit, buildOpPull(regX)},
0x7a: {"PLY", 1, 4, modeImplicit, buildOpPull(regY)},
0x80: {"BRA", 2, 3, modeRelative, opJMP}, // Extra cycles
0x64: {"STZ", 2, 3, modeZeroPage, opSTZ},
0x74: {"STZ", 2, 4, modeZeroPageX, opSTZ},
0x9c: {"STZ", 3, 4, modeAbsolute, opSTZ},
0x9e: {"STZ", 3, 5, modeAbsoluteX, opSTZ},
0x14: {"TRB", 2, 5, modeZeroPage, opTRB},
0x1c: {"TRB", 3, 6, modeAbsolute, opTRB},
0x04: {"TSB", 2, 5, modeZeroPage, opTSB},
0x0c: {"TSB", 3, 6, modeAbsolute, opTSB},
// Additional in Rockwell 65c02 and WDC 65c02?
// They have a double addressing mode: zeropage and relative.
0x0f: {"BBR0", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(0, false)}, // Extra cycles
0x1f: {"BBR1", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(1, false)}, // Extra cycles
0x2f: {"BBR2", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(2, false)}, // Extra cycles
0x3f: {"BBR3", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(3, false)}, // Extra cycles
0x4f: {"BBR4", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(4, false)}, // Extra cycles
0x5f: {"BBR5", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(5, false)}, // Extra cycles
0x6f: {"BBR6", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(6, false)}, // Extra cycles
0x7f: {"BBR7", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(7, false)}, // Extra cycles
0x8f: {"BBS0", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(0, true)}, // Extra cycles
0x9f: {"BBS1", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(1, true)}, // Extra cycles
0xaf: {"BBS2", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(2, true)}, // Extra cycles
0xbf: {"BBS3", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(3, true)}, // Extra cycles
0xcf: {"BBS4", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(4, true)}, // Extra cycles
0xdf: {"BBS5", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(5, true)}, // Extra cycles
0xef: {"BBS6", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(6, true)}, // Extra cycles
0xff: {"BBS7", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(7, true)}, // Extra cycles
0x07: {"RMB0", 2, 5, modeZeroPage, buildOpSetBit(0, false)},
0x17: {"RMB1", 2, 5, modeZeroPage, buildOpSetBit(1, false)},
0x27: {"RMB2", 2, 5, modeZeroPage, buildOpSetBit(2, false)},
0x37: {"RMB3", 2, 5, modeZeroPage, buildOpSetBit(3, false)},
0x47: {"RMB4", 2, 5, modeZeroPage, buildOpSetBit(4, false)},
0x57: {"RMB5", 2, 5, modeZeroPage, buildOpSetBit(5, false)},
0x67: {"RMB6", 2, 5, modeZeroPage, buildOpSetBit(6, false)},
0x77: {"RMB7", 2, 5, modeZeroPage, buildOpSetBit(7, false)},
0x87: {"SMB0", 2, 5, modeZeroPage, buildOpSetBit(0, true)},
0x97: {"SMB1", 2, 5, modeZeroPage, buildOpSetBit(1, true)},
0xa7: {"SMB2", 2, 5, modeZeroPage, buildOpSetBit(2, true)},
0xb7: {"SMB3", 2, 5, modeZeroPage, buildOpSetBit(3, true)},
0xc7: {"SMB4", 2, 5, modeZeroPage, buildOpSetBit(4, true)},
0xd7: {"SMB5", 2, 5, modeZeroPage, buildOpSetBit(5, true)},
0xe7: {"SMB6", 2, 5, modeZeroPage, buildOpSetBit(6, true)},
0xf7: {"SMB7", 2, 5, modeZeroPage, buildOpSetBit(7, true)},
// Maybe additional Rockwell: STP, WAI
}

View File

@ -1,32 +0,0 @@
package core6502
import (
"testing"
)
func TestCMOs65c02NoUndocumented(t *testing.T) {
m := new(FlatMemory)
s := NewCMOS65c02(m)
for i := 0; i < 256; i++ {
if s.opcodes[i].cycles == 0 {
t.Errorf("Opcode missing for $%02x.", i)
}
}
}
func TestCMOS65c02asNMOS(t *testing.T) {
m := new(FlatMemory)
s := NewCMOS65c02(m)
m.loadBinary("testdata/6502_functional_test.bin")
executeSuite(t, s, 0x200, 240, false, 255)
}
func TestCMOS65c02(t *testing.T) {
m := new(FlatMemory)
s := NewCMOS65c02(m)
m.loadBinary("testdata/65C02_extended_opcodes_test.bin")
executeSuite(t, s, 0x202, 240, false, 255)
}

View File

@ -1,183 +0,0 @@
package core6502
import (
"encoding/binary"
"fmt"
"io"
)
// https://www.masswerk.at/6502/6502_instruction_set.html
// http://www.emulator101.com/reference/6502-reference.html
// https://www.csh.rit.edu/~moffitt/docs/6502.html#FLAGS
// https://ia800509.us.archive.org/18/items/Programming_the_6502/Programming_the_6502.pdf
const (
maxInstructionSize = 3
)
// State represents the state of the simulated device
type State struct {
opcodes *[256]opcode
trace bool
reg registers
mem Memory
cycles uint64
extraCycleCrossingBoundaries bool
extraCycleBranchTaken bool
extraCycleBCD bool
lineCache []uint8
// We cache the allocation of a line to avoid a malloc per instruction. To be used only
// by ExecuteInstruction(). 2x speedup on the emulation!!
}
const (
vectorNMI uint16 = 0xfffa
vectorReset uint16 = 0xfffc
vectorBreak uint16 = 0xfffe
)
type opcode struct {
name string
bytes uint16
cycles int
addressMode int
action opFunc
}
type opFunc func(s *State, line []uint8, opcode opcode)
func (s *State) executeLine(line []uint8) {
opcode := s.opcodes[line[0]]
if opcode.cycles == 0 {
panic(fmt.Sprintf("Unknown opcode 0x%02x\n", line[0]))
}
opcode.action(s, line, opcode)
}
// ExecuteInstruction transforms the state given after a single instruction is executed.
func (s *State) ExecuteInstruction() {
pc := s.reg.getPC()
opcodeID := s.mem.PeekCode(pc)
opcode := s.opcodes[opcodeID]
if opcode.cycles == 0 {
panic(fmt.Sprintf("Unknown opcode 0x%02x\n", opcodeID))
}
if s.lineCache == nil {
s.lineCache = make([]uint8, maxInstructionSize)
}
for i := uint16(0); i < opcode.bytes; i++ {
s.lineCache[i] = s.mem.PeekCode(pc)
pc++
}
s.reg.setPC(pc)
if s.trace {
//fmt.Printf("%#04x %#02x\n", pc-opcode.bytes, opcodeID)
fmt.Printf("%#04x %-13s: ", pc-opcode.bytes, lineString(s.lineCache, opcode))
}
opcode.action(s, s.lineCache, opcode)
s.cycles += uint64(opcode.cycles)
// Extra cycles
if s.extraCycleBranchTaken {
s.cycles++
s.extraCycleBranchTaken = false
}
if s.extraCycleCrossingBoundaries {
s.cycles++
s.extraCycleCrossingBoundaries = false
}
if s.extraCycleBCD {
s.cycles++
s.extraCycleBCD = false
}
if s.trace {
fmt.Printf("%v, [%02x]\n", s.reg, s.lineCache[0:opcode.bytes])
}
}
// Reset resets the processor. Moves the program counter to the vector in 0cfffc.
func (s *State) Reset() {
startAddress := getWord(s.mem, vectorReset)
s.cycles += 6
s.reg.setPC(startAddress)
}
// GetCycles returns the count of CPU cycles since last reset.
func (s *State) GetCycles() uint64 {
return s.cycles
}
// SetTrace activates tracing of the cpu execution
func (s *State) SetTrace(trace bool) {
s.trace = trace
}
// GetTrace gets trhe tracing state of the cpu execution
func (s *State) GetTrace() bool {
return s.trace
}
// SetMemory changes the memory provider
func (s *State) SetMemory(mem Memory) {
s.mem = mem
}
// GetPCAndSP returns the current program counter and stack pointer. Used to trace MLI calls
func (s *State) GetPCAndSP() (uint16, uint8) {
return s.reg.getPC(), s.reg.getSP()
}
// GetCarryAndAcc returns the value of the carry flag and the accumulator. Used to trace MLI calls
func (s *State) GetCarryAndAcc() (bool, uint8) {
return s.reg.getFlag(flagC), s.reg.getA()
}
// GetAXYP returns the value of the A, X, Y and P registers
func (s *State) GetAXYP() (uint8, uint8, uint8, uint8) {
return s.reg.getA(), s.reg.getX(), s.reg.getY(), s.reg.getP()
}
// SetAXYP changes the value of the A, X, Y and P registers
func (s *State) SetAXYP(regA uint8, regX uint8, regY uint8, regP uint8) {
s.reg.setA(regA)
s.reg.setX(regX)
s.reg.setY(regY)
s.reg.setP(regP)
}
// SetPC changes the program counter, as a JMP instruction
func (s *State) SetPC(pc uint16) {
s.reg.setPC(pc)
}
// Save saves the CPU state (registers and cycle counter)
func (s *State) Save(w io.Writer) error {
err := binary.Write(w, binary.BigEndian, s.cycles)
if err != nil {
return err
}
binary.Write(w, binary.BigEndian, s.reg.data)
if err != nil {
return err
}
return nil
}
// Load loads the CPU state (registers and cycle counter)
func (s *State) Load(r io.Reader) error {
err := binary.Read(r, binary.BigEndian, &s.cycles)
if err != nil {
return err
}
err = binary.Read(r, binary.BigEndian, &s.reg.data)
if err != nil {
return err
}
return nil
}

View File

@ -1,485 +0,0 @@
package core6502
import (
"testing"
)
func TestLoad(t *testing.T) {
s := NewNMOS6502(new(FlatMemory))
s.executeLine([]uint8{0xA9, 0x42})
if s.reg.getA() != 0x42 {
t.Error("Error in LDA #")
}
s.executeLine([]uint8{0xA9, 0x00})
if s.reg.getP() != flagZ {
t.Error("Error in flags for LDA $0")
}
s.executeLine([]uint8{0xA9, 0xF0})
if s.reg.getP() != flagN {
t.Error("Error in flags for LDA $F0")
}
s.executeLine([]uint8{0xA0, 0xFE})
if s.reg.getY() != 0xFE {
t.Error("Error in LDY #")
}
s.mem.Poke(0x38, 0x87)
s.executeLine([]uint8{0xA5, 0x38})
if s.reg.getA() != 0x87 {
t.Error("Error in LDA zpg")
}
s.mem.Poke(0x57, 0x90)
s.reg.setX(0x10)
s.executeLine([]uint8{0xB5, 0x47})
if s.reg.getA() != 0x90 {
t.Error("Error in LDA zpg, X")
}
s.mem.Poke(0x38, 0x12)
s.reg.setX(0x89)
s.executeLine([]uint8{0xB5, 0xAF})
if s.reg.getA() != 0x12 {
t.Error("Error in LDA zpgX with sero page overflow")
}
s.mem.Poke(0x1234, 0x67)
s.executeLine([]uint8{0xAD, 0x34, 0x12})
if s.reg.getA() != 0x67 {
t.Error("Error in LDA abs")
}
s.mem.Poke(0xC057, 0x7E)
s.reg.setX(0x57)
s.executeLine([]uint8{0xBD, 0x00, 0xC0})
if s.reg.getA() != 0x7E {
t.Error("Error in LDA abs, X")
}
s.mem.Poke(0xD059, 0x7A)
s.reg.setY(0x59)
s.executeLine([]uint8{0xB9, 0x00, 0xD0})
if s.reg.getA() != 0x7A {
t.Error("Error in LDA abs, Y")
}
s.mem.Poke(0x24, 0x74)
s.mem.Poke(0x25, 0x20)
s.reg.setX(0x04)
s.mem.Poke(0x2074, 0x66)
s.executeLine([]uint8{0xA1, 0x20})
if s.reg.getA() != 0x66 {
t.Error("Error in LDA (oper,X)")
}
s.mem.Poke(0x86, 0x28)
s.mem.Poke(0x87, 0x40)
s.reg.setY(0x10)
s.mem.Poke(0x4038, 0x99)
s.executeLine([]uint8{0xB1, 0x86})
if s.reg.getA() != 0x99 {
t.Error("Error in LDA (oper),Y")
}
}
func TestStore(t *testing.T) {
s := NewNMOS6502(new(FlatMemory))
s.reg.setA(0x10)
s.reg.setX(0x40)
s.reg.setY(0x80)
s.executeLine([]uint8{0x85, 0x50})
if s.mem.Peek(0x0050) != 0x10 {
t.Error("Error in STA zpg")
}
s.executeLine([]uint8{0x86, 0x51})
if s.mem.Peek(0x0051) != 0x40 {
t.Error("Error in STX zpg")
}
s.executeLine([]uint8{0x84, 0x52})
if s.mem.Peek(0x0052) != 0x80 {
t.Error("Error in STY zpg")
}
s.executeLine([]uint8{0x8D, 0x20, 0xC0})
if s.mem.Peek(0xC020) != 0x10 {
t.Error("Error in STA abs")
}
s.executeLine([]uint8{0x9D, 0x08, 0x10})
if s.mem.Peek(0x1048) != 0x10 {
t.Error("Error in STA abs, X")
}
}
func TestTransfer(t *testing.T) {
s := NewNMOS6502(new(FlatMemory))
s.reg.setA(0xB0)
s.executeLine([]uint8{0xAA})
if s.reg.getX() != 0xB0 {
t.Error("Error in TAX")
}
if s.reg.getP() != flagN {
t.Error("Error in TAX flags")
}
s.reg.setA(0xB1)
s.executeLine([]uint8{0xA8})
if s.reg.getY() != 0xB1 {
t.Error("Error in TAY")
}
s.reg.setSP(0xB2)
s.executeLine([]uint8{0xBA})
if s.reg.getX() != 0xB2 {
t.Error("Error in TSX")
}
s.reg.setX(0xB3)
s.executeLine([]uint8{0x8A})
if s.reg.getA() != 0xB3 {
t.Error("Error in TXA")
}
s.reg.setX(0xB4)
s.executeLine([]uint8{0x9A})
if s.reg.getSP() != 0xB4 {
t.Error("Error in TXS")
}
s.reg.setY(0xB5)
s.executeLine([]uint8{0x98})
if s.reg.getA() != 0xB5 {
t.Error("Error in TYA")
}
}
func TestIncDec(t *testing.T) {
s := NewNMOS6502(new(FlatMemory))
s.reg.setX(0x7E)
s.executeLine([]uint8{0xE8})
if s.reg.getX() != 0x7F {
t.Errorf("Error in INX")
}
s.reg.setY(0xFC)
s.executeLine([]uint8{0x88})
if s.reg.getY() != 0xFB {
t.Error("Error in DEY")
}
if s.reg.getP() != flagN {
t.Error("Error in DEY flags")
}
}
func TestShiftRotate(t *testing.T) {
s := NewNMOS6502(new(FlatMemory))
s.reg.setA(0xF0)
s.executeLine([]uint8{0x2A})
if s.reg.getA() != 0xE0 {
t.Errorf("Error in ROL")
}
if !s.reg.getFlag(flagC) {
t.Errorf("Error in ROL carry. %v", s.reg)
}
s.reg.setFlag(flagC)
s.reg.setA(0x0F)
s.executeLine([]uint8{0x6A})
if s.reg.getA() != 0x87 {
t.Errorf("Error in ROR. %v", s.reg)
}
if !s.reg.getFlag(flagC) {
t.Errorf("Error in ROR carry")
}
s.reg.setFlag(flagC)
s.reg.setA(0x81)
s.executeLine([]uint8{0x0A})
if s.reg.getA() != 0x02 {
t.Errorf("Error in ASL. %v", s.reg)
}
if !s.reg.getFlag(flagC) {
t.Errorf("Error in ASL carry")
}
s.reg.setFlag(flagC)
s.reg.setA(0x02)
s.executeLine([]uint8{0x4A})
if s.reg.getA() != 0x01 {
t.Errorf("Error in LSR. %v", s.reg)
}
if s.reg.getFlag(flagC) {
t.Errorf("Error in LSR carry")
}
}
func TestClearSetFlag(t *testing.T) {
s := NewNMOS6502(new(FlatMemory))
s.reg.setP(0x00)
s.executeLine([]uint8{0xF8})
if !s.reg.getFlag(flagD) {
t.Errorf("Error in SED. %v", s.reg)
}
s.executeLine([]uint8{0xD8})
if s.reg.getFlag(flagD) {
t.Errorf("Error in CLD. %v", s.reg)
}
}
func TestLogic(t *testing.T) {
s := NewNMOS6502(new(FlatMemory))
s.reg.setA(0xF0)
s.executeLine([]uint8{0x29, 0x1C})
if s.reg.getA() != 0x10 {
t.Errorf("Error in AND <. %v", s.reg)
}
s.reg.setA(0xF0)
s.executeLine([]uint8{0x49, 0x1C})
if s.reg.getA() != 0xEC {
t.Errorf("Error in EOR <. %v", s.reg)
}
s.reg.setA(0xF0)
s.executeLine([]uint8{0x09, 0x0C})
if s.reg.getA() != 0xFC {
t.Errorf("Error in ORA <. %v", s.reg)
}
}
func TestAdd(t *testing.T) {
s := NewNMOS6502(new(FlatMemory))
s.reg.setA(0xA0)
s.reg.clearFlag(flagC)
s.executeLine([]uint8{0x69, 0x0B})
if s.reg.getA() != 0xAB {
t.Errorf("Error in ADC. %v", s.reg)
}
if s.reg.getFlag(flagC) {
t.Errorf("Error in carry ADC. %v", s.reg)
}
s.reg.setA(0xFF)
s.reg.clearFlag(flagC)
s.executeLine([]uint8{0x69, 0x02})
if s.reg.getA() != 0x01 {
t.Errorf("Error in ADC with carry. %v", s.reg)
}
if !s.reg.getFlag(flagC) {
t.Errorf("Error in carry ADC with carry. %v", s.reg)
}
s.reg.setA(0xA0)
s.reg.setFlag(flagC)
s.executeLine([]uint8{0x69, 0x01})
if s.reg.getA() != 0xA2 {
t.Errorf("Error in carried ADC with carry. %v", s.reg)
}
if s.reg.getFlag(flagC) {
t.Errorf("Error in carry in carried ADC with carry. %v", s.reg)
}
}
func TestAddDecimal(t *testing.T) {
s := NewNMOS6502(new(FlatMemory))
s.reg.setFlag(flagD)
s.reg.setA(0x12)
s.reg.clearFlag(flagC)
s.executeLine([]uint8{0x69, 0x013})
if s.reg.getA() != 0x25 {
t.Errorf("Error in ADC decimal. %v", s.reg)
}
if s.reg.getFlag(flagC) {
t.Errorf("Error in carry ADC. %v", s.reg)
}
s.reg.setA(0x44)
s.reg.clearFlag(flagC)
s.executeLine([]uint8{0x69, 0x68})
if s.reg.getA() != 0x12 {
t.Errorf("Error in ADC decimal with carry. %v", s.reg)
}
if !s.reg.getFlag(flagC) {
t.Errorf("Error in carry ADC decimal with carry. %v", s.reg)
}
s.reg.setA(0x44)
s.reg.setFlag(flagC)
s.executeLine([]uint8{0x69, 0x23})
if s.reg.getA() != 0x68 {
t.Errorf("Error in carried ADC decimal with carry. %v", s.reg)
}
if s.reg.getFlag(flagC) {
t.Errorf("Error in carry in carried ADC decimal with carry. %v", s.reg)
}
}
func TestSub(t *testing.T) {
s := NewNMOS6502(new(FlatMemory))
s.reg.setA(0x09)
s.reg.clearFlag(flagC)
s.executeLine([]uint8{0xE9, 0x05})
if s.reg.getA() != 0x03 {
t.Errorf("Error in SBC. %v", s.reg)
}
if !s.reg.getFlag(flagC) {
t.Errorf("Error in carry SBC. %v", s.reg)
}
s.reg.setA(0x01)
s.reg.clearFlag(flagC)
s.executeLine([]uint8{0xE9, 0x02})
if s.reg.getA() != 0xFE {
t.Errorf("Error in SBC with carry. %v", s.reg)
}
if s.reg.getFlag(flagC) {
t.Errorf("Error in carry SBC with carry. %v", s.reg)
}
s.reg.setA(0x08)
s.reg.setFlag(flagC)
s.executeLine([]uint8{0xE9, 0x02})
if s.reg.getA() != 0x06 {
t.Errorf("Error in carried SBC with carry. %v", s.reg)
}
if !s.reg.getFlag(flagC) {
t.Errorf("Error in carry in carried SBC with carry. %v", s.reg)
}
}
func TestCompare(t *testing.T) {
s := NewNMOS6502(new(FlatMemory))
s.reg.setA(0x02)
s.executeLine([]uint8{0xC9, 0x01})
if s.reg.getP() != 0x01 {
t.Errorf("Error in CMP <. %v", s.reg)
}
s.executeLine([]uint8{0xC9, 0x02})
if s.reg.getP() != 0x03 {
t.Errorf("Error in CMP =. %v", s.reg)
}
s.executeLine([]uint8{0xC9, 0x03})
if s.reg.getP() != 0x80 {
t.Errorf("Error in CMP >. %v", s.reg)
}
s.reg.setX(0x04)
s.executeLine([]uint8{0xE0, 0x05})
if s.reg.getP() != 0x80 {
t.Errorf("Error in CPX >. %v", s.reg)
}
s.reg.setY(0x08)
s.executeLine([]uint8{0xC0, 0x09})
if s.reg.getP() != 0x80 {
t.Errorf("Error in CPY >. %v", s.reg)
}
}
func TestBit(t *testing.T) {
s := NewNMOS6502(new(FlatMemory))
s.reg.setA(0x0F)
s.mem.Poke(0x0040, 0xF0)
s.executeLine([]uint8{0x24, 0x40})
if s.reg.getP() != 0xC2 {
t.Errorf("Error in BIT. %v", s.reg)
}
s.reg.setA(0xF0)
s.mem.Poke(0x0040, 0xF0)
s.executeLine([]uint8{0x24, 0x40})
if s.reg.getP() != 0xC0 {
t.Errorf("Error in BIT, 2. %v", s.reg)
}
s.reg.setA(0xF0)
s.mem.Poke(0x01240, 0x80)
s.executeLine([]uint8{0x2C, 0x40, 0x12})
if s.reg.getP() != 0x80 {
t.Errorf("Error in BIT, 2. %v", s.reg)
}
}
func TestBranch(t *testing.T) {
s := NewNMOS6502(new(FlatMemory))
s.reg.setPC(0xC600)
s.reg.setFlag(flagV)
s.executeLine([]uint8{0x50, 0x20})
if s.reg.getPC() != 0xC600 {
t.Errorf("Error in BVC, %v", s.reg)
}
s.executeLine([]uint8{0x70, 0x20})
if s.reg.getPC() != 0xC620 {
t.Errorf("Error in BVS, %v", s.reg)
}
s.reg.setPC(0xD600)
s.reg.clearFlag(flagC)
s.executeLine([]uint8{0x90, 0xA0})
if s.reg.getPC() != 0xD5A0 {
t.Errorf("Error in BCC, %v", s.reg)
}
}
func TestStack(t *testing.T) {
s := NewNMOS6502(new(FlatMemory))
s.reg.setSP(0xF0)
s.reg.setA(0xA0)
s.reg.setP(0x0A)
s.executeLine([]uint8{0x48})
if s.reg.getSP() != 0xEF {
t.Errorf("Error in PHA stack pointer, %v", s.reg)
}
if s.mem.Peek(0x01F0) != 0xA0 {
t.Errorf("Error in PHA, %v", s.reg)
}
s.executeLine([]uint8{0x08})
if s.reg.getSP() != 0xEE {
t.Errorf("Error in PHP stack pointer, %v", s.reg)
}
if s.mem.Peek(0x01EF) != 0x3A {
t.Errorf("Error in PHP, %v", s.reg)
}
s.executeLine([]uint8{0x68})
if s.reg.getSP() != 0xEF {
t.Errorf("Error in PLA stack pointer, %v", s.reg)
}
if s.reg.getA() != 0x3A {
t.Errorf("Error in PLA, %v", s.reg)
}
s.executeLine([]uint8{0x28})
if s.reg.getSP() != 0xF0 {
t.Errorf("Error in PLP stack pointer, %v", s.reg)
}
if s.reg.getP() != 0xA0 {
t.Errorf("Error in PLP, %v", s.reg)
}
}

View File

@ -1,168 +0,0 @@
package core6502
/*
Tests from https://github.com/TomHarte/ProcessorTests
Know issues:
- Test 6502/v1/20_55_13 (Note 1)
- Not implemented undocumented opcodes for NMOS (Note 2)
- Errors on flag N for ADC in BCD mode (Note 3)
The tests are disabled by defaut because they take long to run
and require a huge download.
To enable them, clone the repo https://github.com/TomHarte/ProcessorTests
and change the variables ProcessorTestsEnable and ProcessorTestsPath.
*/
import (
"encoding/json"
"fmt"
"io/ioutil"
"testing"
)
var ProcessorTestsEnable = false
var ProcessorTestsPath = "/home/casa/code/ProcessorTests/"
type scenarioState struct {
Pc uint16
S uint8
A uint8
X uint8
Y uint8
P uint8
Ram [][]uint16
}
type scenario struct {
Name string
Initial scenarioState
Final scenarioState
Cycles [][]interface{}
}
func TestHarteNMOS6502(t *testing.T) {
if !ProcessorTestsEnable {
t.Skip("TomHarte/ProcessorTests are not enabled")
}
s := NewNMOS6502(nil) // Use to get the opcodes names
path := ProcessorTestsPath + "6502/v1/"
for i := 0x00; i <= 0xff; i++ {
mnemonic := s.opcodes[i].name
if mnemonic != "" { // Note 2
opcode := fmt.Sprintf("%02x", i)
t.Run(opcode+mnemonic, func(t *testing.T) {
t.Parallel()
m := new(FlatMemory)
s := NewNMOS6502(m)
testOpcode(t, s, path, opcode, mnemonic)
})
//} else {
// opcode := fmt.Sprintf("%02x", i)
// t.Run(opcode+mnemonic, func(t *testing.T) {
// t.Error("Opcode not implemented")
// })
}
}
}
func TestHarteCMOS65c02(t *testing.T) {
if !ProcessorTestsEnable {
t.Skip("TomHarte/ProcessorTests are not enabled")
}
s := NewCMOS65c02(nil) // Use to get the opcodes names
path := ProcessorTestsPath + "wdc65c02/v1/"
for i := 0x00; i <= 0xff; i++ {
mnemonic := s.opcodes[i].name
opcode := fmt.Sprintf("%02x", i)
t.Run(opcode+mnemonic, func(t *testing.T) {
t.Parallel()
m := new(FlatMemory)
s := NewCMOS65c02(m)
testOpcode(t, s, path, opcode, mnemonic)
})
}
}
func testOpcode(t *testing.T, s *State, path string, opcode string, mnemonic string) {
data, err := ioutil.ReadFile(path + opcode + ".json")
if err != nil {
t.Fatal(err)
}
if len(data) == 0 {
return
}
var scenarios []scenario
err = json.Unmarshal(data, &scenarios)
if err != nil {
t.Fatal(err)
}
for _, scenario := range scenarios {
if scenario.Name != "20 55 13" { // Note 1
t.Run(scenario.Name, func(t *testing.T) {
testScenario(t, s, &scenario, mnemonic)
})
}
}
}
func testScenario(t *testing.T, s *State, sc *scenario, mnemonic string) {
// Setup CPU
start := s.GetCycles()
s.reg.setPC(sc.Initial.Pc)
s.reg.setSP(sc.Initial.S)
s.reg.setA(sc.Initial.A)
s.reg.setX(sc.Initial.X)
s.reg.setY(sc.Initial.Y)
s.reg.setP(sc.Initial.P)
for _, e := range sc.Initial.Ram {
s.mem.Poke(uint16(e[0]), uint8(e[1]))
}
// Execute instruction
s.ExecuteInstruction()
// Check result
assertReg8(t, sc, "A", s.reg.getA(), sc.Final.A)
assertReg8(t, sc, "X", s.reg.getX(), sc.Final.X)
assertReg8(t, sc, "Y", s.reg.getY(), sc.Final.Y)
if s.reg.getFlag(flagD) && (mnemonic == "ADC") {
// Note 3
assertFlags(t, sc, sc.Initial.P, s.reg.getP()&0x7f, sc.Final.P&0x7f)
} else {
assertFlags(t, sc, sc.Initial.P, s.reg.getP(), sc.Final.P)
}
assertReg8(t, sc, "SP", s.reg.getSP(), sc.Final.S)
assertReg16(t, sc, "PC", s.reg.getPC(), sc.Final.Pc)
cycles := s.GetCycles() - start
if cycles != uint64(len(sc.Cycles)) {
t.Errorf("Took %v cycles, it should be %v for %+v", cycles, len(sc.Cycles), sc)
}
}
func assertReg8(t *testing.T, sc *scenario, name string, actual uint8, wanted uint8) {
if actual != wanted {
t.Errorf("Register %s is $%02x and should be $%02x for %+v", name, actual, wanted, sc)
}
}
func assertReg16(t *testing.T, sc *scenario, name string, actual uint16, wanted uint16) {
if actual != wanted {
t.Errorf("Register %s is $%04x and should be $%04x for %+v", name, actual, wanted, sc)
}
}
func assertFlags(t *testing.T, sc *scenario, initial uint8, actual uint8, wanted uint8) {
if actual != wanted {
t.Errorf("%08b flag diffs, they are %08b and should be %08b, initial %08b for %+v", actual^wanted, actual, wanted, initial, sc)
}
}

View File

@ -1,63 +0,0 @@
package core6502
import "io/ioutil"
// Memory represents the addressable space of the processor
type Memory interface {
Peek(address uint16) uint8
Poke(address uint16, value uint8)
// PeekCode can bu used to optimize the memory manager to requests with more
// locality. It must return the same as a call to Peek()
PeekCode(address uint16) uint8
}
func getWord(m Memory, address uint16) uint16 {
return uint16(m.Peek(address)) + 0x100*uint16(m.Peek(address+1))
}
func getWordNoCrossPage(m Memory, address uint16) uint16 {
addressMSB := address + 1
if address&0xff == 0xff {
// We won't cross the page bounday for the MSB byte
addressMSB -= 0x100
}
return uint16(m.Peek(address)) + 0x100*uint16(m.Peek(addressMSB))
}
func getZeroPageWord(m Memory, address uint8) uint16 {
return uint16(m.Peek(uint16(address))) + 0x100*uint16(m.Peek(uint16(address+1)))
}
// FlatMemory puts RAM on the 64Kb addressable by the processor
type FlatMemory struct {
data [65536]uint8
}
// Peek returns the data on the given address
func (m *FlatMemory) Peek(address uint16) uint8 {
return m.data[address]
}
// PeekCode returns the data on the given address
func (m *FlatMemory) PeekCode(address uint16) uint8 {
return m.data[address]
}
// Poke sets the data at the given address
func (m *FlatMemory) Poke(address uint16, value uint8) {
m.data[address] = value
}
func (m *FlatMemory) loadBinary(filename string) error {
bytes, err := ioutil.ReadFile(filename)
if err != nil {
return err
}
for i, v := range bytes {
m.Poke(uint16(i), uint8(v))
}
return nil
}

View File

@ -1,232 +0,0 @@
package core6502
// NewNMOS6502 returns an initialized NMOS6502
func NewNMOS6502(m Memory) *State {
var s State
s.mem = m
s.opcodes = &opcodesNMOS6502
return &s
}
var opcodesNMOS6502 = [256]opcode{
0x00: {"BRK", 1, 7, modeImplicit, opBRK},
0x4C: {"JMP", 3, 3, modeAbsolute, opJMP},
0x6C: {"JMP", 3, 5, modeIndirect, opJMP},
0x20: {"JSR", 3, 6, modeAbsolute, opJSR},
0x40: {"RTI", 1, 6, modeImplicit, opRTI},
0x60: {"RTS", 1, 6, modeImplicit, opRTS},
0x48: {"PHA", 1, 3, modeImplicit, buildOpPush(regA)},
0x08: {"PHP", 1, 3, modeImplicit, buildOpPush(regP)},
0x68: {"PLA", 1, 4, modeImplicit, buildOpPull(regA)},
0x28: {"PLP", 1, 4, modeImplicit, buildOpPull(regP)},
0x09: {"ORA", 2, 2, modeImmediate, buildOpLogic(operationOr)},
0x05: {"ORA", 2, 3, modeZeroPage, buildOpLogic(operationOr)},
0x15: {"ORA", 2, 4, modeZeroPageX, buildOpLogic(operationOr)},