Fully implemented CPU and memory busses, but not yet tested

This commit is contained in:
2023-08-31 13:46:28 +02:00
parent 35736a5fd2
commit a1daa4c4b3
13 changed files with 1019 additions and 128 deletions

1
.idea/gradle.xml generated
View File

@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>

6
.idea/vcs.xml generated Normal file
View File

@@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

11
doc/pdp10-impl.adoc Normal file
View File

@@ -0,0 +1,11 @@
# Basic model
This emulates a PDP-11/70 with some changes
# Privilege levels
There is an additional "executive" privilege level
# Available peripheral page addrs
EAE is not implemented; thus 777300/5 is available

73
doc/peripheral-map.txt Normal file
View File

@@ -0,0 +1,73 @@
# D <Name> <Description>
# A <base>/<prefix>
# R <addr> <name> <desc>
# F <size of floating window, in bytes (oct)>
D ROM
A 3000/3
D CD11 Card reader
A 2460/9
R 2460 CDST Status and control register
R 2462 CDCC Column count register
R 2464 CDBA Current address register
R 2466 CDDB Data buffer register
D CR11/CM11 Card reader
R 7160 CRS Status register
R 7162 CRB1 Data buffer register 1
R 7164 CRB2 Data buffer register 2
D DH11 16-line serial multiplexer
F 20
R 00 DHSCR System control register
R 02 DHNRC Next received character
R 04 DHLPR Line parameter register
R 06 DHCAR Current address register
R 10 DHBCR Byte count register
R 12 DHBAR Buffer active register
R 14 DHBRK Break control register
R 16 DHSIL Silo status register
D DJ11 16-line serial multiplexer
F 10
R 00 DJCSR Control status register
R 02 DJRBUF Receive buffer
R 04 DJTCR Transmitter control
R 04 DJBCR Break control register
R 06 DJTBUF Transmit buffer
D DL11 1-line serial interface (console)
A 7560/9
F 10
R 0 RCSR Receive status register
R 2 RBUF Receive data buffer
R 4 XCST Transmit status register
R 6 XBUF Transmit buffer
D DMC11 Network link
F 10
D DN11 Automatic calling unit
A 5200/5 (1 word each)
D DQ11 NPR Synchronous line interface
F 10
R 0 RXSTAT Receive status register
R 2 TXSTAT Transmit status register
R 4 REGERR Reg/Err register
R 6 SECREG Secondary register
D DR11-B DMA controller
A 2410/9
R 2410 DRWC Word count
R 2412 DRBA Bus address
R 2414 DRST Status and command
R 2416 DRDB Data buffer
D PC11 Paper tape reader/punch
A 7550/
R 7550 PRS Reader status register
R 7552 PRB Reader buffer
R 7554 PPB Punch status
R 7556 PPB Punch buffer

View File

@@ -1,8 +1,31 @@
package com.thequux.mcpdp.core
interface AddressSpace {
fun getw(addr: UShort): UShort
import com.thequux.mcpdp.ext.bit.bic
import com.thequux.mcpdp.ext.bit.bit
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 getb(addr: UShort): UByte
fun setw(addr: UShort, value: UShort)
fun setw(addr: UShort, value: UShort, dspace: Boolean = false)
fun setb(addr: UShort, value: UByte)
}
// Implements the full 22-bit physical address space.
// Odd addresses accessed through word instructions are a bus fault; this is handled by the main memory map
interface PAddressSpace {
fun getw(addr: UInt): UShort
fun getb(addr: UInt): UByte = (getw(addr bic 0) shr (if (addr bit 0) 8 else 0)).toUByte()
fun setw(addr: UInt, value: UShort)
fun setb(addr: UInt, value: UByte) {
val waddr = addr bic 0
val load = getw(waddr)
val store = if (addr bit 0) {
load and 0xFF00u or value.toUShort()
} else {
load and 0x00FFu or (value.toUShort() shl 8)
}
setw(waddr, store)
}
}

View File

@@ -1,6 +1,8 @@
package com.thequux.mcpdp.core
import com.thequux.mcpdp.ext.bit.*
import com.thequux.mcpdp.peripheral.MemBus
import com.thequux.mcpdp.util.ProgrammerError
/// The main CPU
///
@@ -9,14 +11,73 @@ import com.thequux.mcpdp.ext.bit.*
/// Exxx: ROM
/// xxxx: rest
@OptIn(ExperimentalUnsignedTypes::class)
class CPU(private val core: AddressSpace) {
class CPU(private val mbus: MemBus) {
private val registers = UShortArray(8)
public var psw: UShort = 0U
private val general_registers = Array<UShortArray>(2) { UShortArray(5) }
private val shadow_r6 = UShortArray(4) //
private val core = PagingUnit(mbus)
var runState: RunState = RunState.HALTED
var psw: UShort
get() {
var res = 0
if (C) res = res or 0x0001
if (V) res = res or 0x0002
if (Z) res = res or 0x0004
if (N) res = res or 0x0008
if (T) res = res or 0x0010
res = res or (psw_pl shl 5)
res = res or (cur_mode shl 14) or (prv_mode shl 12)
res = res or (registerSet shl 11)
return res.toUShort()
}
set(value) {
var newpsw = value.toInt()
C = value bit 1
V = value bit 2
Z = value bit 3
N = value bit 4
T = value bit 5 // TODO: handle suspended trap
psw_pl = newpsw shr 5 and 7
registerSet = newpsw shr 11 and 1
cur_mode = newpsw shr 14
}
private var cur_mode: Int = 0
set(value) {
prv_mode = field
shadow_r6[field] = sp
field = if (value == 2) 3 else value
sp = shadow_r6[field]
core.mode = value
}
private var prv_mode: Int = 0
private var psw_pl: Int = 0
private var pc: UShort
get() = registers[7]
set(value) {
registers[7] = value
}
private var sp: UShort
get() = registers[6]
set(value) {
registers[6] = value
}
private var registerSet: Int = 0
set(value) {
if (value !in 0..1) {
throw ProgrammerError("Invalid register set number")
}
val old = general_registers[field]
val new = general_registers[value]
for (i in 0..5) {
old[i] = registers[i]
registers[i] = new[i]
}
field = value
}
private var cc: UShort
get() = psw and 0xFu
@@ -28,14 +89,25 @@ class CPU(private val core: AddressSpace) {
private var C: Boolean = false
private var Z: Boolean = false
private var V: Boolean = false
private var T: Boolean = false
private val PADDR_REG_BIT: Int = 23
/// Load an operand. This performs any indicated pre-inc/post-dec, so this or op_adjust must be called exactly once per operand
init {
val regs = Registers()
mbus.unibus.run {
attach(0x3FFF8u, 3, regs)
attach(0x3FFF4u, 2, regs)
}
core.mount(mbus.unibus)
}
private fun is_paddr_reg(addr: UInt): Boolean = addr bit PADDR_REG_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
return when (mode) {
0 -> reg.toUInt() + 0x1_0000U
return when (mode and 0x7) {
0 -> reg.toUInt() bis PADDR_REG_BIT
1 -> registers[reg].toUInt()
2 -> {
val addr = registers[reg]
@@ -65,14 +137,13 @@ class CPU(private val core: AddressSpace) {
pc = (pc+2u).toUShort()
core.getw((idx+registers[reg]).toUShort()).toUInt()
}
else -> throw InvalidOpcodeException()
else -> throw InvalidOpcodeException() // unreachable
}
}
/// Store an operand. This assumes that the spec has already been `load`ed or `adjust`ed
private fun op_storb(spec: UInt, value: UByte) {
val addr = (spec and 0xFFFFu).toUShort()
if (spec >= 0x1_0000u) {
if (is_paddr_reg(spec)) {
// register
registers[addr.toInt()] = registers[addr.toInt()] and 0xFF00u or value.toUShort()
} else {
@@ -82,40 +153,42 @@ class CPU(private val core: AddressSpace) {
private fun op_loadb(spec: UInt): UByte {
val addr = (spec and 0xFFFFu).toUShort()
return if (spec > 0x1_0000u) {
return if (is_paddr_reg(spec)) {
(registers[addr.toInt()] and 0xFFu).toUByte()
} else {
core.getb(addr)
}
}
private fun op_storw(spec: UInt, value: UShort) {
private fun op_storw(spec: UInt, value: UShort, dspace: Boolean = true) {
val addr = (spec and 0xFFFFu).toUShort()
if (spec >= 0x1_0000u) {
if (is_paddr_reg(spec)) {
// register
registers[addr.toInt()] = value
} else {
core.setw(addr, value)
core.setw(addr, value, dspace)
}
}
private fun op_loadw(spec: UInt): UShort {
private fun op_loadw(spec: UInt, dspace: Boolean=true): UShort {
val addr = (spec and 0xFFFFu).toUShort()
return if (spec > 0x1_0000u) {
return if (is_paddr_reg(spec)) {
(registers[addr.toInt()] and 0xFFu)
} else {
core.getw(addr)
core.getw(addr, dspace)
}
}
companion object {
val CC_C: UShort = 1U
val CC_V: UShort = 2U
val CC_Z: UShort = 4U
val CC_N: UShort = 8U
}
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 stack_pop(): UShort = core.getw(sp).also { sp = (sp + 2u).toUShort() }
private fun stack_push(value: UShort) {
sp = (sp-2u).toUShort()
core.setw(sp, value)
}
private fun br_rel(cond: Boolean, opcode: Int) {
if (cond) {
@@ -126,103 +199,345 @@ class CPU(private val core: AddressSpace) {
val opcode = core.getw(pc).toInt()
pc = (pc + 2u).toUShort()
when (opcode and 0xF000) {
0x0, 0x8000 -> when (opcode and 0xFFC0) {
0x0000 -> when (opcode) {
0x0003 -> throw Trap(14) // BPT
0x0, 0x8000 -> when (opcode and 0xFF00) {
0x0000 -> when (opcode) {
0x0000 -> runState = RunState.HALTED
0x0001 -> runState = RunState.WAIT_FOR_INTERRUPT
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
0x0005 -> throw InvalidOpcodeException() // bus reset TODO: bus init
0x0006 -> {
// TODO: handle suspending the trace trap
pc = stack_pop()
psw = stack_pop()
} // RTT
in 0x40..0x7f -> {
// jmp
val dst = opc_dst(opcode)
if (is_paddr_reg(dst)) {
throw InvalidOpcodeException()
}
pc = dst.toUShort()
} // JMP
in 0x80..0x87 -> {
val reg = opcode and 0x7
pc = registers[reg]
registers[reg] = stack_pop()
} // RTS
in 0x98..0x9F -> {
psw_pl = opcode and 0x7
} // SPL
in 0xA0..0xBF -> {
val flag = opcode bit 4
if (opcode bit 0) C = flag
if (opcode bit 1) V = flag
if (opcode bit 2) Z = flag
if (opcode bit 3) N = flag
} // Scc/Ccc
in 0xC0..0xFF -> {
val dst = opc_dst(opcode)
val v = op_loadw(dst)
val res = (v shl 8) or (v shr 8)
op_storw(dst, res)
V = false
C = false
N = res bit 7
Z = (res and 0xFFu) == 0.toUShort()
} // SWAB
else -> throw InvalidOpcodeException()
}
0x0080 ->
0x0100, 0x0140, 0x0180, 0x01C0 -> br_rel(true, opcode) // BR
0x0200, 0x0240, 0x0280, 0x02C0 -> br_rel(!Z, opcode) // BNE
0x0300, 0x0340, 0x0380, 0x03C0 -> br_rel(Z, opcode) // BEQ
0x0400, 0x0440, 0x0480, 0x04C0 -> br_rel(N == V, opcode) // BGE
0x0600, 0x0640, 0x0680, 0x06C0 -> br_rel(!Z and (N == V), opcode) // BGT
0x0500, 0x0540, 0x0580, 0x05C0 -> br_rel(N xor V, opcode) // BLT
0x0700, 0x0740, 0x0780, 0x07C0 -> br_rel(Z or (N xor V), opcode) // BLE
0x0100 -> br_rel(true, opcode) // BR
0x0200 -> br_rel(!Z, opcode) // BNE
0x0300 -> br_rel(Z, opcode) // BEQ
0x0400 -> br_rel(N == V, opcode) // BGE
0x0500 -> br_rel(N xor V, opcode) // BLT
0x0600 -> br_rel(!Z and (N == V), opcode) // BGT
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))
stack_push(registers[r])
registers[r] = pc
pc = dst
} // JSR
0x0A00 -> when (opcode shr 6 and 3) {
0 -> { // CLR
op_storw(opc_dst(opcode), 0U)
N = false
V = false
C = false
Z = true
} // CLR
1 -> { // COM
val dst = opc_dst(opcode)
val res = op_loadw(dst).inv().also { op_storw(dst, it) }
N = res bit 15
Z = res == 0U.toUShort()
C = true
V = false
} // COM
2 -> { // INC
val dst = opc_dst(opcode)
val res = op_loadw(dst).inc().also { op_storw(dst, it) }
N = res bit 15
Z = res == 0.toUShort()
V = res == 0x8000.toUShort()
} // INC
3 -> { // DEC
val dst = opc_dst(opcode)
val res = op_loadw(dst).dec().also { op_storw(dst, it) }
N = res bit 15
Z = res == 0.toUShort()
V = res == 0x7FFF.toUShort()
} // DEC
}
0x0B00 -> when (opcode shr 6 and 3) {
0 -> { // NEG
val dst = opc_dst(opcode)
val res = op_loadw(dst).inv().inc()
op_storw(dst, res)
N = res bit 15
Z = res == 0.toUShort()
V = res == 0x8000.toUShort()
C = !Z
} // NEG
1 -> { // ADC
val dst = opc_dst(opcode)
val c: UShort = if (C) 1u else 0u
val res = (op_loadw(dst) + c).toUShort()
op_storw(dst, res)
N = res bit 15
Z = res == 0u.toUShort()
V = (res == 0x8000u.toUShort()) and C
C = Z and C
} // ADC
2 -> {
val dst = opc_dst(opcode)
val src = op_loadw(dst)
val res = if (C) src.dec() else src
op_storw(dst, res)
N = res bit 15
Z = res == 0.toUShort()
V = res == 0x8000.toUShort()
C = C and (src == 0.toUShort())
} // SBC
3 -> {
val dst = op_loadw(opc_dst(opcode))
Z = dst == 0.toUShort()
N = dst bit 15
V = false
C = false
} // TST
}
0x0C00 -> when (opcode shr 6 and 3) {
0 -> {
val dst = opc_dst(opcode)
val src = op_loadw(dst)
val res = (src shr 1).bit(15, C)
op_storw(dst, res)
C = src bit 0
N = res bit 15
Z = res == 0.toUShort()
V = N xor C
} // ROR
1 -> {
val dst = opc_dst(opcode)
val src = op_loadw(dst)
val res = (src shl 1).bit(0, C)
op_storw(dst, res)
C = src bit 15
N = res bit 15
Z = res == 0.toUShort()
V = N xor C
} // ROL
2 -> { // ASR
val dst = opc_dst(opcode)
val src = op_loadw(dst).toShort()
val res = (src shr 1).toUShort()
op_storw(dst, res)
N = res bit 15
Z = res == 0.toUShort()
C = src bit 0
V = N xor C
} // ASR
3 -> { // ASL
val dst = opc_dst(opcode)
val src = op_loadw(dst)
val res = src shl 1
op_storw(dst, res.toUShort())
N = res bit 15
Z = res == 0.toUShort()
C = src and 0x8000u != 0u.toUShort()
V = N xor C
} // ASL
}
0x0D00 -> when (opcode shr 6 and 3) {
0 -> { // MARK
val count = opcode and 0x3F
sp = (sp + (2 * count).toUInt()).toUShort()
pc = registers[5]
registers[5] = stack_pop()
} // MARK
1 -> { throw InvalidOpcodeException() } // MFPI // TODO
2 -> { throw InvalidOpcodeException() } // MTPI // TODO
3 -> {
op_storw(opc_dst(opcode), if (N) 0.toUShort() else (-1).toUShort())
Z = !N
V = false
} // SXT
}
0x0F00 -> throw InvalidOpcodeException() // CSM
0x8000 -> br_rel(!N, opcode) // BPL
0x8100 -> br_rel(N, opcode) // BMI
0x8200 -> br_rel(!C and !Z, opcode) // BHI
0x8300 -> br_rel(C or Z, opcode) // BLOS
0x8400 -> br_rel(!V, opcode) // BVC
0x8500 -> br_rel(V, opcode) // BVS
0x8600 -> br_rel(!C, opcode) // BCC/BHIS
0x8700 -> br_rel(C, opcode) // BCS/BLO
0x8800 -> trap(0x18u) // EMT
0x8900 -> trap(0x1Cu) // TRAP
0x0A00 -> { // CLR
op_storw(opc_dst(opcode), 0U)
N = false
V = false
C = false
Z = true
} // CLR
0x0B40 -> { // ADC
val dst = opc_dst(opcode)
val c: UShort = if (C) 1u else 0u
val res = (op_loadw(dst) + c).toUShort()
op_storw(dst, res)
N = res bit 15
Z = res == 0u.toUShort()
V = (res == 0x8000u.toUShort()) and C
C = Z and C
} // ADC
0x0C80 -> { // ASR
val dst = opc_dst(opcode)
val src = op_loadw(dst).toShort()
val res = (src shr 1).toUShort()
op_storw(dst, res)
N = res bit 15
Z = res == 0.toUShort()
C = src bit 0
V = N xor C
} // ASR
0x0CC0 -> { // ASL
val dst = opc_dst(opcode)
val src = op_loadw(dst)
val res = src shl 1
op_storw(dst, res.toUShort())
N = res bit 15
Z = res == 0.toUShort()
C = src and 0x8000u != 0u.toUShort()
V = N xor C
} // ASL
0x8000, 0x8040, 0x8080, 0x80C0 -> br_rel(!N, opcode) // BPL
0x8100, 0x8140, 0x8180, 0x81C0 -> br_rel(N, opcode) // BMI
0x8200, 0x8240, 0x8280, 0x82C0 -> br_rel(!C and !Z, opcode) // BHI
0x8300, 0x8340, 0x8380, 0x83C0 -> br_rel(C or Z, opcode) // BLOS
0x8400, 0x8440, 0x8480, 0x84C0 -> br_rel(!V, opcode) // BVC
0x8500, 0x8540, 0x8580, 0x85C0 -> br_rel(V, opcode) // BVS
0x8600, 0x8640, 0x8680, 0x86C0 -> br_rel(!C, opcode) // BCC/BHIS
0x8700, 0x8740, 0x8780, 0x87C0 -> br_rel(C, opcode) // BCS/BLO
0x8A00 -> { // CLRB
op_storb(opc_dst(opcode), 0U)
N = false
V = false
C = false
Z = true
} // CLRB
0x8B40 -> { // ADCB
val dst = opc_dst(opcode)
val c: UShort = if (C) 1u else 0u
val res = (op_loadb(dst) + c).toUByte()
op_storb(dst, res)
N = res bit 7
Z = res == 0u.toUByte()
V = (res == 0x80u.toUByte()) and C
C = Z and C
} // ADCB
0x8C80 -> { // ASRB
val dst = opc_dst(opcode)
val src = op_loadb(dst).toByte()
val res = (src shr 1).toUByte()
op_storb(dst, res)
N = res bit 7
Z = res == 0.toUByte()
C = src bit 0
V = N xor C
} // ASRB
0x8CC0 -> { // ASLB
val dst = opc_dst(opcode)
val src = op_loadb(dst)
val res = (src shl 1).toUByte()
op_storw(dst, res.toUShort())
N = res bit 7
Z = res == 0.toUByte()
C = src bit 7
V = N xor C
} // ASLB
0x8A00 -> when (opcode shr 6 and 3) {
0 -> { // CLRB
op_storb(opc_dstb(opcode), 0U)
N = false
V = false
C = false
Z = true
} // CLRB
1 -> { // COMB
val dst = opc_dstb(opcode)
val res = op_loadb(dst).inv().also { op_storb(dst, it) }
N = res bit 7
Z = res == 0U.toUByte()
C = true
V = false
} // COMB
2 -> { // INC
val dst = opc_dstb(opcode)
val res = op_loadb(dst).inc().also { op_storb(dst, it) }
N = res bit 7
Z = res == 0.toUByte()
V = res == 0x80.toUByte()
} // INC
3 -> { // DEC
val dst = opc_dstb(opcode)
val res = op_loadb(dst).dec().also { op_storb(dst, it) }
N = res bit 7
Z = res == 0.toUByte()
V = res == 0x7F.toUByte()
} // DEC
}
0x8B00 -> when (opcode shr 6 and 3) {
0 -> { // NEGB
val dst = opc_dstb(opcode)
val res = op_loadb(dst).inv().inc()
op_storb(dst, res)
N = res bit 7
Z = res == 0.toUByte()
V = res == 0x8000.toUByte()
C = !Z
} // NEGB
1 -> { // ADCB
val dst = opc_dstb(opcode)
val c: UShort = if (C) 1u else 0u
val res = (op_loadb(dst) + c).toUByte()
op_storb(dst, res)
N = res bit 7
Z = res == 0u.toUByte()
V = (res == 0x80u.toUByte()) and C
C = Z and C
} // ADCB
2 -> {
val dst = opc_dstb(opcode)
val src = op_loadb(dst)
val res = if (C) src.dec() else src
op_storb(dst, res)
N = res bit 8
Z = res == 0.toUByte()
V = res == 0x80.toUByte()
C = C and (src == 0.toUByte())
} // SBCB
3 -> {
val dst = op_loadb(opc_dstb(opcode))
Z = dst == 0.toUByte()
N = dst bit 7
V = false
C = false
} // TSTB
}
0x8C00 -> when (opcode shr 6 and 3) {
0 -> {
val dst = opc_dstb(opcode)
val src = op_loadb(dst)
val res = (src shr 1).bit(7, C)
op_storb(dst, res)
C = src bit 0
N = res bit 7
Z = res == 0.toUByte()
V = N xor C
} // RORB
1 -> {
val dst = opc_dstb(opcode)
val src = op_loadb(dst)
val res = (src shl 1).bit(0, C)
op_storb(dst, res)
C = src bit 7
N = res bit 7
Z = res == 0.toUByte()
V = N xor C
} // ROLB
2 -> { // ASRB
val dst = opc_dstb(opcode)
val src = op_loadb(dst).toByte()
val res = (src shr 1).toUByte()
op_storb(dst, res)
N = res bit 7
Z = res == 0.toUByte()
C = src bit 0
V = N xor C
} // ASRB
3 -> { // ASLB
val dst = opc_dstb(opcode)
val src = op_loadb(dst)
val res = (src shl 1).toUByte()
op_storw(dst, res.toUShort())
N = res bit 7
Z = res == 0.toUByte()
C = src bit 7
V = N xor C
} // ASLB
}
0x8D00 -> when (opcode shr 6 and 3) {
1 -> { throw InvalidOpcodeException() } // MFPD // TODO
2 -> { throw InvalidOpcodeException() } // MTPD // TODO
else -> { throw InvalidOpcodeException() } // Reserved
}
}
0x1000 -> { // MOV
val src = opc_src(opcode)
val dst = opc_dst(opcode)
op_loadw(src).also {
op_storw(dst, it)
N = it bit 15
Z = it == 0.toUShort()
V = false
}
} // MOV
0x2000 -> { // CMP
val src = op_loadw(opc_src(opcode))
val src2 = op_loadw(opc_dst(opcode))
val res = (src - src2) and 0xFFFFu
N = res bit 15
Z = res == 0U
C = src < src2 // unsigned lt
V = ((src bit 15) xor (src2 bit 15)) and ((src2 bit 15) == (res bit 15))
} // CMP
0x3000 -> { // BIT
val src = opc_src(opcode)
val dst = opc_dst(opcode)
@@ -263,7 +578,36 @@ class CPU(private val core: AddressSpace) {
V = (src_sign == dstv and 0x8000u) && (src_sign != resw and 0x8000u)
C = (resw >= 0x10000u)
} // ADD
7000 -> when (opcode shr 9 and 0x7) {
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 res = registers[r].toShort().toInt() * src
registers[r bis 1] = (res shr 16).toUShort()
registers[r] = res.toUShort()
N = res bit 31
Z = res == 0
V = false
C = (res shr 16) == -(res shr 15 and 1) // check if overflow extends the sign bit
} // MUL
1 -> { // DIV
val r = opcode shr 6 and 0x7
val src = op_loadw(opc_src(opcode)).toInt()
val lho = (registers[r].toUInt() shl 16 or registers[r bis 1].toUInt()).toInt()
if (src != 0) {
val quot = lho / src
val rem = lho % src
registers[r] = quot.toUShort()
registers[r bis 1] = rem.toUShort()
val overflow = quot shr 16
V = overflow == -(quot shr 15 and 1) // check if overflow extends the sign bit
N = quot bit 15
Z = quot == 0
} else {
C = true
V = true
}
} // DIV
2 -> { // ASH
val r = opcode shr 6 and 0x7
var count = (op_loadb(opc_src(opcode)) and 0x3Fu).toInt()
@@ -309,25 +653,64 @@ class CPU(private val core: AddressSpace) {
C = false
src
}
val resS = res.toUShort()
registers[r] = (res and 0xFFFF).toUShort()
registers[r or 1] = (res shr 16 and 0xFFFF).toUShort()
N = res < 0
Z = res == 0
} // ASHC
4 -> {
val r = opcode shr 6 and 7
val dst = opc_dst(r)
val res = op_loadw(dst) xor registers[r]
op_storw(dst, res)
N = res bit 15
Z = res == 0.toUShort()
V = false
}
5,6 -> throw InvalidOpcodeException()
7 -> {
val reg = opcode shr 6 and 7
registers[reg] = registers[reg].dec()
if (registers[reg] != 0.toUShort()) {
pc = (pc.toInt() - (opcode and 0x3F shl 1)).toUShort()
}
} // SOB
}
// 0x800 handled above
0x9000 -> { // MOVB
val src = opc_srcb(opcode)
val dst = opc_dstb(opcode)
op_loadb(src).also {
if (dst bit 32) {
op_storw(dst, it.toUShort() sex 8)
} else {
op_storb(dst, it)
}
N = it bit 15
Z = it == 0.toUByte()
V = false
}
} // MOVB
0xA000 -> { // CMPB
val src = op_loadb(opc_srcb(opcode))
val src2 = op_loadb(opc_dstb(opcode))
val res = (src - src2) and 0xFFu
N = res bit 8
Z = res == 0U
C = src < src2 // unsigned lt
V = ((src bit 8) xor (src2 bit 8)) and ((src2 bit 8) == (res bit 8))
} // CMPB
0xB000 -> { // BITB
val src = opc_src(opcode)
val dst = opc_dst(opcode)
val src = opc_srcb(opcode)
val dst = opc_dstb(opcode)
val res = op_loadb(dst) and op_loadb(src).inv()
N = res bit 7
Z = res != 0u.toUByte()
V = false
} // BITB
0xC000 -> { // BICB
val src = opc_src(opcode)
val dst = opc_dst(opcode)
val src = opc_srcb(opcode)
val dst = opc_dstb(opcode)
val res = op_loadb(dst) and op_loadb(src).inv()
op_storb(dst, res)
N = res bit 7
@@ -335,15 +718,52 @@ class CPU(private val core: AddressSpace) {
V = false
} // BICB
0xD000 -> { // BISB
val src = opc_src(opcode)
val dst = opc_dst(opcode)
val src = opc_srcb(opcode)
val dst = opc_dstb(opcode)
val res = op_loadb(dst) or op_loadb(src)
op_storb(dst, res)
N = res bit 7
Z = res != 0u.toUByte()
V = false
} // BISB
0xE000 -> {
val src = opc_src(opcode)
val dst = opc_dst(opcode)
val srcv = op_loadw(src)
val dstv = op_loadw(dst)
val res = (dstv.toShort() - srcv.toShort())
op_storw(dst, res.toUShort())
N = res < 0
Z = res == 0
V = ((srcv bit 31) xor (dstv bit 15)) and ((srcv bit 15) == (res bit 31))
C = (dst.toInt() + src.inv().inc().toInt()) < 0x1_0000
} // SUB
}
}
fun trap(vector: UShort) {
stack_push(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) {
0x3FFFEu -> psw
else -> throw MemoryError(MemoryErrorType.BusTimeout, addr)
}
override fun setw(addr: UInt, value: UShort) = when (addr) {
0x3FFFEu -> psw = value
else -> throw MemoryError(MemoryErrorType.BusTimeout, addr)
}
}
enum class RunState(val handleInterrupt: Boolean) {
RUNNING(handleInterrupt = true),
HALTED(handleInterrupt = false),
WAIT_FOR_INTERRUPT(handleInterrupt = true)
}
}

View File

@@ -0,0 +1,5 @@
package com.thequux.mcpdp.core
class HaltInsn : Throwable() {
}

View File

@@ -0,0 +1,9 @@
package com.thequux.mcpdp.core
class MemoryError(val type: MemoryErrorType, var addr: UInt): Exception("Memory error $type at ${addr.toString(8)}")
enum class MemoryErrorType {
BusTimeout,
OddAddress,
NonExistent,
}

View File

@@ -0,0 +1,206 @@
package com.thequux.mcpdp.core
import com.thequux.mcpdp.ext.bit.bic
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
enum class AccessAction {
Allow,
Trap,
Abort,
}
enum class ACF(val bin: UShort, val read: AccessAction, val write: AccessAction) {
NonResident(0U, AccessAction.Abort, AccessAction.Abort),
ReadTrap(1U, AccessAction.Trap, AccessAction.Abort),
ReadOnly(2U, AccessAction.Abort, AccessAction.Allow),
Unused1(3U, AccessAction.Abort, AccessAction.Abort),
TrapAlways(4U, AccessAction.Trap, AccessAction.Trap),
WriteTrap(5U, AccessAction.Allow, AccessAction.Trap),
ReadWrite(6U, AccessAction.Allow, AccessAction.Allow),
Unused2(7U, AccessAction.Abort, AccessAction.Abort);
fun action(writep: Boolean): AccessAction = if (writep) write else read
companion object {
fun fromField(value: UShort): ACF = when ((value and 0x7U).toInt()) {
0 -> NonResident
1 -> ReadTrap
2 -> ReadOnly
3 -> Unused1
4 -> TrapAlways
5 -> WriteTrap
6 -> ReadWrite
7 -> Unused2
else -> throw ProgrammerError("Unreachable")
}
}
}
private data class PDR(val plf: UShort, var A: Boolean, var W: Boolean, var ed: Boolean, var acf: ACF) {
constructor(): this(0U, false, false, false, ACF.NonResident)
constructor(value: UShort): this(value shr 8 and 0x7Fu, false, false, value bit 3, ACF.fromField(value))
val range: UIntRange
get() = if (ed) {
(0x2000U - (plf.toUInt() shl 6))..0x1FFFu
} else {
0U..((plf.toUInt() shl 6) -1U)
}
fun touched() { A = true }
fun written() { A = true; W = true }
}
@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 enableDSpaceByMode = BooleanArray(4)
/// The 18-bit address space exposed to peripherals
val unibusMap: PAddressSpace = UnibusMap()
private inner class PageTable(val mode: Int, val dspace: Boolean) {
val par = UShortArray(16) { 0U }
val pdr = Array(16) { PDR() }
fun setMmr0(causeBit: Int, apf: Int, completed: Boolean = false) {
val reg = mmr[0].toInt()
val cause_flag = 1 shl causeBit
if (reg and 0xF000 and -cause_flag == 0) {
// no higher priority flags
mmr[0] = (reg and 0xF200
or 0x4000 // trap: page length
or mode.shl(5)
or (if (completed) 0x80 else 0)
or (if (dspace) 8 else 0)
or apf).toUShort()
}
}
fun map(addr: UShort, write: Boolean): UInt {
val apf = (addr shr 13).toInt()
val par = par[apf]
val pdr = pdr[apf]
if (write) pdr.written() else pdr.touched()
var trap = false
// check range
if (addr and 0x1FFFU !in pdr.range) {
setMmr0(14, apf)
// TODO: handle rest of trap
throw Trap(0xA8)
}
when (pdr.acf.action(write)) {
AccessAction.Allow -> {}
AccessAction.Trap -> {
setMmr0(12, apf, true)
trap = true
}
AccessAction.Abort -> {
setMmr0(13, apf)
throw Trap(0xA8)
}
}
return addr.toUInt() + (par.toUInt() shl 6) and 0x3F_FFFFu
}
}
var mode: Int = 0
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 var curIspace: PageTable = itabs[0]
private var curDspace: PageTable = dtabs[0]
private val unibusTable = UIntArray(32)
private fun getSpace(dspace: Boolean): PageTable = if (dspace) curDspace else curIspace
override fun getw(addr: UShort, dspace: Boolean): UShort = pspace.getw(getSpace(dspace).map(addr, write = false))
override fun getb(addr: UShort): UByte = pspace.getb(curDspace.map(addr, write = false))
override fun setw(addr: UShort, value: UShort, dspace: Boolean) {
pspace.setw(getSpace(dspace).map(addr, write = true), value)
}
override fun setb(addr: UShort, value: UByte) {
pspace.setb(curDspace.map(addr, write = true), value)
}
companion object {
private val MMR3: UInt = 0x3F54Eu
}
private inner class ConfigRegisters: PAddressSpace {
override fun getw(addr: UInt): UShort = when (addr) {
MMR3 -> {
(0U.bit(0, enableDSpaceByMode[3])
.bit(1, enableDSpaceByMode[1])
.bit(2, enableDSpaceByMode[0])
.bit(4, enable22bit)
.bit(5, enableUnibusMap)
.toUShort())
} // 772516 MMR3
else -> throw MemoryError(MemoryErrorType.BusTimeout, addr)
}
override fun setw(addr: UInt, value: UShort): Unit = when(addr) {
MMR3 -> {
enableUnibusMap = value bit 5
enable22bit = value bit 4
enableDSpaceByMode[0] = value bit 2
enableDSpaceByMode[1] = value bit 1
enableDSpaceByMode[3] = value bit 0
}
in 0x3F080u..0x3F0FFu -> {
val uaddr = (addr shr 1 and 31u).toInt()
val hiWord = addr bit 1
if (hiWord) {
unibusTable[uaddr] = unibusTable[uaddr] and 0xFFFFu or (value.toUInt() and 0x3Fu shl 16)
} else {
unibusTable[uaddr] = unibusTable[uaddr] and 0xFFFF_0000u or (value.toUInt() bic 0)
}
} // 770200..770377 Unibus map
else -> throw MemoryError(MemoryErrorType.BusTimeout, addr)
}
}
fun mount(unibus: Unibus) {
val regs = ConfigRegisters()
unibus.run {
deviceView = UnibusMap()
attach(0x3_FF80u, 6, regs) // User PAR/PDR
attach(0x3_FF7Au, 1, regs) // MMR0
attach(0x3_FF7Cu, 2, regs) // MMR1-2
attach(0x3_F54Eu, 1, regs) // MMR3
attach(0x3_F4C0u, 6, regs) // Kernal PAR/PDF
attach(0x3_F480u, 6, regs) // Supervisor PAR/PDR
attach(0x3_F080u, 7, regs) // Unibus map
}
}
private inner class UnibusMap: PAddressSpace {
// This implements an *18-bit* address space, for the view of unibus peripherals
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
}
}
override fun getw(addr: UInt): UShort = pspace.getw(map(addr))
override fun getb(addr: UInt): UByte = pspace.getb(map(addr))
override fun setw(addr: UInt, value: UShort) = pspace.setw(map(addr), value)
override fun setb(addr: UInt, value: UByte) = pspace.setb(map(addr), value)
}
}

View File

@@ -0,0 +1,58 @@
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.ext.bit.bit
import com.thequux.mcpdp.util.ConfigurationError
/// This is the main dispatcher for the PDP-11 address space, as seen from the PDP-11 CPU
///
/// Size represents the amount of physical memory, and must be a multiple of 32
@OptIn(ExperimentalUnsignedTypes::class)
class MemBus(val size: Int) : PAddressSpace {
private val data: UShortArray
public val unibus = Unibus()
init {
if (size % 64 != 0) {
throw ConfigurationError("Memory size must be a multiple of 64 bytes")
}
if (size < 0) {
throw ConfigurationError("Memory size must be positive")
}
if (size > 0x3C_0000) {
throw ConfigurationError("Memory overlaps unibus")
}
data = UShortArray(size shr 1)
unibus.attach(0x3FFF0u, 2, Registers())
}
private inner class Registers: PAddressSpace {
override fun getw(addr: UInt): UShort = when (addr) {
0x3FFF2u -> 0u // upper size
0x3FFF0u -> (size shr 6).toUShort()
else -> throw MemoryError(MemoryErrorType.BusTimeout, addr)
}
// Both registers are write-only
override fun setw(addr: UInt, value: UShort) {}
}
override fun getw(addr: UInt): UShort {
if (size bit 0) {
throw MemoryError(MemoryErrorType.OddAddress, 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)
}
}
override fun setw(addr: UInt, value: UShort) {
if (size bit 0) {
throw MemoryError(MemoryErrorType.OddAddress, addr)
}
}
}

View File

@@ -0,0 +1,71 @@
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.util.ConfigurationError
import com.thequux.mcpdp.util.ProgrammerError
import java.lang.Integer.min
interface Region {
fun attach(address: UInt, suffix: Int, device: PAddressSpace)
fun map(address: UInt): PAddressSpace?
}
private class MappedDevice(val device: PAddressSpace): Region {
override fun attach(address: UInt, suffix: Int, device: PAddressSpace) {
throw ConfigurationError("Attempted to map one device over another at ${address.toString(8)}")
}
override fun map(address: UInt): PAddressSpace = device
}
/// Note: this is open and public as an implementation detail.
open class Subregion(private val suffix: Int, private val size: Int): Region {
val map: Array<Region?>
val mask: UInt = (1u shl size).dec()
val submask = (1u shl suffix).dec()
init {
if (suffix + size > 18) {
throw ProgrammerError("tried to construct a mapper for more address space than exists")
}
map = Array(1 shl size) { null }
}
override fun attach(address: UInt, suffix: Int, device: PAddressSpace) {
val loc = (address shr suffix and mask).toInt()
if (suffix <= 0) throw ProgrammerError("Suffix must be positive")
if (suffix < this.suffix) {
// needs a submap
if (map[loc] == null) {
// Divisions at /12, /6, and /3
val subsize = if (this.suffix == 6) 3 else min(size, this.suffix)
map[loc] = Subregion(this.suffix - subsize, subsize)
}
map[loc]!!.attach(address, suffix, device)
} else {
val nunits = 1 shl (suffix - this.suffix)
for (i in 0..nunits.dec()) {
if (map[loc + i] != null) {
throw ConfigurationError("Unibus conflict at ${(address and submask.inv()).toString(8)}/${18-suffix}")
}
map[loc+i] = MappedDevice(device)
}
}
}
override fun map(address: UInt): PAddressSpace? = map[(address shr suffix and mask).toInt()]?.map(address)
}
class Unibus: PAddressSpace, Subregion(12, 6) {
/// The view of the unibus from a device
var deviceView: PAddressSpace = this
internal set
override fun map(address: UInt): PAddressSpace =
super.map(address) ?: throw MemoryError(MemoryErrorType.BusTimeout, 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)
override fun setb(addr: UInt, value: UByte) = map(addr).setb(addr, value)
}

View File

@@ -0,0 +1,4 @@
package com.thequux.mcpdp.util
class ConfigurationError(message: String?) : Exception(message) {
}

View File

@@ -0,0 +1,4 @@
package com.thequux.mcpdp.util
class ProgrammerError(message: String?) : Error(message) {
}