diff --git a/src/main/kotlin/com/thequux/mcpdp/core/AddressSpace.kt b/src/main/kotlin/com/thequux/mcpdp/core/AddressSpace.kt index 7161445..43b03fe 100644 --- a/src/main/kotlin/com/thequux/mcpdp/core/AddressSpace.kt +++ b/src/main/kotlin/com/thequux/mcpdp/core/AddressSpace.kt @@ -6,9 +6,9 @@ import com.thequux.mcpdp.ext.bit.shl import com.thequux.mcpdp.ext.bit.shr interface VAddressSpace { - fun getw(addr: UShort, dspace: Boolean = false): UShort + fun getw(addr: UShort, dspace: Boolean = true): UShort fun getb(addr: UShort): UByte - fun setw(addr: UShort, value: UShort, dspace: Boolean = false) + fun setw(addr: UShort, value: UShort, dspace: Boolean = true) fun setb(addr: UShort, value: UByte) } diff --git a/src/main/kotlin/com/thequux/mcpdp/core/CPU.kt b/src/main/kotlin/com/thequux/mcpdp/core/CPU.kt index 3e351c8..846d617 100644 --- a/src/main/kotlin/com/thequux/mcpdp/core/CPU.kt +++ b/src/main/kotlin/com/thequux/mcpdp/core/CPU.kt @@ -2,9 +2,11 @@ package com.thequux.mcpdp.core import com.thequux.mcpdp.ext.bit.* import com.thequux.mcpdp.peripheral.MemBus +import com.thequux.mcpdp.util.Disassembler import com.thequux.mcpdp.util.ProgrammerError import org.slf4j.Logger import org.slf4j.LoggerFactory +import kotlin.math.log /// The main CPU /// @@ -15,6 +17,12 @@ import org.slf4j.LoggerFactory @OptIn(ExperimentalUnsignedTypes::class) class CPU(val mbus: MemBus) { + companion object { + val debugMode = System.getProperty("jdp11.itrace").toBoolean() + } + + + private var control_reg: UShort = 0u val logger: Logger = LoggerFactory.getLogger(this.javaClass) private val flagStr: String get() { @@ -29,6 +37,7 @@ class CPU(val mbus: MemBus) { private val general_registers = Array(2) { UShortArray(6) } private val shadow_r6 = UShortArray(4) // val core = PagingUnit(mbus) + private val dasm = Disassembler(core) var runState: RunState = RunState.HALTED var psw: UShort @@ -119,10 +128,12 @@ class CPU(val mbus: MemBus) { private var V: Boolean = false private var T: Boolean = false private val PADDR_REG_BIT: Int = 23 + private val PADDR_ISPACE_BIT: Int = 22 init { val regs = Registers() mbus.unibus.run { + attach(0x3FFE6u, 1, regs) attach(0x3FFF8u, 3, regs) attach(0x3FFF4u, 2, regs) } @@ -130,38 +141,40 @@ class CPU(val mbus: MemBus) { } private fun is_paddr_reg(addr: UInt): Boolean = addr bit PADDR_REG_BIT + private fun is_paddr_ispace(addr: UInt): Boolean = addr bit PADDR_ISPACE_BIT private fun op_resolve(operand: Int, byte_mode: Boolean = false): UInt { val mode = operand shr 3 val reg = operand and 0x7 - val increment = if (byte_mode && reg != 7) 1U else 2U + val is_pc = reg == 7 + val increment = if (byte_mode && !is_pc) 1U else 2U return 0x80_FFFFu and when (mode and 0x7) { 0 -> reg.toUInt() bis PADDR_REG_BIT 1 -> registers[reg].toUInt() 2 -> { val addr = registers[reg] registers[reg] = (addr + increment).toUShort() - addr.toUInt() + addr.toUInt().bit(PADDR_ISPACE_BIT, is_pc) } 3 -> { val addr = registers[reg] registers[reg] = (addr + 2U).toUShort() - core.getw(addr).toUInt() + core.getw(addr, dspace = !is_pc).toUInt() } 4 -> { registers[reg] = (registers[reg] - increment).toUShort() - return registers[reg].toUInt() + return registers[reg].toUInt().bit(PADDR_ISPACE_BIT, is_pc) } 5 -> { registers[reg] = (registers[reg] - 2U).toUShort() - core.getw(registers[reg]).toUInt() + core.getw(registers[reg], dspace = !is_pc).toUInt() } 6 -> { - val idx = core.getw(pc) + val idx = core.getw(pc, dspace = false) pc = (pc + 2u).toUShort() - idx + registers[reg] + (idx + registers[reg]) and 0xFFFFu } 7 -> { - val idx = core.getw(pc) + val idx = core.getw(pc, dspace = false) pc = (pc+2u).toUShort() core.getw((idx+registers[reg]).toUShort()).toUInt() } @@ -226,6 +239,9 @@ class CPU(val mbus: MemBus) { /// Internal step; evaluates one opcode, but does not handle errors, interrupts, or updating system registers fun step_int() { + if (debugMode) { + logger.debug("${pc.toString(8).padStart(6, '0')}: ${dasm.dasm_at(pc)}") + } val opcode = core.getw(pc).toInt() pc = (pc + 2u).toUShort() when (opcode and 0xF000) { @@ -618,7 +634,7 @@ class CPU(val mbus: MemBus) { 0x7000 -> when (opcode shr 9 and 0x7) { 0 -> { // MUL val r = opcode shr 6 and 0x7 - val src = op_loadw(opc_src(opcode)).toShort().toInt() + val src = op_loadw(opc_dst(opcode)).toShort().toInt() val res = registers[r].toShort().toInt() * src registers[r bis 1] = (res shr 16).toUShort() registers[r] = res.toUShort() @@ -629,7 +645,7 @@ class CPU(val mbus: MemBus) { } // MUL 1 -> { // DIV val r = opcode shr 6 and 0x7 - val src = op_loadw(opc_src(opcode)).toInt() + val src = op_loadw(opc_dst(opcode)).toInt() val lho = (registers[r].toUInt() shl 16 or registers[r bis 1].toUInt()).toInt() if (src != 0) { val quot = lho / src @@ -647,7 +663,7 @@ class CPU(val mbus: MemBus) { } // DIV 2 -> { // ASH val r = opcode shr 6 and 0x7 - var count = (op_loadb(opc_src(opcode)) and 0x3Fu).toInt() + var count = (op_loadb(opc_dst(opcode)) and 0x3Fu).toInt() val src = registers[r].toShort().toInt() // two casts to sign extend count = count sex 6 V = false @@ -674,7 +690,7 @@ class CPU(val mbus: MemBus) { } // ASH 3 -> { // ASHC val r = opcode shr 6 and 0x7 - var count = (op_loadb(opc_src(opcode)) and 0x3Fu).toInt() sex 6 + var count = (op_loadb(opc_dst(opcode)) and 0x3Fu).toInt() sex 6 val src = (registers[r].toUInt() shl 16 or registers[r or 1].toUInt()).toInt() // two casts to sign extend V = false val res = if (count > 0) { @@ -782,9 +798,9 @@ class CPU(val mbus: MemBus) { try { if (runState == RunState.HALTED) return - // TODO: handle PIRQ for (i in 7 downTo (psw_priority + 1)) { if (pirq bit 8+i) { + logger.debug("PIRQ{} trap to 0xA0", i) trap(0xA0u) break } @@ -792,12 +808,16 @@ class CPU(val mbus: MemBus) { if (source != null) { // we might have been waiting for an interrupt runState = RunState.RUNNING - trap(source.vector) + val vector = source.vector + logger.debug("Unibus interrupt at pri {} to {}", i, vector) + trap(vector) source.handled() break } } - if (runState == RunState.WAIT_FOR_INTERRUPT) return + if (runState == RunState.WAIT_FOR_INTERRUPT) { + return + } // System.err.println("Executing insn at ${pc.toString(8)}") step_int() // TODO: handle T bit @@ -808,9 +828,9 @@ class CPU(val mbus: MemBus) { is NonExistentMemoryError -> cpu_err or 0x20u is BusTimeoutError -> cpu_err or 0x10u } - trap(4u) + trap(0x04u) } catch (_: InvalidOpcodeException) { - trap(10u) + trap(0x10u) } } @@ -824,14 +844,18 @@ class CPU(val mbus: MemBus) { } fun trap(vector: UShort) { - stack_push(psw) + logger.debug("Trap to {}", vector.toString(8)) + val old_psw = psw + // update PSW first so that this gets pushed to the + psw = core.getw((vector + 2u).toUShort()) + stack_push(old_psw) stack_push(pc) pc = core.getw(vector) - psw = core.getw((vector + 2u).toUShort()) } private inner class Registers: PAddressSpace { override fun getw(addr: UInt): UShort = when (addr) { + 0x3FFE6u -> control_reg 0x3FFF0u -> (mbus.size shr 6).toUShort() 0x3FFF2u -> 0u // upper size 0x3FFF4u -> 0x1170u // system ID @@ -843,6 +867,7 @@ class CPU(val mbus: MemBus) { } override fun setw(addr: UInt, value: UShort) = when (addr) { + 0x3FFE6u -> control_reg = value 0x3_FFF0u , 0x3_FFF2u , 0x3_FFF4u, 0x3_FFF8u -> {} // read-only registers 0x3_FFF6u -> cpu_err = value 0x3_FFFAu -> pirq = value diff --git a/src/main/kotlin/com/thequux/mcpdp/core/PagingUnit.kt b/src/main/kotlin/com/thequux/mcpdp/core/PagingUnit.kt index ccca081..a6b51ca 100644 --- a/src/main/kotlin/com/thequux/mcpdp/core/PagingUnit.kt +++ b/src/main/kotlin/com/thequux/mcpdp/core/PagingUnit.kt @@ -6,6 +6,7 @@ import com.thequux.mcpdp.ext.bit.bit import com.thequux.mcpdp.ext.bit.shr import com.thequux.mcpdp.peripheral.Unibus import com.thequux.mcpdp.util.ProgrammerError +import kotlin.math.max enum class AccessAction { Allow, @@ -43,20 +44,27 @@ private data class PDR(val plf: UShort, var A: Boolean, var W: Boolean, var ed: val range: UIntRange get() = if (ed) { - (0x2000U - (plf.toUInt() shl 6))..0x1FFFu + (plf.toUInt() shl 6) ..< 0x2000u } else { - 0U..((plf.toUInt() shl 6) -1U) + 0U..< (plf.toUInt() shl 6) } fun touched() { A = true } fun written() { A = true; W = true } } +private enum class MManMode(val mmanEnable: Boolean, val addrMask: UInt, val hipageMask: UInt) { + MM_16(addrMask = 0xFFFFu, mmanEnable = false, hipageMask = 0xF000u), + MM_18(addrMask = 0x3_FFFFu, mmanEnable = true, hipageMask = 0x3_E000u), + MM_22(addrMask = 0x3F_FFFFu, mmanEnable = true, hipageMask = 0x3C_0000u), +} + @OptIn(ExperimentalUnsignedTypes::class) class PagingUnit(val pspace: PAddressSpace): VAddressSpace { private var mmr = UShortArray(4) private var enableUnibusMap: Boolean = false private var enable22bit: Boolean = false + private var mapMode: MManMode = MManMode.MM_16 /// The 18-bit address space exposed to peripherals val unibusMap: PAddressSpace = UnibusMap() @@ -102,20 +110,30 @@ class PagingUnit(val pspace: PAddressSpace): VAddressSpace { throw Trap(0xA8) } } - return addr.toUInt() + (par.toUInt() shl 6) and 0x3F_FFFFu + val tmp1 = addr.toUInt() + (par.toUInt() shl 6) and mapMode.addrMask + if (tmp1 and mapMode.hipageMask == mapMode.hipageMask) { + return tmp1 or 0x3C_0000u + } else { + return tmp1 + } } } var mode: Int = 0 set(value) { field = value - modeSpace = if (mmr[0] bit 0) { - modeVTabs[if (field == 3) 2 else field] - } else { - noMmanSpace - } + updateMode() } + fun updateMode() { + mapMode = when { + !(mmr[0] bit 0) -> MManMode.MM_16 + enable22bit -> MManMode.MM_22 + else -> MManMode.MM_18 + } + modeSpace = if (mapMode.mmanEnable) modeVTabs[max(mode, 2)] else noMmanSpace + } + private val itabs: Array = Array(3) { PageTable(if (it == 2) 3 else it, dspace = false) } private val dtabs: Array = Array(3) { PageTable(if (it == 2) 3 else it, dspace = true) } private val modeVTabs: Array = Array(3) { ModeVSpace(itabs[it], dtabs[it], false) } @@ -141,7 +159,13 @@ class PagingUnit(val pspace: PAddressSpace): VAddressSpace { } private inner class NoMmanSpace: VAddressSpace { - private fun map(addr: UShort): UInt = if ((addr shr 13) == 7u.toUShort()) addr.toUInt().or(0x3F0000u) else addr.toUInt() + private fun map(addr: UShort): UInt { + if ((addr shr 13) == 7u.toUShort()) { + return addr.toUInt() or 0x3F0000u + } else { + return addr.toUInt() + } + } override fun getb(addr: UShort): UByte = pspace.getb(map(addr)) override fun getw(addr: UShort, dspace: Boolean): UShort = pspace.getw(map(addr)) override fun setb(addr: UShort, value: UByte) = pspace.setb(map(addr), value) @@ -149,20 +173,34 @@ class PagingUnit(val pspace: PAddressSpace): VAddressSpace { } companion object { + private val MMR0: UInt = 0x3FF8Au + private val MMR1: UInt = 0x3FF8Cu + private val MMR2: UInt = 0x3FF8Eu private val MMR3: UInt = 0x3F54Eu } - override fun getw(addr: UShort, dspace: Boolean): UShort = modeSpace.getw(addr, dspace) + private fun withRecovery(addr: UShort, op: PagingUnit.() -> E): E { + try { + return this.op() + } catch (e: MemoryError) { + e.addr = addr.toUInt() + throw e + } + } + override fun getw(addr: UShort, dspace: Boolean): UShort = withRecovery(addr) { modeSpace.getw(addr, dspace) } - override fun getb(addr: UShort): UByte = modeSpace.getb(addr) + override fun getb(addr: UShort): UByte = withRecovery(addr) { modeSpace.getb(addr) } - override fun setw(addr: UShort, value: UShort, dspace: Boolean) = modeSpace.setw(addr, value, dspace) + override fun setw(addr: UShort, value: UShort, dspace: Boolean) = withRecovery(addr) { modeSpace.setw(addr, value, dspace) } - override fun setb(addr: UShort, value: UByte) = modeSpace.setb(addr, value) + override fun setb(addr: UShort, value: UByte) = withRecovery(addr) { modeSpace.setb(addr, value) } private inner class ConfigRegisters: PAddressSpace { override fun getw(addr: UInt): UShort = when (addr) { + MMR0 -> mmr[0] + MMR1 -> mmr[1] + MMR2 -> mmr[2] MMR3 -> { (0U.bit(0, modeVTabs[2].useDSpace) .bit(1, modeVTabs[1].useDSpace) @@ -181,6 +219,7 @@ class PagingUnit(val pspace: PAddressSpace): VAddressSpace { modeVTabs[0].useDSpace = value bit 2 modeVTabs[1].useDSpace = value bit 1 modeVTabs[2].useDSpace = value bit 0 + updateMode() } in 0x3F080u..0x3F0FFu -> { val uaddr = (addr shr 1 and 31u).toInt() diff --git a/src/main/kotlin/com/thequux/mcpdp/util/DebugTools.kt b/src/main/kotlin/com/thequux/mcpdp/util/DebugTools.kt new file mode 100644 index 0000000..6513a62 --- /dev/null +++ b/src/main/kotlin/com/thequux/mcpdp/util/DebugTools.kt @@ -0,0 +1,241 @@ +package com.thequux.mcpdp.util + +import com.thequux.mcpdp.core.CPU +import com.thequux.mcpdp.core.InvalidOpcodeException +import com.thequux.mcpdp.core.VAddressSpace +import com.thequux.mcpdp.ext.bit.* + +object DebugTools { + + +} + +class NullDisassembler() { + fun dasm_at(loc: UShort): String = "" +} +class Disassembler(val core: VAddressSpace) { + + var vpc: UShort = 0u + var opc: UShort = 0u + var opcode: Int = 0 + + private fun fmt(opcode: String, vararg args: String): String { + val res = StringBuilder() + res.append(opcode.uppercase()) + + for ((index, arg) in args.withIndex()) { + if (index == 0) { + res.append("\t") + } else { + res.append(", ") + } + res.append(arg) + } + return res.toString() + } + + private fun reg_name(reg: Int): String { + return when(reg) { + 6 -> "SP" + 7 -> "PC" + else -> "R$reg" + } + } + + private fun getw(): UShort { + val ret = core.getw(vpc, dspace = false) + vpc = vpc.inc().inc() + return ret + } + private fun arg_at(offset: Int): String { + val rv = opcode shr offset and 0x3F + val reg = rv and 0x7 + val rn = reg_name(reg) + val mode = rv shr 3 and 0x6 + val ind = if (rv bit 3) "@" else "" + return when (mode) { + 0 -> "$ind$rn" + 2 -> if (reg == 7) "$ind#${getw().toString(8)}" else "$ind($rn)+" + 4 -> "$ind-($rn)" + 6 -> if (reg == 7) { + ind + (getw() + vpc).toShort().toString(8) + } else { + "$ind${getw().toString(8)}($rn)" + } + else -> throw Exception("Failed to decode arg: ${rv.toString(8)}") + } + } + private fun dst() = arg_at(0) + private fun src() = arg_at(6) + private fun reg0(): String = reg_name(opcode and 0x7) + private fun reg6(): String = reg_name(opcode shr 6 and 0x7) + + private fun br_rel(opc: String): String { + val rel = (opcode and 0xFF) - (opcode and 0x80 shl 1) + val dst = (vpc.toInt() + rel).toUShort() + return fmt(opc, dst.toString(8)) + } + + private fun rel6(): String { + val rel = (opcode and 0x3F) - (opcode and 0x20 shl 1) + val dst = vpc.toInt() + rel + return dst.toUShort().toString(8) + } + + fun dasm_at(loc: UShort): String { + var vpc = loc + + opcode = core.getw(vpc, dspace = false).toInt() + vpc = (vpc + 2u).toUShort() + try { + return when (opcode and 0xF000) { + 0x0, 0x8000 -> when (opcode and 0xFF00) { + 0x0000 -> when (opcode) { + 0x0000 -> fmt("HALT") // HALT + 0x0001 -> fmt("WAIT") + 0x0002 -> fmt("WTI") + 0x0003 -> fmt("BPT") + 0x0004 -> fmt("IOT") + 0x0005 -> fmt("RESET") + 0x0006 -> fmt("RTT") + in 0x40..0x7f -> fmt("JMP", dst()) // JMP + in 0x80..0x87 -> fmt("RTS", reg0()) // RTS + in 0x98..0x9F -> fmt("SPL", (opcode and 7).toString()) + 0xBF -> fmt("SCC") + in 0xA0..0xBE -> { + val items: ArrayList = ArrayList() + if (opcode bit 0) items.add("SEC") + if (opcode bit 1) items.add("SEZ") + if (opcode bit 2) items.add("SEV") + if (opcode bit 3) items.add("SEN") + items.joinToString(" ") + } // Scc/Ccc + in 0xC0..0xFF -> fmt("SWAB", dst()) // SWAB + else -> throw InvalidOpcodeException() + } + + 0x0100 -> br_rel("BR") // BR + 0x0200 -> br_rel("BNE") // BNE + 0x0300 -> br_rel("BEQ") // BEQ + 0x0400 -> br_rel("BGE") // BGE + 0x0500 -> br_rel("BLT") // BLT + 0x0600 -> br_rel("BGT") // BGT + 0x0700 -> br_rel("BLE") // BLE + 0x0800, 0x0900 -> fmt("JSR", reg6(), dst()) // JSR + 0x0A00 -> when (opcode shr 6 and 3) { + 0 -> fmt("CLR", dst()) // CLR + 1 -> fmt("COM", dst()) // COM + 2 -> fmt("INC", dst()) // INC + 3 -> fmt("DEC", dst()) // DEC + else -> throw InvalidOpcodeException() + } + + 0x0B00 -> when (opcode shr 6 and 3) { + 0 -> fmt("NEG", dst()) // NEG + 1 -> fmt("ADC", dst()) // ADC + 2 -> fmt("SBC", dst()) // SBC + 3 -> fmt("TST", dst()) // TST + else -> throw InvalidOpcodeException() + } + + 0x0C00 -> when (opcode shr 6 and 3) { + 0 -> fmt("ROR", dst()) // ROR + 1 -> fmt("ROL", dst()) // ROL + 2 -> fmt("ASR", dst()) // ASR + 3 -> fmt("ASL", dst()) // ASL + else -> throw InvalidOpcodeException() + } + + 0x0D00 -> when (opcode shr 6 and 3) { + 0 -> fmt("MARK", (opcode and 0x3F).toString(8)) // MARK + 1 -> { + throw InvalidOpcodeException() + } // MFPI // TODO + 2 -> { + throw InvalidOpcodeException() + } // MTPI // TODO + 3 -> fmt("SXT", dst()) // SXT + else -> throw InvalidOpcodeException() + } + + 0x0F00 -> throw InvalidOpcodeException() // CSM + 0x8000 -> br_rel("BPL") // BPL + 0x8100 -> br_rel("BMI") // BMI + 0x8200 -> br_rel("BHI") // BHI + 0x8300 -> br_rel("BLOS") // BLOS + 0x8400 -> br_rel("BVC") // BVC + 0x8500 -> br_rel("BVS") // BVS + 0x8600 -> br_rel("BHIS") // BCC/BHIS + 0x8700 -> br_rel("BLO") // BCS/BLO + 0x8800 -> fmt("EMT", (opcode and 0xFF).toString(8)) // EMT + 0x8900 -> fmt("TRAP", (opcode and 0xFF).toString(8)) // TRAP + + 0x8A00 -> when (opcode shr 6 and 3) { + 0 -> fmt("CLRB", dst()) // CLRB + 1 -> fmt("COMB", dst()) // COMB + 2 -> fmt("INCB", dst()) // INC + 3 -> fmt("DECB", dst()) // DEC + else -> throw InvalidOpcodeException() + } + + 0x8B00 -> when (opcode shr 6 and 3) { + 0 -> fmt("NEGB", dst()) // NEGB + 1 -> fmt("ADCB", dst()) // ADCB + 2 -> fmt("SBCB", dst()) // SBCB + 3 -> fmt("TSTB", dst()) // TSTB + else -> throw InvalidOpcodeException() + } + + 0x8C00 -> when (opcode shr 6 and 3) { + 0 -> fmt("RORB", dst()) // RORB + 1 -> fmt("ROLB", dst()) // ROLB + 2 -> fmt("ASRB", dst()) // ASRB + 3 -> fmt("ASLB", dst()) // ASLB + else -> throw InvalidOpcodeException() + } + + 0x8D00 -> when (opcode shr 6 and 3) { + 1 -> { + throw InvalidOpcodeException() + } // MFPD // TODO + 2 -> { + throw InvalidOpcodeException() + } // MTPD // TODO + else -> throw InvalidOpcodeException()// Reserved + } + + else -> throw InvalidOpcodeException() + } + + 0x1000 -> fmt("MOV", src(), dst()) // MOV + 0x2000 -> fmt("CMP", src(), dst()) // CMP + 0x3000 -> fmt("BIT", src(), dst()) // BIT + 0x4000 -> fmt("BIC", src(), dst()) // BIC + 0x5000 -> fmt("BIS", src(), dst()) // BIS + 0x6000 -> fmt("ADD", src(), dst()) // ADD + 0x7000 -> when (opcode shr 9 and 0x7) { + 0 -> fmt("MUL", reg6(), dst()) // MUL + 1 -> fmt("DIV", reg6(), dst()) // DIV + 2 -> fmt("ASH", reg6(), dst()) // ASH + 3 -> fmt("ASHC", reg6(), dst()) // ASHC + 4 -> fmt("XOR", reg6(), dst()) // XOR + 5, 6 -> throw InvalidOpcodeException() + 7 -> fmt("SOB", reg6(), rel6()) // SOB + else -> throw InvalidOpcodeException() + } + // 0x800 handled above + 0x9000 -> fmt("MOVB", src(), dst()) // MOVB + 0xA000 -> fmt("CMPB", src(), dst()) // CMPB + 0xB000 -> fmt("BITB", src(), dst()) // BITB + 0xC000 -> fmt("BICB", src(), dst()) // BICB + 0xD000 -> fmt("BISB", src(), dst()) // BISB + 0xE000 -> fmt("SUB", src(), dst()) // SUB + else -> throw InvalidOpcodeException() + } + } catch (_: InvalidOpcodeException) { + return fmt(".WORD", opcode.toUShort().toString(8)) + } + + } + +} \ No newline at end of file