New trap handling mechanism to support multiple trap causes in a single cycle

This commit is contained in:
2023-09-28 17:27:40 +02:00
parent 311fab9801
commit c5683a8383
3 changed files with 138 additions and 40 deletions

View File

@@ -37,8 +37,8 @@ fun main(args: Array<String>) {
cpu.runState = CPU.RunState.RUNNING cpu.runState = CPU.RunState.RUNNING
cpu.pc = 0x80u cpu.pc = 0x80u
val start = System.nanoTime() val start = System.nanoTime()
// val ninsn = cpu.run(600000000) val ninsn = cpu.run(600000000)
var ninsn = cpu.run(30000) // var ninsn = cpu.run(30000)
// cpu.tracer = tracer // cpu.tracer = tracer
// ninsn += cpu.run(100) // ninsn += cpu.run(100)
val end = System.nanoTime() val end = System.nanoTime()

View File

@@ -9,12 +9,44 @@ import org.slf4j.Logger
import org.slf4j.LoggerFactory import org.slf4j.LoggerFactory
import java.lang.StringBuilder import java.lang.StringBuilder
/// The main CPU private enum class TrapReason(val vector: UShort, val load_mmr2: Boolean, val clear: Int) {
/// /** Floating point error */
/// By default, the memory map is arranged as follows: FPE(0xA4u, true, 0),
/// Fxxx: Channel I/O /** Power fail */
/// Exxx: ROM PWR(0x14u, true, 0),
/// xxxx: rest /** 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) @OptIn(ExperimentalUnsignedTypes::class)
class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) { 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_DST_BIT: Int = 24
val PADDR_ARG_BIT: Int = 25 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() val debugMode = System.getProperty("jdp11.itrace").toBoolean()
private val insnTable: Array<CPU.(Int) -> Unit> = Array(256) {{throw InvalidOpcodeException()}} private val insnTable: Array<CPU.(Int) -> Unit> = Array(256) {{throw InvalidOpcodeException()}}
@@ -32,16 +72,20 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) {
init { init {
insnTable[0x00] = { opcode -> when (opcode) { insnTable[0x00] = { opcode -> when (opcode) {
0x0000 -> { 0x0000 -> {
if (cur_mode == 0) if (cur_mode == 0) {
runState = RunState.HALTED runState = RunState.HALTED
} else {
cpu_err = cpu_err or CPU_ERR_ILL_HALT
setTrap(TrapReason.PRV)
}
} // HALT } // HALT
0x0001 -> runState = RunState.WAIT_FOR_INTERRUPT // WAIT 0x0001 -> runState = RunState.WAIT_FOR_INTERRUPT // WAIT
0x0002 -> { // RTI 0x0002 -> { // RTI
pc = stack_pop() pc = stack_pop()
psw = stack_pop() // TODO: check privilege on mode; check psw[11] psw = stack_pop() // TODO: check privilege on mode; check psw[11]
} // RTI } // RTI
0x0003 -> trap(0x0Cu) // BPT 0x0003 -> setTrap(TrapReason.BPT) // BPT
0x0004 -> trap(0x10u) // IOT 0x0004 -> setTrap(TrapReason.IOT) // IOT
0x0005 -> { 0x0005 -> {
stack_limit = 0u stack_limit = 0u
pirq = 0u pirq = 0u
@@ -57,7 +101,7 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) {
// jmp // jmp
val dst = opc_dst(opcode) val dst = opc_dst(opcode)
if (is_paddr_reg(dst)) { if (is_paddr_reg(dst)) {
trap(4u); // Illegal instruction throw InvalidOpcodeException() // ABORT Illegal instruction
} }
pc = dst.toUShort() pc = dst.toUShort()
} // JMP } // JMP
@@ -100,7 +144,7 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) {
val r = opcode shr 6 and 0x7 val r = opcode shr 6 and 0x7
val dst = opc_dst(opcode) val dst = opc_dst(opcode)
if (is_paddr_reg(dst)) { if (is_paddr_reg(dst)) {
trap(4u) // illegal opcode abort(4u) // ABORT illegal opcode
} }
stack_push(registers[r]) stack_push(registers[r])
registers[r] = pc registers[r] = pc
@@ -230,7 +274,7 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) {
when (opcode shr 6 and 3) { when (opcode shr 6 and 3) {
0 -> { // MARK 0 -> { // MARK
val count = opcode and 0x3F val count = opcode and 0x3F
sp = check_sp((pc + (2 * count).toUInt()).toUShort()) sp = checkSP((pc + (2 * count).toUInt()).toUShort())
pc = registers[5] pc = registers[5]
registers[5] = stack_pop() registers[5] = stack_pop()
} // MARK } // MARK
@@ -477,8 +521,8 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) {
insnTable[0x85] = { br_rel(V, it) } // BVS insnTable[0x85] = { br_rel(V, it) } // BVS
insnTable[0x86] = { br_rel(!C, it) } // BCC/BHIS insnTable[0x86] = { br_rel(!C, it) } // BCC/BHIS
insnTable[0x87] = { br_rel(C, it) } // BCS/BLO insnTable[0x87] = { br_rel(C, it) } // BCS/BLO
insnTable[0x88] = { trap(0x18u) } // EMT insnTable[0x88] = { setTrap(TrapReason.EMT) } // EMT
insnTable[0x89] = { trap(0x1Cu) } // TRAP insnTable[0x89] = { setTrap(TrapReason.TRP) } // TRAP
insnTable[0x8A] = { opcode -> insnTable[0x8A] = { opcode ->
when (opcode shr 6 and 3) { when (opcode shr 6 and 3) {
0 -> { // CLRB 0 -> { // CLRB
@@ -718,6 +762,7 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) {
private val general_registers = Array<UShortArray>(2) { UShortArray(6) } private val general_registers = Array<UShortArray>(2) { UShortArray(6) }
private val shadow_r6 = UShortArray(4) // private val shadow_r6 = UShortArray(4) //
private var stack_limit: UShort = 0u private var stack_limit: UShort = 0u
private var trapReq: Int = 0
val core = PagingUnit(mbus) val core = PagingUnit(mbus)
var runState: RunState = RunState.HALTED 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() { fun step_int() {
try { try {
tracer.noteBegin(this) tracer.noteBegin(this)
@@ -967,14 +1014,27 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) {
} }
fun step() { 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)) { for (i in 7 downTo (psw_priority + 1)) {
if (pirq bit 8 + i) { if (pirq bit 8 + i) {
logger.debug("PIRQ{} trap to 0xA0", i) logger.debug("PIRQ{} trap to 0xA0", i)
trap(0xA0u) callVector(0xA0u)
break break
} }
val source = mbus.unibus.checkInterrupt(i) val source = mbus.unibus.checkInterrupt(i)
@@ -983,7 +1043,7 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) {
runState = RunState.RUNNING runState = RunState.RUNNING
val vector = source.vector val vector = source.vector
logger.debug("Unibus interrupt at pri {} to {}", i, vector) logger.debug("Unibus interrupt at pri {} to {}", i, vector)
trap(vector) callVector(vector)
source.handled() source.handled()
break break
} }
@@ -992,26 +1052,49 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) {
val pirqLvl = pirq.toInt() shr 1 and 7; val pirqLvl = pirq.toInt() shr 1 and 7;
if (pirqLvl > psw_priority) { if (pirqLvl > psw_priority) {
logger.debug("PIRQ{} trap to 0xA0", pirqLvl) logger.debug("PIRQ{} trap to 0xA0", pirqLvl)
trap(0xA0u) callVector(0xA0u)
} }
} }
// 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) { if (runState == RunState.WAIT_FOR_INTERRUPT) {
return return
} }
// System.err.println("Executing insn at ${pc.toString(8)}")
// Proceed to handling instruction
try {
step_int() step_int()
// TODO: handle T bit
} catch (error: MemoryError) { } catch (error: MemoryError) {
// TODO: fill in memory manager fields // TODO: fill in memory manager fields
cpu_err = when (error) { when (error) {
is OddAddressError -> cpu_err or 0x40u is OddAddressError -> {
is NonExistentMemoryError -> cpu_err or 0x20u cpu_err = cpu_err or CPU_ERR_ODD_ADDR
is BusTimeoutError -> cpu_err or 0x10u setTrap(TrapReason.ODD)
} }
logger.warn("Threw error: ${error}") is NonExistentMemoryError -> {
trap(0x04u) 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")
} catch (_: InvalidOpcodeException) { } 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 return ninsn
} }
fun trap(vector: UShort) { fun callVector(vector: UShort) {
// logger.info("Trap to {}", vector.toString(8))
val old_psw = psw val old_psw = psw
// update PSW first so that this gets pushed to the // update PSW first so that this gets pushed to the
psw = core.getw((vector + 2u).toUShort()) psw = core.getw((vector + 2u).toUShort())
@@ -1043,6 +1125,10 @@ class CPU(val mbus: MemBus, var tracer: ITracer = NullTracer()) {
pc = core.getw(vector) pc = core.getw(vector)
} }
private fun setTrap(reason: TrapReason) {
trapReq = trapReq or reason.mask
}
private inner class Registers: PAddressSpace { private inner class Registers: PAddressSpace {
override fun getw(addr: UInt): UShort = when (addr) { override fun getw(addr: UInt): UShort = when (addr) {
0x3FF78u -> 0x70u // Console switch/display 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 (cur_mode != 0) return addr
if (sp < stack_limit + 224u) { 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 // we can always assume that this is running in kernel mode, as no stack check occurs in exec or user mode
sp = 4u sp = 4u
cpu_err = cpu_err or CPU_ERR_STK_RED
throw Trap(0x04) throw Trap(0x04)
// stack limit red // stack limit red
} else if (sp < stack_limit + 256u) { } else if (sp < stack_limit + 256u) {
// stack limit yellow // stack limit yellow
cpu_err = cpu_err or CPU_ERR_STK_YLW
// setTRAP(TRAP_YELLOW) // setTRAP(TRAP_YELLOW)
// setCPUERR(CPUE_YELLOW)
} }
return addr return addr
} }
fun abort(vector: UShort) {
throw CpuAbort(vector)
}
enum class RunState(val handleInterrupt: Boolean) { enum class RunState(val handleInterrupt: Boolean) {
RUNNING(handleInterrupt = true), RUNNING(handleInterrupt = true),
HALTED(handleInterrupt = false), HALTED(handleInterrupt = false),

View File

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