From 52f6823c0eb042bf418a695d20eb565750431548 Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Tue, 24 Sep 2019 01:22:54 +0200 Subject: [PATCH] added Load button. began to implement c64 CIAs to provide keyboard matrix. --- src/main/kotlin/razorvine/c64emu/Cia.kt | 96 +++++++++++++++++++ src/main/kotlin/razorvine/c64emu/GUI.kt | 4 +- src/main/kotlin/razorvine/c64emu/VicII.kt | 28 +++--- src/main/kotlin/razorvine/c64emu/c64Main.kt | 11 +++ .../kotlin/razorvine/examplemachines/GUI.kt | 31 +++++- .../razorvine/examplemachines/machineMain.kt | 8 ++ .../razorvine/ksim65/IVirtualMachine.kt | 3 + 7 files changed, 167 insertions(+), 14 deletions(-) create mode 100644 src/main/kotlin/razorvine/c64emu/Cia.kt diff --git a/src/main/kotlin/razorvine/c64emu/Cia.kt b/src/main/kotlin/razorvine/c64emu/Cia.kt new file mode 100644 index 0000000..0e2f141 --- /dev/null +++ b/src/main/kotlin/razorvine/c64emu/Cia.kt @@ -0,0 +1,96 @@ +package razorvine.c64emu + +import razorvine.ksim65.components.Address +import razorvine.ksim65.components.MemMappedComponent +import razorvine.ksim65.components.UByte + +/** + * Minimal simulation of the MOS 6526 CIA chip. + * Depending on what CIA it is (1 or 2), some registers do different things on the C64. + */ +class Cia(val number: Int, startAddress: Address, endAddress: Address): MemMappedComponent(startAddress, endAddress) { + private var ramBuffer = Array(endAddress - startAddress + 1) { 0x00 } + private var pra = 0xff + + init { + require(endAddress - startAddress + 1 == 256) { "cia requires exactly 256 memory bytes (16*16 mirrored)" } + } + + override fun clock() { + // TODO: TOD timer, timer A and B countdowns, IRQ triggering. + } + + override fun reset() { + // TODO: reset TOD timer, timer A and B + } + + override fun get(address: Address): UByte { + val register = (address - startAddress) and 15 + if(number==1) { + return if (register == 0x01) { + // PRB data port B (if bit is cleared in PRA, contains keys pressed in that column of the matrix) + println("read PRB, pra=${pra.toString(2)}") + when(pra) { + 0b00000000 -> { + // check if any keys are pressed at all (by checking all columns at once) + // TODO zero if there's any key pressed, 0xff otherwise + 0xff + } + 0b11111110 -> { + // read column 0 + 0xff + } + 0b11111101 -> { + // read column 1 + 0xff + } + 0b11111011 -> { + 0b11011111.toShort() // 'F' + } + 0b11110111 -> { + // read column 3 + 0xff + } + 0b11101111 -> { + // read column 4 + 0xff + } + 0b11011111 -> { + // read column 5 + 0xff + } + 0b10111111 -> { + // read column 6 + 0xff + } + 0b01111111 -> { + // read column 7 + 0xff + } + else -> { + // invalid column selection + 0xff + } + } + } + else ramBuffer[register] + } + + // CIA #2 is not emulated yet + return ramBuffer[register] + } + + override fun set(address: Address, data: UByte) { + val register = (address - startAddress) and 15 + ramBuffer[register] = data + if(number==1) { + when (register) { + 0x00 -> { + // PRA data port A (select keyboard matrix column) + pra = data.toInt() + } + } + } + // CIA #2 is not emulated yet + } +} diff --git a/src/main/kotlin/razorvine/c64emu/GUI.kt b/src/main/kotlin/razorvine/c64emu/GUI.kt index bab9d1c..1e82303 100644 --- a/src/main/kotlin/razorvine/c64emu/GUI.kt +++ b/src/main/kotlin/razorvine/c64emu/GUI.kt @@ -93,7 +93,7 @@ private class BitmapScreenPanel(val chargenData: ByteArray, val ram: MemoryCompo val screen = 0x0400 val colors = 0xd800 val shifted = (ram[0xd018].toInt() and 0b00000010) != 0 - g2d.background = ScreenDefs.colorPalette[ram[0xd021].toInt()] + g2d.background = ScreenDefs.colorPalette[ram[0xd021].toInt() and 15] g2d.clearRect(0, 0, ScreenDefs.SCREEN_WIDTH, ScreenDefs.SCREEN_HEIGHT) for(y in 0 until ScreenDefs.SCREEN_HEIGHT_CHARS) { for(x in 0 until ScreenDefs.SCREEN_WIDTH_CHARS) { @@ -236,7 +236,7 @@ class MainC64Window(title: String, chargenData: ByteArray, val ram: MemoryCompon * This avoids having to deal with the 'real' keyboard matrix, * but it can't map keys like RUN/STOP and RESTORE properly. * - * TODO: replace this by the real keyboard matrix including RUN/STOP and RESTORE handling. + * TODO: replace this by the real keyboard matrix. */ private fun keyEventToPetscii(ke: KeyEvent): UByte { if(ke.isActionKey) { diff --git a/src/main/kotlin/razorvine/c64emu/VicII.kt b/src/main/kotlin/razorvine/c64emu/VicII.kt index 19c1e16..15e0562 100644 --- a/src/main/kotlin/razorvine/c64emu/VicII.kt +++ b/src/main/kotlin/razorvine/c64emu/VicII.kt @@ -9,15 +9,20 @@ import razorvine.ksim65.components.UByte * It only has some logic to keep track of the raster line */ class VicII(startAddress: Address, endAddress: Address): MemMappedComponent(startAddress, endAddress) { - private var ramBuffer = Array(endAddress - startAddress + 1) { 0 } + private var ramBuffer = Array(endAddress - startAddress + 1) { 0xff } private var rasterIrqLine = 0 var currentRasterLine = 1 - private var totalClocks = 0L + private var scanlineClocks = 0 private var interruptStatusRegisterD019 = 0 + init { + require(endAddress - startAddress + 1 == 0x400) { "vic-II requires exactly 1024 memory bytes (64*16 mirrored)" } + } + override fun clock() { - totalClocks++ - if(totalClocks % 63L == 0L) { + scanlineClocks++ + if(scanlineClocks == 63) { + scanlineClocks = 0 currentRasterLine++ if(currentRasterLine >= 312) currentRasterLine = 0 @@ -32,7 +37,6 @@ class VicII(startAddress: Address, endAddress: Address): MemMappedComponent(star override fun reset() { rasterIrqLine = 0 currentRasterLine = 1 - totalClocks = 0L interruptStatusRegisterD019 = 0 } @@ -49,13 +53,15 @@ class VicII(startAddress: Address, endAddress: Address): MemMappedComponent(star override fun set(address: Address, data: UByte) { val register = (address - startAddress) and 63 - ramBuffer[register] = data - when(register) { - 0x11 -> { - val rasterHigh = (data.toInt() ushr 7) shl 8 - rasterIrqLine = (rasterIrqLine and 0x00ff) or rasterHigh + if(register<47) { + ramBuffer[register] = data + when (register) { + 0x11 -> { + val rasterHigh = (data.toInt() ushr 7) shl 8 + rasterIrqLine = (rasterIrqLine and 0x00ff) or rasterHigh + } + 0x12 -> rasterIrqLine = (rasterIrqLine and 0xff00) or data.toInt() } - 0x12 -> rasterIrqLine = (rasterIrqLine and 0xff00) or data.toInt() } } } diff --git a/src/main/kotlin/razorvine/c64emu/c64Main.kt b/src/main/kotlin/razorvine/c64emu/c64Main.kt index 5435271..2d508a7 100644 --- a/src/main/kotlin/razorvine/c64emu/c64Main.kt +++ b/src/main/kotlin/razorvine/c64emu/c64Main.kt @@ -24,9 +24,12 @@ class C64Machine(title: String) : IVirtualMachine { override val cpu = Cpu6502(false) val ram = Ram(0x0000, 0xffff) val vic = VicII(0xd000, 0xd3ff) + val cia1 = Cia(1, 0xdc00, 0xdcff) + val cia2 = Cia(2, 0xdd00, 0xddff) val basicRom = Rom(0xa000, 0xbfff).also { it.load(basicData) } val kernalRom = Rom(0xe000, 0xffff).also { it.load(kernalData) } // TODO: implement the two CIAs to add timer and joystick support, and the keyboard matrix. + // TODO: implement something so that RND(0) actually works in basic. private val debugWindow = DebugWindow(this) private val hostDisplay = MainC64Window(title, chargenData, ram) @@ -40,6 +43,8 @@ class C64Machine(title: String) : IVirtualMachine { bus += basicRom bus += kernalRom bus += vic + bus += cia1 + bus += cia2 bus += ram bus += cpu bus.reset() @@ -57,6 +62,12 @@ class C64Machine(title: String) : IVirtualMachine { } } + override fun loadFileInRam(file: File, loadAddress: Address?) { + if(file.extension=="prg" && loadAddress==null) + ram.loadPrg(file.inputStream()) + else + ram.load(file.readBytes(), loadAddress!!) + } override fun getZeroAndStackPages(): Array = ram.getPages(0, 2) diff --git a/src/main/kotlin/razorvine/examplemachines/GUI.kt b/src/main/kotlin/razorvine/examplemachines/GUI.kt index 614e1ee..0df9f90 100644 --- a/src/main/kotlin/razorvine/examplemachines/GUI.kt +++ b/src/main/kotlin/razorvine/examplemachines/GUI.kt @@ -9,6 +9,8 @@ import javax.swing.event.MouseInputListener import razorvine.ksim65.IHostInterface import razorvine.ksim65.IVirtualMachine import java.awt.event.* +import java.io.File +import java.lang.Integer.parseInt import java.util.* import javax.swing.* import javax.swing.Timer @@ -215,12 +217,13 @@ class DebugWindow(private val vm: IVirtualMachine) : JFrame("debugger"), ActionL val buttonPanel = JPanel(FlowLayout()) buttonPanel.border = BorderFactory.createTitledBorder("Control") + val loadBt = JButton("Load program").also { it.actionCommand = "load" } 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(resetBt, irqBt, nmiBt, pauseBt, stepBt, quitBt).forEach { + listOf(loadBt, resetBt, irqBt, nmiBt, pauseBt, stepBt, quitBt).forEach { it.addActionListener(this) buttonPanel.add(it) } @@ -245,6 +248,32 @@ class DebugWindow(private val vm: IVirtualMachine) : JFrame("debugger"), ActionL override fun actionPerformed(e: ActionEvent) { when(e.actionCommand) { + "load" -> { + 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 = parseInt(addressStr.removePrefix("$"), 16) + vm.loadFileInRam(chooser.selectedFile, loadAddress) + } + } + + } "reset" -> { vm.bus.reset() updateCpu(vm.cpu, vm.bus) diff --git a/src/main/kotlin/razorvine/examplemachines/machineMain.kt b/src/main/kotlin/razorvine/examplemachines/machineMain.kt index bbf446c..c556668 100644 --- a/src/main/kotlin/razorvine/examplemachines/machineMain.kt +++ b/src/main/kotlin/razorvine/examplemachines/machineMain.kt @@ -7,6 +7,7 @@ import razorvine.ksim65.IVirtualMachine import razorvine.ksim65.Version import razorvine.ksim65.components.* import razorvine.ksim65.components.Timer +import java.io.File import javax.swing.ImageIcon /** @@ -53,6 +54,13 @@ class VirtualMachine(title: String) : IVirtualMachine { override fun getZeroAndStackPages(): Array = ram.getPages(0, 2) + override fun loadFileInRam(file: File, loadAddress: Address?) { + if(file.extension=="prg" && loadAddress==null) + ram.loadPrg(file.inputStream()) + else + ram.load(file.readBytes(), loadAddress!!) + } + override fun pause(paused: Boolean) { this.paused = paused } diff --git a/src/main/kotlin/razorvine/ksim65/IVirtualMachine.kt b/src/main/kotlin/razorvine/ksim65/IVirtualMachine.kt index 8a6c352..e58ede8 100644 --- a/src/main/kotlin/razorvine/ksim65/IVirtualMachine.kt +++ b/src/main/kotlin/razorvine/ksim65/IVirtualMachine.kt @@ -1,11 +1,14 @@ package razorvine.ksim65 +import razorvine.ksim65.components.Address import razorvine.ksim65.components.UByte +import java.io.File interface IVirtualMachine { fun step() fun pause(paused: Boolean) fun getZeroAndStackPages(): Array + fun loadFileInRam(file: File, loadAddress: Address?) val cpu: Cpu6502 val bus: Bus