diff --git a/build.gradle.kts b/build.gradle.kts index 1a4ebcf..2066143 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,6 +17,9 @@ dependencies { // For the utilities classes kapt("info.picocli:picocli-codegen:4.7.5") implementation("info.picocli:picocli:4.7.5") + implementation("org.jline:jline-terminal:3.23.0") + implementation("org.jline:jline-terminal-jansi:3.23.0") + } tasks.test { diff --git a/src/main/kotlin/Main.kt b/src/main/kotlin/Main.kt index adb5b33..1f4ec73 100644 --- a/src/main/kotlin/Main.kt +++ b/src/main/kotlin/Main.kt @@ -1,3 +1,22 @@ +import com.thequux.mcpdp.core.CPU +import com.thequux.mcpdp.loadAbs +import com.thequux.mcpdp.peripheral.DL11 +import com.thequux.mcpdp.peripheral.MemBus +import java.io.File + fun main(args: Array) { + val tb = org.jline.terminal.TerminalBuilder.terminal() + try { + tb.enterRawMode() + var mbus = MemBus(65536) + var cpu = CPU(mbus) + val console = DL11(tb.input(), tb.output()).apply { mount(mbus.unibus) } + console.start() + + cpu.loadAbs(File(args[0])) + cpu.runState = CPU.RunState.RUNNING + cpu.run() + } finally { + } } \ No newline at end of file diff --git a/src/main/kotlin/com/thequux/mcpdp/RT11Loader.kt b/src/main/kotlin/com/thequux/mcpdp/RT11Loader.kt index e766a74..44c083d 100644 --- a/src/main/kotlin/com/thequux/mcpdp/RT11Loader.kt +++ b/src/main/kotlin/com/thequux/mcpdp/RT11Loader.kt @@ -2,21 +2,56 @@ package com.thequux.mcpdp import com.thequux.mcpdp.core.CPU import com.thequux.mcpdp.core.PAddressSpace +import com.thequux.mcpdp.ext.bit.bit +import com.thequux.mcpdp.ext.bit.shl import java.io.File /// Loads an RT-11 object file into memory fun CPU.loadAbs(infile: File) { val core = this.core.modeSpace val inStream = infile.inputStream().buffered() - val buf = ByteArray(6) - val + var buf = ByteArray(6) + var offset = 0 + var addr: UShort = 0u + var len: Int = 0 + var cksum: Int = 0 while (true) { var read = inStream.read(buf, 0, 6) - if (read == 0) { + if (read == -1) { return - } else if (read < 6) { - } - if (hdr[0] != 0) + if (read < 6) { + // TODO: report the error + throw Exception("Short record: $read bytes") + } + if (buf[0] != 1.toByte() || buf[1] != 0.toByte()) { + throw Exception("Invalid block header") + } + len = buf[3].toInt().shl(8) + buf[2].toInt() - 6 + addr = (buf[5].toUByte().toUShort().shl(8) + buf[4].toUByte().toUShort()).toUShort() + cksum = 0 + for (i in 0..<6) { + cksum += buf[i] + } + if (buf.size < len+1) { + buf = ByteArray(len+1) + } + inStream.read(buf, 0, len+1) + for (i in 0..len) { + cksum += buf[i] + } + cksum = cksum and 0xFF + // TODO: validate checksum == 0 + if (len == 0) { + // end of file + if (!(addr bit 0)) this.pc = addr + return + } else { + // copy data to memory + // TODO: copy a word at a time + for (i in 0..(2) { UShortArray(5) } + private val general_registers = Array(2) { UShortArray(6) } private val shadow_r6 = UShortArray(4) // val core = PagingUnit(mbus) @@ -54,7 +63,7 @@ class CPU(val mbus: MemBus) { } private var prv_mode: Int = 0 private var psw_pl: Int = 0 - private var pc: UShort + var pc: UShort get() = registers[7] set(value) { registers[7] = value @@ -181,8 +190,8 @@ class CPU(val mbus: MemBus) { private fun opc_dst(opcode: Int): UInt = op_resolve(opcode and 0x3F) private fun opc_dstb(opcode: Int): UInt = op_resolve(opcode and 0x3F, byte_mode = true) - private fun opc_src(opcode: Int): UInt = op_resolve(opcode shr 6 and 0xFC0) - private fun opc_srcb(opcode: Int): UInt = op_resolve(opcode shr 6 and 0xFC0, byte_mode = true) + private fun opc_src(opcode: Int): UInt = op_resolve(opcode shr 6 and 0x3F) + private fun opc_srcb(opcode: Int): UInt = op_resolve(opcode shr 6 and 0x3F, byte_mode = true) private fun stack_pop(): UShort = core.getw(sp).also { sp = (sp + 2u).toUShort() } private fun stack_push(value: UShort) { @@ -195,7 +204,9 @@ class CPU(val mbus: MemBus) { pc = (pc + 2u * (opcode and 0xFF).toByte().toShort().toUShort()).toUShort() } } - fun step() { + + /// Internal step; evaluates one opcode, but does not handle errors, interrupts, or updating system registers + fun step_int() { val opcode = core.getw(pc).toInt() pc = (pc + 2u).toUShort() when (opcode and 0xF000) { @@ -219,7 +230,7 @@ class CPU(val mbus: MemBus) { // jmp val dst = opc_dst(opcode) if (is_paddr_reg(dst)) { - throw InvalidOpcodeException() + trap(4u); // Illegal instruction } pc = dst.toUShort() } // JMP @@ -259,10 +270,13 @@ class CPU(val mbus: MemBus) { 0x0700 -> br_rel(Z or (N xor V), opcode) // BLE 0x0800, 0x0900 -> { // JSR val r = opcode shr 6 and 0x7 - val dst = op_loadw(opc_dst(r)) + val dst = opc_dst(r) + if (is_paddr_reg(dst)) { + trap(4u) // illegal opcode + } stack_push(registers[r]) registers[r] = pc - pc = dst + pc = dst.toUShort() } // JSR 0x0A00 -> when (opcode shr 6 and 3) { 0 -> { // CLR @@ -541,7 +555,7 @@ class CPU(val mbus: MemBus) { 0x3000 -> { // BIT val src = opc_src(opcode) val dst = opc_dst(opcode) - val res = op_loadw(dst) and op_loadw(src).inv() + val res = op_loadw(dst) and op_loadw(src) N = res bit 15 Z = res != 0u.toUShort() V = false @@ -660,13 +674,13 @@ class CPU(val mbus: MemBus) { } // ASHC 4 -> { val r = opcode shr 6 and 7 - val dst = opc_dst(r) + val dst = opc_dst(opcode) val res = op_loadw(dst) xor registers[r] op_storw(dst, res) N = res bit 15 Z = res == 0.toUShort() V = false - } + } // XOR 5,6 -> throw InvalidOpcodeException() 7 -> { val reg = opcode shr 6 and 7 @@ -695,7 +709,7 @@ class CPU(val mbus: MemBus) { val src = op_loadb(opc_srcb(opcode)) val src2 = op_loadb(opc_dstb(opcode)) val res = (src - src2) and 0xFFu - N = res bit 8 + N = res bit 7 Z = res == 0U C = src < src2 // unsigned lt V = ((src bit 8) xor (src2 bit 8)) and ((src2 bit 8) == (res bit 8)) @@ -703,9 +717,9 @@ class CPU(val mbus: MemBus) { 0xB000 -> { // BITB val src = opc_srcb(opcode) val dst = opc_dstb(opcode) - val res = op_loadb(dst) and op_loadb(src).inv() + val res = op_loadb(dst) and op_loadb(src) N = res bit 7 - Z = res != 0u.toUByte() + Z = res == 0u.toUByte() V = false } // BITB 0xC000 -> { // BICB @@ -741,6 +755,28 @@ class CPU(val mbus: MemBus) { } } + fun step() { + if (runState == RunState.HALTED) return + // TODO: check for interrupts + if (runState == RunState.WAIT_FOR_INTERRUPT) return + + try { + step_int() + // TODO: handle T bit + } catch (error: MemoryError) { + // TODO: fill in memory manager fields + trap(4u) + } catch (_: InvalidOpcodeException) { + trap(10u) + } + } + + fun run() { + while (runState == RunState.RUNNING) { + step() + } + } + fun trap(vector: UShort) { stack_push(psw) stack_push(pc) @@ -751,12 +787,12 @@ class CPU(val mbus: MemBus) { private inner class Registers: PAddressSpace { override fun getw(addr: UInt): UShort = when (addr) { 0x3FFFEu -> psw - else -> throw MemoryError(MemoryErrorType.BusTimeout, addr) + else -> throw BusTimeoutError(addr) } override fun setw(addr: UInt, value: UShort) = when (addr) { 0x3FFFEu -> psw = value - else -> throw MemoryError(MemoryErrorType.BusTimeout, addr) + else -> throw BusTimeoutError(addr) } } diff --git a/src/main/kotlin/com/thequux/mcpdp/core/MemoryError.kt b/src/main/kotlin/com/thequux/mcpdp/core/MemoryError.kt index cd01292..a962717 100644 --- a/src/main/kotlin/com/thequux/mcpdp/core/MemoryError.kt +++ b/src/main/kotlin/com/thequux/mcpdp/core/MemoryError.kt @@ -1,6 +1,9 @@ package com.thequux.mcpdp.core -class MemoryError(val type: MemoryErrorType, var addr: UInt): Exception("Memory error $type at ${addr.toString(8)}") +sealed class MemoryError(val type: MemoryErrorType, var addr: UInt): Exception("Memory error: $type at ${addr.toString(8)}") +class BusTimeoutError(addr: UInt): MemoryError(MemoryErrorType.BusTimeout, addr) +class OddAddressError(addr: UInt): MemoryError(MemoryErrorType.OddAddress, addr) +class NonExistentMemoryError(addr: UInt): MemoryError(MemoryErrorType.NonExistent, addr) enum class MemoryErrorType { BusTimeout, diff --git a/src/main/kotlin/com/thequux/mcpdp/core/PagingUnit.kt b/src/main/kotlin/com/thequux/mcpdp/core/PagingUnit.kt index 3a6855a..ccca081 100644 --- a/src/main/kotlin/com/thequux/mcpdp/core/PagingUnit.kt +++ b/src/main/kotlin/com/thequux/mcpdp/core/PagingUnit.kt @@ -109,13 +109,18 @@ class PagingUnit(val pspace: PAddressSpace): VAddressSpace { var mode: Int = 0 set(value) { field = value - modeSpace = modeVTabs[if (field == 3) 2 else field] + modeSpace = if (mmr[0] bit 0) { + modeVTabs[if (field == 3) 2 else field] + } 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) } - var modeSpace: VAddressSpace = modeVTabs[0] + private var noMmanSpace = NoMmanSpace() + var modeSpace: VAddressSpace = noMmanSpace private set private val unibusTable = UIntArray(32) @@ -135,6 +140,14 @@ 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() + 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) + override fun setw(addr: UShort, value: UShort, dspace: Boolean) = pspace.setw(map(addr), value) + } + companion object { private val MMR3: UInt = 0x3F54Eu } @@ -158,7 +171,7 @@ class PagingUnit(val pspace: PAddressSpace): VAddressSpace { .bit(5, enableUnibusMap) .toUShort()) } // 772516 MMR3 - else -> throw MemoryError(MemoryErrorType.BusTimeout, addr) + else -> throw BusTimeoutError(addr) } override fun setw(addr: UInt, value: UShort): Unit = when(addr) { @@ -178,7 +191,7 @@ class PagingUnit(val pspace: PAddressSpace): VAddressSpace { unibusTable[uaddr] = unibusTable[uaddr] and 0xFFFF_0000u or (value.toUInt() bic 0) } } // 770200..770377 Unibus map - else -> throw MemoryError(MemoryErrorType.BusTimeout, addr) + else -> throw BusTimeoutError(addr) } } @@ -204,10 +217,12 @@ class PagingUnit(val pspace: PAddressSpace): VAddressSpace { private fun map(addr: UInt): UInt { val uaddr = addr and 0x3_FFFFu return if (uaddr > 0x3_0000u) { - // I/O page is always mapped as follows - addr or 0x3FC000u - } else { - unibusTable[(uaddr shr 13).toInt()] + (uaddr and 0x1FFFu) or 0x3C0000U + // I/O page is always mapped as follows + addr or 0x3FC000u + } else if (mmr[3] bit 5) { + unibusTable[(uaddr shr 13).toInt()] + (uaddr and 0x1FFFu) or 0x3C0000U + } else { + addr } } diff --git a/src/main/kotlin/com/thequux/mcpdp/ext/bit/bitext.kt b/src/main/kotlin/com/thequux/mcpdp/ext/bit/bitext.kt index 9ff3a93..16a60de 100644 --- a/src/main/kotlin/com/thequux/mcpdp/ext/bit/bitext.kt +++ b/src/main/kotlin/com/thequux/mcpdp/ext/bit/bitext.kt @@ -4,8 +4,9 @@ package com.thequux.mcpdp.ext.bit import kotlin.experimental.and import kotlin.math.max +import kotlin.math.min -inline infix fun Byte.shr(qty: Int): Byte = this.toInt().shr(max(qty, 7)).toByte() +inline infix fun Byte.shr(qty: Int): Byte = this.toInt().shr(min(qty, 7)).toByte() inline infix fun Byte.shl(qty: Int): Byte = if (qty > 7) 0 else this.toInt().shl(qty).toByte() inline infix fun Byte.bit(n: Int): Boolean = this.toInt() shr n and 1 != 0 inline fun Byte.bit(n: Int, v: Boolean): Byte = if (v) this bis n else this bic n @@ -16,7 +17,7 @@ inline infix fun Byte.sex(n: Int): Byte { return ((this and sign.dec()) - (this and sign)).toByte() } -inline infix fun UByte.shr(qty: Int): UByte = this.toUInt().shr(max(qty, 7)).toUByte() +inline infix fun UByte.shr(qty: Int): UByte = this.toUInt().shr(min(qty, 7)).toUByte() inline infix fun UByte.shl(qty: Int): UByte = if (qty > 7) 0U else this.toInt().shl(qty).toUByte() inline infix fun UByte.bit(n: Int): Boolean = this.toUInt() shr n and 1U != 0U inline fun UByte.bit(n: Int, v: Boolean): UByte = if (v) this bis n else this bic n @@ -27,7 +28,7 @@ inline infix fun UByte.sex(n: Int): UByte { return ((this and sign.dec()) - (this and sign)).toUByte() } -inline infix fun Short.shr(qty: Int): Short = this.toInt().shr(max(qty, 15)).toShort() +inline infix fun Short.shr(qty: Int): Short = this.toInt().shr(min(qty, 15)).toShort() inline infix fun Short.shl(qty: Int): Short = if (qty > 15) 0 else this.toInt().shl(qty).toShort() inline infix fun Short.bit(n: Int): Boolean = this.toInt() shr n and 1 != 0 inline fun Short.bit(n: Int, v: Boolean): Short = if (v) this bis n else this bic n @@ -38,7 +39,7 @@ inline infix fun Short.sex(n: Int): Short { return ((this and sign.dec()) - (this and sign)).toShort() } -inline infix fun UShort.shr(qty: Int): UShort = this.toUInt().shr(max(qty, 15)).toUShort() +inline infix fun UShort.shr(qty: Int): UShort = this.toUInt().shr(min(qty, 15)).toUShort() inline infix fun UShort.shl(qty: Int): UShort = if (qty > 15) 0U else this.toInt().shl(qty).toUShort() inline infix fun UShort.bit(n: Int): Boolean = this.toUInt() shr n and 1U != 0U inline fun UShort.bit(n: Int, v: Boolean): UShort = if (v) this bis n else this bic n diff --git a/src/main/kotlin/com/thequux/mcpdp/peripheral/DL11.kt b/src/main/kotlin/com/thequux/mcpdp/peripheral/DL11.kt index ab97645..6b08bbd 100644 --- a/src/main/kotlin/com/thequux/mcpdp/peripheral/DL11.kt +++ b/src/main/kotlin/com/thequux/mcpdp/peripheral/DL11.kt @@ -2,6 +2,7 @@ package com.thequux.mcpdp.peripheral import com.thequux.mcpdp.core.MemoryError import com.thequux.mcpdp.core.MemoryErrorType +import com.thequux.mcpdp.core.OddAddressError import com.thequux.mcpdp.core.PAddressSpace import com.thequux.mcpdp.ext.bit.bic import com.thequux.mcpdp.ext.bit.bis @@ -33,7 +34,7 @@ class DL11(private var istr: InputStream, private val ostr: OutputStream, val re 2 -> { rcsr = rcsr bic 7; rbuf } 4 -> xcsr 6 -> 0u - else -> throw MemoryError(MemoryErrorType.OddAddress, addr) + else -> throw OddAddressError(addr) } } @@ -52,7 +53,7 @@ class DL11(private var istr: InputStream, private val ostr: OutputStream, val re } ostr.write(b) } - else -> throw MemoryError(MemoryErrorType.OddAddress, addr) + else -> throw OddAddressError(addr) } } @@ -87,6 +88,6 @@ class DL11(private var istr: InputStream, private val ostr: OutputStream, val re super.start() reader = thread(block = this::readThread) - reader?.interrupt() +// reader?.interrupt() } } \ No newline at end of file diff --git a/src/main/kotlin/com/thequux/mcpdp/peripheral/MemBus.kt b/src/main/kotlin/com/thequux/mcpdp/peripheral/MemBus.kt index ae380ad..b78d76c 100644 --- a/src/main/kotlin/com/thequux/mcpdp/peripheral/MemBus.kt +++ b/src/main/kotlin/com/thequux/mcpdp/peripheral/MemBus.kt @@ -1,8 +1,6 @@ package com.thequux.mcpdp.peripheral -import com.thequux.mcpdp.core.MemoryError -import com.thequux.mcpdp.core.MemoryErrorType -import com.thequux.mcpdp.core.PAddressSpace +import com.thequux.mcpdp.core.* import com.thequux.mcpdp.ext.bit.bit import com.thequux.mcpdp.util.ConfigurationError @@ -32,7 +30,7 @@ class MemBus(val size: Int) : PAddressSpace { override fun getw(addr: UInt): UShort = when (addr) { 0x3FFF2u -> 0u // upper size 0x3FFF0u -> (size shr 6).toUShort() - else -> throw MemoryError(MemoryErrorType.BusTimeout, addr) + else -> throw BusTimeoutError(addr) } // Both registers are write-only @@ -41,18 +39,23 @@ class MemBus(val size: Int) : PAddressSpace { override fun getw(addr: UInt): UShort { if (size bit 0) { - throw MemoryError(MemoryErrorType.OddAddress, addr) + throw OddAddressError(addr) } return when (addr.toInt()) { - in 0..size.dec() -> data[addr.toInt() shr 1] in 0x3c0000..0x3FFFFF -> unibus.getw(addr) - else -> throw MemoryError(MemoryErrorType.NonExistent, addr) + in 0..size.dec() -> data[addr.toInt() shr 1] + else -> throw NonExistentMemoryError(addr) } } override fun setw(addr: UInt, value: UShort) { if (size bit 0) { - throw MemoryError(MemoryErrorType.OddAddress, addr) + throw OddAddressError(addr) + } + return when (addr.toInt()) { + in 0x3c0000..0x3FFFFF -> unibus.setw(addr, value) + in 0..size.dec() -> data[addr.toInt() shr 1] = value + else -> throw NonExistentMemoryError(addr) } } } diff --git a/src/main/kotlin/com/thequux/mcpdp/peripheral/Unibus.kt b/src/main/kotlin/com/thequux/mcpdp/peripheral/Unibus.kt index 40b8241..2bbaa15 100644 --- a/src/main/kotlin/com/thequux/mcpdp/peripheral/Unibus.kt +++ b/src/main/kotlin/com/thequux/mcpdp/peripheral/Unibus.kt @@ -1,5 +1,6 @@ package com.thequux.mcpdp.peripheral +import com.thequux.mcpdp.core.BusTimeoutError import com.thequux.mcpdp.core.MemoryError import com.thequux.mcpdp.core.MemoryErrorType import com.thequux.mcpdp.core.PAddressSpace @@ -35,7 +36,7 @@ open class Subregion(private val suffix: Int, private val size: Int): Region { } override fun attach(address: UInt, suffix: Int, device: PAddressSpace) { - val loc = (address shr suffix and mask).toInt() + val loc = (address shr this.suffix and mask).toInt() if (suffix <= 0) throw ProgrammerError("Suffix must be positive") if (suffix < this.suffix) { // needs a submap @@ -64,7 +65,7 @@ class Unibus: PAddressSpace, Subregion(12, 6) { var deviceView: PAddressSpace = this internal set override fun map(address: UInt): PAddressSpace = - super.map(address) ?: throw MemoryError(MemoryErrorType.BusTimeout, address) + super.map(address) ?: throw BusTimeoutError(address) override fun getw(addr: UInt): UShort = map(addr).getw(addr) override fun getb(addr: UInt): UByte = map(addr).getb(addr) override fun setw(addr: UInt, value: UShort) = map(addr).setw(addr, value) diff --git a/test/echo.mac b/test/echo.mac index 14669e8..5b284e9 100644 --- a/test/echo.mac +++ b/test/echo.mac @@ -6,11 +6,14 @@ xbuf = 177566 .psect .txt .title echo start: + mov #40,r3 tstb @#rcsr - bmi start + bge start mov @#rbuf,r1 - xor r1,#40 - mov r1,@#xbuf + cmpb r1,#40 + ble noxor + xor r3,r1 +noxor: mov r1,@#xbuf br start .byte 123 .end start