mirror of
https://github.com/irmen/ksim65.git
synced 2024-06-13 22:29:29 +00:00
implemented C64 VIC & CIA interrupts
This commit is contained in:
parent
3a97adafa4
commit
fbdc08d696
|
@ -1,5 +1,6 @@
|
|||
package razorvine.c64emu
|
||||
|
||||
import razorvine.ksim65.Cpu6502
|
||||
import razorvine.ksim65.components.Address
|
||||
import razorvine.ksim65.components.MemMappedComponent
|
||||
import razorvine.ksim65.components.UByte
|
||||
|
@ -9,11 +10,13 @@ import java.awt.event.KeyEvent
|
|||
* 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.
|
||||
* This implementation provides a working keyboard matrix, TOD clock, and the essentials of the timer A and B.
|
||||
* TODO: timerA IRQ, timerB NMI triggering
|
||||
*/
|
||||
class Cia(val number: Int, startAddress: Address, endAddress: Address) : MemMappedComponent(startAddress, endAddress) {
|
||||
class Cia(val number: Int,
|
||||
startAddress: Address, endAddress: Address,
|
||||
val cpu: Cpu6502) : MemMappedComponent(startAddress, endAddress)
|
||||
{
|
||||
private var ramBuffer = Array<UByte>(endAddress - startAddress + 1) { 0 }
|
||||
private var pra = 0xff
|
||||
private var regPRA = 0xff
|
||||
|
||||
class TimeOfDay {
|
||||
private var updatedAt = 0L
|
||||
|
@ -70,6 +73,8 @@ class Cia(val number: Int, startAddress: Address, endAddress: Address) : MemMapp
|
|||
private var timerBset = 0
|
||||
private var timerAactual = 0
|
||||
private var timerBactual = 0
|
||||
private var timerAinterruptEnabled = false
|
||||
private var timerBinterruptEnabled = false
|
||||
|
||||
private data class HostKeyPress(val code: Int, val rightSide: Boolean=false, val numpad: Boolean=false)
|
||||
|
||||
|
@ -90,13 +95,19 @@ class Cia(val number: Int, startAddress: Address, endAddress: Address) : MemMapp
|
|||
if(ramBuffer[0x0e].toInt() and 1 != 0) {
|
||||
// timer A is enabled, assume system cycles counting for now
|
||||
timerAactual--
|
||||
if(timerAactual==0 && timerAinterruptEnabled) {
|
||||
if(number==1)
|
||||
cpu.irq()
|
||||
else if(number==2)
|
||||
cpu.nmi()
|
||||
}
|
||||
if(timerAactual<0)
|
||||
timerAactual = if(ramBuffer[0x0e].toInt() and 0b00001000 != 0) 0 else timerAset
|
||||
}
|
||||
if(ramBuffer[0x0f].toInt() and 1 != 0) {
|
||||
// timer B is enabled
|
||||
val crb = ramBuffer[0x0f].toInt()
|
||||
if(crb and 0b01000000 != 0) {
|
||||
val regCRB = ramBuffer[0x0f].toInt()
|
||||
if(regCRB and 0b01000000 != 0) {
|
||||
// timer B counts timer A underruns
|
||||
if(timerAactual==0)
|
||||
timerBactual--
|
||||
|
@ -104,8 +115,14 @@ class Cia(val number: Int, startAddress: Address, endAddress: Address) : MemMapp
|
|||
// timer B counts just the system cycles
|
||||
timerBactual--
|
||||
}
|
||||
if(timerBactual==0 && timerBinterruptEnabled) {
|
||||
if(number==1)
|
||||
cpu.irq()
|
||||
else if(number==2)
|
||||
cpu.nmi()
|
||||
}
|
||||
if(timerBactual<0)
|
||||
timerBactual = if(crb and 0b00001000 != 0) 0 else timerBset
|
||||
timerBactual = if(regCRB and 0b00001000 != 0) 0 else timerBset
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -134,7 +151,7 @@ class Cia(val number: Int, startAddress: Address, endAddress: Address) : MemMapp
|
|||
if (number == 1 && register == 0x01) {
|
||||
// register 1 on CIA#1 is the keyboard data port
|
||||
// if bit is cleared in PRA, contains keys pressed in that column of the matrix
|
||||
return when (pra) {
|
||||
return when (regPRA) {
|
||||
0b00000000 -> {
|
||||
// check if any keys are pressed at all (by checking all columns at once)
|
||||
if (hostKeyPresses.isEmpty()) 0xff.toShort() else 0x00.toShort()
|
||||
|
@ -284,12 +301,14 @@ class Cia(val number: Int, startAddress: Address, endAddress: Address) : MemMapp
|
|||
|
||||
override fun set(address: Address, data: UByte) {
|
||||
val register = (address - startAddress) and 15
|
||||
ramBuffer[register] = data
|
||||
if (number == 1 && register == 0x00) {
|
||||
// PRA data port A (select keyboard matrix column)
|
||||
pra = data.toInt()
|
||||
regPRA = data.toInt()
|
||||
}
|
||||
|
||||
if(register!=0x0d)
|
||||
ramBuffer[register] = data
|
||||
|
||||
when (register) {
|
||||
0x04 -> {
|
||||
if(ramBuffer[0x0e].toInt() and 0b10000000 == 0) {
|
||||
|
@ -322,7 +341,21 @@ class Cia(val number: Int, startAddress: Address, endAddress: Address) : MemMapp
|
|||
tod.stop()
|
||||
tod.hours = fromBCD(data)
|
||||
}
|
||||
// the timer A and B control registers are simply provided by the rambuffer for now.
|
||||
0x0d -> {
|
||||
if(data.toInt() and 0b10000000 != 0) {
|
||||
// set ICR bits
|
||||
val newICR = ramBuffer[0x0d].toInt() or (data.toInt() and 0b01111111)
|
||||
timerAinterruptEnabled = newICR and 1 != 0
|
||||
timerBinterruptEnabled = newICR and 2 != 0
|
||||
ramBuffer[0x0d] = newICR.toShort()
|
||||
} else {
|
||||
// clear ICR bits
|
||||
val newICR = ramBuffer[0x0d].toInt() and (data.toInt() and 0b01111111).inv()
|
||||
timerAinterruptEnabled = newICR and 1 != 0
|
||||
timerBinterruptEnabled = newICR and 2 != 0
|
||||
ramBuffer[0x0d] = newICR.toShort()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -341,8 +374,8 @@ class Cia(val number: Int, startAddress: Address, endAddress: Address) : MemMapp
|
|||
}
|
||||
|
||||
// to avoid some 'stuck' keys, if we receive a shift/control/alt RELEASE, we wipe the keyboard buffer
|
||||
// (this can happen because we're changing the keycode for some pressed keys below,
|
||||
// and a released key doesn't always match the pressed keycode anymore then)
|
||||
// (this can happen because we're changing the key code for some pressed keys below,
|
||||
// and a released key doesn't always match the pressed key code anymore then)
|
||||
if (event.id == KeyEvent.KEY_RELEASED && event.keyCode in listOf(
|
||||
KeyEvent.VK_SHIFT,
|
||||
KeyEvent.VK_CONTROL,
|
||||
|
|
|
@ -3,10 +3,13 @@ package razorvine.c64emu
|
|||
import razorvine.ksim65.Cpu6502
|
||||
import razorvine.ksim65.components.MemoryComponent
|
||||
import java.awt.*
|
||||
import java.awt.event.KeyEvent
|
||||
import java.awt.event.KeyListener
|
||||
import java.awt.image.BufferedImage
|
||||
import java.awt.image.VolatileImage
|
||||
import java.awt.event.*
|
||||
import javax.swing.*
|
||||
import javax.swing.JFrame
|
||||
import javax.swing.JPanel
|
||||
import javax.swing.Timer
|
||||
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package razorvine.c64emu
|
||||
|
||||
import razorvine.ksim65.Cpu6502
|
||||
import razorvine.ksim65.components.Address
|
||||
import razorvine.ksim65.components.MemMappedComponent
|
||||
import razorvine.ksim65.components.UByte
|
||||
|
@ -9,7 +10,7 @@ import razorvine.ksim65.components.UByte
|
|||
* It only has some logic to keep track of the raster line
|
||||
* This chip is the PAL version (50 Hz screen refresh, 312 vertical raster lines)
|
||||
*/
|
||||
class VicII(startAddress: Address, endAddress: Address): MemMappedComponent(startAddress, endAddress) {
|
||||
class VicII(startAddress: Address, endAddress: Address, val cpu: Cpu6502): MemMappedComponent(startAddress, endAddress) {
|
||||
private var ramBuffer = Array<UByte>(endAddress - startAddress + 1) { 0xff }
|
||||
private var rasterIrqLine = 0
|
||||
private var scanlineClocks = 0
|
||||
|
@ -17,23 +18,30 @@ class VicII(startAddress: Address, endAddress: Address): MemMappedComponent(star
|
|||
var currentRasterLine = 1
|
||||
val vsync: Boolean
|
||||
get() = currentRasterLine==0
|
||||
val framerate = 50
|
||||
val numRasterLines = 312
|
||||
|
||||
companion object {
|
||||
const val framerate = 50
|
||||
const val rasterlines = 312
|
||||
}
|
||||
|
||||
init {
|
||||
require(endAddress - startAddress + 1 == 0x400) { "vic-II requires exactly 1024 memory bytes (64*16 mirrored)" }
|
||||
ramBuffer[0x1a] = 0 // initially, disable IRQs
|
||||
}
|
||||
|
||||
override fun clock() {
|
||||
scanlineClocks++
|
||||
if(scanlineClocks == 63) {
|
||||
if (scanlineClocks == 63) {
|
||||
scanlineClocks = 0
|
||||
currentRasterLine++
|
||||
if(currentRasterLine >= numRasterLines)
|
||||
if (currentRasterLine >= rasterlines)
|
||||
currentRasterLine = 0
|
||||
interruptStatusRegisterD019 = if(currentRasterLine == rasterIrqLine) {
|
||||
interruptStatusRegisterD019 = if (currentRasterLine == rasterIrqLine) {
|
||||
// signal that current raster line is equal to the desired IRQ raster line
|
||||
interruptStatusRegisterD019 or 0b00000001
|
||||
// schedule an IRQ as well if the raster interrupt is enabled
|
||||
if((ramBuffer[0x1a].toInt() and 1) != 0)
|
||||
cpu.irq()
|
||||
interruptStatusRegisterD019 or 0b10000001
|
||||
} else
|
||||
interruptStatusRegisterD019 and 0b11111110
|
||||
}
|
||||
|
@ -58,15 +66,13 @@ class VicII(startAddress: Address, endAddress: Address): MemMappedComponent(star
|
|||
|
||||
override fun set(address: Address, data: UByte) {
|
||||
val register = (address - startAddress) and 63
|
||||
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()
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,6 +13,7 @@ import java.io.IOException
|
|||
import java.nio.file.Path
|
||||
import java.nio.file.Paths
|
||||
import javax.swing.ImageIcon
|
||||
import javax.swing.JOptionPane
|
||||
import kotlin.concurrent.scheduleAtFixedRate
|
||||
|
||||
/**
|
||||
|
@ -31,9 +32,9 @@ class C64Machine(title: String) : IVirtualMachine {
|
|||
override val bus = Bus()
|
||||
override val cpu = Cpu6502()
|
||||
val ram = Ram(0x0000, 0xffff)
|
||||
val vic = VicII(0xd000, 0xd3ff)
|
||||
val cia1 = Cia(1, 0xdc00, 0xdcff)
|
||||
val cia2 = Cia(2, 0xdd00, 0xddff)
|
||||
val vic = VicII(0xd000, 0xd3ff, cpu)
|
||||
val cia1 = Cia(1, 0xdc00, 0xdcff, cpu)
|
||||
val cia2 = Cia(2, 0xdd00, 0xddff, cpu)
|
||||
val basicRom = Rom(0xa000, 0xbfff).also { it.load(basicData) }
|
||||
val kernalRom = Rom(0xe000, 0xffff).also { it.load(kernalData) }
|
||||
|
||||
|
@ -106,7 +107,7 @@ class C64Machine(title: String) : IVirtualMachine {
|
|||
}
|
||||
|
||||
fun breakpointBRK(cpu: Cpu6502, pc: Address): Cpu6502.BreakpointResultAction {
|
||||
throw Cpu6502.InstructionError("BRK instruction hit at ${hexW(pc)}")
|
||||
throw Cpu6502.InstructionError("BRK instruction hit at $${hexW(pc)}")
|
||||
}
|
||||
|
||||
private fun searchAndLoadFile(
|
||||
|
@ -245,15 +246,20 @@ class C64Machine(title: String) : IVirtualMachine {
|
|||
}.start()
|
||||
|
||||
val timer = java.util.Timer("cpu-cycle", true)
|
||||
timer.scheduleAtFixedRate(0, 1000L/vic.framerate) {
|
||||
timer.scheduleAtFixedRate(0, 1000L/VicII.framerate) {
|
||||
if(!paused) {
|
||||
// we synchronise cpu cycles to the vertical blank of the Vic chip
|
||||
// this should result in ~1 Mhz cpu speed
|
||||
while(vic.vsync) step()
|
||||
while(!vic.vsync) step()
|
||||
// we force an irq here ourselves rather than fully emulating the VIC-II's raster IRQ
|
||||
// or the CIA timer IRQ/NMI.
|
||||
cpu.irq()
|
||||
try {
|
||||
while (vic.vsync) step()
|
||||
while (!vic.vsync) step()
|
||||
} catch(rx: RuntimeException) {
|
||||
JOptionPane.showMessageDialog(hostDisplay, "Run time error: $rx", "Error during execution", JOptionPane.ERROR_MESSAGE)
|
||||
this.cancel()
|
||||
} catch(ex: Error) {
|
||||
JOptionPane.showMessageDialog(hostDisplay, "Run time error: $ex", "Error during execution", JOptionPane.ERROR_MESSAGE)
|
||||
this.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import razorvine.ksim65.components.Keyboard
|
|||
import razorvine.ksim65.components.Ram
|
||||
import razorvine.ksim65.components.Rom
|
||||
import javax.swing.ImageIcon
|
||||
import javax.swing.JOptionPane
|
||||
import kotlin.concurrent.scheduleAtFixedRate
|
||||
|
||||
/**
|
||||
|
@ -53,9 +54,17 @@ class EhBasicMachine(title: String) {
|
|||
val timer = java.util.Timer("cpu-cycle", true)
|
||||
timer.scheduleAtFixedRate(500, 1000/frameRate) {
|
||||
if(!paused) {
|
||||
val prevCycles = cpu.totalCycles
|
||||
while(cpu.totalCycles - prevCycles < desiredCyclesPerFrame) {
|
||||
step()
|
||||
try {
|
||||
val prevCycles = cpu.totalCycles
|
||||
while (cpu.totalCycles - prevCycles < desiredCyclesPerFrame) {
|
||||
step()
|
||||
}
|
||||
} catch(rx: RuntimeException) {
|
||||
JOptionPane.showMessageDialog(hostDisplay, "Run time error: $rx", "Error during execution", JOptionPane.ERROR_MESSAGE)
|
||||
this.cancel()
|
||||
} catch(ex: Error) {
|
||||
JOptionPane.showMessageDialog(hostDisplay, "Run time error: $ex", "Error during execution", JOptionPane.ERROR_MESSAGE)
|
||||
this.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
package razorvine.examplemachines
|
||||
|
||||
import java.io.File
|
||||
import javax.swing.ImageIcon
|
||||
import kotlin.concurrent.scheduleAtFixedRate
|
||||
import razorvine.ksim65.Bus
|
||||
import razorvine.ksim65.Cpu6502
|
||||
import razorvine.ksim65.IVirtualMachine
|
||||
import razorvine.ksim65.Version
|
||||
import razorvine.ksim65.components.*
|
||||
import java.io.File
|
||||
import javax.swing.ImageIcon
|
||||
import javax.swing.JOptionPane
|
||||
import kotlin.concurrent.scheduleAtFixedRate
|
||||
|
||||
|
||||
/**
|
||||
|
@ -83,9 +84,17 @@ class VirtualMachine(title: String) : IVirtualMachine {
|
|||
val timer = java.util.Timer("cpu-cycle", true)
|
||||
timer.scheduleAtFixedRate(500, 1000/frameRate) {
|
||||
if(!paused) {
|
||||
val prevCycles = cpu.totalCycles
|
||||
while(cpu.totalCycles - prevCycles < desiredCyclesPerFrame) {
|
||||
step()
|
||||
try {
|
||||
val prevCycles = cpu.totalCycles
|
||||
while (cpu.totalCycles - prevCycles < desiredCyclesPerFrame) {
|
||||
step()
|
||||
}
|
||||
} catch(rx: RuntimeException) {
|
||||
JOptionPane.showMessageDialog(hostDisplay, "Run time error: $rx", "Error during execution", JOptionPane.ERROR_MESSAGE)
|
||||
this.cancel()
|
||||
} catch(ex: Error) {
|
||||
JOptionPane.showMessageDialog(hostDisplay, "Run time error: $ex", "Error during execution", JOptionPane.ERROR_MESSAGE)
|
||||
this.cancel()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue
Block a user