From c5683a8383fd9c25f2487286be2da975a2f97a1c Mon Sep 17 00:00:00 2001 From: TQ Hirsch Date: Thu, 28 Sep 2023 17:27:40 +0200 Subject: [PATCH] New trap handling mechanism to support multiple trap causes in a single cycle --- src/main/kotlin/Main.kt | 4 +- src/main/kotlin/com/thequux/mcpdp/core/CPU.kt | 167 ++++++++++++++---- .../kotlin/com/thequux/mcpdp/core/aborts.kt | 7 + 3 files changed, 138 insertions(+), 40 deletions(-) create mode 100644 src/main/kotlin/com/thequux/mcpdp/core/aborts.kt diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index cec8d8a..f887541 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -37,8 +37,8 @@ fun main(args: Array) { cpu.runState = CPU.RunState.RUNNING cpu.pc = 0x80u val start = System.nanoTime() -// val ninsn = cpu.run(600000000) - var ninsn = cpu.run(30000) + val ninsn = cpu.run(600000000) +// var ninsn = cpu.run(30000) // cpu.tracer = tracer // ninsn += cpu.run(100) val end = System.nanoTime() diff --git a/src/main/kotlin/com/thequux/mcpdp/core/CPU.kt b/src/main/kotlin/com/thequux/mcpdp/core/CPU.kt index 3a65fb6..cbf654f 100644 --- a/src/main/kotlin/com/thequux/mcpdp/core/CPU.kt +++ b/src/main/kotlin/com/thequux/mcpdp/core/CPU.kt @@ -9,12 +9,44 @@ import org.slf4j.Logger import org.slf4j.LoggerFactory import java.lang.StringBuilder -/// The main CPU -/// -/// By default, the memory map is arranged as follows: -/// Fxxx: Channel I/O -/// Exxx: ROM -/// xxxx: rest +private enum class TrapReason(val vector: UShort, val load_mmr2: Boolean, val clear: Int) { + /** Floating point error */ + FPE(0xA4u, true, 0), + /** Power fail */ + PWR(0x14u, true, 0), + /** Stack limit yellow */ + YEL(0x04u, true, 0), + /** T bit set */ + TRC(0x0Cu, true, 0), + /** TRAP insn */ + TRP(0x1Cu, false, TRC.mask), + /** EMT insn */ + EMT(0x18u, false, TRC.mask), + /** IOT insn */ + IOT(0x10u, false, TRC.mask), + /** Breakpoint */ + BPT(0x0Cu, false, TRC.mask), + /** Illegal instruction */ + ILL(0x08u, false, TRC.mask), + /** Privileged insn */ + PRV(0x04u, false, TRC.mask), + /** Parity error */ + PAR(0x4Cu, false, TRC.mask or YEL.mask), + /** Memory management exception */ + MME(0xA8u, true, TRC.mask or YEL.mask or PAR.mask), + /** Non-existent memory (or bus timeout) */ + NXM(0x04u, true, TRC.mask or YEL.mask or PAR.mask or MME.mask), + /** Odd address error */ + ODD(0x04u, true, TRC.mask or YEL.mask or PAR.mask or MME.mask or NXM.mask), + /** Stack limit red */ + RED(0x04u, true, TRC.mask or YEL.mask or PAR.mask or MME.mask or NXM.mask or ODD.mask); + + + val mask: Int = 1 shl ordinal +} + +/** The main CPU + */ @OptIn(ExperimentalUnsignedTypes::class) class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) { @@ -24,6 +56,14 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) { val PADDR_DST_BIT: Int = 24 val PADDR_ARG_BIT: Int = 25 + // CPU errors + val CPU_ERR_STK_RED: UShort = 0x04u + val CPU_ERR_STK_YLW: UShort = 0x08u + val CPU_ERR_UNIBUS_TIMEOUT: UShort = 0x10u + val CPU_ERR_NXM: UShort = 0x20u + val CPU_ERR_ODD_ADDR: UShort = 0x40u + val CPU_ERR_ILL_HALT: UShort = 0x80u + val debugMode = System.getProperty("jdp11.itrace").toBoolean() private val insnTable: Array Unit> = Array(256) {{throw InvalidOpcodeException()}} @@ -32,16 +72,20 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) { init { insnTable[0x00] = { opcode -> when (opcode) { 0x0000 -> { - if (cur_mode == 0) + if (cur_mode == 0) { runState = RunState.HALTED + } else { + cpu_err = cpu_err or CPU_ERR_ILL_HALT + setTrap(TrapReason.PRV) + } } // HALT 0x0001 -> runState = RunState.WAIT_FOR_INTERRUPT // WAIT 0x0002 -> { // RTI pc = stack_pop() psw = stack_pop() // TODO: check privilege on mode; check psw[11] } // RTI - 0x0003 -> trap(0x0Cu) // BPT - 0x0004 -> trap(0x10u) // IOT + 0x0003 -> setTrap(TrapReason.BPT) // BPT + 0x0004 -> setTrap(TrapReason.IOT) // IOT 0x0005 -> { stack_limit = 0u pirq = 0u @@ -57,7 +101,7 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) { // jmp val dst = opc_dst(opcode) if (is_paddr_reg(dst)) { - trap(4u); // Illegal instruction + throw InvalidOpcodeException() // ABORT Illegal instruction } pc = dst.toUShort() } // JMP @@ -100,7 +144,7 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) { val r = opcode shr 6 and 0x7 val dst = opc_dst(opcode) if (is_paddr_reg(dst)) { - trap(4u) // illegal opcode + abort(4u) // ABORT illegal opcode } stack_push(registers[r]) registers[r] = pc @@ -230,7 +274,7 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) { when (opcode shr 6 and 3) { 0 -> { // MARK val count = opcode and 0x3F - sp = check_sp((pc + (2 * count).toUInt()).toUShort()) + sp = checkSP((pc + (2 * count).toUInt()).toUShort()) pc = registers[5] registers[5] = stack_pop() } // MARK @@ -477,8 +521,8 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) { insnTable[0x85] = { br_rel(V, it) } // BVS insnTable[0x86] = { br_rel(!C, it) } // BCC/BHIS insnTable[0x87] = { br_rel(C, it) } // BCS/BLO - insnTable[0x88] = { trap(0x18u) } // EMT - insnTable[0x89] = { trap(0x1Cu) } // TRAP + insnTable[0x88] = { setTrap(TrapReason.EMT) } // EMT + insnTable[0x89] = { setTrap(TrapReason.TRP) } // TRAP insnTable[0x8A] = { opcode -> when (opcode shr 6 and 3) { 0 -> { // CLRB @@ -718,6 +762,7 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) { private val general_registers = Array(2) { UShortArray(6) } private val shadow_r6 = UShortArray(4) // private var stack_limit: UShort = 0u + private var trapReq: Int = 0 val core = PagingUnit(mbus) var runState: RunState = RunState.HALTED @@ -942,7 +987,9 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) { } } - /// Internal step; evaluates one opcode, but does not handle errors, interrupts, or updating system registers + /** + * Internal step; evaluates one opcode, but does not handle errors, interrupts, or updating system registers + */ fun step_int() { try { tracer.noteBegin(this) @@ -967,14 +1014,27 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) { } fun step() { - try { - if (runState == RunState.HALTED) return + if (runState == RunState.HALTED) return - if (mbus.unibus.interruptPending) { + try { + // Check early traps + if (trapReq != 0) { + for (cause in TrapReason.entries.reversed()) { + if (trapReq and cause.mask != 0) { + trapReq = trapReq and cause.clear.inv() and cause.mask.inv() + // TODO: load MMR2 if necessary + callVector(cause.vector) + break + } + } + + } + // Check unibus/PIRQ + else if (mbus.unibus.interruptPending) { for (i in 7 downTo (psw_priority + 1)) { if (pirq bit 8 + i) { logger.debug("PIRQ{} trap to 0xA0", i) - trap(0xA0u) + callVector(0xA0u) break } val source = mbus.unibus.checkInterrupt(i) @@ -983,7 +1043,7 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) { runState = RunState.RUNNING val vector = source.vector logger.debug("Unibus interrupt at pri {} to {}", i, vector) - trap(vector) + callVector(vector) source.handled() break } @@ -992,26 +1052,49 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) { val pirqLvl = pirq.toInt() shr 1 and 7; if (pirqLvl > psw_priority) { logger.debug("PIRQ{} trap to 0xA0", pirqLvl) - trap(0xA0u) + callVector(0xA0u) } } - if (runState == RunState.WAIT_FOR_INTERRUPT) { - return - } -// System.err.println("Executing insn at ${pc.toString(8)}") - step_int() + // TODO: handle T bit + } catch (_: EndCycle) { + return + } catch (_: MemoryError) { + // Failed to transfer to vector, treat as STK_RED + cur_mode = 0 + sp = 4u + cpu_err = cpu_err or CPU_ERR_STK_RED + setTrap(TrapReason.RED) + } + + if (runState == RunState.WAIT_FOR_INTERRUPT) { + return + } + + // Proceed to handling instruction + try { + step_int() } catch (error: MemoryError) { // TODO: fill in memory manager fields - cpu_err = when (error) { - is OddAddressError -> cpu_err or 0x40u - is NonExistentMemoryError -> cpu_err or 0x20u - is BusTimeoutError -> cpu_err or 0x10u + when (error) { + is OddAddressError -> { + cpu_err = cpu_err or CPU_ERR_ODD_ADDR + setTrap(TrapReason.ODD) + } + is NonExistentMemoryError -> { + cpu_err = cpu_err or CPU_ERR_NXM + setTrap(TrapReason.NXM) + } + is BusTimeoutError -> { + cpu_err = cpu_err or CPU_ERR_UNIBUS_TIMEOUT + setTrap(TrapReason.NXM) + } } - logger.warn("Threw error: ${error}") - trap(0x04u) + logger.warn("Threw error: $error") } catch (_: InvalidOpcodeException) { - trap(0x10u) + setTrap(TrapReason.ILL) + } catch (_: EndCycle) { + // all end-of-cycle handling already performed } } @@ -1033,8 +1116,7 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) { return ninsn } - fun trap(vector: UShort) { -// logger.info("Trap to {}", vector.toString(8)) + fun callVector(vector: UShort) { val old_psw = psw // update PSW first so that this gets pushed to the psw = core.getw((vector + 2u).toUShort()) @@ -1043,6 +1125,10 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) { pc = core.getw(vector) } + private fun setTrap(reason: TrapReason) { + trapReq = trapReq or reason.mask + } + private inner class Registers: PAddressSpace { override fun getw(addr: UInt): UShort = when (addr) { 0x3FF78u -> 0x70u // Console switch/display @@ -1074,22 +1160,27 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) { } } - fun check_sp(addr: UShort): UShort { + fun checkSP(addr: UShort): UShort { if (cur_mode != 0) return addr if (sp < stack_limit + 224u) { - // setCPUERR(CPUE_RED) // we can always assume that this is running in kernel mode, as no stack check occurs in exec or user mode sp = 4u + cpu_err = cpu_err or CPU_ERR_STK_RED throw Trap(0x04) // stack limit red } else if (sp < stack_limit + 256u) { // stack limit yellow + cpu_err = cpu_err or CPU_ERR_STK_YLW + // setTRAP(TRAP_YELLOW) - // setCPUERR(CPUE_YELLOW) } return addr } + fun abort(vector: UShort) { + throw CpuAbort(vector) + } + enum class RunState(val handleInterrupt: Boolean) { RUNNING(handleInterrupt = true), HALTED(handleInterrupt = false), diff --git a/src/main/kotlin/com/thequux/mcpdp/core/aborts.kt b/src/main/kotlin/com/thequux/mcpdp/core/aborts.kt new file mode 100644 index 0000000..0c8e325 --- /dev/null +++ b/src/main/kotlin/com/thequux/mcpdp/core/aborts.kt @@ -0,0 +1,7 @@ +package com.thequux.mcpdp.core + +open class CpuAbort(val vector: UShort): Exception() +class CpuError(val errcode: UShort): CpuAbort(0x04u) + +// Immediate end of insn cycle +class EndCycle: Exception() \ No newline at end of file