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
modeAbsolute
modeAbsoluteX
modeAbsoluteX65c02
modeAbsoluteY
modeIndirect
modeIndexedIndirectX
@ -64,8 +65,10 @@ func resolveSetValue(s *State, line []uint8, opcode opcode, value uint8) {
s.mem.Poke(address, value)
// On writes, the possible extra cycle crossing page boundaries is
// always added and already accounted for.
s.extraCycleCrossingBoundaries = false
// added and already accounted for on NMOS
if opcode.addressMode != modeAbsoluteX65c02 {
s.extraCycleCrossingBoundaries = false
}
}
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())
case modeAbsolute:
address = getWordInLine(line)
case modeAbsoluteX65c02:
fallthrough
case modeAbsoluteX:
base := getWordInLine(line)
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,
// placed one byte after the zeropage reference
base := s.reg.getPC()
address, extraCycle = addOffsetRelative(base, line[2])
address, _ = addOffsetRelative(base, line[2])
default:
panic("Assert failed. Missing addressing mode")
}
@ -179,6 +184,8 @@ func lineString(line []uint8, opcode opcode) string {
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:

View File

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

View File

@ -34,7 +34,7 @@ func TestHarteNMOS6502(t *testing.T) {
s := NewNMOS6502(nil) // Use to get the opcodes names
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
s.opcodes[i].name != "SBC" && // Issue with ADC crossing page boundaries
s.opcodes[i].name != "" {
@ -56,10 +56,9 @@ func TestHarteCMOS65c02(t *testing.T) {
s := NewCMOS65c02(nil) // Use to get the opcodes names
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
s.opcodes[i].name != "SBC" && // Issue with SBC crossing page boundaries
s.opcodes[i].name != "" {
s.opcodes[i].name != "SBC" { // Issue with SBC crossing page boundaries
opcode := fmt.Sprintf("%02x", i)
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) {
// Setup CPU
start := s.GetCycles()
s.reg.setPC(sc.Initial.Pc)
s.reg.setSP(sc.Initial.S)
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)
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) {

View File

@ -11,7 +11,7 @@ func NewNMOS6502(m Memory) *State {
var opcodesNMOS6502 = [256]opcode{
0x00: {"BRK", 1, 7, modeImplicit, opBRK},
0x4C: {"JMP", 3, 3, modeAbsolute, opJMP},
0x6C: {"JMP", 3, 3, modeIndirect, 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},
@ -67,7 +67,7 @@ var opcodesNMOS6502 = [256]opcode{
0xF1: {"SBC", 2, 5, modeIndirectIndexedY, opSBC}, // Extra cycles
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)},
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
0x1A: {"NOP", 1, 2, modeImplicit, opNOP}, // INC A in the 65c02
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
if bitValue == test {
s.extraCycleBranchTaken = true
address := resolveAddress(s, line, opcode)
s.reg.setPC(address)
}