From 95998ba37de3422c58959cb36ee297275c249bfb Mon Sep 17 00:00:00 2001 From: Ivan Izaguirre Date: Sun, 6 Dec 2020 21:18:20 +0100 Subject: [PATCH] Add proper extra cycle when adding an index cross boundaries on memory reads --- core6502/addressing.go | 61 +++++++++++++++++++++++++++++++++++++----- core6502/cmos65c02.go | 48 ++++++++++++++++----------------- core6502/execute.go | 15 ++++++++--- core6502/operations.go | 3 ++- 4 files changed, 91 insertions(+), 36 deletions(-) diff --git a/core6502/addressing.go b/core6502/addressing.go index 6d2e63e..730413f 100644 --- a/core6502/addressing.go +++ b/core6502/addressing.go @@ -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 { diff --git a/core6502/cmos65c02.go b/core6502/cmos65c02.go index 31c370e..28aa976 100644 --- a/core6502/cmos65c02.go +++ b/core6502/cmos65c02.go @@ -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)}, diff --git a/core6502/execute.go b/core6502/execute.go index d55f7f7..cdceb55 100644 --- a/core6502/execute.go +++ b/core6502/execute.go @@ -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]) } diff --git a/core6502/operations.go b/core6502/operations.go index 405de53..26814c2 100644 --- a/core6502/operations.go +++ b/core6502/operations.go @@ -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) }