1
0
mirror of https://github.com/irmen/ksim65.git synced 2024-06-06 07:29:29 +00:00

better breakpoints

This commit is contained in:
Irmen de Jong 2019-09-29 11:29:11 +02:00
parent 60543f358b
commit 4601839d26
12 changed files with 115 additions and 51 deletions

View File

@ -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(

View File

@ -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()) }

View File

@ -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)

View File

@ -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
*/

View File

@ -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 {

View File

@ -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

View File

@ -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)
}
}

View File

@ -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)

View File

@ -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)

View File

@ -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)
}
}

View File

@ -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 {

View File

@ -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