Extract the 6502 emulation to a separate module
This commit is contained in:
parent
765b1fba28
commit
2ae91f8585
|
@ -4,13 +4,13 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ivanizag/izapple2/core6502"
|
"github.com/ivanizag/iz6502"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Apple2 represents all the components and state of the emulated machine
|
// Apple2 represents all the components and state of the emulated machine
|
||||||
type Apple2 struct {
|
type Apple2 struct {
|
||||||
Name string
|
Name string
|
||||||
cpu *core6502.State
|
cpu *iz6502.State
|
||||||
mmu *memoryManager
|
mmu *memoryManager
|
||||||
io *ioC0Page
|
io *ioC0Page
|
||||||
cg *CharacterGenerator
|
cg *CharacterGenerator
|
||||||
|
|
|
@ -3,7 +3,7 @@ package izapple2
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
|
||||||
"github.com/ivanizag/izapple2/core6502"
|
"github.com/ivanizag/iz6502"
|
||||||
"github.com/ivanizag/izapple2/storage"
|
"github.com/ivanizag/izapple2/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,14 +30,14 @@ func (a *Apple2) setup(clockMhz float64, fastMode bool) {
|
||||||
|
|
||||||
func setApple2plus(a *Apple2) {
|
func setApple2plus(a *Apple2) {
|
||||||
a.Name = "Apple ][+"
|
a.Name = "Apple ][+"
|
||||||
a.cpu = core6502.NewNMOS6502(a.mmu)
|
a.cpu = iz6502.NewNMOS6502(a.mmu)
|
||||||
addApple2SoftSwitches(a.io)
|
addApple2SoftSwitches(a.io)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setApple2e(a *Apple2) {
|
func setApple2e(a *Apple2) {
|
||||||
a.Name = "Apple IIe"
|
a.Name = "Apple IIe"
|
||||||
a.isApple2e = true
|
a.isApple2e = true
|
||||||
a.cpu = core6502.NewNMOS6502(a.mmu)
|
a.cpu = iz6502.NewNMOS6502(a.mmu)
|
||||||
a.mmu.initExtendedRAM(1)
|
a.mmu.initExtendedRAM(1)
|
||||||
addApple2SoftSwitches(a.io)
|
addApple2SoftSwitches(a.io)
|
||||||
addApple2ESoftSwitches(a.io)
|
addApple2ESoftSwitches(a.io)
|
||||||
|
@ -46,7 +46,7 @@ func setApple2e(a *Apple2) {
|
||||||
func setApple2eEnhanced(a *Apple2) {
|
func setApple2eEnhanced(a *Apple2) {
|
||||||
a.Name = "Apple //e"
|
a.Name = "Apple //e"
|
||||||
a.isApple2e = true
|
a.isApple2e = true
|
||||||
a.cpu = core6502.NewCMOS65c02(a.mmu)
|
a.cpu = iz6502.NewCMOS65c02(a.mmu)
|
||||||
a.mmu.initExtendedRAM(1)
|
a.mmu.initExtendedRAM(1)
|
||||||
addApple2SoftSwitches(a.io)
|
addApple2SoftSwitches(a.io)
|
||||||
addApple2ESoftSwitches(a.io)
|
addApple2ESoftSwitches(a.io)
|
||||||
|
|
|
@ -3,7 +3,7 @@ package izapple2
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ivanizag/izapple2/core6502"
|
"github.com/ivanizag/iz6502"
|
||||||
"github.com/ivanizag/izapple2/storage"
|
"github.com/ivanizag/izapple2/storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -13,7 +13,7 @@ import (
|
||||||
|
|
||||||
func setBase64a(a *Apple2) {
|
func setBase64a(a *Apple2) {
|
||||||
a.Name = "Base 64A"
|
a.Name = "Base 64A"
|
||||||
a.cpu = core6502.NewNMOS6502(a.mmu)
|
a.cpu = iz6502.NewNMOS6502(a.mmu)
|
||||||
addApple2SoftSwitches(a.io)
|
addApple2SoftSwitches(a.io)
|
||||||
addBase64aSoftSwitches(a.io)
|
addBase64aSoftSwitches(a.io)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)},
|
|
||||||
0x0D: {"ORA", 3, 4, modeAbsolute, buildOpLogic(operationOr)},
|
|
||||||
0x1D: {"ORA", 3, 4, modeAbsoluteX, buildOpLogic(operationOr)}, // Extra cycles
|
|
||||||
0x19: {"ORA", 3, 4, modeAbsoluteY, buildOpLogic(operationOr)}, // Extra cycles
|
|
||||||
0x01: {"ORA", 2, 6, modeIndexedIndirectX, buildOpLogic(operationOr)},
|
|
||||||
0x11: {"ORA", 2, 5, modeIndirectIndexedY, buildOpLogic(operationOr)}, // Extra cycles
|
|
||||||
|
|
||||||
0x29: {"AND", 2, 2, modeImmediate, buildOpLogic(operationAnd)},
|
|
||||||
0x25: {"AND", 2, 3, modeZeroPage, buildOpLogic(operationAnd)},
|
|
||||||
0x35: {"AND", 2, 4, modeZeroPageX, buildOpLogic(operationAnd)},
|
|
||||||
0x2D: {"AND", 3, 4, modeAbsolute, buildOpLogic(operationAnd)},
|
|
||||||
0x3D: {"AND", 3, 4, modeAbsoluteX, buildOpLogic(operationAnd)}, // Extra cycles
|
|
||||||
0x39: {"AND", 3, 4, modeAbsoluteY, buildOpLogic(operationAnd)}, // Extra cycles
|
|
||||||
0x21: {"AND", 2, 6, modeIndexedIndirectX, buildOpLogic(operationAnd)},
|
|
||||||
0x31: {"AND", 2, 5, modeIndirectIndexedY, buildOpLogic(operationAnd)}, // Extra cycles
|
|
||||||
|
|
||||||
0x49: {"EOR", 2, 2, modeImmediate, buildOpLogic(operationXor)},
|
|
||||||
0x45: {"EOR", 2, 3, modeZeroPage, buildOpLogic(operationXor)},
|
|
||||||
0x55: {"EOR", 2, 4, modeZeroPageX, buildOpLogic(operationXor)},
|
|
||||||
0x4D: {"EOR", 3, 4, modeAbsolute, buildOpLogic(operationXor)},
|
|
||||||
0x5D: {"EOR", 3, 4, modeAbsoluteX, buildOpLogic(operationXor)}, // Extra cycles
|
|
||||||
0x59: {"EOR", 3, 4, modeAbsoluteY, buildOpLogic(operationXor)}, // Extra cycles
|
|
||||||
0x41: {"EOR", 2, 6, modeIndexedIndirectX, buildOpLogic(operationXor)},
|
|
||||||
0x51: {"EOR", 2, 5, modeIndirectIndexedY, buildOpLogic(operationXor)}, // Extra cycles
|
|
||||||
|
|
||||||
0x69: {"ADC", 2, 2, modeImmediate, opADC},
|
|
||||||
0x65: {"ADC", 2, 3, modeZeroPage, opADC},
|
|
||||||
0x75: {"ADC", 2, 4, modeZeroPageX, opADC},
|
|
||||||
0x6D: {"ADC", 3, 4, modeAbsolute, opADC},
|
|
||||||
0x7D: {"ADC", 3, 4, modeAbsoluteX, opADC}, // Extra cycles
|
|
||||||
0x79: {"ADC", 3, 4, modeAbsoluteY, opADC}, // Extra cycles
|
|
||||||
0x61: {"ADC", 2, 6, modeIndexedIndirectX, opADC},
|
|
||||||
0x71: {"ADC", 2, 5, modeIndirectIndexedY, opADC}, // Extra cycles
|
|
||||||
|
|
||||||
0xE9: {"SBC", 2, 2, modeImmediate, opSBC},
|
|
||||||
0xE5: {"SBC", 2, 3, modeZeroPage, opSBC},
|
|
||||||
0xF5: {"SBC", 2, 4, modeZeroPageX, opSBC},
|
|
||||||
0xED: {"SBC", 3, 4, modeAbsolute, opSBC},
|
|
||||||
0xFD: {"SBC", 3, 4, modeAbsoluteX, opSBC}, // Extra cycles
|
|
||||||
0xF9: {"SBC", 3, 4, modeAbsoluteY, opSBC}, // Extra cycles
|
|
||||||
0xE1: {"SBC", 2, 6, modeIndexedIndirectX, opSBC},
|
|
||||||
0xF1: {"SBC", 2, 5, modeIndirectIndexedY, opSBC}, // Extra cycles
|
|
||||||
|
|
||||||
0x24: {"BIT", 2, 3, modeZeroPage, opBIT},
|
|
||||||
0x2C: {"BIT", 3, 4, modeAbsolute, opBIT},
|
|
||||||
|
|
||||||
0xC9: {"CMP", 2, 2, modeImmediate, buildOpCompare(regA)},
|
|
||||||
0xC5: {"CMP", 2, 3, modeZeroPage, buildOpCompare(regA)},
|
|
||||||
0xD5: {"CMP", 2, 4, modeZeroPageX, buildOpCompare(regA)},
|
|
||||||
0xCD: {"CMP", 3, 4, modeAbsolute, buildOpCompare(regA)},
|
|
||||||
0xDD: {"CMP", 3, 4, modeAbsoluteX, buildOpCompare(regA)}, // Extra cycles
|
|
||||||
0xD9: {"CMP", 3, 4, modeAbsoluteY, buildOpCompare(regA)}, // Extra cycles
|
|
||||||
0xC1: {"CMP", 2, 6, modeIndexedIndirectX, buildOpCompare(regA)},
|
|
||||||
0xD1: {"CMP", 2, 5, modeIndirectIndexedY, buildOpCompare(regA)}, // Extra cycles
|
|
||||||
|
|
||||||
0xE0: {"CPX", 2, 2, modeImmediate, buildOpCompare(regX)},
|
|
||||||
0xE4: {"CPX", 2, 3, modeZeroPage, buildOpCompare(regX)},
|
|
||||||
0xEC: {"CPX", 3, 4, modeAbsolute, buildOpCompare(regX)},
|
|
||||||
|
|
||||||
0xC0: {"CPY", 2, 2, modeImmediate, buildOpCompare(regY)},
|
|
||||||
0xC4: {"CPY", 2, 3, modeZeroPage, buildOpCompare(regY)},
|
|
||||||
0xCC: {"CPY", 3, 4, modeAbsolute, buildOpCompare(regY)},
|
|
||||||
|
|
||||||
0x2A: {"ROL", 1, 2, modeAccumulator, buildOpShift(true, true)},
|
|
||||||
0x26: {"ROL", 2, 5, modeZeroPage, buildOpShift(true, true)},
|
|
||||||
0x36: {"ROL", 2, 6, modeZeroPageX, buildOpShift(true, true)},
|
|
||||||
0x2E: {"ROL", 3, 6, modeAbsolute, buildOpShift(true, true)},
|
|
||||||
0x3E: {"ROL", 3, 7, modeAbsoluteX, buildOpShift(true, true)},
|
|
||||||
|
|
||||||
0x6A: {"ROR", 1, 2, modeAccumulator, buildOpShift(false, true)},
|
|
||||||
0x66: {"ROR", 2, 5, modeZeroPage, buildOpShift(false, true)},
|
|
||||||
0x76: {"ROR", 2, 6, modeZeroPageX, buildOpShift(false, true)},
|
|
||||||
0x6E: {"ROR", 3, 6, modeAbsolute, buildOpShift(false, true)},
|
|
||||||
0x7E: {"ROR", 3, 7, modeAbsoluteX, buildOpShift(false, true)},
|
|
||||||
|
|
||||||
0x0A: {"ASL", 1, 2, modeAccumulator, buildOpShift(true, false)},
|
|
||||||
0x06: {"ASL", 2, 5, modeZeroPage, buildOpShift(true, false)},
|
|
||||||
0x16: {"ASL", 2, 6, modeZeroPageX, buildOpShift(true, false)},
|
|
||||||
0x0E: {"ASL", 3, 6, modeAbsolute, buildOpShift(true, false)},
|
|
||||||
0x1E: {"ASL", 3, 7, modeAbsoluteX, buildOpShift(true, false)},
|
|
||||||
|
|
||||||
0x4A: {"LSR", 1, 2, modeAccumulator, buildOpShift(false, false)},
|
|
||||||
0x46: {"LSR", 2, 5, modeZeroPage, buildOpShift(false, false)},
|
|
||||||
0x56: {"LSR", 2, 6, modeZeroPageX, buildOpShift(false, false)},
|
|
||||||
0x4E: {"LSR", 3, 6, modeAbsolute, buildOpShift(false, false)},
|
|
||||||
0x5E: {"LSR", 3, 7, modeAbsoluteX, buildOpShift(false, false)},
|
|
||||||
|
|
||||||
0x38: {"SEC", 1, 2, modeImplicit, buildOpUpdateFlag(flagC, true)},
|
|
||||||
0xF8: {"SED", 1, 2, modeImplicit, buildOpUpdateFlag(flagD, true)},
|
|
||||||
0x78: {"SEI", 1, 2, modeImplicit, buildOpUpdateFlag(flagI, true)},
|
|
||||||
0x18: {"CLC", 1, 2, modeImplicit, buildOpUpdateFlag(flagC, false)},
|
|
||||||
0xD8: {"CLD", 1, 2, modeImplicit, buildOpUpdateFlag(flagD, false)},
|
|
||||||
0x58: {"CLI", 1, 2, modeImplicit, buildOpUpdateFlag(flagI, false)},
|
|
||||||
0xB8: {"CLV", 1, 2, modeImplicit, buildOpUpdateFlag(flagV, false)},
|
|
||||||
|
|
||||||
0xE6: {"INC", 2, 5, modeZeroPage, buildOpIncDec(true)},
|
|
||||||
0xF6: {"INC", 2, 6, modeZeroPageX, buildOpIncDec(true)},
|
|
||||||
0xEE: {"INC", 3, 6, modeAbsolute, buildOpIncDec(true)},
|
|
||||||
0xFE: {"INC", 3, 7, modeAbsoluteX, buildOpIncDec(true)},
|
|
||||||
0xC6: {"DEC", 2, 5, modeZeroPage, buildOpIncDec(false)},
|
|
||||||
0xD6: {"DEC", 2, 6, modeZeroPageX, buildOpIncDec(false)},
|
|
||||||
0xCE: {"DEC", 3, 6, modeAbsolute, buildOpIncDec(false)},
|
|
||||||
0xDE: {"DEC", 3, 7, modeAbsoluteX, buildOpIncDec(false)},
|
|
||||||
0xE8: {"INX", 1, 2, modeImplicitX, buildOpIncDec(true)},
|
|
||||||
0xC8: {"INY", 1, 2, modeImplicitY, buildOpIncDec(true)},
|
|
||||||
0xCA: {"DEX", 1, 2, modeImplicitX, buildOpIncDec(false)},
|
|
||||||
0x88: {"DEY", 1, 2, modeImplicitY, buildOpIncDec(false)},
|
|
||||||
|
|
||||||
0xAA: {"TAX", 1, 2, modeImplicit, buildOpTransfer(regA, regX)},
|
|
||||||
0xA8: {"TAY", 1, 2, modeImplicit, buildOpTransfer(regA, regY)},
|
|
||||||
0x8A: {"TXA", 1, 2, modeImplicit, buildOpTransfer(regX, regA)},
|
|
||||||
0x98: {"TYA", 1, 2, modeImplicit, buildOpTransfer(regY, regA)},
|
|
||||||
0x9A: {"TXS", 1, 2, modeImplicit, buildOpTransfer(regX, regSP)},
|
|
||||||
0xBA: {"TSX", 1, 2, modeImplicit, buildOpTransfer(regSP, regX)},
|
|
||||||
|
|
||||||
0xA9: {"LDA", 2, 2, modeImmediate, buildOpLoad(regA)},
|
|
||||||
0xA5: {"LDA", 2, 3, modeZeroPage, buildOpLoad(regA)},
|
|
||||||
0xB5: {"LDA", 2, 4, modeZeroPageX, buildOpLoad(regA)},
|
|
||||||
0xAD: {"LDA", 3, 4, modeAbsolute, buildOpLoad(regA)},
|
|
||||||
0xBD: {"LDA", 3, 4, modeAbsoluteX, buildOpLoad(regA)}, // Extra cycles
|
|
||||||
0xB9: {"LDA", 3, 4, modeAbsoluteY, buildOpLoad(regA)}, // Extra cycles
|
|
||||||
0xA1: {"LDA", 2, 6, modeIndexedIndirectX, buildOpLoad(regA)},
|
|
||||||
0xB1: {"LDA", 2, 5, modeIndirectIndexedY, buildOpLoad(regA)}, // Extra cycles
|
|
||||||
0xA2: {"LDX", 2, 2, modeImmediate, buildOpLoad(regX)},
|
|
||||||
0xA6: {"LDX", 2, 3, modeZeroPage, buildOpLoad(regX)},
|
|
||||||
0xB6: {"LDX", 2, 4, modeZeroPageY, buildOpLoad(regX)},
|
|
||||||
0xAE: {"LDX", 3, 4, modeAbsolute, buildOpLoad(regX)},
|
|
||||||
0xBE: {"LDX", 3, 4, modeAbsoluteY, buildOpLoad(regX)}, // Extra cycles
|
|
||||||
0xA0: {"LDY", 2, 2, modeImmediate, buildOpLoad(regY)},
|
|
||||||
0xA4: {"LDY", 2, 3, modeZeroPage, buildOpLoad(regY)},
|
|
||||||
0xB4: {"LDY", 2, 4, modeZeroPageX, buildOpLoad(regY)},
|
|
||||||
0xAC: {"LDY", 3, 4, modeAbsolute, buildOpLoad(regY)},
|
|
||||||
0xBC: {"LDY", 3, 4, modeAbsoluteX, buildOpLoad(regY)}, // Extra cycles
|
|
||||||
|
|
||||||
0x85: {"STA", 2, 3, modeZeroPage, buildOpStore(regA)},
|
|
||||||
0x95: {"STA", 2, 4, modeZeroPageX, buildOpStore(regA)},
|
|
||||||
0x8D: {"STA", 3, 4, modeAbsolute, buildOpStore(regA)},
|
|
||||||
0x9D: {"STA", 3, 5, modeAbsoluteX, buildOpStore(regA)},
|
|
||||||
0x99: {"STA", 3, 5, modeAbsoluteY, buildOpStore(regA)},
|
|
||||||
0x81: {"STA", 2, 6, modeIndexedIndirectX, buildOpStore(regA)},
|
|
||||||
0x91: {"STA", 2, 6, modeIndirectIndexedY, buildOpStore(regA)},
|
|
||||||
0x86: {"STX", 2, 3, modeZeroPage, buildOpStore(regX)},
|
|
||||||
0x96: {"STX", 2, 4, modeZeroPageY, buildOpStore(regX)},
|
|
||||||
0x8E: {"STX", 3, 4, modeAbsolute, buildOpStore(regX)},
|
|
||||||
0x84: {"STY", 2, 3, modeZeroPage, buildOpStore(regY)},
|
|
||||||
0x94: {"STY", 2, 4, modeZeroPageX, buildOpStore(regY)},
|
|
||||||
0x8C: {"STY", 3, 4, modeAbsolute, buildOpStore(regY)},
|
|
||||||
|
|
||||||
0x90: {"BCC", 2, 2, modeRelative, buildOpBranch(flagC, false)}, // Extra cycles
|
|
||||||
0xB0: {"BCS", 2, 2, modeRelative, buildOpBranch(flagC, true)}, // Extra cycles
|
|
||||||
0xD0: {"BNE", 2, 2, modeRelative, buildOpBranch(flagZ, false)}, // Extra cycles
|
|
||||||
0xF0: {"BEQ", 2, 2, modeRelative, buildOpBranch(flagZ, true)}, // Extra cycles
|
|
||||||
0x10: {"BPL", 2, 2, modeRelative, buildOpBranch(flagN, false)}, // Extra cycles
|
|
||||||
0x30: {"BMI", 2, 2, modeRelative, buildOpBranch(flagN, true)}, // Extra cycles
|
|
||||||
0x50: {"BVC", 2, 2, modeRelative, buildOpBranch(flagV, false)}, // Extra cycles
|
|
||||||
0x70: {"BVS", 2, 2, modeRelative, buildOpBranch(flagV, true)}, // Extra cycles
|
|
||||||
|
|
||||||
0xEA: {"NOP", 1, 2, modeImplicit, opNOP},
|
|
||||||
|
|
||||||
/*
|
|
||||||
Undocumented opcodes,
|
|
||||||
see http://bbc.nvg.org/doc/6502OpList.txt
|
|
||||||
see https://www.nesdev.com/undocumented_opcodes.txt
|
|
||||||
*/
|
|
||||||
0x1A: {"NOP", 1, 2, modeImplicit, opNOP},
|
|
||||||
0x3A: {"NOP", 1, 2, modeImplicit, opNOP},
|
|
||||||
0x5A: {"NOP", 1, 2, modeImplicit, opNOP},
|
|
||||||
0x7A: {"NOP", 1, 2, modeImplicit, opNOP},
|
|
||||||
0xDA: {"NOP", 1, 2, modeImplicit, opNOP},
|
|
||||||
0xFA: {"NOP", 1, 2, modeImplicit, opNOP},
|
|
||||||
|
|
||||||
0x04: {"DOP", 2, 3, modeImplicit, opNOP},
|
|
||||||
0x14: {"DOP", 2, 4, modeImplicit, opNOP},
|
|
||||||
0x34: {"DOP", 2, 4, modeImplicit, opNOP},
|
|
||||||
0x44: {"DOP", 2, 3, modeImplicit, opNOP},
|
|
||||||
0x54: {"DOP", 2, 4, modeImplicit, opNOP},
|
|
||||||
0x64: {"DOP", 2, 3, modeImplicit, opNOP},
|
|
||||||
0x74: {"DOP", 2, 4, modeImplicit, opNOP},
|
|
||||||
0x80: {"DOP", 2, 2, modeImplicit, opNOP},
|
|
||||||
0x82: {"DOP", 2, 2, modeImplicit, opNOP},
|
|
||||||
0x89: {"DOP", 2, 2, modeImplicit, opNOP},
|
|
||||||
0xC2: {"DOP", 2, 2, modeImplicit, opNOP},
|
|
||||||
0xD4: {"DOP", 2, 4, modeImplicit, opNOP},
|
|
||||||
0xE2: {"DOP", 2, 2, modeImplicit, opNOP},
|
|
||||||
0xF4: {"DOP", 2, 4, modeImplicit, opNOP},
|
|
||||||
|
|
||||||
0x0C: {"TOP", 3, 3, modeImplicit, opNOP},
|
|
||||||
0x1C: {"TOP", 3, 4, modeImplicit, opNOP},
|
|
||||||
0x3C: {"TOP", 3, 4, modeImplicit, opNOP},
|
|
||||||
0x5C: {"TOP", 3, 4, modeImplicit, opNOP},
|
|
||||||
0x7C: {"TOP", 3, 4, modeImplicit, opNOP},
|
|
||||||
0xDC: {"TOP", 3, 4, modeImplicit, opNOP},
|
|
||||||
0xFC: {"TOP", 3, 4, modeImplicit, opNOP},
|
|
||||||
|
|
||||||
0x02: {"KIL", 1, 3, modeImplicit, opHALT},
|
|
||||||
0x12: {"KIL", 1, 3, modeImplicit, opHALT},
|
|
||||||
0x22: {"KIL", 1, 3, modeImplicit, opHALT},
|
|
||||||
0x32: {"KIL", 1, 3, modeImplicit, opHALT},
|
|
||||||
0x42: {"KIL", 1, 3, modeImplicit, opHALT},
|
|
||||||
0x52: {"KIL", 1, 3, modeImplicit, opHALT},
|
|
||||||
0x62: {"KIL", 1, 3, modeImplicit, opHALT},
|
|
||||||
0x72: {"KIL", 1, 3, modeImplicit, opHALT},
|
|
||||||
0x92: {"KIL", 1, 3, modeImplicit, opHALT},
|
|
||||||
0xB2: {"KIL", 1, 3, modeImplicit, opHALT},
|
|
||||||
0xD2: {"KIL", 1, 3, modeImplicit, opHALT},
|
|
||||||
0xF2: {"KIL", 1, 3, modeImplicit, opHALT},
|
|
||||||
}
|
|
|
@ -1,38 +0,0 @@
|
||||||
package core6502
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"testing"
|
|
||||||
)
|
|
||||||
|
|
||||||
func TestNMOS6502(t *testing.T) {
|
|
||||||
m := new(FlatMemory)
|
|
||||||
s := NewNMOS6502(m)
|
|
||||||
|
|
||||||
m.loadBinary("testdata/6502_functional_test.bin")
|
|
||||||
executeSuite(t, s, 0x200, 240, false, 255)
|
|
||||||
}
|
|
||||||
|
|
||||||
// To execute test suites from https://github.com/Klaus2m5/6502_65C02_functional_tests
|
|
||||||
func executeSuite(t *testing.T, s *State, stepAddress uint16, steps uint8, showStep bool, traceCPUStep uint8) {
|
|
||||||
s.reg.setPC(0x0400)
|
|
||||||
currentStep := uint8(255)
|
|
||||||
for {
|
|
||||||
testCase := s.mem.Peek(stepAddress)
|
|
||||||
if testCase != currentStep {
|
|
||||||
currentStep = testCase
|
|
||||||
if showStep {
|
|
||||||
fmt.Printf("[ Step %d ]\n", testCase)
|
|
||||||
}
|
|
||||||
s.SetTrace(testCase == traceCPUStep)
|
|
||||||
}
|
|
||||||
if testCase >= steps {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
pc := s.reg.getPC()
|
|
||||||
s.ExecuteInstruction()
|
|
||||||
if pc == s.reg.getPC() {
|
|
||||||
t.Fatalf("Failure in test %v.", testCase)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,346 +0,0 @@
|
||||||
package core6502
|
|
||||||
|
|
||||||
func buildOpTransfer(regSrc int, regDst int) opFunc {
|
|
||||||
return func(s *State, line []uint8, opcode opcode) {
|
|
||||||
value := s.reg.getRegister(regSrc)
|
|
||||||
s.reg.setRegister(regDst, value)
|
|
||||||
if regDst != regSP {
|
|
||||||
s.reg.updateFlagZN(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildOpIncDec(inc bool) opFunc {
|
|
||||||
return func(s *State, line []uint8, opcode opcode) {
|
|
||||||
value := resolveValue(s, line, opcode)
|
|
||||||
if opcode.addressMode == modeAbsoluteX || opcode.addressMode == modeAbsoluteY {
|
|
||||||
// Double read, needed to pass A2Audit for the Language Card
|
|
||||||
value = resolveValue(s, line, opcode)
|
|
||||||
}
|
|
||||||
if inc {
|
|
||||||
value++
|
|
||||||
} else {
|
|
||||||
value--
|
|
||||||
}
|
|
||||||
s.reg.updateFlagZN(value)
|
|
||||||
resolveSetValue(s, line, opcode, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildOpShift(isLeft bool, isRotate bool) opFunc {
|
|
||||||
return func(s *State, line []uint8, opcode opcode) {
|
|
||||||
value := resolveValue(s, line, opcode)
|
|
||||||
|
|
||||||
oldCarry := s.reg.getFlagBit(flagC)
|
|
||||||
var carry bool
|
|
||||||
if isLeft {
|
|
||||||
carry = (value & 0x80) != 0
|
|
||||||
value <<= 1
|
|
||||||
if isRotate {
|
|
||||||
value += oldCarry
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
carry = (value & 0x01) != 0
|
|
||||||
value >>= 1
|
|
||||||
if isRotate {
|
|
||||||
value += oldCarry << 7
|
|
||||||
}
|
|
||||||
}
|
|
||||||
s.reg.updateFlag(flagC, carry)
|
|
||||||
s.reg.updateFlagZN(value)
|
|
||||||
resolveSetValue(s, line, opcode, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildOpLoad(regDst int) opFunc {
|
|
||||||
return func(s *State, line []uint8, opcode opcode) {
|
|
||||||
value := resolveValue(s, line, opcode)
|
|
||||||
s.reg.setRegister(regDst, value)
|
|
||||||
s.reg.updateFlagZN(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildOpStore(regSrc int) opFunc {
|
|
||||||
return func(s *State, line []uint8, opcode opcode) {
|
|
||||||
value := s.reg.getRegister(regSrc)
|
|
||||||
resolveSetValue(s, line, opcode, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildOpUpdateFlag(flag uint8, value bool) opFunc {
|
|
||||||
return func(s *State, line []uint8, opcode opcode) {
|
|
||||||
s.reg.updateFlag(flag, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildOpBranch(flag uint8, test bool) opFunc {
|
|
||||||
return func(s *State, line []uint8, opcode opcode) {
|
|
||||||
if s.reg.getFlag(flag) == test {
|
|
||||||
s.extraCycleBranchTaken = true
|
|
||||||
address := resolveAddress(s, line, opcode)
|
|
||||||
s.reg.setPC(address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildOpBranchOnBit(bit uint8, test bool) opFunc {
|
|
||||||
return func(s *State, line []uint8, opcode opcode) {
|
|
||||||
// Note that those operations have two addressing modes:
|
|
||||||
// one for the zero page value, another for the relative jump.
|
|
||||||
// We will have to resolve the first one here.
|
|
||||||
value := s.mem.Peek(uint16(line[1]))
|
|
||||||
bitValue := ((value >> bit) & 1) == 1
|
|
||||||
|
|
||||||
if bitValue == test {
|
|
||||||
address := resolveAddress(s, line, opcode)
|
|
||||||
s.reg.setPC(address)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildOpSetBit(bit uint8, set bool) opFunc {
|
|
||||||
return func(s *State, line []uint8, opcode opcode) {
|
|
||||||
value := resolveValue(s, line, opcode)
|
|
||||||
if set {
|
|
||||||
value = value | (1 << bit)
|
|
||||||
} else {
|
|
||||||
value = value &^ (1 << bit)
|
|
||||||
}
|
|
||||||
resolveSetValue(s, line, opcode, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func opBIT(s *State, line []uint8, opcode opcode) {
|
|
||||||
value := resolveValue(s, line, opcode)
|
|
||||||
acc := s.reg.getA()
|
|
||||||
s.reg.updateFlag(flagZ, value&acc == 0)
|
|
||||||
// The immediate addressing mode (65C02 or 65816 only) does not affect N & V.
|
|
||||||
if opcode.addressMode != modeImmediate {
|
|
||||||
s.reg.updateFlag(flagN, value&(1<<7) != 0)
|
|
||||||
s.reg.updateFlag(flagV, value&(1<<6) != 0)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func opTRB(s *State, line []uint8, opcode opcode) {
|
|
||||||
value := resolveValue(s, line, opcode)
|
|
||||||
a := s.reg.getA()
|
|
||||||
s.reg.updateFlag(flagZ, (value&a) == 0)
|
|
||||||
resolveSetValue(s, line, opcode, value&^a)
|
|
||||||
}
|
|
||||||
|
|
||||||
func opTSB(s *State, line []uint8, opcode opcode) {
|
|
||||||
value := resolveValue(s, line, opcode)
|
|
||||||
a := s.reg.getA()
|
|
||||||
s.reg.updateFlag(flagZ, (value&a) == 0)
|
|
||||||
resolveSetValue(s, line, opcode, value|a)
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildOpCompare(reg int) opFunc {
|
|
||||||
return func(s *State, line []uint8, opcode opcode) {
|
|
||||||
value := resolveValue(s, line, opcode)
|
|
||||||
reference := s.reg.getRegister(reg)
|
|
||||||
s.reg.updateFlagZN(reference - value)
|
|
||||||
s.reg.updateFlag(flagC, reference >= value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func operationAnd(a uint8, b uint8) uint8 { return a & b }
|
|
||||||
func operationOr(a uint8, b uint8) uint8 { return a | b }
|
|
||||||
func operationXor(a uint8, b uint8) uint8 { return a ^ b }
|
|
||||||
|
|
||||||
func buildOpLogic(operation func(uint8, uint8) uint8) opFunc {
|
|
||||||
return func(s *State, line []uint8, opcode opcode) {
|
|
||||||
value := resolveValue(s, line, opcode)
|
|
||||||
result := operation(value, s.reg.getA())
|
|
||||||
s.reg.setA(result)
|
|
||||||
s.reg.updateFlagZN(result)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func opADC(s *State, line []uint8, opcode opcode) {
|
|
||||||
value := resolveValue(s, line, opcode)
|
|
||||||
aValue := s.reg.getA()
|
|
||||||
carry := s.reg.getFlagBit(flagC)
|
|
||||||
|
|
||||||
total := uint16(aValue) + uint16(value) + uint16(carry)
|
|
||||||
signedTotal := int16(int8(aValue)) + int16(int8(value)) + int16(carry)
|
|
||||||
truncated := uint8(total)
|
|
||||||
|
|
||||||
if s.reg.getFlag(flagD) {
|
|
||||||
totalBcdLo := uint(aValue&0x0f) + uint(value&0x0f) + uint(carry)
|
|
||||||
totalBcdHi := uint(aValue>>4) + uint(value>>4)
|
|
||||||
if totalBcdLo >= 10 {
|
|
||||||
totalBcdLo -= 10
|
|
||||||
totalBcdHi++
|
|
||||||
}
|
|
||||||
totalBcdHiPrenormalised := uint8(totalBcdHi & 0xf)
|
|
||||||
newCarry := false
|
|
||||||
if totalBcdHi >= 10 {
|
|
||||||
totalBcdHi -= 10
|
|
||||||
newCarry = true
|
|
||||||
}
|
|
||||||
totalBcd := uint8(totalBcdHi)<<4 + (uint8(totalBcdLo) & 0xf)
|
|
||||||
s.reg.setA(uint8(totalBcd))
|
|
||||||
s.reg.updateFlag(flagC, newCarry)
|
|
||||||
s.reg.updateFlag(flagV, (value>>7 == aValue>>7) &&
|
|
||||||
(value>>7 != totalBcdHiPrenormalised>>3))
|
|
||||||
} else {
|
|
||||||
s.reg.setA(truncated)
|
|
||||||
s.reg.updateFlag(flagC, total > 0xFF)
|
|
||||||
s.reg.updateFlag(flagV, signedTotal < -128 || signedTotal > 127)
|
|
||||||
// Effectively the same as the less clear:
|
|
||||||
// s.reg.updateFlag(flagV, (value>>7 == aValue>>7) && (value>>7 != truncated>>7))
|
|
||||||
// See http://www.6502.org/tutorials/vflag.html
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZN flags behave for BCD as if the operation was binary?
|
|
||||||
s.reg.updateFlagZN(truncated)
|
|
||||||
}
|
|
||||||
|
|
||||||
func opADCAlt(s *State, line []uint8, opcode opcode) {
|
|
||||||
opADC(s, line, opcode)
|
|
||||||
if s.reg.getFlag(flagD) {
|
|
||||||
s.extraCycleBCD = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// The Z and N flags on BCD are fixed in 65c02.
|
|
||||||
s.reg.updateFlagZN(s.reg.getA())
|
|
||||||
}
|
|
||||||
|
|
||||||
func opSBC(s *State, line []uint8, opcode opcode) {
|
|
||||||
value := resolveValue(s, line, opcode)
|
|
||||||
aValue := s.reg.getA()
|
|
||||||
carry := s.reg.getFlagBit(flagC)
|
|
||||||
|
|
||||||
total := 0x100 + uint16(aValue) - uint16(value) + uint16(carry) - 1
|
|
||||||
signedTotal := int16(int8(aValue)) - int16(int8(value)) + int16(carry) - 1
|
|
||||||
truncated := uint8(total)
|
|
||||||
|
|
||||||
if s.reg.getFlag(flagD) {
|
|
||||||
totalBcdLo := int(aValue&0x0f) - int(value&0x0f) + int(carry) - 1
|
|
||||||
totalBcdHi := int(aValue>>4) - int(value>>4)
|
|
||||||
if totalBcdLo < 0 {
|
|
||||||
totalBcdLo += 10
|
|
||||||
totalBcdHi--
|
|
||||||
}
|
|
||||||
newCarry := true
|
|
||||||
if totalBcdHi < 0 {
|
|
||||||
totalBcdHi += 10
|
|
||||||
newCarry = false
|
|
||||||
}
|
|
||||||
totalBcd := uint8(totalBcdHi)<<4 + (uint8(totalBcdLo) & 0xf)
|
|
||||||
s.reg.setA(uint8(totalBcd))
|
|
||||||
s.reg.updateFlag(flagC, newCarry)
|
|
||||||
} else {
|
|
||||||
s.reg.setA(truncated)
|
|
||||||
s.reg.updateFlag(flagC, total > 0xFF)
|
|
||||||
}
|
|
||||||
|
|
||||||
// ZNV flags behave for SBC as if the operation was binary
|
|
||||||
s.reg.updateFlagZN(truncated)
|
|
||||||
s.reg.updateFlag(flagV, signedTotal < -128 || signedTotal > 127)
|
|
||||||
}
|
|
||||||
|
|
||||||
func opSBCAlt(s *State, line []uint8, opcode opcode) {
|
|
||||||
opSBC(s, line, opcode)
|
|
||||||
if s.reg.getFlag(flagD) {
|
|
||||||
s.extraCycleBCD = true
|
|
||||||
}
|
|
||||||
// The Z and N flags on BCD are fixed in 65c02.
|
|
||||||
s.reg.updateFlagZN(s.reg.getA())
|
|
||||||
}
|
|
||||||
|
|
||||||
const stackAddress uint16 = 0x0100
|
|
||||||
|
|
||||||
func pushByte(s *State, value uint8) {
|
|
||||||
adresss := stackAddress + uint16(s.reg.getSP())
|
|
||||||
s.mem.Poke(adresss, value)
|
|
||||||
s.reg.setSP(s.reg.getSP() - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func pullByte(s *State) uint8 {
|
|
||||||
s.reg.setSP(s.reg.getSP() + 1)
|
|
||||||
adresss := stackAddress + uint16(s.reg.getSP())
|
|
||||||
return s.mem.Peek(adresss)
|
|
||||||
}
|
|
||||||
|
|
||||||
func pushWord(s *State, value uint16) {
|
|
||||||
pushByte(s, uint8(value>>8))
|
|
||||||
pushByte(s, uint8(value))
|
|
||||||
}
|
|
||||||
|
|
||||||
func pullWord(s *State) uint16 {
|
|
||||||
return uint16(pullByte(s)) +
|
|
||||||
(uint16(pullByte(s)) << 8)
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildOpPull(regDst int) opFunc {
|
|
||||||
return func(s *State, line []uint8, opcode opcode) {
|
|
||||||
value := pullByte(s)
|
|
||||||
s.reg.setRegister(regDst, value)
|
|
||||||
if regDst == regP {
|
|
||||||
s.reg.updateFlag5B()
|
|
||||||
} else {
|
|
||||||
s.reg.updateFlagZN(value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func buildOpPush(regSrc int) opFunc {
|
|
||||||
return func(s *State, line []uint8, opcode opcode) {
|
|
||||||
value := s.reg.getRegister(regSrc)
|
|
||||||
if regSrc == regP {
|
|
||||||
value |= flagB + flag5
|
|
||||||
}
|
|
||||||
pushByte(s, value)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func opJMP(s *State, line []uint8, opcode opcode) {
|
|
||||||
address := resolveAddress(s, line, opcode)
|
|
||||||
s.reg.setPC(address)
|
|
||||||
}
|
|
||||||
|
|
||||||
func opNOP(s *State, line []uint8, opcode opcode) {}
|
|
||||||
|
|
||||||
func opHALT(s *State, line []uint8, opcode opcode) {
|
|
||||||
s.reg.setPC(s.reg.getPC() - 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func opJSR(s *State, line []uint8, opcode opcode) {
|
|
||||||
pushWord(s, s.reg.getPC()-1)
|
|
||||||
address := resolveAddress(s, line, opcode)
|
|
||||||
s.reg.setPC(address)
|
|
||||||
}
|
|
||||||
|
|
||||||
func opRTI(s *State, line []uint8, opcode opcode) {
|
|
||||||
s.reg.setP(pullByte(s))
|
|
||||||
s.reg.updateFlag5B()
|
|
||||||
s.reg.setPC(pullWord(s))
|
|
||||||
}
|
|
||||||
|
|
||||||
func opRTS(s *State, line []uint8, opcode opcode) {
|
|
||||||
s.reg.setPC(pullWord(s) + 1)
|
|
||||||
}
|
|
||||||
|
|
||||||
func opBRK(s *State, line []uint8, opcode opcode) {
|
|
||||||
pushWord(s, s.reg.getPC()+1)
|
|
||||||
pushByte(s, s.reg.getP()|(flagB+flag5))
|
|
||||||
s.reg.setFlag(flagI)
|
|
||||||
s.reg.setPC(getWord(s.mem, vectorBreak))
|
|
||||||
}
|
|
||||||
|
|
||||||
func opBRKAlt(s *State, line []uint8, opcode opcode) {
|
|
||||||
opBRK(s, line, opcode)
|
|
||||||
/*
|
|
||||||
The only difference in the BRK instruction on the 65C02 and the 6502
|
|
||||||
is that the 65C02 clears the D (decimal) flag on the 65C02, whereas
|
|
||||||
the D flag is not affected on the 6502.
|
|
||||||
*/
|
|
||||||
s.reg.clearFlag(flagD)
|
|
||||||
}
|
|
||||||
|
|
||||||
func opSTZ(s *State, line []uint8, opcode opcode) {
|
|
||||||
resolveSetValue(s, line, opcode, 0)
|
|
||||||
}
|
|
|
@ -1,102 +0,0 @@
|
||||||
package core6502
|
|
||||||
|
|
||||||
import "fmt"
|
|
||||||
|
|
||||||
const (
|
|
||||||
regA = 0
|
|
||||||
regX = 1
|
|
||||||
regY = 2
|
|
||||||
regP = 3
|
|
||||||
regSP = 4
|
|
||||||
regPC = 5 // 2 bytes
|
|
||||||
regPC2 = 6
|
|
||||||
regNone = -1
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
flagN uint8 = 1 << 7
|
|
||||||
flagV uint8 = 1 << 6
|
|
||||||
flag5 uint8 = 1 << 5
|
|
||||||
flagB uint8 = 1 << 4
|
|
||||||
flagD uint8 = 1 << 3
|
|
||||||
flagI uint8 = 1 << 2
|
|
||||||
flagZ uint8 = 1 << 1
|
|
||||||
flagC uint8 = 1 << 0
|
|
||||||
)
|
|
||||||
|
|
||||||
type registers struct {
|
|
||||||
data [7]uint8
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *registers) getRegister(i int) uint8 { return r.data[i] }
|
|
||||||
|
|
||||||
func (r *registers) getA() uint8 { return r.data[regA] }
|
|
||||||
func (r *registers) getX() uint8 { return r.data[regX] }
|
|
||||||
func (r *registers) getY() uint8 { return r.data[regY] }
|
|
||||||
func (r *registers) getP() uint8 { return r.data[regP] }
|
|
||||||
func (r *registers) getSP() uint8 { return r.data[regSP] }
|
|
||||||
|
|
||||||
func (r *registers) setRegister(i int, v uint8) {
|
|
||||||
r.data[i] = v
|
|
||||||
}
|
|
||||||
func (r *registers) setA(v uint8) { r.setRegister(regA, v) }
|
|
||||||
func (r *registers) setX(v uint8) { r.setRegister(regX, v) }
|
|
||||||
func (r *registers) setY(v uint8) { r.setRegister(regY, v) }
|
|
||||||
func (r *registers) setP(v uint8) { r.setRegister(regP, v) }
|
|
||||||
func (r *registers) setSP(v uint8) { r.setRegister(regSP, v) }
|
|
||||||
|
|
||||||
func (r *registers) getPC() uint16 {
|
|
||||||
return uint16(r.data[regPC])*256 + uint16(r.data[regPC2])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *registers) setPC(v uint16) {
|
|
||||||
r.data[regPC] = uint8(v >> 8)
|
|
||||||
r.data[regPC2] = uint8(v)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *registers) getFlagBit(i uint8) uint8 {
|
|
||||||
if r.getFlag(i) {
|
|
||||||
return 1
|
|
||||||
}
|
|
||||||
return 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *registers) getFlag(i uint8) bool {
|
|
||||||
return (r.data[regP] & i) != 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *registers) setFlag(i uint8) {
|
|
||||||
r.data[regP] |= i
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *registers) clearFlag(i uint8) {
|
|
||||||
r.data[regP] &^= i
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *registers) updateFlag(i uint8, v bool) {
|
|
||||||
if v {
|
|
||||||
r.setFlag(i)
|
|
||||||
} else {
|
|
||||||
r.clearFlag(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *registers) updateFlagZN(t uint8) {
|
|
||||||
r.updateFlag(flagZ, t == 0)
|
|
||||||
r.updateFlag(flagN, t >= (1<<7))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r *registers) updateFlag5B() {
|
|
||||||
r.setFlag(flag5)
|
|
||||||
r.clearFlag(flagB)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (r registers) String() string {
|
|
||||||
//ch := (r.getA() & 0x3F) + 0x40
|
|
||||||
ch := (r.getA() & 0x7F)
|
|
||||||
if ch < 0x20 {
|
|
||||||
ch += 0x40
|
|
||||||
}
|
|
||||||
return fmt.Sprintf("A: %#02x(%v), X: %#02x, Y: %#02x, SP: %#02x, PC: %#04x, P: %#02x, (NV-BDIZC): %08b",
|
|
||||||
r.getA(), string(ch), r.getX(), r.getY(), r.getSP(), r.getPC(), r.getP(), r.getP())
|
|
||||||
}
|
|
|
@ -1,67 +0,0 @@
|
||||||
package core6502
|
|
||||||
|
|
||||||
import "testing"
|
|
||||||
|
|
||||||
func TestRegA(t *testing.T) {
|
|
||||||
var r registers
|
|
||||||
data := uint8(200)
|
|
||||||
r.setA(data)
|
|
||||||
if r.getA() != data {
|
|
||||||
t.Error("Error storing and loading A")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
func TestRegPC(t *testing.T) {
|
|
||||||
var r registers
|
|
||||||
data := uint16(0xc600)
|
|
||||||
r.setPC(data)
|
|
||||||
if r.getPC() != data {
|
|
||||||
t.Error("Error storing and loading PC")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFlags(t *testing.T) {
|
|
||||||
var r registers
|
|
||||||
r.setP(0x23)
|
|
||||||
if r.getP() != 0x23 {
|
|
||||||
t.Error("Error storing and loading P")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.setP(0)
|
|
||||||
r.setFlag(flagD)
|
|
||||||
if !r.getFlag(flagD) {
|
|
||||||
t.Error("Error setting and getting flag")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.clearFlag(flagD)
|
|
||||||
if r.getFlag(flagD) {
|
|
||||||
t.Error("Error clearing flag")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.updateFlag(flagD, true)
|
|
||||||
if !r.getFlag(flagD) {
|
|
||||||
t.Error("Error update flag to true")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.updateFlag(flagD, false)
|
|
||||||
if r.getFlag(flagD) {
|
|
||||||
t.Error("Error updating flag to false")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestUpdateFlagZN(t *testing.T) {
|
|
||||||
var r registers
|
|
||||||
r.updateFlagZN(0)
|
|
||||||
if r.getP() != flagZ {
|
|
||||||
t.Error("Error update flags ZN with 0")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.updateFlagZN(0x10)
|
|
||||||
if r.getP() != 0 {
|
|
||||||
t.Error("Error update flags ZN with 0x10")
|
|
||||||
}
|
|
||||||
|
|
||||||
r.updateFlagZN(0xF2)
|
|
||||||
if r.getP() != flagN {
|
|
||||||
t.Error("Error update flags ZN with 0xF2")
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
File diff suppressed because it is too large
Load Diff
1
go.mod
1
go.mod
|
@ -12,6 +12,7 @@ require (
|
||||||
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200625191551-73d3c3675aa3
|
||||||
github.com/godbus/dbus/v5 v5.0.3 // indirect
|
github.com/godbus/dbus/v5 v5.0.3 // indirect
|
||||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
|
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff // indirect
|
||||||
|
github.com/ivanizag/iz6502 v1.2.1
|
||||||
github.com/kr/text v0.2.0 // indirect
|
github.com/kr/text v0.2.0 // indirect
|
||||||
github.com/pkg/profile v1.5.0
|
github.com/pkg/profile v1.5.0
|
||||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -20,6 +20,8 @@ github.com/godbus/dbus/v5 v5.0.3 h1:ZqHaoEF7TBzh4jzPmqVhE/5A1z9of6orkAe5uHoAeME=
|
||||||
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.3/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
|
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff h1:W71vTCKoxtdXgnm1ECDFkfQnpdqAO00zzGXLA5yaEX8=
|
||||||
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
github.com/goki/freetype v0.0.0-20181231101311-fa8a33aabaff/go.mod h1:wfqRWLHRBsRgkp5dmbG56SA0DmVtwrF5N3oPdI8t+Aw=
|
||||||
|
github.com/ivanizag/iz6502 v1.2.1 h1:nwrKzSKVhG9mO6kUE68o07aX6Qn5AuA6WFm4twCx8mA=
|
||||||
|
github.com/ivanizag/iz6502 v1.2.1/go.mod h1:h4gbw3IK6WCYawi00kBhQ4ACeQkGWgqbUeAgDaQpy6s=
|
||||||
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
github.com/jackmordaunt/icns v0.0.0-20181231085925-4f16af745526/go.mod h1:UQkeMHVoNcyXYq9otUupF7/h/2tmHlhrS2zw7ZVvUqc=
|
||||||
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
|
github.com/josephspurrier/goversioninfo v0.0.0-20200309025242-14b0ab84c6ca/go.mod h1:eJTEwMjXb7kZ633hO3Ln9mBUCOjX2+FlTljvpl9SYdE=
|
||||||
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
|
6
romX.go
6
romX.go
|
@ -3,7 +3,7 @@ package izapple2
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/ivanizag/izapple2/core6502"
|
"github.com/ivanizag/iz6502"
|
||||||
)
|
)
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -31,7 +31,7 @@ go run *.go -rom ROMX.FIRM.dump -disk ROM\ X\ 20-10-22.dsk
|
||||||
|
|
||||||
type romX struct {
|
type romX struct {
|
||||||
a *Apple2
|
a *Apple2
|
||||||
memory core6502.Memory
|
memory iz6502.Memory
|
||||||
activationStep int
|
activationStep int
|
||||||
systemBank uint8
|
systemBank uint8
|
||||||
mainBank uint8
|
mainBank uint8
|
||||||
|
@ -74,7 +74,7 @@ const (
|
||||||
*/
|
*/
|
||||||
)
|
)
|
||||||
|
|
||||||
func newRomX(a *Apple2, memory core6502.Memory) (*romX, error) {
|
func newRomX(a *Apple2, memory iz6502.Memory) (*romX, error) {
|
||||||
var rx romX
|
var rx romX
|
||||||
rx.a = a
|
rx.a = a
|
||||||
rx.memory = memory
|
rx.memory = memory
|
||||||
|
|
Loading…
Reference in New Issue