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:
parent
fbdc08d696
commit
af4e901f6c
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
190
src/main/kotlin/razorvine/ksim65/Monitor.kt
Normal file
190
src/main/kotlin/razorvine/ksim65/Monitor.kt
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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")
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user