1
0
mirror of https://github.com/irmen/ksim65.git synced 2024-06-01 06:41:34 +00:00

added most of a built-in machine code monitor

This commit is contained in:
Irmen de Jong 2019-10-09 03:24:51 +02:00
parent fbdc08d696
commit af4e901f6c
8 changed files with 243 additions and 20 deletions

View File

@ -40,6 +40,7 @@ class C64Machine(title: String) : IVirtualMachine {
private val debugWindow = DebugWindow(this)
private val hostDisplay = MainC64Window(title, chargenData, ram, cpu, cia1)
private val monitor = Monitor(bus, cpu)
private var paused = false
init {
@ -240,6 +241,8 @@ class C64Machine(title: String) : IVirtualMachine {
while (cpu.instrCycles > 0) bus.clock()
}
override fun executeMonitorCommand(command: String) = monitor.command(command)
fun start() {
javax.swing.Timer(50) {
debugWindow.updateCpu(cpu, bus)

View File

@ -251,6 +251,28 @@ class DebugWindow(private val vm: IVirtualMachine) : JFrame("Debugger - ksim65 v
zeropagePanel.add(zeropageTf)
zeropagePanel.add(stackpageTf)
val monitorPanel = JPanel()
monitorPanel.layout = BoxLayout(monitorPanel, BoxLayout.Y_AXIS)
monitorPanel.border = BorderFactory.createTitledBorder("Built-in Monitor")
val output = JTextArea(5, 50)
output.font = Font(Font.MONOSPACED, Font.PLAIN, 14)
output.isEditable = false
val outputScroll = JScrollPane(output)
monitorPanel.add(outputScroll)
val input = JTextField(50)
input.border = BorderFactory.createLineBorder(Color.LIGHT_GRAY)
input.font = Font(Font.MONOSPACED, Font.PLAIN, 14)
input.addActionListener {
output.append("\n")
val command = input.text.trim()
val result = vm.executeMonitorCommand(command)
if(result.echo)
output.append("> $command\n")
output.append(result.output)
input.text = result.prompt
}
monitorPanel.add(input)
gc.gridx=0
gc.gridy=0
gc.fill=GridBagConstraints.BOTH
@ -258,6 +280,8 @@ class DebugWindow(private val vm: IVirtualMachine) : JFrame("Debugger - ksim65 v
gc.gridy++
contentPane.add(zeropagePanel, gc)
gc.gridy++
contentPane.add(monitorPanel, gc)
gc.gridy++
contentPane.add(buttonPanel, gc)
pack()
}

View File

@ -1,9 +1,6 @@
package razorvine.examplemachines
import razorvine.ksim65.Bus
import razorvine.ksim65.Cpu6502
import razorvine.ksim65.IVirtualMachine
import razorvine.ksim65.Version
import razorvine.ksim65.*
import razorvine.ksim65.components.*
import java.io.File
import javax.swing.ImageIcon
@ -21,6 +18,7 @@ class VirtualMachine(title: String) : IVirtualMachine {
private val rtc = RealTimeClock(0xd100, 0xd108)
private val timer = Timer(0xd200, 0xd203, cpu)
private val monitor = Monitor(bus, cpu)
private val debugWindow = DebugWindow(this)
private val hostDisplay = MainWindow(title)
private val display = Display(
@ -74,6 +72,8 @@ class VirtualMachine(title: String) : IVirtualMachine {
while (cpu.instrCycles > 0) bus.clock()
}
override fun executeMonitorCommand(command: String) = monitor.command(command)
fun start() {
javax.swing.Timer(50) {
debugWindow.updateCpu(cpu, bus)

View File

@ -116,7 +116,7 @@ open class Cpu6502 : BusComponent() {
}
}
protected enum class AddrMode {
enum class AddrMode {
Imp,
Acc,
Imm,
@ -135,7 +135,7 @@ open class Cpu6502 : BusComponent() {
IaX, // special addressing mode used by the 65C02
}
protected class Instruction(val mnemonic: String, val mode: AddrMode, val cycles: Int)
class Instruction(val mnemonic: String, val mode: AddrMode, val cycles: Int)
var regA: Int = 0
var regX: Int = 0
@ -198,7 +198,7 @@ open class Cpu6502 : BusComponent() {
fun disassemble(memory: MemoryComponent, from: Address, to: Address) =
disassemble(memory.data, memory.startAddress, from, to)
fun disassemble(memory: Array<UByte>, baseAddress: Address, from: Address, to: Address): List<String> {
fun disassemble(memory: Array<UByte>, baseAddress: Address, from: Address, to: Address): Pair<List<String>, Address> {
var location = from
val result = mutableListOf<String>()
@ -208,7 +208,7 @@ open class Cpu6502 : BusComponent() {
location += dis.second
}
return result
return Pair(result, location)
}
fun disassembleOneInstruction(memory: Array<UByte>, address: Address, baseAddress: Address): Pair<String, Int> {
@ -459,7 +459,7 @@ open class Cpu6502 : BusComponent() {
protected fun write(address: Address, data: Int) = bus.write(address, data.toShort())
// opcodes table from http://www.oxyron.de/html/opcodes02.html
protected open val instructions: Array<Instruction> =
open val instructions: Array<Instruction> =
listOf(
/* 00 */ Instruction("brk", AddrMode.Imp, 7),
/* 01 */ Instruction("ora", AddrMode.IzX, 6),
@ -777,14 +777,14 @@ open class Cpu6502 : BusComponent() {
fetchedAddress = lo or (hi shl 8)
}
AddrMode.IzX -> {
// note: not able to fetch an address which crosses the page boundary
// note: not able to fetch an address which crosses the (zero)page boundary
fetchedAddress = readPc()
val lo = read((fetchedAddress + regX) and 0xff)
val hi = read((fetchedAddress + regX + 1) and 0xff)
fetchedAddress = lo or (hi shl 8)
}
AddrMode.IzY -> {
// note: not able to fetch an address which crosses the page boundary
// note: not able to fetch an address which crosses the (zero)page boundary
fetchedAddress = readPc()
val lo = read(fetchedAddress)
val hi = read((fetchedAddress + 1) and 0xff)

View File

@ -10,6 +10,9 @@ interface IVirtualMachine {
fun getZeroAndStackPages(): Array<UByte>
fun loadFileInRam(file: File, loadAddress: Address?)
class MonitorCmdResult(val output: String, val prompt: String, val echo: Boolean)
fun executeMonitorCommand(command: String): MonitorCmdResult
val cpu: Cpu6502
val bus: Bus
}

View File

@ -0,0 +1,190 @@
package razorvine.ksim65
class Monitor(val bus: Bus, val cpu: Cpu6502) {
private val instructions by lazy {
val instr = cpu.instructions.withIndex().associate {
Pair(it.value.mnemonic, it.value.mode) to it.index
}.toMutableMap()
instr[Pair("nop", Cpu6502.AddrMode.Imp)] = 0xea
instr.toMap()
}
fun command(command: String): IVirtualMachine.MonitorCmdResult {
if(command.isEmpty())
return IVirtualMachine.MonitorCmdResult("", "", false)
return when(command[0]) {
'f' -> {
val parts = command.substring(1).trim().split(' ')
if(parts.size!=3)
IVirtualMachine.MonitorCmdResult("?syntax error", command, false)
else {
val start = parseNumber(parts[0])
val end = parseNumber(parts[1])
val value = parseNumber(parts[2]).toShort()
for(addr in start..end) {
bus.write(addr, value)
}
IVirtualMachine.MonitorCmdResult("ok", "", true)
}
}
'm' -> {
// TODO add possibility to change bytes
val addresses = command.substring(1).trim().split(' ')
val start = parseNumber(addresses[0])
val end = if(addresses.size>1) parseNumber(addresses[1]) else start+1
val result = mutableListOf<String>()
for(addr in start until end step 16) {
result.add(
"m$${hexW(addr)} " +
(0..15).joinToString(" ") { hexB(bus.read(addr + it)) } + " " +
(0..15).joinToString("") {
val chr = bus.read(addr+it).toChar()
if(chr.isLetterOrDigit())
chr.toString()
else
"."
}
)
}
IVirtualMachine.MonitorCmdResult(result.joinToString("\n"), "", true)
}
'i' -> {
val addresses = command.substring(1).trim().split(' ')
val start = parseNumber(addresses[0])
val end = if(addresses.size>1) parseNumber(addresses[1]) else start+1
val result = mutableListOf<String>()
for(addr in start until end step 64) {
result.add(
"i$${hexW(addr)} " +
(0..63).joinToString("") {
val chr = bus.read(addr+it).toChar()
if(chr.isLetterOrDigit())
chr.toString()
else
"."
}
)
}
IVirtualMachine.MonitorCmdResult(result.joinToString("\n"), "", true)
}
'$' -> {
val number = parseNumber(command)
val output = "$${hexW(number)} #$number %${number.toString(2)}"
IVirtualMachine.MonitorCmdResult(output, "", true)
}
'#' -> {
val number = parseNumber(command)
val output = "$${hexW(number)} #$number %${number.toString(2)}"
IVirtualMachine.MonitorCmdResult(output, "", true)
}
'%' -> {
val number = parseNumber(command)
val output = "$${hexW(number)} #$number %${number.toString(2)}"
IVirtualMachine.MonitorCmdResult(output, "", true)
}
'g' -> {
val address = parseNumber(command.substring(1))
cpu.regPC = address
IVirtualMachine.MonitorCmdResult("", "", true)
}
'a' -> {
val parts = command.substring(1).trim().split(' ')
assemble(command, parts)
}
'd' -> {
val addresses = command.substring(1).trim().split(' ')
val start = parseNumber(addresses[0])
val end = if(addresses.size>1) parseNumber(addresses[1]) else start
val disassem = cpu.disassemble(bus.memoryComponentFor(start), start, end)
IVirtualMachine.MonitorCmdResult(disassem.first.joinToString("\n") { "d$it" }, "d$${hexW(disassem.second)}", false)
}
else -> {
IVirtualMachine.MonitorCmdResult("?unknown command", "", true)
}
}
}
private fun assemble(command: String, parts: List<String>): IVirtualMachine.MonitorCmdResult {
if(parts.size<2)
return IVirtualMachine.MonitorCmdResult("done", "", false)
val address = parseNumber(parts[0])
val mnemonic = parts[1].toLowerCase()
when {
parts.size==2 -> {
// implied or acc
var instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Imp)]
if(instruction==null)
instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Acc)]
if(instruction==null)
return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
bus.write(address, instruction.toShort())
val disassem = cpu.disassemble(bus.memoryComponentFor(address), address, address)
return IVirtualMachine.MonitorCmdResult(disassem.first.single(), "a$${hexW(disassem.second)} ", false)
}
parts.size==3 -> {
val arg = parts[2]
when {
arg.startsWith('#') -> {
// immediate
val instruction = instructions[Pair(mnemonic, Cpu6502.AddrMode.Imm)]
?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
bus.write(address, instruction.toShort())
bus.write(address+1, parseNumber(arg.substring(1), true).toShort())
val disassem = cpu.disassemble(bus.memoryComponentFor(address), address, address)
return IVirtualMachine.MonitorCmdResult(disassem.first.single(), "a$${hexW(disassem.second)} ", false)
}
arg.startsWith('(') -> // indirect or indirect+indexed
TODO("assemble indirect addrmode $arg")
arg.contains(",x") -> // indexed x
TODO("assemble indexed X addrmode $arg")
arg.contains(",y") -> // indexed y
TODO("assemble indexed X addrmode $arg")
else -> {
val instr = instructions[Pair(mnemonic, Cpu6502.AddrMode.Rel)]
if(instr!=null) {
// relative address
val rel = parseNumber(arg)
bus.write(address, instr.toShort())
bus.write(address+1, (rel-address-2 and 255).toShort())
} else {
// absolute or absZp
val absAddress = parseNumber(arg)
if (absAddress <= 255) {
val absInstr = instructions[Pair(mnemonic, Cpu6502.AddrMode.Zp)]
?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
bus.write(address, absInstr.toShort())
bus.write(address + 1, absAddress.toShort())
} else {
val absInstr = instructions[Pair(mnemonic, Cpu6502.AddrMode.Abs)]
?: return IVirtualMachine.MonitorCmdResult("?invalid instruction", command, false)
bus.write(address, absInstr.toShort())
bus.write(address + 1, (absAddress and 255).toShort())
bus.write(address + 2, (absAddress ushr 8).toShort())
}
}
val disassem = cpu.disassemble(bus.memoryComponentFor(address), address, address)
return IVirtualMachine.MonitorCmdResult(disassem.first.single(), "a$${hexW(disassem.second)} ", false)
}
}
}
else -> return IVirtualMachine.MonitorCmdResult("?syntax error", command, false)
}
}
private fun parseNumber(number: String, decimalFirst: Boolean = false): Int {
val num=number.trim()
if(num.isBlank())
return 0
if(decimalFirst && num[0].isDigit())
return num.toInt(10)
return when(num[0]) {
'$' -> num.substring(1).trimStart().toInt(16)
'#' -> num.substring(1).trimStart().toInt(10)
'%' -> num.substring(1).trimStart().toInt(2)
else -> num.toInt(16)
}
}
}

View File

@ -38,7 +38,7 @@ class Test6502Functional {
println(cpu.snapshot())
val d = cpu.disassemble(ram, cpu.regPC-20, cpu.regPC+20)
println(d.joinToString ("\n"))
println(d.first.joinToString ("\n"))
fail("test failed")
}
@ -70,7 +70,7 @@ class Test6502Functional {
println(cpu.snapshot())
val d = cpu.disassemble(ram, cpu.regPC-20, cpu.regPC+20)
println(d.joinToString ("\n"))
println(d.first.joinToString ("\n"))
fail("test failed")
}

View File

@ -13,12 +13,13 @@ class TestDisassembler {
val binfile = javaClass.classLoader.getResourceAsStream("disassem_instr_test.prg")?.readBytes()!!
memory.load(binfile, 0x1000-2)
val result = cpu.disassemble(memory, 0x1000, 0x1221)
assertEquals(256, result.size)
assertEquals("\$1000 69 01 adc #\$01", result[0])
assertEquals(256, result.first.size)
assertEquals(0x1222, result.second)
assertEquals("\$1000 69 01 adc #\$01", result.first[0])
val reference = javaClass.classLoader.getResource("disassem_ref_output.txt")?.readText()!!.trim().lines()
assertEquals(256, reference.size)
for (line in result.zip(reference)) {
for (line in result.first.zip(reference)) {
if (line.first != line.second) {
fail("disassembled instruction mismatch: '${line.first}', expected '${line.second}'")
}
@ -31,8 +32,9 @@ class TestDisassembler {
val memory = Ram(0, 0x0fff)
val source = javaClass.classLoader.getResource("disassem_r65c02.bin").readBytes()
memory.load(source, 0x0200)
val resultLines = cpu.disassemble(memory, 0x0200, 0x0250)
val result = resultLines.joinToString("\n")
val disassem = cpu.disassemble(memory, 0x0200, 0x0250)
assertEquals(0x251, disassem.second)
val result = disassem.first.joinToString("\n")
assertEquals("""${'$'}0200 07 12 rmb0 ${'$'}12
${'$'}0202 17 12 rmb1 ${'$'}12
${'$'}0204 27 12 rmb2 ${'$'}12
@ -76,8 +78,9 @@ ${'$'}0250 00 brk""", result)
val memory = Ram(0, 0x0fff)
val source = javaClass.classLoader.getResource("disassem_wdc65c02.bin").readBytes()
memory.load(source, 0x200)
val resultLines = cpu.disassemble(memory, 0x0200, 0x0215)
val result = resultLines.joinToString("\n")
val disassem = cpu.disassemble(memory, 0x0200, 0x0215)
assertEquals(0x216, disassem.second)
val result = disassem.first.joinToString("\n")
assertEquals("""${'$'}0200 cb wai
${'$'}0201 db stp
${'$'}0202 3a dec a