1
0
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:
Irmen de Jong 2019-10-08 23:11:18 +02:00
parent 3a97adafa4
commit fbdc08d696
6 changed files with 114 additions and 48 deletions

View File

@ -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,

View File

@ -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
/**

View File

@ -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()
}
}
}

View File

@ -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()
}
}
}
}

View File

@ -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()
}
}
}

View File

@ -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()
}
}
}