mirror of
https://github.com/irmen/ksim65.git
synced 2024-06-06 07:29:29 +00:00
better breakpoints
This commit is contained in:
parent
60543f358b
commit
4601839d26
|
@ -1,10 +1,7 @@
|
|||
package razorvine.c64emu
|
||||
|
||||
import razorvine.examplemachines.DebugWindow
|
||||
import razorvine.ksim65.Bus
|
||||
import razorvine.ksim65.Cpu6502
|
||||
import razorvine.ksim65.IVirtualMachine
|
||||
import razorvine.ksim65.Version
|
||||
import razorvine.ksim65.*
|
||||
import razorvine.ksim65.components.Address
|
||||
import razorvine.ksim65.components.Ram
|
||||
import razorvine.ksim65.components.Rom
|
||||
|
@ -31,7 +28,7 @@ class C64Machine(title: String) : IVirtualMachine {
|
|||
private val kernalData = romsPath.resolve("kernal").toFile().readBytes()
|
||||
|
||||
override val bus = Bus()
|
||||
override val cpu = Cpu6502(false)
|
||||
override val cpu = Cpu6502()
|
||||
val ram = Ram(0x0000, 0xffff)
|
||||
val vic = VicII(0xd000, 0xd3ff)
|
||||
val cia1 = Cia(1, 0xdc00, 0xdcff)
|
||||
|
@ -46,6 +43,7 @@ class C64Machine(title: String) : IVirtualMachine {
|
|||
init {
|
||||
cpu.addBreakpoint(0xffd5, ::breakpointKernelLoad) // intercept LOAD subroutine in the kernal
|
||||
cpu.addBreakpoint(0xffd8, ::breakpointKernelSave) // intercept SAVE subroutine in the kernal
|
||||
cpu.breakpointForBRK = ::breakpointBRK
|
||||
|
||||
bus += basicRom
|
||||
bus += kernalRom
|
||||
|
@ -64,7 +62,7 @@ class C64Machine(title: String) : IVirtualMachine {
|
|||
hostDisplay.start()
|
||||
}
|
||||
|
||||
fun breakpointKernelLoad(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResult {
|
||||
fun breakpointKernelLoad(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResultAction {
|
||||
if (cpu.regA == 0) {
|
||||
val fnlen = ram[0xb7] // file name length
|
||||
val fa = ram[0xba] // device number
|
||||
|
@ -78,13 +76,13 @@ class C64Machine(title: String) : IVirtualMachine {
|
|||
ram[0x90] = 0 // status OK
|
||||
ram[0xae] = (loadEndAddress and 0xff).toShort()
|
||||
ram[0xaf] = (loadEndAddress ushr 8).toShort()
|
||||
Cpu6502.BreakpointResult(0xf5a9, 0) // success!
|
||||
} else Cpu6502.BreakpointResult(0xf704, null) // 'file not found'
|
||||
} else Cpu6502.BreakpointResult(0xf710, null) // 'missing file name'
|
||||
} else return Cpu6502.BreakpointResult(0xf707, null) // 'device not present' (VERIFY command not supported)
|
||||
Cpu6502.BreakpointResultAction(changePC = 0xf5a9) // success!
|
||||
} else Cpu6502.BreakpointResultAction(changePC = 0xf704) // 'file not found'
|
||||
} else Cpu6502.BreakpointResultAction(changePC = 0xf710) // 'missing file name'
|
||||
} else return Cpu6502.BreakpointResultAction(changePC = 0xf707) // 'device not present' (VERIFY command not supported)
|
||||
}
|
||||
|
||||
fun breakpointKernelSave(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResult {
|
||||
fun breakpointKernelSave(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResultAction {
|
||||
val fnlen = ram[0xb7] // file name length
|
||||
// val fa = ram[0xba] // device number
|
||||
// val sa = ram[0xb9] // secondary address
|
||||
|
@ -102,8 +100,12 @@ class C64Machine(title: String) : IVirtualMachine {
|
|||
it.write(data)
|
||||
}
|
||||
ram[0x90] = 0 // status OK
|
||||
Cpu6502.BreakpointResult(0xf5a9, 0) // success!
|
||||
} else Cpu6502.BreakpointResult(0xf710, null) // 'missing file name'
|
||||
Cpu6502.BreakpointResultAction(changePC = 0xf5a9) // success!
|
||||
} else Cpu6502.BreakpointResultAction(changePC = 0xf710) // 'missing file name'
|
||||
}
|
||||
|
||||
fun breakpointBRK(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResultAction {
|
||||
throw Cpu6502.InstructionError("BRK instruction hit at ${hexW(pc)}")
|
||||
}
|
||||
|
||||
private fun searchAndLoadFile(
|
||||
|
|
|
@ -15,7 +15,7 @@ import javax.swing.ImageIcon
|
|||
*/
|
||||
class EhBasicMachine(title: String) {
|
||||
val bus = Bus()
|
||||
val cpu = Cpu6502(false)
|
||||
val cpu = Cpu6502()
|
||||
val ram = Ram(0x0000, 0xbfff)
|
||||
val rom = Rom(0xc000, 0xffff).also { it.load(javaClass.getResourceAsStream("/ehbasic_C000.bin").readBytes()) }
|
||||
|
||||
|
|
|
@ -13,7 +13,7 @@ import javax.swing.ImageIcon
|
|||
*/
|
||||
class VirtualMachine(title: String) : IVirtualMachine {
|
||||
override val bus = Bus()
|
||||
override val cpu = Cpu6502(false)
|
||||
override val cpu = Cpu6502()
|
||||
val ram = Ram(0x0000, 0xffff)
|
||||
private val rtc = RealTimeClock(0xd100, 0xd108)
|
||||
private val timer = Timer(0xd200, 0xd203, cpu)
|
||||
|
|
|
@ -11,7 +11,7 @@ import razorvine.ksim65.components.UByte
|
|||
* TODO: actually implement the illegal opcodes, see http://www.ffd2.com/fridge/docs/6502-NMOS.extra.opcodes
|
||||
* TODO: add the optional additional cycles to certain instructions and addressing modes
|
||||
*/
|
||||
open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
||||
open class Cpu6502 : BusComponent() {
|
||||
open val name = "6502"
|
||||
var tracing: ((state:String) -> Unit)? = null
|
||||
var totalCycles = 0L
|
||||
|
@ -20,6 +20,8 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
|||
private var speedMeasureStart = System.nanoTime()
|
||||
private var resetTime = System.nanoTime()
|
||||
|
||||
var breakpointForBRK: BreakpointHandler? = null
|
||||
|
||||
class InstructionError(msg: String) : RuntimeException(msg)
|
||||
|
||||
companion object {
|
||||
|
@ -73,7 +75,21 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
|||
}
|
||||
}
|
||||
|
||||
class BreakpointResult(val newPC: Address?, val newOpcode: Int?)
|
||||
/**
|
||||
* Breakpoint handlers have to return this to specify to the CPU simulator
|
||||
* what should happen after the breakpoint code has executed.
|
||||
*
|
||||
* Setting changePC will continue execution from a different memory location.
|
||||
* Setting changeOpcode will execute a different opcode in place of the one
|
||||
* that's actually on the location of the breakpoint.
|
||||
* (it's a bit limited; you can only use one-byte instructions for this)
|
||||
* Setting causeBRK will simulate a software interrupt via BRK,
|
||||
* without having to actually have a BRK in the breakpoint's memory location
|
||||
* (this is the same as changeOpcode=0x00)
|
||||
*/
|
||||
class BreakpointResultAction(val changePC: Address? = null,
|
||||
val changeOpcode: Int? = null,
|
||||
val causeBRK: Boolean = false)
|
||||
|
||||
class State (
|
||||
val A: UByte,
|
||||
|
@ -171,10 +187,10 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
|||
// all other addressing modes yield a fetched memory address
|
||||
protected var fetchedAddress: Address = 0
|
||||
|
||||
private val breakpoints = mutableMapOf<Address, (cpu: Cpu6502, pc: Address) -> BreakpointResult>()
|
||||
private val breakpoints = mutableMapOf<Address, BreakpointHandler>()
|
||||
|
||||
fun addBreakpoint(address: Address, action: (cpu: Cpu6502, pc: Address) -> BreakpointResult) {
|
||||
breakpoints[address] = action
|
||||
fun addBreakpoint(address: Address, handler: BreakpointHandler) {
|
||||
breakpoints[address] = handler
|
||||
}
|
||||
|
||||
fun removeBreakpoint(address: Address) = breakpoints.remove(address)
|
||||
|
@ -331,24 +347,18 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
|||
currentOpcode = read(regPC)
|
||||
currentInstruction = instructions[currentOpcode]
|
||||
|
||||
// tracing and breakpoint handling
|
||||
tracing?.invoke(snapshot().toString())
|
||||
breakpoints[regPC]?.let {
|
||||
if(breakpoint(it))
|
||||
return
|
||||
}
|
||||
|
||||
breakpoints[regPC]?.let { breakpoint ->
|
||||
val oldPC = regPC
|
||||
val result = breakpoint(this, regPC)
|
||||
if(result.newPC!=null)
|
||||
regPC = result.newPC
|
||||
if (regPC != oldPC)
|
||||
return clock()
|
||||
else if(result.newOpcode!=null) {
|
||||
currentOpcode = result.newOpcode
|
||||
currentInstruction = instructions[currentOpcode]
|
||||
if(currentOpcode==0x00)
|
||||
breakpointForBRK?.let {
|
||||
if(breakpoint(it))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if (stopOnBrk && currentOpcode == 0) {
|
||||
throw InstructionError("stopped on BRK instruction at ${hexW(regPC)}")
|
||||
}
|
||||
}
|
||||
|
||||
regPC++
|
||||
|
@ -361,6 +371,29 @@ open class Cpu6502(private val stopOnBrk: Boolean = false) : BusComponent() {
|
|||
totalCycles++
|
||||
}
|
||||
|
||||
private fun breakpoint(handler: BreakpointHandler): Boolean {
|
||||
val oldPC = regPC
|
||||
val result = handler(this, regPC)
|
||||
|
||||
when {
|
||||
result.changePC != null -> regPC = result.changePC
|
||||
result.changeOpcode != null -> {
|
||||
currentOpcode = result.changeOpcode
|
||||
currentInstruction = instructions[currentOpcode]
|
||||
}
|
||||
result.causeBRK -> {
|
||||
currentOpcode = 0x00
|
||||
currentInstruction = instructions[0x00]
|
||||
}
|
||||
}
|
||||
|
||||
return if (regPC != oldPC) {
|
||||
clock()
|
||||
true
|
||||
} else
|
||||
false
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute one single complete instruction
|
||||
*/
|
||||
|
|
|
@ -6,7 +6,7 @@ import razorvine.ksim65.components.Address
|
|||
* 65C02 cpu simulation (the CMOS version of the 6502).
|
||||
* TODO: add the optional additional cycles to certain instructions and addressing modes
|
||||
*/
|
||||
class Cpu65C02(stopOnBrk: Boolean = false) : Cpu6502(stopOnBrk) {
|
||||
class Cpu65C02 : Cpu6502() {
|
||||
override val name = "65C02"
|
||||
|
||||
enum class Wait {
|
||||
|
|
|
@ -19,3 +19,5 @@ fun hexB(number: Int): String {
|
|||
val hiNibble = number ushr 4
|
||||
return hexdigits[hiNibble].toString() + hexdigits[loNibble]
|
||||
}
|
||||
|
||||
typealias BreakpointHandler = (cpu: Cpu6502, pc: Address) -> Cpu6502.BreakpointResultAction
|
||||
|
|
|
@ -5,7 +5,7 @@ import razorvine.ksim65.components.Ram
|
|||
|
||||
class C64KernalStubs(private val ram: Ram) {
|
||||
|
||||
fun handleBreakpoint(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResult {
|
||||
fun handleBreakpoint(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResultAction {
|
||||
when(pc) {
|
||||
0xffd2 -> {
|
||||
// CHROUT
|
||||
|
@ -15,7 +15,7 @@ class C64KernalStubs(private val ram: Ram) {
|
|||
println()
|
||||
else if(char in ' '..'~')
|
||||
print(char)
|
||||
return Cpu6502.BreakpointResult(null, 0x60) // perform an RTS to exit this subroutine
|
||||
return Cpu6502.BreakpointResultAction(null, 0x60) // perform an RTS to exit this subroutine
|
||||
}
|
||||
0xffe4 -> {
|
||||
// GETIN
|
||||
|
@ -40,7 +40,7 @@ class C64KernalStubs(private val ram: Ram) {
|
|||
}
|
||||
}
|
||||
|
||||
return Cpu6502.BreakpointResult(null, null)
|
||||
return Cpu6502.BreakpointResultAction(null, null)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@ import kotlin.test.*
|
|||
|
||||
abstract class FunctionalTestsBase {
|
||||
|
||||
val cpu: Cpu6502 = Cpu6502(stopOnBrk = false)
|
||||
val cpu: Cpu6502 = Cpu6502()
|
||||
val ram = Ram(0, 0xffff)
|
||||
val bus = Bus()
|
||||
val kernalStubs = C64KernalStubs(ram)
|
||||
|
|
|
@ -42,7 +42,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
class Test6502 : TestCommon6502() {
|
||||
// NMOS 6502 tests
|
||||
|
||||
override fun createCpu() = Cpu6502(false)
|
||||
override fun createCpu() = Cpu6502()
|
||||
|
||||
// ADC Indirect, Indexed (X)
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ class Test6502CpuBasics {
|
|||
|
||||
@Test
|
||||
fun testCpuFlagsAfterReset6502() {
|
||||
val cpu = Cpu6502(true)
|
||||
val cpu = Cpu6502()
|
||||
val bus = Bus()
|
||||
bus.add(cpu)
|
||||
cpu.reset()
|
||||
|
@ -30,7 +30,7 @@ class Test6502CpuBasics {
|
|||
|
||||
@Test
|
||||
fun testCpuFlagsAfterReset65c02() {
|
||||
val cpu = Cpu65C02(true)
|
||||
val cpu = Cpu65C02()
|
||||
val bus = Bus()
|
||||
bus.add(cpu)
|
||||
cpu.reset()
|
||||
|
@ -48,7 +48,7 @@ class Test6502CpuBasics {
|
|||
|
||||
@Test
|
||||
fun testCpuPerformance6502() {
|
||||
val cpu = Cpu6502(true)
|
||||
val cpu = Cpu6502()
|
||||
val ram = Ram(0x1000, 0x1fff)
|
||||
// load a simple program that loops a few instructions
|
||||
for(b in listOf(0xa9, 0x63, 0xaa, 0x86, 0x22, 0x8e, 0x22, 0x22, 0x91, 0x22, 0x6d, 0x33, 0x33, 0xcd, 0x55, 0x55, 0xd0, 0xee, 0xf0, 0xec).withIndex()) {
|
||||
|
@ -79,7 +79,7 @@ class Test6502CpuBasics {
|
|||
|
||||
@Test
|
||||
fun testCpuPerformance65C02() {
|
||||
val cpu = Cpu65C02(true)
|
||||
val cpu = Cpu65C02()
|
||||
val ram = Ram(0x0000, 0x1fff)
|
||||
// load a simple program that loops a few instructions
|
||||
for(b in listOf(0xa9, 0x63, 0xaa, 0x86, 0x22, 0x8e, 0x22, 0x22, 0x91, 0x22, 0x6d, 0x33, 0x33, 0xcd, 0x55, 0x55,
|
||||
|
@ -111,7 +111,7 @@ class Test6502CpuBasics {
|
|||
|
||||
@Test
|
||||
fun testBCD6502() {
|
||||
val cpu = Cpu6502(true)
|
||||
val cpu = Cpu6502()
|
||||
val bus = Bus()
|
||||
bus.add(cpu)
|
||||
val ram = Ram(0, 0xffff)
|
||||
|
@ -150,4 +150,31 @@ class Test6502CpuBasics {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testBRKbreakpoint() {
|
||||
val cpu = Cpu6502()
|
||||
val bus = Bus()
|
||||
val ram = Ram(0, 0x0ffff)
|
||||
ram[0xfffe] = 0x00
|
||||
ram[0xffff] = 0xc0
|
||||
ram[0x3333] = 0xea
|
||||
ram[0x3334] = 0xea
|
||||
ram[0x200] = 0x00
|
||||
bus.add(cpu)
|
||||
bus.add(ram)
|
||||
bus.reset()
|
||||
cpu.regPC = 0x200
|
||||
cpu.breakpointForBRK = null
|
||||
cpu.step()
|
||||
assertEquals(0xc000, cpu.regPC)
|
||||
cpu.regPC = 0x200
|
||||
cpu.breakpointForBRK = { theCpu, _ ->
|
||||
theCpu.regA = 123
|
||||
Cpu6502.BreakpointResultAction(changePC = 0x3333)
|
||||
}
|
||||
cpu.step()
|
||||
assertEquals(123, cpu.regA)
|
||||
assertEquals(0x3334, cpu.regPC)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -12,7 +12,7 @@ class Test6502Functional {
|
|||
|
||||
@Test
|
||||
fun testFunctional6502() {
|
||||
val cpu = Cpu6502(false)
|
||||
val cpu = Cpu6502()
|
||||
val bus = Bus()
|
||||
val ram = Ram(0, 0xffff)
|
||||
ram.load("src/test/kotlin/6502_functional_tests/bin_files/6502_functional_test.bin", 0)
|
||||
|
@ -24,7 +24,7 @@ class Test6502Functional {
|
|||
// reaching this address means successful test result
|
||||
if(cpu.currentOpcode==0x4c)
|
||||
throw SuccessfulTestResult()
|
||||
Cpu6502.BreakpointResult(null, null)
|
||||
Cpu6502.BreakpointResultAction(null, null)
|
||||
}
|
||||
|
||||
try {
|
||||
|
@ -44,7 +44,7 @@ class Test6502Functional {
|
|||
|
||||
@Test
|
||||
fun testFunctional65C02() {
|
||||
val cpu = Cpu65C02(false)
|
||||
val cpu = Cpu65C02()
|
||||
val bus = Bus()
|
||||
val ram = Ram(0, 0xffff)
|
||||
ram.load("src/test/kotlin/6502_functional_tests/bin_files/65C02_extended_opcodes_test.bin", 0)
|
||||
|
@ -56,7 +56,7 @@ class Test6502Functional {
|
|||
// reaching this address means successful test result
|
||||
if(cpu.currentOpcode==0x4c)
|
||||
throw SuccessfulTestResult()
|
||||
Cpu6502.BreakpointResult(null, null)
|
||||
Cpu6502.BreakpointResultAction(null, null)
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
@ -40,7 +40,7 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|||
@TestInstance(TestInstance.Lifecycle.PER_METHOD)
|
||||
class Test65C02 : TestCommon6502() {
|
||||
// CMOS 65C02 Tests
|
||||
override fun createCpu() = Cpu65C02(false)
|
||||
override fun createCpu() = Cpu65C02()
|
||||
|
||||
// Reset
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user