Improved paging simulation, added disassembler

This commit is contained in:
2023-09-19 22:08:47 +02:00
parent 1bc7f93d57
commit 20e57e3a19
4 changed files with 339 additions and 34 deletions

View File

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

View File

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

View File

@@ -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<PageTable> = Array(3) { PageTable(if (it == 2) 3 else it, dspace = false) }
private val dtabs: Array<PageTable> = Array(3) { PageTable(if (it == 2) 3 else it, dspace = true) }
private val modeVTabs: Array<ModeVSpace> = 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 <E> 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()

View File

@@ -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<String> = 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))
}
}
}