1
0
mirror of https://github.com/irmen/ksim65.git synced 2024-05-29 03:41:30 +00:00
ksim65/src/main/kotlin/razorvine/examplemachines/DebugWindow.kt
2020-01-04 13:45:16 +01:00

234 lines
9.7 KiB
Kotlin

package razorvine.examplemachines
import razorvine.ksim65.*
import java.awt.*
import java.awt.event.ActionEvent
import java.awt.event.ActionListener
import java.awt.event.WindowEvent
import java.io.File
import javax.swing.*
import javax.swing.text.DefaultCaret
class DebugWindow(private val vm: IVirtualMachine) : JFrame("Debugger - ksim65 v${Version.version}"),
ActionListener {
private class UnaliasedTextBox(rows: Int, columns: Int) : JTextArea(rows, columns) {
override fun paintComponent(g: Graphics) {
g as Graphics2D
g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_OFF)
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF)
super.paintComponent(g)
}
}
private val cyclesTf = JTextField("00000000000000")
private val speedKhzTf = JTextField("0000000")
private val regAtf = JTextField("000")
private val regXtf = JTextField("000")
private val regYtf = JTextField("000")
private val regPCtf = JTextField("00000")
private val regSPtf = JTextField("000")
private val regPtf = JTextArea("NV-BDIZC\n000000000")
private val disassemTf = JTextField("00 00 00 lda (fffff),x")
private val pauseBt = JButton("Pause").also { it.actionCommand = "pause" }
private val zeropageTf = UnaliasedTextBox(8, 102).also {
it.border = BorderFactory.createEtchedBorder()
it.isEnabled = false
it.disabledTextColor = Color.DARK_GRAY
it.font = Font(Font.MONOSPACED, Font.PLAIN, 12)
}
private val stackpageTf = UnaliasedTextBox(8, 102).also {
it.border = BorderFactory.createEtchedBorder()
it.isEnabled = false
it.disabledTextColor = Color.DARK_GRAY
it.font = Font(Font.MONOSPACED, Font.PLAIN, 12)
}
init {
contentPane.layout = GridBagLayout()
defaultCloseOperation = EXIT_ON_CLOSE
val cpuPanel = JPanel(GridBagLayout())
cpuPanel.border = BorderFactory.createTitledBorder("CPU: ${vm.cpu.name}")
val gc = GridBagConstraints()
gc.insets = Insets(2, 2, 2, 2)
gc.anchor = GridBagConstraints.EAST
gc.gridx = 0
gc.gridy = 0
val cyclesLb = JLabel("cycles")
val speedKhzLb = JLabel("speed (kHz)")
val regAlb = JLabel("A")
val regXlb = JLabel("X")
val regYlb = JLabel("Y")
val regSPlb = JLabel("SP")
val regPClb = JLabel("PC")
val regPlb = JLabel("Status")
val disassemLb = JLabel("Instruction")
listOf(cyclesLb, speedKhzLb, regAlb, regXlb, regYlb, regSPlb, regPClb, disassemLb, regPlb).forEach {
cpuPanel.add(it, gc)
gc.gridy++
}
gc.anchor = GridBagConstraints.WEST
gc.gridx = 1
gc.gridy = 0
listOf(cyclesTf, speedKhzTf, regAtf, regXtf, regYtf, regSPtf, regPCtf, disassemTf, regPtf).forEach {
it.font = Font(Font.MONOSPACED, Font.PLAIN, 14)
it.disabledTextColor = Color.DARK_GRAY
it.isEnabled = false
if (it is JTextField) {
it.columns = it.text.length
} else if (it is JTextArea) {
it.border = BorderFactory.createCompoundBorder(BorderFactory.createLineBorder(Color.LIGHT_GRAY),
BorderFactory.createEmptyBorder(2, 2, 2, 2))
}
cpuPanel.add(it, gc)
gc.gridy++
}
val buttonPanel = JPanel(FlowLayout())
buttonPanel.border = BorderFactory.createTitledBorder("Control")
val loadBt = JButton("Inject program").also { it.actionCommand = "inject" }
val resetBt = JButton("Reset").also { it.actionCommand = "reset" }
val stepBt = JButton("Step").also { it.actionCommand = "step" }
val irqBt = JButton("IRQ").also { it.actionCommand = "irq" }
val nmiBt = JButton("NMI").also { it.actionCommand = "nmi" }
val quitBt = JButton("Quit").also { it.actionCommand = "quit" }
listOf(loadBt, resetBt, irqBt, nmiBt, pauseBt, stepBt, quitBt).forEach {
it.addActionListener(this)
buttonPanel.add(it)
}
val zeropagePanel = JPanel()
zeropagePanel.layout = BoxLayout(zeropagePanel, BoxLayout.Y_AXIS)
zeropagePanel.border = BorderFactory.createTitledBorder("Zeropage and Stack")
val showZp = JCheckBox("show Zero page and Stack dumps", true)
showZp.addActionListener {
val visible = (it.source as JCheckBox).isSelected
zeropageTf.isVisible = visible
stackpageTf.isVisible = visible
}
zeropagePanel.add(showZp)
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(6, 80)
output.font = Font(Font.MONOSPACED, Font.PLAIN, 14)
output.isEditable = false
val outputScroll = JScrollPane(output)
monitorPanel.add(outputScroll)
outputScroll.verticalScrollBarPolicy = JScrollPane.VERTICAL_SCROLLBAR_ALWAYS
(output.caret as DefaultCaret).updatePolicy = DefaultCaret.ALWAYS_UPDATE
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)
output.append(vm.executeMonitorCommand("h").output)
gc.gridx = 0
gc.gridy = 0
gc.fill = GridBagConstraints.BOTH
contentPane.add(cpuPanel, gc)
gc.gridy++
contentPane.add(zeropagePanel, gc)
gc.gridy++
contentPane.add(monitorPanel, gc)
gc.gridy++
contentPane.add(buttonPanel, gc)
pack()
}
override fun actionPerformed(e: ActionEvent) {
when (e.actionCommand) {
"inject" -> {
val chooser = JFileChooser()
chooser.dialogTitle = "Choose binary program or .prg to load"
chooser.currentDirectory = File(".")
chooser.isMultiSelectionEnabled = false
val result = chooser.showOpenDialog(this)
if (result == JFileChooser.APPROVE_OPTION) {
if (chooser.selectedFile.extension == "prg") {
vm.loadFileInRam(chooser.selectedFile, null)
} else {
val addressStr = JOptionPane.showInputDialog(this,
"The selected file isn't a .prg.\nSpecify memory load address (hexadecimal) manually.",
"Load address", JOptionPane.QUESTION_MESSAGE, null, null,
"$") as String
val loadAddress = Integer.parseInt(addressStr.removePrefix("$"), 16)
vm.loadFileInRam(chooser.selectedFile, loadAddress)
}
}
}
"reset" -> {
vm.bus.reset()
updateCpu(vm.cpu, vm.bus)
}
"step" -> {
vm.step()
updateCpu(vm.cpu, vm.bus)
}
"pause" -> {
vm.pause(true)
pauseBt.actionCommand = "continue"
pauseBt.text = "Continue"
}
"continue" -> {
vm.pause(false)
pauseBt.actionCommand = "pause"
pauseBt.text = "Pause"
}
"irq" -> vm.cpu.irq()
"nmi" -> vm.cpu.nmi()
"quit" -> {
dispatchEvent(WindowEvent(this, WindowEvent.WINDOW_CLOSING))
}
}
}
fun updateCpu(cpu: Cpu6502, bus: Bus) {
val state = cpu.snapshot()
cyclesTf.text = state.cycles.toString()
regAtf.text = hexB(state.A)
regXtf.text = hexB(state.X)
regYtf.text = hexB(state.Y)
regPtf.text = "NV-BDIZC\n"+state.P.asInt().toString(2).padStart(8, '0')
regPCtf.text = hexW(state.PC)
regSPtf.text = hexB(state.SP)
val memory = bus.memoryComponentFor(state.PC)
disassemTf.text = cpu.disassembleOneInstruction(memory.data, state.PC, memory.startAddress).first.substringAfter(' ').trim()
if (zeropageTf.isVisible || stackpageTf.isVisible) {
val pages = vm.getZeroAndStackPages()
if (pages.isNotEmpty()) {
val zpLines = (0..0xff step 32).map { location ->
" ${'$'}${location.toString(16).padStart(2, '0')} "+((0..31).joinToString(" ") { lineoffset ->
pages[location+lineoffset].toString(16).padStart(2, '0')
})
}
val stackLines = (0x100..0x1ff step 32).map { location ->
"${'$'}${location.toString(16).padStart(2, '0')} "+((0..31).joinToString(" ") { lineoffset ->
pages[location+lineoffset].toString(16).padStart(2, '0')
})
}
zeropageTf.text = zpLines.joinToString("\n")
stackpageTf.text = stackLines.joinToString("\n")
}
}
speedKhzTf.text = "%.1f".format(cpu.averageSpeedKhzSinceReset)
}
}