mirror of
https://github.com/irmen/ksim65.git
synced 2024-09-27 07:54:29 +00:00
added Load button. began to implement c64 CIAs to provide keyboard matrix.
This commit is contained in:
parent
bb4819c6d0
commit
52f6823c0e
96
src/main/kotlin/razorvine/c64emu/Cia.kt
Normal file
96
src/main/kotlin/razorvine/c64emu/Cia.kt
Normal file
@ -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<UByte>(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
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
@ -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<UByte>(endAddress - startAddress + 1) { 0 }
|
||||
private var ramBuffer = Array<UByte>(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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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<UByte> = ram.getPages(0, 2)
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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<UByte> = 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
|
||||
}
|
||||
|
@ -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<UByte>
|
||||
fun loadFileInRam(file: File, loadAddress: Address?)
|
||||
|
||||
val cpu: Cpu6502
|
||||
val bus: Bus
|
||||
|
Loading…
Reference in New Issue
Block a user