From c619ddabf1b1a96ae51f8c14e9e15e65f4fcdc67 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Tue, 18 Feb 2020 22:01:12 +0100 Subject: [PATCH] introduced separate Disassembler class --- src/main/kotlin/razorvine/c64emu/c64Main.kt | 38 +++--- .../razorvine/examplemachines/DebugWindow.kt | 4 +- src/main/kotlin/razorvine/ksim65/Cpu6502.kt | 109 ++--------------- .../kotlin/razorvine/ksim65/Disassembler.kt | 110 ++++++++++++++++++ src/main/kotlin/razorvine/ksim65/Monitor.kt | 6 +- src/test/kotlin/FunctionalTestsBase.kt | 12 +- src/test/kotlin/Test6502CpuBasics.kt | 7 +- .../kotlin/Test6502TestSuiteC64Specific.kt | 91 ++++++++++++++- src/test/kotlin/TestDisassembler.kt | 10 +- 9 files changed, 252 insertions(+), 135 deletions(-) create mode 100644 src/main/kotlin/razorvine/ksim65/Disassembler.kt diff --git a/src/main/kotlin/razorvine/c64emu/c64Main.kt b/src/main/kotlin/razorvine/c64emu/c64Main.kt index 224bc7e..ed1dc83 100644 --- a/src/main/kotlin/razorvine/c64emu/c64Main.kt +++ b/src/main/kotlin/razorvine/c64emu/c64Main.kt @@ -198,24 +198,6 @@ class C64Machine(title: String) : IVirtualMachine { return listing.toTypedArray() } - private fun determineRomPath(): Path { - val candidates = listOf("./roms", "~/roms/c64", "~/roms", "~/.vice/C64") - candidates.forEach { - val path = Paths.get(expandUser(it)) - if (path.toFile().isDirectory) return path - } - throw FileNotFoundException("no roms directory found, tried: $candidates") - } - - private fun expandUser(path: String): String { - return when { - path.startsWith("~/") -> System.getProperty("user.home")+path.substring(1) - path.startsWith("~"+File.separatorChar) -> System.getProperty("user.home")+path.substring(1) - path.startsWith("~") -> throw UnsupportedOperationException("home dir expansion not implemented for other users") - else -> path - } - } - override fun loadFileInRam(file: File, loadAddress: Address?) { if (file.extension == "prg" && (loadAddress == null || loadAddress == 0x0801)) ram.loadPrg(file.inputStream(), null) else ram.load(file.readBytes(), loadAddress!!) @@ -268,6 +250,26 @@ class C64Machine(title: String) : IVirtualMachine { } } + +fun determineRomPath(): Path { + val candidates = listOf("./roms", "~/roms/c64", "~/roms", "~/.vice/C64") + candidates.forEach { + val path = Paths.get(expandUser(it)) + if (path.toFile().isDirectory) return path + } + throw FileNotFoundException("no roms directory found, tried: $candidates") +} + +fun expandUser(path: String): String { + return when { + path.startsWith("~/") -> System.getProperty("user.home")+path.substring(1) + path.startsWith("~"+File.separatorChar) -> System.getProperty("user.home")+path.substring(1) + path.startsWith("~") -> throw UnsupportedOperationException("home dir expansion not implemented for other users") + else -> path + } +} + + fun main() { val machine = C64Machine("virtual Commodore-64 - using KSim65 v${Version.version}") machine.start() diff --git a/src/main/kotlin/razorvine/examplemachines/DebugWindow.kt b/src/main/kotlin/razorvine/examplemachines/DebugWindow.kt index 542b6a1..c50f293 100644 --- a/src/main/kotlin/razorvine/examplemachines/DebugWindow.kt +++ b/src/main/kotlin/razorvine/examplemachines/DebugWindow.kt @@ -44,6 +44,7 @@ class DebugWindow(private val vm: IVirtualMachine) : JFrame("Debugger - ksim65 v it.disabledTextColor = Color.DARK_GRAY it.font = Font(Font.MONOSPACED, Font.PLAIN, 12) } + private val disassembler = Disassembler(vm.cpu) init { contentPane.layout = GridBagLayout() @@ -217,7 +218,8 @@ class DebugWindow(private val vm: IVirtualMachine) : JFrame("Debugger - ksim65 v regSPtf.text = hexB(state.SP) val memory = listOf(bus[state.PC], bus[state.PC+1], bus[state.PC+2]).toTypedArray() - val disassem = cpu.disassembleOneInstruction(memory, 0, state.PC).first.substringAfter(' ').trim() + + val disassem = disassembler.disassembleOneInstruction(memory, 0, state.PC).first.substringAfter(' ').trim() disassemTf.text = disassem if (zeropageTf.isVisible || stackpageTf.isVisible) { diff --git a/src/main/kotlin/razorvine/ksim65/Cpu6502.kt b/src/main/kotlin/razorvine/ksim65/Cpu6502.kt index da05015..7fa51ce 100644 --- a/src/main/kotlin/razorvine/ksim65/Cpu6502.kt +++ b/src/main/kotlin/razorvine/ksim65/Cpu6502.kt @@ -29,7 +29,14 @@ open class Cpu6502 : BusComponent() { class StatusRegister(var C: Boolean = false, var Z: Boolean = false, var I: Boolean = false, var D: Boolean = false, var B: Boolean = false, var V: Boolean = false, var N: Boolean = false) { fun asInt(): Int { - return (0b00100000 or (if (N) 0b10000000 else 0) or (if (V) 0b01000000 else 0) or (if (B) 0b00010000 else 0) or (if (D) 0b00001000 else 0) or (if (I) 0b00000100 else 0) or (if (Z) 0b00000010 else 0) or (if (C) 0b00000001 else 0)) + return (0b00100000 + or (if (N) 0b10000000 else 0) + or (if (V) 0b01000000 else 0) + or (if (B) 0b00010000 else 0) + or (if (D) 0b00001000 else 0) + or (if (I) 0b00000100 else 0) + or (if (Z) 0b00000010 else 0) + or (if (C) 0b00000001 else 0)) } fun fromInt(byte: Int) { @@ -128,106 +135,6 @@ open class Cpu6502 : BusComponent() { fun removeBreakpoint(address: Address) = breakpoints.remove(address) - fun disassemble(memory: Array, range: IntRange, baseAddress: Address): Pair, Address> { - var offset = range.first - val result = mutableListOf() - while (offset <= range.last) { - val dis = disassembleOneInstruction(memory, offset, baseAddress) - result.add(dis.first) - offset += dis.second - } - return Pair(result, offset+baseAddress) - } - - fun disassembleOneInstruction(memory: Array, offset: Int, baseAddress: Address): Pair { - val spacing1 = " " - val spacing2 = " " - val spacing3 = " " - val byte = memory[offset] - val line = "\$${hexW(offset+baseAddress)} ${hexB(byte)} " - val opcode = instructions[byte.toInt()] - return when (opcode.mode) { - AddrMode.Acc -> { - Pair(line+"$spacing1 ${opcode.mnemonic} a", 1) - } - AddrMode.Imp -> { - Pair(line+"$spacing1 ${opcode.mnemonic}", 1) - } - AddrMode.Imm -> { - val value = memory[offset+1] - Pair(line+"${hexB(value)} $spacing2 ${opcode.mnemonic} #\$${hexB(value)}", 2) - } - AddrMode.Zp -> { - val zpAddr = memory[offset+1] - Pair(line+"${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB(zpAddr)}", 2) - } - AddrMode.Zpr -> { - // addressing mode used by the 65C02, put here for convenience rather than the subclass - val zpAddr = memory[offset+1] - val rel = memory[offset+2] - val target = (if (rel <= 0x7f) offset+3+rel+baseAddress else offset+3-(256-rel)+baseAddress) and 0xffff - Pair(line+"${hexB(zpAddr)} ${hexB(rel)} $spacing3 ${opcode.mnemonic} \$${hexB(zpAddr)}, \$${hexW(target, true)}", 3) - } - AddrMode.Izp -> { - // addressing mode used by the 65C02, put here for convenience rather than the subclass - val zpAddr = memory[offset+1] - Pair(line+"${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$(${hexB(zpAddr)})", 2) - } - AddrMode.IaX -> { - // addressing mode used by the 65C02, put here for convenience rather than the subclass - val lo = memory[offset+1] - val hi = memory[offset+2] - val absAddr = lo.toInt() or (hi.toInt() shl 8) - Pair(line+"${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$(${hexW(absAddr)},x)", 3) - } - AddrMode.ZpX -> { - val zpAddr = memory[offset+1] - Pair(line+"${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB(zpAddr)},x", 2) - } - AddrMode.ZpY -> { - val zpAddr = memory[offset+1] - Pair(line+"${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB(zpAddr)},y", 2) - } - AddrMode.Rel -> { - val rel = memory[offset+1] - val target = (if (rel <= 0x7f) offset+2+rel+baseAddress else offset+2-(256-rel)+baseAddress) and 0xffff - Pair(line+"${hexB(rel)} $spacing2 ${opcode.mnemonic} \$${hexW(target, true)}", 2) - } - AddrMode.Abs -> { - val lo = memory[offset+1] - val hi = memory[offset+2] - val absAddr = lo.toInt() or (hi.toInt() shl 8) - Pair(line+"${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)}", 3) - } - AddrMode.AbsX -> { - val lo = memory[offset+1] - val hi = memory[offset+2] - val absAddr = lo.toInt() or (hi.toInt() shl 8) - Pair(line+"${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)},x", 3) - } - AddrMode.AbsY -> { - val lo = memory[offset+1] - val hi = memory[offset+2] - val absAddr = lo.toInt() or (hi.toInt() shl 8) - Pair(line+"${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)},y", 3) - } - AddrMode.Ind -> { - val lo = memory[offset+1] - val hi = memory[offset+2] - val indirectAddr = lo.toInt() or (hi.toInt() shl 8) - Pair(line+"${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} (\$${hexW(indirectAddr)})", 3) - } - AddrMode.IzX -> { - val zpAddr = memory[offset+1] - Pair(line+"${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} (\$${hexB(zpAddr)},x)", 2) - } - AddrMode.IzY -> { - val zpAddr = memory[offset+1] - Pair(line+"${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} (\$${hexB(zpAddr)}),y", 2) - } - } - } - /** * Reset the cpu */ diff --git a/src/main/kotlin/razorvine/ksim65/Disassembler.kt b/src/main/kotlin/razorvine/ksim65/Disassembler.kt new file mode 100644 index 0000000..43914b6 --- /dev/null +++ b/src/main/kotlin/razorvine/ksim65/Disassembler.kt @@ -0,0 +1,110 @@ +package razorvine.ksim65 + +import razorvine.ksim65.components.Address +import razorvine.ksim65.components.UByte + + +class Disassembler(cpu: Cpu6502) { + + private val instructions = cpu.instructions + + fun disassemble(memory: Array, range: IntRange, baseAddress: Address): Pair, Address> { + var offset = range.first + val result = mutableListOf() + while (offset <= range.last) { + val dis = disassembleOneInstruction(memory, offset, baseAddress) + result.add(dis.first) + offset += dis.second + } + return Pair(result, offset+baseAddress) + } + + fun disassembleOneInstruction(memory: Array, offset: Int, baseAddress: Address): Pair { + val spacing1 = " " + val spacing2 = " " + val spacing3 = " " + val byte = memory[offset] + val line = "\$${hexW(offset+baseAddress)} ${hexB(byte)} " + val opcode = instructions[byte.toInt()] + return when (opcode.mode) { + Cpu6502.AddrMode.Acc -> { + Pair(line+"$spacing1 ${opcode.mnemonic} a", 1) + } + Cpu6502.AddrMode.Imp -> { + Pair(line+"$spacing1 ${opcode.mnemonic}", 1) + } + Cpu6502.AddrMode.Imm -> { + val value = memory[offset+1] + Pair(line+"${hexB(value)} $spacing2 ${opcode.mnemonic} #\$${hexB(value)}", 2) + } + Cpu6502.AddrMode.Zp -> { + val zpAddr = memory[offset+1] + Pair(line+"${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB(zpAddr)}", 2) + } + Cpu6502.AddrMode.Zpr -> { + // addressing mode used by the 65C02, put here for convenience rather than the subclass + val zpAddr = memory[offset+1] + val rel = memory[offset+2] + val target = (if (rel <= 0x7f) offset+3+rel+baseAddress else offset+3-(256-rel)+baseAddress) and 0xffff + Pair(line+"${hexB(zpAddr)} ${hexB(rel)} $spacing3 ${opcode.mnemonic} \$${hexB(zpAddr)}, \$${hexW(target, true)}", 3) + } + Cpu6502.AddrMode.Izp -> { + // addressing mode used by the 65C02, put here for convenience rather than the subclass + val zpAddr = memory[offset+1] + Pair(line+"${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$(${hexB(zpAddr)})", 2) + } + Cpu6502.AddrMode.IaX -> { + // addressing mode used by the 65C02, put here for convenience rather than the subclass + val lo = memory[offset+1] + val hi = memory[offset+2] + val absAddr = lo.toInt() or (hi.toInt() shl 8) + Pair(line+"${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$(${hexW(absAddr)},x)", 3) + } + Cpu6502.AddrMode.ZpX -> { + val zpAddr = memory[offset+1] + Pair(line+"${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB(zpAddr)},x", 2) + } + Cpu6502.AddrMode.ZpY -> { + val zpAddr = memory[offset+1] + Pair(line+"${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} \$${hexB(zpAddr)},y", 2) + } + Cpu6502.AddrMode.Rel -> { + val rel = memory[offset+1] + val target = (if (rel <= 0x7f) offset+2+rel+baseAddress else offset+2-(256-rel)+baseAddress) and 0xffff + Pair(line+"${hexB(rel)} $spacing2 ${opcode.mnemonic} \$${hexW(target, true)}", 2) + } + Cpu6502.AddrMode.Abs -> { + val lo = memory[offset+1] + val hi = memory[offset+2] + val absAddr = lo.toInt() or (hi.toInt() shl 8) + Pair(line+"${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)}", 3) + } + Cpu6502.AddrMode.AbsX -> { + val lo = memory[offset+1] + val hi = memory[offset+2] + val absAddr = lo.toInt() or (hi.toInt() shl 8) + Pair(line+"${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)},x", 3) + } + Cpu6502.AddrMode.AbsY -> { + val lo = memory[offset+1] + val hi = memory[offset+2] + val absAddr = lo.toInt() or (hi.toInt() shl 8) + Pair(line+"${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} \$${hexW(absAddr)},y", 3) + } + Cpu6502.AddrMode.Ind -> { + val lo = memory[offset+1] + val hi = memory[offset+2] + val indirectAddr = lo.toInt() or (hi.toInt() shl 8) + Pair(line+"${hexB(lo)} ${hexB(hi)} $spacing3 ${opcode.mnemonic} (\$${hexW(indirectAddr)})", 3) + } + Cpu6502.AddrMode.IzX -> { + val zpAddr = memory[offset+1] + Pair(line+"${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} (\$${hexB(zpAddr)},x)", 2) + } + Cpu6502.AddrMode.IzY -> { + val zpAddr = memory[offset+1] + Pair(line+"${hexB(zpAddr)} $spacing2 ${opcode.mnemonic} (\$${hexB(zpAddr)}),y", 2) + } + } + } +} diff --git a/src/main/kotlin/razorvine/ksim65/Monitor.kt b/src/main/kotlin/razorvine/ksim65/Monitor.kt index 5a35db0..a04e35d 100644 --- a/src/main/kotlin/razorvine/ksim65/Monitor.kt +++ b/src/main/kotlin/razorvine/ksim65/Monitor.kt @@ -12,6 +12,8 @@ class Monitor(val bus: Bus, val cpu: Cpu6502) { instr.toMap() } + private val disassembler = Disassembler(cpu) + fun command(command: String): IVirtualMachine.MonitorCmdResult { if (command.isEmpty()) return IVirtualMachine.MonitorCmdResult("", "", false) @@ -97,7 +99,7 @@ class Monitor(val bus: Bus, val cpu: Cpu6502) { val start = parseNumber(addresses[0]) val end = if (addresses.size > 1) parseNumber(addresses[1]) else start val memory = (start .. max(0xffff, end+3)).map {bus[it]}.toTypedArray() - val disassem = cpu.disassemble(memory, 0 .. end-start, start) + val disassem = disassembler.disassemble(memory, 0 .. end-start, start) IVirtualMachine.MonitorCmdResult(disassem.first.joinToString("\n") { "d$it" }, "d$${hexW(disassem.second)}", false) } else -> { @@ -238,7 +240,7 @@ class Monitor(val bus: Bus, val cpu: Cpu6502) { } val memory = listOf(bus[address], bus[address+1], bus[address+2]).toTypedArray() - val disassem = cpu.disassembleOneInstruction(memory, 0, address) + val disassem = disassembler.disassembleOneInstruction(memory, 0, address) return IVirtualMachine.MonitorCmdResult(disassem.first, "a$${hexW(disassem.second + address)} ", false) } diff --git a/src/test/kotlin/FunctionalTestsBase.kt b/src/test/kotlin/FunctionalTestsBase.kt index 59564e1..4908a5b 100644 --- a/src/test/kotlin/FunctionalTestsBase.kt +++ b/src/test/kotlin/FunctionalTestsBase.kt @@ -19,13 +19,16 @@ abstract class FunctionalTestsBase { cpu.addBreakpoint(0xffd2) { cpu, pc -> kernalStubs.handleBreakpoint(cpu, pc) } cpu.addBreakpoint(0xffe4) { cpu, pc -> kernalStubs.handleBreakpoint(cpu, pc) } cpu.addBreakpoint(0xe16f) { cpu, pc -> kernalStubs.handleBreakpoint(cpu, pc) } + cpu.addBreakpoint(0x8000) { cpu, pc -> kernalStubs.handleBreakpoint(cpu, pc) } + cpu.addBreakpoint(0xa474) { cpu, pc -> kernalStubs.handleBreakpoint(cpu, pc) } bus.add(cpu) bus.add(ram) + bus.reset() } protected fun runTest(testprogram: String) { - ram.fill(0) - // setup the irq/brk routine TODO is this the right code? + // setup the irq/brk routine and other stubbing + // http://www.softwolves.com/arkiv/cbm-hackers/7/7114.html for(b in listOf(0x48, 0x8A, 0x48, 0x98, 0x48, 0xBA, 0xBD, 0x04, 0x01, 0x29, 0x10, 0xF0, 0x03, 0x6C, 0x16, 0x03, 0x6C, 0x14, 0x03).withIndex()) { @@ -41,9 +44,8 @@ abstract class FunctionalTestsBase { ram[Cpu6502.RESET_vector + 1] = 0x08 ram[0x01fe] = 0xff ram[0x01ff] = 0x7f - ram[0x8000] = 2 - ram[0xa474] = 2 - bus.reset() + cpu.regP.fromInt(4) + cpu.regPC = 0x0801 try { while (cpu.totalCycles < 40000000L) { bus.clock() diff --git a/src/test/kotlin/Test6502CpuBasics.kt b/src/test/kotlin/Test6502CpuBasics.kt index 0f08455..818abb9 100644 --- a/src/test/kotlin/Test6502CpuBasics.kt +++ b/src/test/kotlin/Test6502CpuBasics.kt @@ -234,6 +234,7 @@ class Test6502CpuBasics { val cpu = NesCpu() val ram = Ram(0, 0xffff) + val disassembler = Disassembler(cpu) val bytes = javaClass.getResource("nestest.nes").readBytes().drop(0x10).take(0x4000).toByteArray() ram.load(bytes, 0x8000) @@ -247,16 +248,16 @@ class Test6502CpuBasics { val neslog = javaClass.getResource("nestest.log").readText().lineSequence() for(logline in neslog) { - val s = cpu.snapshot() + val s = cpu.snapshot() // TODO use cpu.tracing instead val nesAddressHex = logline.substring(0, 4).toInt(16) assertEquals(nesAddressHex, s.PC) println("NES: $logline") - val disassem = cpu.disassembleOneInstruction(ram.data, s.PC, 0).first.substring(1) + val disassem = disassembler.disassembleOneInstruction(ram.data, s.PC, 0).first.substring(1) val spaces = " ".substring(disassem.length-1) println("EMU: $disassem $spaces A:${hexB(s.A)} X:${hexB(s.X)} Y:${hexB(s.Y)} P:${hexB(s.P.asInt())} SP:${hexB(s.SP)} PPU: 0, 0 CYC:${s.cycles}") - // TODO snapshotting as per https://forums.nesdev.com/viewtopic.php?t=19117 (i.e. BEFORE instruction gets executed): + // TODO use cpu.tracing, as per https://forums.nesdev.com/viewtopic.php?t=19117 (i.e. BEFORE instruction gets executed): // "before fetching the first operation code byte, make an internal record of the program counter and other registers; // after reading the final operand byte, log all the values you stored back in step (1) plus the full instruction and its disassembly." diff --git a/src/test/kotlin/Test6502TestSuiteC64Specific.kt b/src/test/kotlin/Test6502TestSuiteC64Specific.kt index 911f62e..968276d 100644 --- a/src/test/kotlin/Test6502TestSuiteC64Specific.kt +++ b/src/test/kotlin/Test6502TestSuiteC64Specific.kt @@ -1,13 +1,99 @@ import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.parallel.Execution import org.junit.jupiter.api.parallel.ExecutionMode +import razorvine.c64emu.* +import razorvine.ksim65.Bus +import razorvine.ksim65.Cpu6502 +import razorvine.ksim65.components.Ram +import razorvine.ksim65.components.Rom import kotlin.test.* // TODO: run these tests by using the C64 machine emulation components @Execution(ExecutionMode.CONCURRENT) -@Disabled("test code is not using C64 specific components yet") -class Test6502TestSuiteC64Specific: FunctionalTestsBase() { +// @Disabled("test code is not using C64 specific components yet") +class Test6502TestSuiteC64Specific { + + val cpu: Cpu6502 = Cpu6502() + val ioPort = CpuIoPort(cpu) + val ram = Ram(0, 0xffff) + val bus: Bus + val kernalStubs = C64KernalStubs(ram) + + init { + val romsPath = determineRomPath() + val chargenRom = Rom(0xd000, 0xdfff).also { + val chargenData = romsPath.resolve("chargen").toFile().readBytes() + it.load(chargenData) + } + val basicRom = Rom(0xa000, 0xbfff).also { + val basicData = romsPath.resolve("basic").toFile().readBytes() + it.load(basicData) + } + val kernalRom = Rom(0xe000, 0xffff).also { + val kernalData = romsPath.resolve("kernal").toFile().readBytes() + it.load(kernalData) + } + + cpu.tracing = null + cpu.addBreakpoint(0xffd2) { cpu, pc -> kernalStubs.handleBreakpoint(cpu, pc) } + cpu.addBreakpoint(0xffe4) { cpu, pc -> kernalStubs.handleBreakpoint(cpu, pc) } + cpu.addBreakpoint(0xe16f) { cpu, pc -> kernalStubs.handleBreakpoint(cpu, pc) } + cpu.addBreakpoint(0x8000) { cpu, pc -> kernalStubs.handleBreakpoint(cpu, pc) } + cpu.addBreakpoint(0xa474) { cpu, pc -> kernalStubs.handleBreakpoint(cpu, pc) } + + bus = Bus6510(ioPort, chargenRom, basicRom, kernalRom) + bus += VicII(0xd000, 0xd3ff, cpu) + bus += Cia(1, 0xdc00, 0xdcff, cpu) + bus += Cia(2, 0xdd00, 0xddff, cpu) + bus += ioPort + bus += cpu + bus += ram // note: the ROMs are mapped depending on the cpu's io port + bus.reset() + } + + private fun runTest(testprogram: String) { + // setup the irq/brk routine and other stubbing + // http://www.softwolves.com/arkiv/cbm-hackers/7/7114.html + bus[0] = 47 + bus[1] = 55 + for(b in listOf(0x48, 0x8A, 0x48, 0x98, 0x48, 0xBA, 0xBD, 0x04, + 0x01, 0x29, 0x10, 0xF0, 0x03, 0x6C, 0x16, 0x03, + 0x6C, 0x14, 0x03).withIndex()) { + ram[0xff48+b.index] = b.value.toShort() + } + ram.loadPrg("src/test/kotlin/6502testsuite/$testprogram", null) + ram[0x02] = 0 + ram[0xa002] = 0 + ram[0xa003] = 0x80 + ram[Cpu6502.IRQ_vector] = 0x48 + ram[Cpu6502.IRQ_vector + 1] = 0xff + ram[Cpu6502.RESET_vector] = 0x01 + ram[Cpu6502.RESET_vector + 1] = 0x08 + ram[0x01fe] = 0xff + ram[0x01ff] = 0x7f + cpu.regPC = 0x0801 + cpu.regP.fromInt(4) + try { + while (cpu.totalCycles < 40000000L) { + bus.clock() + } + fail("test hangs: " + cpu.snapshot()) + } catch (e: Cpu6502.InstructionError) { + println(">>> INSTRUCTION ERROR: ${e.message}") + } catch (le: KernalLoadNextPart) { + return // test ok + } catch (ie: KernalInputRequired) { + fail("test failed") + } + fail("test failed") + } + + @Test + fun testRegularShouldSucceed() { + // as long as this one doesn't succeed, there's something wrong with the test setup + runTest("adca") + } @Test fun testCia1pb6() { @@ -71,6 +157,7 @@ class Test6502TestSuiteC64Specific: FunctionalTestsBase() { @Test fun testCnto2() { + // todo fix: When the timer input is switched from o2 to CNT or from CNT back to o2, there must be a two clock delay until the switch is recognized. runTest("cnto2") } diff --git a/src/test/kotlin/TestDisassembler.kt b/src/test/kotlin/TestDisassembler.kt index 9a3a94c..4ef21d9 100644 --- a/src/test/kotlin/TestDisassembler.kt +++ b/src/test/kotlin/TestDisassembler.kt @@ -1,5 +1,6 @@ import razorvine.ksim65.Cpu6502 import razorvine.ksim65.Cpu65C02 +import razorvine.ksim65.Disassembler import razorvine.ksim65.components.Ram import kotlin.test.* @@ -9,10 +10,11 @@ class TestDisassembler { @Test fun testDisassembleAll6502Opcodes() { val cpu = Cpu6502() + val disassembler = Disassembler(cpu) val memory = Ram(0, 0xffff) val binfile = javaClass.classLoader.getResourceAsStream("disassem_instr_test.prg")?.readBytes()!! memory.load(binfile, 0x1000-2) - val result = cpu.disassemble(memory.data, 0x1000..0x1221, 0) + val result = disassembler.disassemble(memory.data, 0x1000..0x1221, 0) assertEquals(256, result.first.size) assertEquals(0x1222, result.second) assertEquals("\$1000 69 01 adc #\$01", result.first[0]) @@ -29,10 +31,11 @@ class TestDisassembler { @Test fun testDisassembleRockwell65C02() { val cpu = Cpu65C02() + val disassembler = Disassembler(cpu) val memory = Ram(0, 0x0fff) val source = javaClass.classLoader.getResource("disassem_r65c02.bin").readBytes() memory.load(source, 0x0200) - val disassem = cpu.disassemble(memory.data, 0x0200..0x0250, 0) + val disassem = disassembler.disassemble(memory.data, 0x0200..0x0250, 0) assertEquals(0x251, disassem.second) val result = disassem.first.joinToString("\n") assertEquals("""${'$'}0200 07 12 rmb0 ${'$'}12 @@ -75,10 +78,11 @@ ${'$'}0250 00 brk""", result) @Test fun testDisassembleWDC65C02() { val cpu = Cpu65C02() + val disassembler = Disassembler(cpu) val memory = Ram(0, 0x0fff) val source = javaClass.classLoader.getResource("disassem_wdc65c02.bin").readBytes() memory.load(source, 0x200) - val disassem = cpu.disassemble(memory.data, 0x0200..0x0215, 0) + val disassem = disassembler.disassemble(memory.data, 0x0200..0x0215, 0) assertEquals(0x216, disassem.second) val result = disassem.first.joinToString("\n") assertEquals("""${'$'}0200 cb wai