From c493c3e5c65b5d7467af3917201c98982ff2d5ac Mon Sep 17 00:00:00 2001 From: Irmen de Jong Date: Mon, 9 Sep 2019 23:28:41 +0200 Subject: [PATCH] implemented IRQ handling --- sim65/src/Sim65Main.kt | 22 +++++- sim65/src/components/Cpu6502.kt | 103 +++++++++++++++------------ sim65/src/components/Cpu65C02.kt | 10 +++ sim65/src/components/ParallelPort.kt | 2 +- sim65/src/components/Timer.kt | 22 +++--- sim65/test/C64KernalStubs.kt | 12 ++-- sim65/test/Test6502.kt | 2 + sim65/test/Test6502TestSuite.kt | 4 +- sim65/test/Test65C02.kt | 5 +- sim65/test/TestCommon6502.kt | 4 +- 10 files changed, 119 insertions(+), 67 deletions(-) create mode 100644 sim65/src/components/Cpu65C02.kt diff --git a/sim65/src/Sim65Main.kt b/sim65/src/Sim65Main.kt index f38290467..ec634c095 100644 --- a/sim65/src/Sim65Main.kt +++ b/sim65/src/Sim65Main.kt @@ -1,6 +1,8 @@ package sim65 import sim65.components.* +import sim65.components.Cpu6502.Companion.IRQ_vector +import sim65.components.Cpu6502.Companion.NMI_vector import sim65.components.Cpu6502.Companion.RESET_vector @@ -25,6 +27,11 @@ private fun startSimulator(args: Array) { val ram = Ram(0, 0xffff) ram[RESET_vector] = 0x00 ram[RESET_vector + 1] = 0x10 + ram[IRQ_vector] = 0x00 + ram[IRQ_vector + 1] = 0x20 + ram[NMI_vector] = 0x00 + ram[NMI_vector + 1] = 0x30 + // // read the RTC and write the date+time to $2000 // for(b in listOf(0xa0, 0x00, 0xb9, 0x00, 0xd1, 0x99, 0x00, 0x20, 0xc8, 0xc0, 0x09, 0xd0, 0xf5, 0x00).withIndex()) { // ram[0x1000+b.index] = b.value.toShort() @@ -37,9 +44,16 @@ private fun startSimulator(args: Array) { } + // load the irq routine that prints 'irq!' to the parallel port + for(b in listOf(0x48, 0xa9, 0x09, 0x8d, 0x00, 0xd0, 0xee, 0x01, 0xd0, 0xa9, 0x12, 0x8d, 0x00, 0xd0, + 0xee, 0x01, 0xd0, 0xa9, 0x11, 0x8d, 0x00, 0xd0, 0xee, 0x01, 0xd0, 0xa9, 0x21, 0x8d, 0x00, 0xd0, + 0xee, 0x01, 0xd0, 0x68, 0x40).withIndex()) { + ram[0x2000+b.index] = b.value.toShort() + } + val parallel = ParallelPort(0xd000, 0xd001) val clock = RealTimeClock(0xd100, 0xd108) - val timer = Timer(0xd200, 0xd203) + val timer = Timer(0xd200, 0xd203, cpu) val bus = Bus() bus.add(cpu) @@ -49,13 +63,19 @@ private fun startSimulator(args: Array) { bus.add(ram) bus.reset() + cpu.Status.I = false // enable interrupts + try { while (true) { bus.clock() } } catch (ix: InstructionError) { + println("HMMM $ix") // ignore } + ram.hexDump(0x1000, 0x1020) + val dis = cpu.disassemble(ram, 0x1000, 0x1020) + println(dis.joinToString("\n")) ram.hexDump(0x2000, 0x2008) } diff --git a/sim65/src/components/Cpu6502.kt b/sim65/src/components/Cpu6502.kt index eb293810d..477250fbf 100644 --- a/sim65/src/components/Cpu6502.kt +++ b/sim65/src/components/Cpu6502.kt @@ -2,29 +2,14 @@ package sim65.components class InstructionError(msg: String) : RuntimeException(msg) -interface ICpu { - fun disassemble(memory: Array, baseAddress: Address, from: Address, to: Address): List - fun disassemble(component: MemoryComponent, from: Address, to: Address) = - disassemble(component.cloneContents(), component.startAddress, from, to) - - fun clock() - fun reset() - fun step() - fun breakpoint(address: Address, action: (cpu: ICpu, pc: Address) -> Unit) - - var tracing: Boolean - val totalCycles: Long -} // TODO: implement the illegal opcodes, see http://www.ffd2.com/fridge/docs/6502-NMOS.extra.opcodes // TODO: add the optional additional cycles to certain instructions and addressing modes -// TODO: add IRQ and NMI signaling. -// TODO: make a 65c02 variant as well (and re-enable the unit tests for that). -class Cpu6502(private val stopOnBrk: Boolean) : BusComponent(), ICpu { - override var tracing: Boolean = false - override var totalCycles: Long = 0 +open class Cpu6502(private val stopOnBrk: Boolean) : BusComponent() { + var tracing: Boolean = false + var totalCycles: Long = 0 private set companion object { @@ -128,9 +113,11 @@ class Cpu6502(private val stopOnBrk: Boolean) : BusComponent(), ICpu { var PC: Address = 0 val Status = StatusRegister() var currentOpcode: Int = 0 - var waiting: Boolean = false // 65c02 private lateinit var currentInstruction: Instruction + // has an interrupt been requested? + private var pendingInterrupt: Pair? = null + // data byte from the instruction (only set when addr.mode is Accumulator, Immediate or Implied) private var fetchedData: Int = 0 @@ -153,13 +140,16 @@ class Cpu6502(private val stopOnBrk: Boolean) : BusComponent(), ICpu { AddrMode.IzY to ::amIzy ) - private val breakpoints = mutableMapOf Unit>() + private val breakpoints = mutableMapOf Unit>() - override fun breakpoint(address: Address, action: (cpu: ICpu, pc: Address) -> Unit) { + fun breakpoint(address: Address, action: (cpu: Cpu6502, pc: Address) -> Unit) { breakpoints[address] = action } - override fun disassemble(memory: Array, baseAddress: Address, from: Address, to: Address): List { + fun disassemble(component: MemoryComponent, from: Address, to: Address) = + disassemble(component.cloneContents(), component.startAddress, from, to) + + fun disassemble(memory: Array, baseAddress: Address, from: Address, to: Address): List { var address = from - baseAddress val spacing1 = " " val spacing2 = " " @@ -258,27 +248,35 @@ class Cpu6502(private val stopOnBrk: Boolean) : BusComponent(), ICpu { instrCycles = resetCycles // a reset takes time as well currentOpcode = 0 currentInstruction = opcodes[0] - waiting = false } override fun clock() { if (instrCycles == 0) { - currentOpcode = read(PC) - currentInstruction = opcodes[currentOpcode] - if (tracing) printState() + if (pendingInterrupt != null) { + // NMI or IRQ interrupt. + // handled by the BRK instruction logic. + currentOpcode = 0 + currentInstruction = opcodes[0] + } else { + // no interrupt, continue with next instruction + currentOpcode = read(PC) + currentInstruction = opcodes[currentOpcode] - breakpoints[PC]?.let { - val oldPC = PC - val oldOpcode = currentOpcode - it(this, PC) - if (PC != oldPC) - return clock() - if (oldOpcode != currentOpcode) - currentInstruction = opcodes[currentOpcode] - } + if (tracing) printState() - if (currentOpcode == 0 && stopOnBrk) { - throw InstructionError("stopped on BRK instruction at ${hexW(PC)}") + breakpoints[PC]?.let { + val oldPC = PC + val oldOpcode = currentOpcode + it(this, PC) + if (PC != oldPC) + return clock() + if (oldOpcode != currentOpcode) + currentInstruction = opcodes[currentOpcode] + } + + if (currentOpcode == 0 && stopOnBrk) { + throw InstructionError("stopped on BRK instruction at ${hexW(PC)}") + } } PC++ @@ -291,13 +289,22 @@ class Cpu6502(private val stopOnBrk: Boolean) : BusComponent(), ICpu { totalCycles++ } - override fun step() { + fun step() { // step a whole instruction while (instrCycles > 0) clock() // remaining instruction subcycles from the previous instruction clock() // the actual instruction execution cycle while (instrCycles > 0) clock() // instruction subcycles } + fun nmi(source: BusComponent) { + pendingInterrupt = Pair(true, source) + } + + fun irq(source: BusComponent) { + if (!Status.I) + pendingInterrupt = Pair(false, source) + } + fun printState() { println("cycle:$totalCycles - pc=${hexW(PC)} " + "A=${hexB(A)} " + @@ -794,12 +801,21 @@ class Cpu6502(private val stopOnBrk: Boolean) : BusComponent(), ICpu { } private fun iBrk() { - PC++ - pushStackAddr(PC) - Status.B = true + // handle BRK ('software interrupt') or a real hardware IRQ + val interrupt = pendingInterrupt + val nmi = interrupt?.first == true + if (interrupt != null) { + pushStackAddr(PC - 1) + } else { + PC++ + pushStackAddr(PC) + } + Status.B = interrupt == null pushStack(Status) - Status.I = true - PC = readWord(IRQ_vector) + Status.I = true // interrupts are now disabled + // NMOS 6502 doesn't clear the D flag (CMOS version does...) + PC = readWord(if (nmi) NMI_vector else IRQ_vector) + pendingInterrupt = null } private fun iBvc() { @@ -1167,5 +1183,4 @@ class Cpu6502(private val stopOnBrk: Boolean) : BusComponent(), ICpu { private fun iXaa() { TODO("xaa - ('illegal' instruction)") } - } diff --git a/sim65/src/components/Cpu65C02.kt b/sim65/src/components/Cpu65C02.kt new file mode 100644 index 000000000..1c9c076db --- /dev/null +++ b/sim65/src/components/Cpu65C02.kt @@ -0,0 +1,10 @@ +package sim65.components + +class Cpu65C02(stopOnBrk: Boolean): Cpu6502(stopOnBrk) { + + + val waiting: Boolean = false + + // TODO implement this CPU type 65C02, and re-enable the unit tests for that + +} diff --git a/sim65/src/components/ParallelPort.kt b/sim65/src/components/ParallelPort.kt index e86fe3e8d..9298fd427 100644 --- a/sim65/src/components/ParallelPort.kt +++ b/sim65/src/components/ParallelPort.kt @@ -8,7 +8,7 @@ import sim65.Petscii * Second address = control byte (bit 0 high = write byte) */ class ParallelPort(startAddress: Address, endAddress: Address) : MemMappedComponent(startAddress, endAddress) { - private var dataByte: UByte = 0 + var dataByte: UByte = 0 init { require(endAddress - startAddress + 1 == 2) { "parallel needs exactly 2 memory bytes (data + control)" } diff --git a/sim65/src/components/Timer.kt b/sim65/src/components/Timer.kt index 55d482b1f..982563fa6 100644 --- a/sim65/src/components/Timer.kt +++ b/sim65/src/components/Timer.kt @@ -8,11 +8,18 @@ package sim65.components * 02 24 bits interval value, bits 8-15 (mid) * 03 24 bits interval value, bits 16-23 (hi) */ -class Timer(startAddress: Address, endAddress: Address) : MemMappedComponent(startAddress, endAddress) { +class Timer(startAddress: Address, endAddress: Address, val cpu: Cpu6502) : MemMappedComponent(startAddress, endAddress) { private var counter: Int = 0 private var interval: Int = 0 - private var enabled = false private var nmi = false + private var enabled = false + set(value) { + if(value && !field) { + // timer is set to enabled (was disabled) - reset the counter + counter = 0 + } + field = value + } init { require(endAddress - startAddress + 1 == 4) { "timer needs exactly 4 memory bytes" } @@ -23,9 +30,9 @@ class Timer(startAddress: Address, endAddress: Address) : MemMappedComponent(sta counter++ if (counter == interval) { if (nmi) - println("TODO: timer causes CPU nmi $counter") // TODO + cpu.nmi(this) else - println("TODO: timer causes CPU irq $counter") // TODO + cpu.irq(this) counter = 0 } } @@ -63,13 +70,8 @@ class Timer(startAddress: Address, endAddress: Address) : MemMappedComponent(sta when (address - startAddress) { 0 -> { val i = data.toInt() - val newEnabled = (i and 0b00000001) != 0 + enabled = (i and 0b00000001) != 0 nmi = (i and 0b00000010) != 0 - if(newEnabled && !enabled) { - // timer is set to enabled (was disabled) - reset the counter - counter = 0 - } - enabled = newEnabled } 1 -> { interval = (interval and 0x7fffff00) or data.toInt() diff --git a/sim65/test/C64KernalStubs.kt b/sim65/test/C64KernalStubs.kt index 92f2c3389..7fc2c5ba2 100644 --- a/sim65/test/C64KernalStubs.kt +++ b/sim65/test/C64KernalStubs.kt @@ -1,13 +1,11 @@ import sim65.Petscii import sim65.components.Address import sim65.components.Cpu6502 -import sim65.components.ICpu import sim65.components.Ram class C64KernalStubs(private val ram: Ram) { - fun handleBreakpoint(cpu: ICpu, pc: Address) { - cpu as Cpu6502 + fun handleBreakpoint(cpu: Cpu6502, pc: Address) { when(pc) { 0xffd2 -> { // CHROUT @@ -21,7 +19,7 @@ class C64KernalStubs(private val ram: Ram) { } 0xffe4 -> { // GETIN - throw InputRequired() + throw KernalInputRequired() // print("[Input required:] ") // val s = readLine() // if(s.isNullOrEmpty()) @@ -31,7 +29,7 @@ class C64KernalStubs(private val ram: Ram) { // cpu.currentOpcode = 0x60 // rts to end the stub } 0xe16f -> { - throw LoadNextPart() + throw KernalLoadNextPart() // LOAD/VERIFY // val loc = ram[0xbb].toInt() or (ram[0xbc].toInt() shl 8) // val len = ram[0xb7].toInt() @@ -44,6 +42,6 @@ class C64KernalStubs(private val ram: Ram) { } } -class LoadNextPart: Exception() -class InputRequired: Exception() +internal class KernalLoadNextPart: Exception() +internal class KernalInputRequired: Exception() diff --git a/sim65/test/Test6502.kt b/sim65/test/Test6502.kt index d4cdda308..15682e27f 100644 --- a/sim65/test/Test6502.kt +++ b/sim65/test/Test6502.kt @@ -43,6 +43,8 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. class Test6502 : TestCommon6502() { // NMOS 6502 tests + override fun createCpu() = Cpu6502(false) + // ADC Indirect, Indexed (X) @Test diff --git a/sim65/test/Test6502TestSuite.kt b/sim65/test/Test6502TestSuite.kt index 9d6edf4ce..e539c1d08 100644 --- a/sim65/test/Test6502TestSuite.kt +++ b/sim65/test/Test6502TestSuite.kt @@ -58,9 +58,9 @@ class Test6502TestSuite { fail("test hangs") } catch (e: InstructionError) { println(">>> INSTRUCTION ERROR: ${e.message}") - } catch (le: LoadNextPart) { + } catch (le: KernalLoadNextPart) { return // test ok - } catch (ie: InputRequired) { + } catch (ie: KernalInputRequired) { fail("test failed") } fail("test failed") diff --git a/sim65/test/Test65C02.kt b/sim65/test/Test65C02.kt index 921fc3596..2e16fc702 100644 --- a/sim65/test/Test65C02.kt +++ b/sim65/test/Test65C02.kt @@ -1,6 +1,7 @@ import org.junit.jupiter.api.Disabled import org.junit.jupiter.api.Test import org.junit.jupiter.api.TestInstance +import sim65.components.Cpu65C02 import kotlin.test.* @@ -40,9 +41,10 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. @TestInstance(TestInstance.Lifecycle.PER_METHOD) -@Disabled("there is no 65C02 cpu implementation at this time") // TODO create a 65c02 cpu! +@Disabled("there is no 65C02 cpu implementation at this time") // TODO create a 65c02 cpu and enable this again class Test65C02 : TestCommon6502() { // CMOS 65C02 Tests + override fun createCpu() = Cpu65C02(false) // Reset @@ -1535,6 +1537,7 @@ class Test65C02 : TestCommon6502() { @Test fun test_wai_sets_waiting() { + mpu as Cpu65C02 assertFalse(mpu.waiting) // $0240 WAI memory[0x0204] = 0xcb diff --git a/sim65/test/TestCommon6502.kt b/sim65/test/TestCommon6502.kt index 7171d3194..f9e7adac9 100644 --- a/sim65/test/TestCommon6502.kt +++ b/sim65/test/TestCommon6502.kt @@ -45,10 +45,12 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. abstract class TestCommon6502 { // Tests common to 6502-based microprocessors - val mpu = Cpu6502(stopOnBrk = false) // TODO make a 65C02 cpu as well and let the subclasses testsuites define the appropriate instance + val mpu = createCpu() val memory = Ram(0, 0xffff) val bus = Bus() + abstract fun createCpu(): Cpu6502 + init { bus.add(mpu) bus.add(memory)