Add proper extra cycle when adding an index cross boundaries on memory reads

This commit is contained in:
Ivan Izaguirre 2020-12-06 21:18:20 +01:00
parent 9c89ad4a61
commit 95998ba37d
4 changed files with 91 additions and 36 deletions

View File

@ -61,10 +61,15 @@ func resolveSetValue(s *State, line []uint8, opcode opcode, value uint8) {
// 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
// always added and already accounted for.
s.extraCycleCrossingBoundaries = false
}
func resolveAddress(s *State, line []uint8, opcode opcode) uint16 {
var address uint16
extraCycle := false
switch opcode.addressMode {
case modeZeroPage:
@ -76,18 +81,20 @@ func resolveAddress(s *State, line []uint8, opcode opcode) uint16 {
case modeAbsolute:
address = getWordInLine(line)
case modeAbsoluteX:
address = getWordInLine(line) + uint16(s.reg.getX())
base := getWordInLine(line)
address, extraCycle = addOffset(base, s.reg.getX())
case modeAbsoluteY:
address = getWordInLine(line) + uint16(s.reg.getY())
base := getWordInLine(line)
address, extraCycle = addOffset(base, s.reg.getY())
case modeIndexedIndirectX:
addressAddress := uint8(line[1] + s.reg.getX())
addressAddress := line[1] + s.reg.getX()
address = getZeroPageWord(s.mem, addressAddress)
case modeIndirect:
addressAddress := getWordInLine(line)
address = getWord(s.mem, addressAddress)
case modeIndirectIndexedY:
address = getZeroPageWord(s.mem, line[1]) +
uint16(s.reg.getY())
base := getZeroPageWord(s.mem, line[1])
address, extraCycle = addOffset(base, s.reg.getY())
// 65c02 additions
case modeIndirectZeroPage:
address = getZeroPageWord(s.mem, line[1])
@ -96,17 +103,57 @@ func resolveAddress(s *State, line []uint8, opcode opcode) uint16 {
address = getWord(s.mem, addressAddress)
case modeRelative:
// This assumes that PC is already pointing to the next instruction
address = s.reg.getPC() + uint16(int8(line[1])) // Note: line[1] is signed
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
address = s.reg.getPC() + uint16(int8(line[2])) // Note: line[2] is signed
base := s.reg.getPC()
address, extraCycle = 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 {

View File

@ -73,18 +73,18 @@ var opcodes65c02Delta = [256]opcode{
0x65: {"ADC", 2, 3, modeZeroPage, opADCAlt},
0x75: {"ADC", 2, 4, modeZeroPageX, opADCAlt},
0x6D: {"ADC", 3, 4, modeAbsolute, opADCAlt},
0x7D: {"ADC", 3, 4, modeAbsoluteX, opADCAlt},
0x79: {"ADC", 3, 4, modeAbsoluteY, 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},
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},
0xF9: {"SBC", 3, 4, modeAbsoluteY, 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},
0xF1: {"SBC", 2, 5, modeIndirectIndexedY, opSBCAlt}, // Extra cycles
// Different cycle count
0x1e: {"ASL", 3, 6, modeAbsoluteX, buildOpShift(true, false)},
@ -105,7 +105,7 @@ var opcodes65c02Delta = [256]opcode{
// New addressing options
0x89: {"BIT", 2, 2, modeImmediate, opBIT},
0x34: {"BIT", 2, 4, modeZeroPageX, opBIT},
0x3c: {"BIT", 3, 4, modeAbsoluteX, 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},
@ -115,7 +115,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},
0x80: {"BRA", 2, 4, modeRelative, opJMP}, // Extra cycles
0x64: {"STZ", 2, 3, modeZeroPage, opSTZ},
0x74: {"STZ", 2, 4, modeZeroPageX, opSTZ},
@ -130,22 +130,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)},
0x1f: {"BBR1", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(1, false)},
0x2f: {"BBR2", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(2, false)},
0x3f: {"BBR3", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(3, false)},
0x4f: {"BBR4", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(4, false)},
0x5f: {"BBR5", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(5, false)},
0x6f: {"BBR6", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(6, false)},
0x7f: {"BBR7", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(7, false)},
0x8f: {"BBS0", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(0, true)},
0x9f: {"BBS1", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(1, true)},
0xaf: {"BBS2", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(2, true)},
0xbf: {"BBS3", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(3, true)},
0xcf: {"BBS4", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(4, true)},
0xdf: {"BBS5", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(5, true)},
0xef: {"BBS6", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(6, true)},
0xff: {"BBS7", 3, 2, modeZeroPageAndRelative, buildOpBranchOnBit(7, true)},
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
0x07: {"RMB0", 2, 5, modeZeroPage, buildOpSetBit(0, false)},
0x17: {"RMB1", 2, 5, modeZeroPage, buildOpSetBit(1, false)},

View File

@ -24,8 +24,9 @@ type State struct {
mem Memory
cycles uint64
extraCycle bool
lineCache []uint8
extraCycleCrossingBoundaries bool
extraCycleBranchTaken 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!!
}
@ -79,10 +80,16 @@ func (s *State) ExecuteInstruction() {
}
opcode.action(s, s.lineCache, opcode)
s.cycles += uint64(opcode.cycles)
if s.extraCycle {
if s.extraCycleBranchTaken {
s.cycles++
s.extraCycle = false
s.extraCycleBranchTaken = false
}
if s.extraCycleCrossingBoundaries {
s.cycles++
s.extraCycleCrossingBoundaries = false
}
if s.trace {
fmt.Printf("%v, [%02x]\n", s.reg, s.lineCache[0:opcode.bytes])
}

View File

@ -76,7 +76,7 @@ func buildOpUpdateFlag(flag uint8, value bool) opFunc {
func buildOpBranch(flag uint8, test bool) opFunc {
return func(s *State, line []uint8, opcode opcode) {
if s.reg.getFlag(flag) == test {
s.extraCycle = true
s.extraCycleBranchTaken = true
address := resolveAddress(s, line, opcode)
s.reg.setPC(address)
}
@ -92,6 +92,7 @@ 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)
}