Cycle count accurate per the Harte test suite

This commit is contained in:
Ivan Izaguirre 2021-09-26 01:22:08 +02:00
parent 1a150d0f96
commit 4f963b0bd6
5 changed files with 45 additions and 36 deletions

View File

@ -14,6 +14,7 @@ const (
modeRelative modeRelative
modeAbsolute modeAbsolute
modeAbsoluteX modeAbsoluteX
modeAbsoluteX65c02
modeAbsoluteY modeAbsoluteY
modeIndirect modeIndirect
modeIndexedIndirectX modeIndexedIndirectX
@ -64,8 +65,10 @@ func resolveSetValue(s *State, line []uint8, opcode opcode, value uint8) {
s.mem.Poke(address, value) s.mem.Poke(address, value)
// On writes, the possible extra cycle crossing page boundaries is // On writes, the possible extra cycle crossing page boundaries is
// always added and already accounted for. // added and already accounted for on NMOS
s.extraCycleCrossingBoundaries = false if opcode.addressMode != modeAbsoluteX65c02 {
s.extraCycleCrossingBoundaries = false
}
} }
func resolveAddress(s *State, line []uint8, opcode opcode) uint16 { func resolveAddress(s *State, line []uint8, opcode opcode) uint16 {
@ -81,6 +84,8 @@ func resolveAddress(s *State, line []uint8, opcode opcode) uint16 {
address = uint16(line[1] + s.reg.getY()) address = uint16(line[1] + s.reg.getY())
case modeAbsolute: case modeAbsolute:
address = getWordInLine(line) address = getWordInLine(line)
case modeAbsoluteX65c02:
fallthrough
case modeAbsoluteX: case modeAbsoluteX:
base := getWordInLine(line) base := getWordInLine(line)
address, extraCycle = addOffset(base, s.reg.getX()) address, extraCycle = addOffset(base, s.reg.getX())
@ -113,7 +118,7 @@ func resolveAddress(s *State, line []uint8, opcode opcode) uint16 {
// Two addressing modes combined. We refer to the second one, relative, // Two addressing modes combined. We refer to the second one, relative,
// placed one byte after the zeropage reference // placed one byte after the zeropage reference
base := s.reg.getPC() base := s.reg.getPC()
address, extraCycle = addOffsetRelative(base, line[2]) address, _ = addOffsetRelative(base, line[2])
default: default:
panic("Assert failed. Missing addressing mode") panic("Assert failed. Missing addressing mode")
} }
@ -179,6 +184,8 @@ func lineString(line []uint8, opcode opcode) string {
t += fmt.Sprintf(" *%+x", int8(line[1])) t += fmt.Sprintf(" *%+x", int8(line[1]))
case modeAbsolute: case modeAbsolute:
t += fmt.Sprintf(" $%04x", getWordInLine(line)) t += fmt.Sprintf(" $%04x", getWordInLine(line))
case modeAbsoluteX65c02:
fallthrough
case modeAbsoluteX: case modeAbsoluteX:
t += fmt.Sprintf(" $%04x,X", getWordInLine(line)) t += fmt.Sprintf(" $%04x,X", getWordInLine(line))
case modeAbsoluteY: case modeAbsoluteY:

View File

@ -32,7 +32,6 @@ func add65c02NOPs(opcodes *[256]opcode) {
nop23 := opcode{"NOP", 2, 3, modeImmediate, opNOP} nop23 := opcode{"NOP", 2, 3, modeImmediate, opNOP}
nop24 := opcode{"NOP", 2, 4, modeImmediate, opNOP} nop24 := opcode{"NOP", 2, 4, modeImmediate, opNOP}
nop34 := opcode{"NOP", 3, 4, modeAbsolute, opNOP} nop34 := opcode{"NOP", 3, 4, modeAbsolute, opNOP}
nop38 := opcode{"NOP", 3, 8, modeAbsolute, opNOP}
opcodes[0x02] = nop22 opcodes[0x02] = nop22
opcodes[0x22] = nop22 opcodes[0x22] = nop22
@ -47,7 +46,7 @@ func add65c02NOPs(opcodes *[256]opcode) {
opcodes[0xD4] = nop24 opcodes[0xD4] = nop24
opcodes[0xF4] = nop24 opcodes[0xF4] = nop24
opcodes[0x5c] = nop38 opcodes[0x5c] = nop34
opcodes[0xdc] = nop34 opcodes[0xdc] = nop34
opcodes[0xfc] = nop34 opcodes[0xfc] = nop34
@ -66,8 +65,7 @@ var opcodes65c02Delta = [256]opcode{
// Functional difference // Functional difference
0x00: {"BRK", 1, 7, modeImplicit, opBRKAlt}, 0x00: {"BRK", 1, 7, modeImplicit, opBRKAlt},
0x24: {"BIT", 2, 3, modeZeroPage, opBIT}, 0x24: {"BIT", 2, 3, modeZeroPage, opBIT},
0x2C: {"BIT", 3, 3, modeAbsolute, opBIT}, 0x6C: {"JMP", 3, 6, modeIndirect65c02Fix, opJMP},
0x6C: {"JMP", 3, 3, modeIndirect65c02Fix, opJMP},
// Fixed BCD arithmetic flags // Fixed BCD arithmetic flags
0x69: {"ADC", 2, 2, modeImmediate, opADCAlt}, 0x69: {"ADC", 2, 2, modeImmediate, opADCAlt},
@ -88,10 +86,10 @@ var opcodes65c02Delta = [256]opcode{
0xF1: {"SBC", 2, 5, modeIndirectIndexedY, opSBCAlt}, // Extra cycles 0xF1: {"SBC", 2, 5, modeIndirectIndexedY, opSBCAlt}, // Extra cycles
// Different cycle count // Different cycle count
0x1e: {"ASL", 3, 6, modeAbsoluteX, buildOpShift(true, false)}, 0x1e: {"ASL", 3, 6, modeAbsoluteX65c02, buildOpShift(true, false)},
0x3e: {"ROL", 3, 6, modeAbsoluteX, buildOpShift(true, true)}, 0x3e: {"ROL", 3, 6, modeAbsoluteX65c02, buildOpShift(true, true)},
0x5e: {"LSR", 3, 6, modeAbsoluteX, buildOpShift(false, false)}, 0x5e: {"LSR", 3, 6, modeAbsoluteX65c02, buildOpShift(false, false)},
0x7e: {"ROR", 3, 6, modeAbsoluteX, buildOpShift(false, true)}, 0x7e: {"ROR", 3, 6, modeAbsoluteX65c02, buildOpShift(false, true)},
// New indirect zero page addresssing mode // New indirect zero page addresssing mode
0x12: {"ORA", 2, 5, modeIndirectZeroPage, buildOpLogic(operationOr)}, 0x12: {"ORA", 2, 5, modeIndirectZeroPage, buildOpLogic(operationOr)},
@ -116,7 +114,7 @@ var opcodes65c02Delta = [256]opcode{
0x5a: {"PHY", 1, 3, modeImplicit, buildOpPush(regY)}, 0x5a: {"PHY", 1, 3, modeImplicit, buildOpPush(regY)},
0xfa: {"PLX", 1, 4, modeImplicit, buildOpPull(regX)}, 0xfa: {"PLX", 1, 4, modeImplicit, buildOpPull(regX)},
0x7a: {"PLY", 1, 4, modeImplicit, buildOpPull(regY)}, 0x7a: {"PLY", 1, 4, modeImplicit, buildOpPull(regY)},
0x80: {"BRA", 2, 4, modeRelative, opJMP}, // Extra cycles 0x80: {"BRA", 2, 3, modeRelative, opJMP}, // Extra cycles
0x64: {"STZ", 2, 3, modeZeroPage, opSTZ}, 0x64: {"STZ", 2, 3, modeZeroPage, opSTZ},
0x74: {"STZ", 2, 4, modeZeroPageX, opSTZ}, 0x74: {"STZ", 2, 4, modeZeroPageX, opSTZ},
@ -131,22 +129,22 @@ var opcodes65c02Delta = [256]opcode{
// Additional in Rockwell 65c02 and WDC 65c02? // Additional in Rockwell 65c02 and WDC 65c02?
// They have a double addressing mode: zeropage and relative. // They have a double addressing mode: zeropage and relative.
0x0f: {"BBR0", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(0, false)}, // Extra cycles 0x0f: {"BBR0", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(0, false)}, // Extra cycles
0x1f: {"BBR1", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(1, false)}, // Extra cycles 0x1f: {"BBR1", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(1, false)}, // Extra cycles
0x2f: {"BBR2", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(2, false)}, // Extra cycles 0x2f: {"BBR2", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(2, false)}, // Extra cycles
0x3f: {"BBR3", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(3, false)}, // Extra cycles 0x3f: {"BBR3", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(3, false)}, // Extra cycles
0x4f: {"BBR4", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(4, false)}, // Extra cycles 0x4f: {"BBR4", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(4, false)}, // Extra cycles
0x5f: {"BBR5", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(5, false)}, // Extra cycles 0x5f: {"BBR5", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(5, false)}, // Extra cycles
0x6f: {"BBR6", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(6, false)}, // Extra cycles 0x6f: {"BBR6", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(6, false)}, // Extra cycles
0x7f: {"BBR7", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(7, false)}, // Extra cycles 0x7f: {"BBR7", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(7, false)}, // Extra cycles
0x8f: {"BBS0", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(0, true)}, // Extra cycles 0x8f: {"BBS0", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(0, true)}, // Extra cycles
0x9f: {"BBS1", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(1, true)}, // Extra cycles 0x9f: {"BBS1", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(1, true)}, // Extra cycles
0xaf: {"BBS2", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(2, true)}, // Extra cycles 0xaf: {"BBS2", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(2, true)}, // Extra cycles
0xbf: {"BBS3", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(3, true)}, // Extra cycles 0xbf: {"BBS3", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(3, true)}, // Extra cycles
0xcf: {"BBS4", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(4, true)}, // Extra cycles 0xcf: {"BBS4", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(4, true)}, // Extra cycles
0xdf: {"BBS5", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(5, true)}, // Extra cycles 0xdf: {"BBS5", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(5, true)}, // Extra cycles
0xef: {"BBS6", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(6, true)}, // Extra cycles 0xef: {"BBS6", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(6, true)}, // Extra cycles
0xff: {"BBS7", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(7, true)}, // Extra cycles 0xff: {"BBS7", 3, 6, modeZeroPageAndRelative, buildOpBranchOnBit(7, true)}, // Extra cycles
0x07: {"RMB0", 2, 5, modeZeroPage, buildOpSetBit(0, false)}, 0x07: {"RMB0", 2, 5, modeZeroPage, buildOpSetBit(0, false)},
0x17: {"RMB1", 2, 5, modeZeroPage, buildOpSetBit(1, false)}, 0x17: {"RMB1", 2, 5, modeZeroPage, buildOpSetBit(1, false)},

View File

@ -34,7 +34,7 @@ func TestHarteNMOS6502(t *testing.T) {
s := NewNMOS6502(nil) // Use to get the opcodes names s := NewNMOS6502(nil) // Use to get the opcodes names
path := "/home/casa/code/ProcessorTests/6502/v1/" path := "/home/casa/code/ProcessorTests/6502/v1/"
for i := 0x00; i <= 0xff; /*0xff*/ i++ { for i := 0x00; i <= 0xff; i++ {
if s.opcodes[i].name != "ADC" && // Issue with ADC crossing page boundaries if s.opcodes[i].name != "ADC" && // Issue with ADC crossing page boundaries
s.opcodes[i].name != "SBC" && // Issue with ADC crossing page boundaries s.opcodes[i].name != "SBC" && // Issue with ADC crossing page boundaries
s.opcodes[i].name != "" { s.opcodes[i].name != "" {
@ -56,10 +56,9 @@ func TestHarteCMOS65c02(t *testing.T) {
s := NewCMOS65c02(nil) // Use to get the opcodes names s := NewCMOS65c02(nil) // Use to get the opcodes names
path := "/home/casa/code/ProcessorTests/wdc65c02/v1/" path := "/home/casa/code/ProcessorTests/wdc65c02/v1/"
for i := 0x00; i <= 0xff; /*0xff*/ i++ { for i := 0x00; i <= 0xff; i++ {
if s.opcodes[i].name != "ADC" && // Issue with ADC crossing page boundaries if s.opcodes[i].name != "ADC" && // Issue with ADC crossing page boundaries
s.opcodes[i].name != "SBC" && // Issue with SBC crossing page boundaries s.opcodes[i].name != "SBC" { // Issue with SBC crossing page boundaries
s.opcodes[i].name != "" {
opcode := fmt.Sprintf("%02x", i) opcode := fmt.Sprintf("%02x", i)
t.Run(opcode+s.opcodes[i].name, func(t *testing.T) { t.Run(opcode+s.opcodes[i].name, func(t *testing.T) {
@ -99,6 +98,7 @@ func testOpcode(t *testing.T, s *State, path string, opcode string) {
func testScenario(t *testing.T, s *State, sc *scenario) { func testScenario(t *testing.T, s *State, sc *scenario) {
// Setup CPU // Setup CPU
start := s.GetCycles()
s.reg.setPC(sc.Initial.Pc) s.reg.setPC(sc.Initial.Pc)
s.reg.setSP(sc.Initial.S) s.reg.setSP(sc.Initial.S)
s.reg.setA(sc.Initial.A) s.reg.setA(sc.Initial.A)
@ -119,6 +119,11 @@ func testScenario(t *testing.T, s *State, sc *scenario) {
assertFlags(t, sc, sc.Initial.P, s.reg.getP(), sc.Final.P) assertFlags(t, sc, sc.Initial.P, s.reg.getP(), sc.Final.P)
assertReg8(t, sc, "SP", s.reg.getSP(), sc.Final.S) assertReg8(t, sc, "SP", s.reg.getSP(), sc.Final.S)
assertReg16(t, sc, "PC", s.reg.getPC(), sc.Final.Pc) 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) { func assertReg8(t *testing.T, sc *scenario, name string, actual uint8, wanted uint8) {

View File

@ -11,7 +11,7 @@ func NewNMOS6502(m Memory) *State {
var opcodesNMOS6502 = [256]opcode{ var opcodesNMOS6502 = [256]opcode{
0x00: {"BRK", 1, 7, modeImplicit, opBRK}, 0x00: {"BRK", 1, 7, modeImplicit, opBRK},
0x4C: {"JMP", 3, 3, modeAbsolute, opJMP}, 0x4C: {"JMP", 3, 3, modeAbsolute, opJMP},
0x6C: {"JMP", 3, 3, modeIndirect, opJMP}, 0x6C: {"JMP", 3, 5, modeIndirect, opJMP},
0x20: {"JSR", 3, 6, modeAbsolute, opJSR}, 0x20: {"JSR", 3, 6, modeAbsolute, opJSR},
0x40: {"RTI", 1, 6, modeImplicit, opRTI}, 0x40: {"RTI", 1, 6, modeImplicit, opRTI},
0x60: {"RTS", 1, 6, modeImplicit, opRTS}, 0x60: {"RTS", 1, 6, modeImplicit, opRTS},
@ -67,7 +67,7 @@ var opcodesNMOS6502 = [256]opcode{
0xF1: {"SBC", 2, 5, modeIndirectIndexedY, opSBC}, // Extra cycles 0xF1: {"SBC", 2, 5, modeIndirectIndexedY, opSBC}, // Extra cycles
0x24: {"BIT", 2, 3, modeZeroPage, opBIT}, 0x24: {"BIT", 2, 3, modeZeroPage, opBIT},
0x2C: {"BIT", 3, 3, modeAbsolute, opBIT}, 0x2C: {"BIT", 3, 4, modeAbsolute, opBIT},
0xC9: {"CMP", 2, 2, modeImmediate, buildOpCompare(regA)}, 0xC9: {"CMP", 2, 2, modeImmediate, buildOpCompare(regA)},
0xC5: {"CMP", 2, 3, modeZeroPage, buildOpCompare(regA)}, 0xC5: {"CMP", 2, 3, modeZeroPage, buildOpCompare(regA)},
@ -185,5 +185,5 @@ var opcodesNMOS6502 = [256]opcode{
// Undocumented opcodes, see http://bbc.nvg.org/doc/6502OpList.txt // Undocumented opcodes, see http://bbc.nvg.org/doc/6502OpList.txt
0x1A: {"NOP", 1, 2, modeImplicit, opNOP}, // INC A in the 65c02 0x1A: {"NOP", 1, 2, modeImplicit, opNOP}, // INC A in the 65c02
0xC2: {"NOP", 2, 2, modeImplicit, opNOP}, 0xC2: {"NOP", 2, 2, modeImplicit, opNOP},
0x02: {"NOP", 1, 1, modeImplicit, opHALT}, 0x02: {"NOP", 1, 3, modeImplicit, opHALT},
} }

View File

@ -92,7 +92,6 @@ func buildOpBranchOnBit(bit uint8, test bool) opFunc {
bitValue := ((value >> bit) & 1) == 1 bitValue := ((value >> bit) & 1) == 1
if bitValue == test { if bitValue == test {
s.extraCycleBranchTaken = true
address := resolveAddress(s, line, opcode) address := resolveAddress(s, line, opcode)
s.reg.setPC(address) s.reg.setPC(address)
} }