Add proper extra cycle when adding an index cross boundaries on memory reads
This commit is contained in:
parent
9c89ad4a61
commit
95998ba37d
|
@ -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 {
|
||||
|
|
|
@ -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)},
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue