Fully implemented CPU and memory busses, but not yet tested
This commit is contained in:
1
.idea/gradle.xml
generated
1
.idea/gradle.xml
generated
@@ -1,5 +1,6 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
|
<component name="GradleMigrationSettings" migrationVersion="1" />
|
||||||
<component name="GradleSettings">
|
<component name="GradleSettings">
|
||||||
<option name="linkedExternalProjectsSettings">
|
<option name="linkedExternalProjectsSettings">
|
||||||
<GradleProjectSettings>
|
<GradleProjectSettings>
|
||||||
|
6
.idea/vcs.xml
generated
Normal file
6
.idea/vcs.xml
generated
Normal 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
11
doc/pdp10-impl.adoc
Normal 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
73
doc/peripheral-map.txt
Normal 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
|
@@ -1,8 +1,31 @@
|
|||||||
package com.thequux.mcpdp.core
|
package com.thequux.mcpdp.core
|
||||||
|
|
||||||
interface AddressSpace {
|
import com.thequux.mcpdp.ext.bit.bic
|
||||||
fun getw(addr: UShort): UShort
|
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 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)
|
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)
|
||||||
|
}
|
||||||
}
|
}
|
@@ -1,6 +1,8 @@
|
|||||||
package com.thequux.mcpdp.core
|
package com.thequux.mcpdp.core
|
||||||
|
|
||||||
import com.thequux.mcpdp.ext.bit.*
|
import com.thequux.mcpdp.ext.bit.*
|
||||||
|
import com.thequux.mcpdp.peripheral.MemBus
|
||||||
|
import com.thequux.mcpdp.util.ProgrammerError
|
||||||
|
|
||||||
/// The main CPU
|
/// The main CPU
|
||||||
///
|
///
|
||||||
@@ -9,14 +11,73 @@ import com.thequux.mcpdp.ext.bit.*
|
|||||||
/// Exxx: ROM
|
/// Exxx: ROM
|
||||||
/// xxxx: rest
|
/// xxxx: rest
|
||||||
@OptIn(ExperimentalUnsignedTypes::class)
|
@OptIn(ExperimentalUnsignedTypes::class)
|
||||||
class CPU(private val core: AddressSpace) {
|
class CPU(private val mbus: MemBus) {
|
||||||
private val registers = UShortArray(8)
|
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
|
private var pc: UShort
|
||||||
get() = registers[7]
|
get() = registers[7]
|
||||||
set(value) {
|
set(value) {
|
||||||
registers[7] = 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
|
private var cc: UShort
|
||||||
get() = psw and 0xFu
|
get() = psw and 0xFu
|
||||||
@@ -28,14 +89,25 @@ class CPU(private val core: AddressSpace) {
|
|||||||
private var C: Boolean = false
|
private var C: Boolean = false
|
||||||
private var Z: Boolean = false
|
private var Z: Boolean = false
|
||||||
private var V: 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 {
|
private fun op_resolve(operand: Int, byte_mode: Boolean = false): UInt {
|
||||||
val mode = operand shr 3
|
val mode = operand shr 3
|
||||||
val reg = operand and 0x7
|
val reg = operand and 0x7
|
||||||
val increment = if (byte_mode && reg != 7) 1U else 2U
|
val increment = if (byte_mode && reg != 7) 1U else 2U
|
||||||
return when (mode) {
|
return when (mode and 0x7) {
|
||||||
0 -> reg.toUInt() + 0x1_0000U
|
0 -> reg.toUInt() bis PADDR_REG_BIT
|
||||||
1 -> registers[reg].toUInt()
|
1 -> registers[reg].toUInt()
|
||||||
2 -> {
|
2 -> {
|
||||||
val addr = registers[reg]
|
val addr = registers[reg]
|
||||||
@@ -65,14 +137,13 @@ class CPU(private val core: AddressSpace) {
|
|||||||
pc = (pc+2u).toUShort()
|
pc = (pc+2u).toUShort()
|
||||||
core.getw((idx+registers[reg]).toUShort()).toUInt()
|
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) {
|
private fun op_storb(spec: UInt, value: UByte) {
|
||||||
val addr = (spec and 0xFFFFu).toUShort()
|
val addr = (spec and 0xFFFFu).toUShort()
|
||||||
if (spec >= 0x1_0000u) {
|
if (is_paddr_reg(spec)) {
|
||||||
// register
|
// register
|
||||||
registers[addr.toInt()] = registers[addr.toInt()] and 0xFF00u or value.toUShort()
|
registers[addr.toInt()] = registers[addr.toInt()] and 0xFF00u or value.toUShort()
|
||||||
} else {
|
} else {
|
||||||
@@ -82,40 +153,42 @@ class CPU(private val core: AddressSpace) {
|
|||||||
|
|
||||||
private fun op_loadb(spec: UInt): UByte {
|
private fun op_loadb(spec: UInt): UByte {
|
||||||
val addr = (spec and 0xFFFFu).toUShort()
|
val addr = (spec and 0xFFFFu).toUShort()
|
||||||
return if (spec > 0x1_0000u) {
|
return if (is_paddr_reg(spec)) {
|
||||||
(registers[addr.toInt()] and 0xFFu).toUByte()
|
(registers[addr.toInt()] and 0xFFu).toUByte()
|
||||||
} else {
|
} else {
|
||||||
core.getb(addr)
|
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()
|
val addr = (spec and 0xFFFFu).toUShort()
|
||||||
if (spec >= 0x1_0000u) {
|
if (is_paddr_reg(spec)) {
|
||||||
// register
|
// register
|
||||||
registers[addr.toInt()] = value
|
registers[addr.toInt()] = value
|
||||||
} else {
|
} 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()
|
val addr = (spec and 0xFFFFu).toUShort()
|
||||||
return if (spec > 0x1_0000u) {
|
return if (is_paddr_reg(spec)) {
|
||||||
(registers[addr.toInt()] and 0xFFu)
|
(registers[addr.toInt()] and 0xFFu)
|
||||||
} else {
|
} 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_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_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) {
|
private fun br_rel(cond: Boolean, opcode: Int) {
|
||||||
if (cond) {
|
if (cond) {
|
||||||
@@ -126,103 +199,345 @@ class CPU(private val core: AddressSpace) {
|
|||||||
val opcode = core.getw(pc).toInt()
|
val opcode = core.getw(pc).toInt()
|
||||||
pc = (pc + 2u).toUShort()
|
pc = (pc + 2u).toUShort()
|
||||||
when (opcode and 0xF000) {
|
when (opcode and 0xF000) {
|
||||||
0x0, 0x8000 -> when (opcode and 0xFFC0) {
|
0x0, 0x8000 -> when (opcode and 0xFF00) {
|
||||||
0x0000 -> when (opcode) {
|
0x0000 -> when (opcode) {
|
||||||
0x0003 -> throw Trap(14) // BPT
|
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 -> br_rel(true, opcode) // BR
|
||||||
0x0100, 0x0140, 0x0180, 0x01C0 -> br_rel(true, opcode) // BR
|
0x0200 -> br_rel(!Z, opcode) // BNE
|
||||||
0x0200, 0x0240, 0x0280, 0x02C0 -> br_rel(!Z, opcode) // BNE
|
0x0300 -> br_rel(Z, opcode) // BEQ
|
||||||
0x0300, 0x0340, 0x0380, 0x03C0 -> br_rel(Z, opcode) // BEQ
|
0x0400 -> br_rel(N == V, opcode) // BGE
|
||||||
0x0400, 0x0440, 0x0480, 0x04C0 -> br_rel(N == V, opcode) // BGE
|
0x0500 -> br_rel(N xor V, opcode) // BLT
|
||||||
0x0600, 0x0640, 0x0680, 0x06C0 -> br_rel(!Z and (N == V), opcode) // BGT
|
0x0600 -> br_rel(!Z and (N == V), opcode) // BGT
|
||||||
0x0500, 0x0540, 0x0580, 0x05C0 -> br_rel(N xor V, opcode) // BLT
|
0x0700 -> br_rel(Z or (N xor V), opcode) // BLE
|
||||||
0x0700, 0x0740, 0x0780, 0x07C0 -> 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
|
0x8A00 -> when (opcode shr 6 and 3) {
|
||||||
op_storw(opc_dst(opcode), 0U)
|
0 -> { // CLRB
|
||||||
N = false
|
op_storb(opc_dstb(opcode), 0U)
|
||||||
V = false
|
N = false
|
||||||
C = false
|
V = false
|
||||||
Z = true
|
C = false
|
||||||
} // CLR
|
Z = true
|
||||||
0x0B40 -> { // ADC
|
} // CLRB
|
||||||
val dst = opc_dst(opcode)
|
1 -> { // COMB
|
||||||
val c: UShort = if (C) 1u else 0u
|
val dst = opc_dstb(opcode)
|
||||||
val res = (op_loadw(dst) + c).toUShort()
|
val res = op_loadb(dst).inv().also { op_storb(dst, it) }
|
||||||
op_storw(dst, res)
|
N = res bit 7
|
||||||
N = res bit 15
|
Z = res == 0U.toUByte()
|
||||||
Z = res == 0u.toUShort()
|
C = true
|
||||||
V = (res == 0x8000u.toUShort()) and C
|
V = false
|
||||||
C = Z and C
|
} // COMB
|
||||||
} // ADC
|
2 -> { // INC
|
||||||
0x0C80 -> { // ASR
|
val dst = opc_dstb(opcode)
|
||||||
val dst = opc_dst(opcode)
|
val res = op_loadb(dst).inc().also { op_storb(dst, it) }
|
||||||
val src = op_loadw(dst).toShort()
|
N = res bit 7
|
||||||
val res = (src shr 1).toUShort()
|
Z = res == 0.toUByte()
|
||||||
op_storw(dst, res)
|
V = res == 0x80.toUByte()
|
||||||
N = res bit 15
|
} // INC
|
||||||
Z = res == 0.toUShort()
|
3 -> { // DEC
|
||||||
C = src bit 0
|
val dst = opc_dstb(opcode)
|
||||||
V = N xor C
|
val res = op_loadb(dst).dec().also { op_storb(dst, it) }
|
||||||
} // ASR
|
N = res bit 7
|
||||||
0x0CC0 -> { // ASL
|
Z = res == 0.toUByte()
|
||||||
val dst = opc_dst(opcode)
|
V = res == 0x7F.toUByte()
|
||||||
val src = op_loadw(dst)
|
} // DEC
|
||||||
val res = src shl 1
|
}
|
||||||
op_storw(dst, res.toUShort())
|
0x8B00 -> when (opcode shr 6 and 3) {
|
||||||
N = res bit 15
|
0 -> { // NEGB
|
||||||
Z = res == 0.toUShort()
|
val dst = opc_dstb(opcode)
|
||||||
C = src and 0x8000u != 0u.toUShort()
|
val res = op_loadb(dst).inv().inc()
|
||||||
V = N xor C
|
op_storb(dst, res)
|
||||||
} // ASL
|
N = res bit 7
|
||||||
0x8000, 0x8040, 0x8080, 0x80C0 -> br_rel(!N, opcode) // BPL
|
Z = res == 0.toUByte()
|
||||||
0x8100, 0x8140, 0x8180, 0x81C0 -> br_rel(N, opcode) // BMI
|
V = res == 0x8000.toUByte()
|
||||||
0x8200, 0x8240, 0x8280, 0x82C0 -> br_rel(!C and !Z, opcode) // BHI
|
C = !Z
|
||||||
0x8300, 0x8340, 0x8380, 0x83C0 -> br_rel(C or Z, opcode) // BLOS
|
} // NEGB
|
||||||
0x8400, 0x8440, 0x8480, 0x84C0 -> br_rel(!V, opcode) // BVC
|
1 -> { // ADCB
|
||||||
0x8500, 0x8540, 0x8580, 0x85C0 -> br_rel(V, opcode) // BVS
|
val dst = opc_dstb(opcode)
|
||||||
0x8600, 0x8640, 0x8680, 0x86C0 -> br_rel(!C, opcode) // BCC/BHIS
|
val c: UShort = if (C) 1u else 0u
|
||||||
0x8700, 0x8740, 0x8780, 0x87C0 -> br_rel(C, opcode) // BCS/BLO
|
val res = (op_loadb(dst) + c).toUByte()
|
||||||
|
op_storb(dst, res)
|
||||||
0x8A00 -> { // CLRB
|
N = res bit 7
|
||||||
op_storb(opc_dst(opcode), 0U)
|
Z = res == 0u.toUByte()
|
||||||
N = false
|
V = (res == 0x80u.toUByte()) and C
|
||||||
V = false
|
C = Z and C
|
||||||
C = false
|
} // ADCB
|
||||||
Z = true
|
2 -> {
|
||||||
} // CLRB
|
val dst = opc_dstb(opcode)
|
||||||
0x8B40 -> { // ADCB
|
val src = op_loadb(dst)
|
||||||
val dst = opc_dst(opcode)
|
val res = if (C) src.dec() else src
|
||||||
val c: UShort = if (C) 1u else 0u
|
op_storb(dst, res)
|
||||||
val res = (op_loadb(dst) + c).toUByte()
|
N = res bit 8
|
||||||
op_storb(dst, res)
|
Z = res == 0.toUByte()
|
||||||
N = res bit 7
|
V = res == 0x80.toUByte()
|
||||||
Z = res == 0u.toUByte()
|
C = C and (src == 0.toUByte())
|
||||||
V = (res == 0x80u.toUByte()) and C
|
} // SBCB
|
||||||
C = Z and C
|
3 -> {
|
||||||
} // ADCB
|
val dst = op_loadb(opc_dstb(opcode))
|
||||||
0x8C80 -> { // ASRB
|
Z = dst == 0.toUByte()
|
||||||
val dst = opc_dst(opcode)
|
N = dst bit 7
|
||||||
val src = op_loadb(dst).toByte()
|
V = false
|
||||||
val res = (src shr 1).toUByte()
|
C = false
|
||||||
op_storb(dst, res)
|
} // TSTB
|
||||||
N = res bit 7
|
}
|
||||||
Z = res == 0.toUByte()
|
0x8C00 -> when (opcode shr 6 and 3) {
|
||||||
C = src bit 0
|
0 -> {
|
||||||
V = N xor C
|
val dst = opc_dstb(opcode)
|
||||||
} // ASRB
|
val src = op_loadb(dst)
|
||||||
0x8CC0 -> { // ASLB
|
val res = (src shr 1).bit(7, C)
|
||||||
val dst = opc_dst(opcode)
|
op_storb(dst, res)
|
||||||
val src = op_loadb(dst)
|
C = src bit 0
|
||||||
val res = (src shl 1).toUByte()
|
N = res bit 7
|
||||||
op_storw(dst, res.toUShort())
|
Z = res == 0.toUByte()
|
||||||
N = res bit 7
|
V = N xor C
|
||||||
Z = res == 0.toUByte()
|
} // RORB
|
||||||
C = src bit 7
|
1 -> {
|
||||||
V = N xor C
|
val dst = opc_dstb(opcode)
|
||||||
} // ASLB
|
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
|
0x3000 -> { // BIT
|
||||||
val src = opc_src(opcode)
|
val src = opc_src(opcode)
|
||||||
val dst = opc_dst(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)
|
V = (src_sign == dstv and 0x8000u) && (src_sign != resw and 0x8000u)
|
||||||
C = (resw >= 0x10000u)
|
C = (resw >= 0x10000u)
|
||||||
} // ADD
|
} // 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
|
2 -> { // ASH
|
||||||
val r = opcode shr 6 and 0x7
|
val r = opcode shr 6 and 0x7
|
||||||
var count = (op_loadb(opc_src(opcode)) and 0x3Fu).toInt()
|
var count = (op_loadb(opc_src(opcode)) and 0x3Fu).toInt()
|
||||||
@@ -309,25 +653,64 @@ class CPU(private val core: AddressSpace) {
|
|||||||
C = false
|
C = false
|
||||||
src
|
src
|
||||||
}
|
}
|
||||||
val resS = res.toUShort()
|
|
||||||
registers[r] = (res and 0xFFFF).toUShort()
|
registers[r] = (res and 0xFFFF).toUShort()
|
||||||
registers[r or 1] = (res shr 16 and 0xFFFF).toUShort()
|
registers[r or 1] = (res shr 16 and 0xFFFF).toUShort()
|
||||||
N = res < 0
|
N = res < 0
|
||||||
Z = res == 0
|
Z = res == 0
|
||||||
} // ASHC
|
} // 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
|
0xB000 -> { // BITB
|
||||||
val src = opc_src(opcode)
|
val src = opc_srcb(opcode)
|
||||||
val dst = opc_dst(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).inv()
|
||||||
N = res bit 7
|
N = res bit 7
|
||||||
Z = res != 0u.toUByte()
|
Z = res != 0u.toUByte()
|
||||||
V = false
|
V = false
|
||||||
} // BITB
|
} // BITB
|
||||||
0xC000 -> { // BICB
|
0xC000 -> { // BICB
|
||||||
val src = opc_src(opcode)
|
val src = opc_srcb(opcode)
|
||||||
val dst = opc_dst(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).inv()
|
||||||
op_storb(dst, res)
|
op_storb(dst, res)
|
||||||
N = res bit 7
|
N = res bit 7
|
||||||
@@ -335,15 +718,52 @@ class CPU(private val core: AddressSpace) {
|
|||||||
V = false
|
V = false
|
||||||
} // BICB
|
} // BICB
|
||||||
0xD000 -> { // BISB
|
0xD000 -> { // BISB
|
||||||
val src = opc_src(opcode)
|
val src = opc_srcb(opcode)
|
||||||
val dst = opc_dst(opcode)
|
val dst = opc_dstb(opcode)
|
||||||
val res = op_loadb(dst) or op_loadb(src)
|
val res = op_loadb(dst) or op_loadb(src)
|
||||||
op_storb(dst, res)
|
op_storb(dst, res)
|
||||||
N = res bit 7
|
N = res bit 7
|
||||||
Z = res != 0u.toUByte()
|
Z = res != 0u.toUByte()
|
||||||
V = false
|
V = false
|
||||||
} // BISB
|
} // 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)
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
5
src/main/kotlin/com/thequux/mcpdp/core/HaltInsn.kt
Normal file
5
src/main/kotlin/com/thequux/mcpdp/core/HaltInsn.kt
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package com.thequux.mcpdp.core
|
||||||
|
|
||||||
|
class HaltInsn : Throwable() {
|
||||||
|
|
||||||
|
}
|
9
src/main/kotlin/com/thequux/mcpdp/core/MemoryError.kt
Normal file
9
src/main/kotlin/com/thequux/mcpdp/core/MemoryError.kt
Normal 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,
|
||||||
|
}
|
206
src/main/kotlin/com/thequux/mcpdp/core/PagingUnit.kt
Normal file
206
src/main/kotlin/com/thequux/mcpdp/core/PagingUnit.kt
Normal 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)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
58
src/main/kotlin/com/thequux/mcpdp/peripheral/MemBus.kt
Normal file
58
src/main/kotlin/com/thequux/mcpdp/peripheral/MemBus.kt
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
71
src/main/kotlin/com/thequux/mcpdp/peripheral/Unibus.kt
Normal file
71
src/main/kotlin/com/thequux/mcpdp/peripheral/Unibus.kt
Normal 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)
|
||||||
|
}
|
@@ -0,0 +1,4 @@
|
|||||||
|
package com.thequux.mcpdp.util
|
||||||
|
|
||||||
|
class ConfigurationError(message: String?) : Exception(message) {
|
||||||
|
}
|
@@ -0,0 +1,4 @@
|
|||||||
|
package com.thequux.mcpdp.util
|
||||||
|
|
||||||
|
class ProgrammerError(message: String?) : Error(message) {
|
||||||
|
}
|
Reference in New Issue
Block a user