DWMC-16: REV 4.2 Introduction
So, I've been silent for a while, mostly doing things and stuff.
Which includes slowly cooking up the next version of the DWMC-16, during which I've silently designed a Version 3 and then jumped to Version 4.1 and finally Version 4.2.
This latest version of the design for the DWMC-16 is based on the PDP-11, as well as the DCPU-16, designed by Notch of Mincecraft fame, originally for a space game he wanted to make, but which didn't go anywhere.
From the PDP-11, I really liked the idea of the orthogonal instruction set, while I took the way Notch encoded the opcodes and address modes from the DCPU-16.
So far, I got quite a bit of work done on this revision of the system, from the Basic Design, over the Registers, OpCode Format and Addressing modes, to the Instruction Set to some Modules that will be available.
EDIT (15/07/2026):
I have realised that I was missing a way to deal with the flag for the lowest byte in Byte Operations and corrected for that oversight by putting the Interrupts into their own Special Purpose register the ISW and added four 'new' flags to the PSW register.
Basic Design Ideas¶
Inspiration taken by Notch's DCPU-16 and PDP-11.
- 8 general purpose registers R0-R7
- R4-R7 can be used for Indirect Addressing, with Increment/Decrement functionality
- R7 acts as hardware System Stack Pointer
- Non General Purpose registers:
- Processor Status Word (PSW)
- Program Counter (PC)
- Word and Byte capable, but only Word addressing
- all internal addressing is 16 bit, requiring the use of an MMU to access more than 64 kiWords (128 kiByte) of memory
- MMU control registers reside in Memory Mapped IO
- external 20 to 24 bit address bus, likely 22 bit for 4MWord of memory
- memory divided into 1kWord sized segments (4096 segments)
- memory word addressable
- lower 8 kiWord segments of memory as System Memory (MMIO, RAM and ROM), with the lowest 32 Words for Interrupt and Trap target addresses,
- 16 External Hardware Interrupt lines
- 16 Trap Interrupts (Error and Exception handling), can be connected to CPU internal fault generation
- Variable length Instructions (1-3 words)
- Orthogonal Instruction design
- Small Instruction set
- Barrel shifter
- Kernel and User Mode operation for the CPU, handled by the MMU
Memory Maps (tentative)¶
| Start Address |
End Address |
Size (Words) |
Use |
|---|---|---|---|
0x0000 |
0x000F |
16 | Interrupt Vector Table (target addresses only) |
0x0010 |
0x001F |
16 | Trap Vector Table (target addresses only) |
0x0020 |
0x007F |
96 | Protected Memory Mapped I/O (Kernel only) |
0x0080 |
0x01FF |
384 | Memory Mapped I/O |
0x0200 |
0x17FF |
5632 | System/Monitor/BIOS ROM |
0x1800 |
0x1EFF |
1792 | System RAM |
0x1F00 |
0x1FFF |
256 | System Stack |
0x2000 |
0xFFFF |
57344 | User RAM/Memory Mapped Area |
| ## Interrupt and Trap Table |
| Address | Interrupt |
|---|---|
0x0000 |
Reset (hardcoded after Power on, can be changed) |
0x0001 |
Interrupt 1 |
| ... | |
0x000F |
Interrupt 15 |
0x0010 |
Trap 0 |
| ... | |
0x001F |
Trap 15 |
| Lower number Interrupts and Traps are handled first. |
| Interrupt Trap |
Used by |
|---|---|
| Interrupt 14 | Intel 8251A USART |
| Interrupt 15 | Intel 8279 Keyboard Controller |
| Trap 0 | Memory Management Unit |
| ## Memory Mapped I/O Space |
Current MMIO map
| Start Address |
End Adress |
Used by |
|---|---|---|
0x0080 |
0x0081 |
Intel 8251A USART built into Blinkenlights Panel for basic connectivity |
0x0082 |
0x0083 |
HD44780 Alphanumeric Display built into Blinkenlights Panel for basic display tasks |
0x0084 |
0x0085 |
Intel 8279 Keyboard Controller built into Blinkenlights Panel for basic input tasks |
0x0086 |
0x0087 |
Latches of the BlinkenLights panel for IO and to turn the Blink lights into IO from bus monitoring |
Registers, Opcode Format & Addressing Modes¶
This part of this post has been streamlined with the help of Claude from the initial design I did by myself and I can prove it. :p
Registers¶
The DWMC-16 has eight general purpose registers, R0–R7. Of these, R4, R5, and R6 additionally support Pointer Addressing modes. R7 is reserved as the hardware Stack Pointer (SP), but can still be accessed like a general-purpose register. The Program Counter (PC) exists as a dedicated register outside the general register file, alongside the Processor Status Word (PSW) and the Interrupt Status Word (INW).
| Register | Used As |
|---|---|
R0–R3 |
General Purpose |
R4–R6 |
General Purpose; Pointer Addressing |
R7/SP |
Stack Pointer |
PC |
Program Counter |
PSW |
Processor Status Word |
ISW |
Interrupt Status Word |
PSW — Processor Status Word¶
The Processor Status Word reflects the current state of the processor. It can be read and written by software, and controls interrupt and trap handling. The four condition flags in the upper nibble are set automatically by arithmetic and logical operations and are used by branch instructions.
| 15 | 14 | 13 | 12 | 11 | 10 | 09 | 08 | 07 | 06 | 05 | 04 | 03 | 02 | 01 | 00 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| C | N | Z | O | CL | NL | ZL | OL |
Condition Flags (bits 15–12)
| Bit | Flag | Description |
|---|---|---|
| 15 | C |
Carry — set when an operation produces a carry out |
| 14 | N |
Negative — set when the MSB of the result is 1 |
| 13 | Z |
Zero — set when the result is 0x0000 |
| 12 | O |
Overflow — set when a signed arithmetic overflow occurs |
| 7 | CL |
Carry Low — set when a byte operation produces a carry out in the low byte of the word |
| 6 | NL |
Negative Low — set when the MSB of the result of a byte operation is 1 in the low byte of the word |
| 5 | ZL |
Zero Low — set when the result of a byte operation is 0x00 in the low byte of the word |
| 4 | OL |
Overflow Low — set when a signed arithmetic overflow occurs in the low byte of the word |
Bits 11 to 8 and 3 to 0 are reserved and unused
ISW — Interrupt Status Word¶
The Interrupt Status words reflects the current states of the processors interrupts. It can be read and partially written by software and controls the interrupt and trap handling.
| 15 | 14 | 13 | 12 | 11 | 10 | 09 | 08 | 07 | 06 | 05 | 04 | 03 | 02 | 01 | 00 |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| T | I | T3 | T2 | T1 | T0 | I3 | I2 | I1 | I0 |
Interrupt Control (bits 09–00)
| Bits | Field | Description |
|---|---|---|
| 09 | T |
Trap / Software Interrupt Enable |
| 08 | I |
Hardware Interrupt Enable |
| 07–04 | T3–T0 |
Active Trap (Software Interrupt) ID |
| 03–00 | I3–I0 |
Active Hardware Interrupt ID |
Bits 15 to 10 are reserved and unused.
Interrupts¶
The DWMC-16 supports 16 hardware interrupt lines and 16 software interrupts (Traps). Each is identified by a 4-bit binary value, reflected in the I3–I0 and T3–T0 fields of the ISW respectively. The I and T enable bits in the ISW control whether hardware interrupts and software traps are recognised.
Hardware Interrupt 0 (0x0) is the Reset Vector, triggered on system startup or reset.
When any interrupt or trap fires, the processor automatically saves R0–R6 onto the stack before entering the service routine. RETI restores these registers from the stack on return, cleanly resuming the interrupted context.
Opcode Format¶
All instructions are encoded as a primary 16-bit word. Depending on the addressing modes of the operands, one or two additional 16-bit extension words may follow, giving a variable instruction length of one to three words.
The layout of the primary word depends on the instruction class:
| Bit Pattern | Class | Example |
|---|---|---|
oooootttttssssss |
Double Operand | MOV R1, R3 |
oooooCNZObssssss |
Branch | BRN C |
00000oooo0ssssss |
Single Operand | NOT R1 |
000000000000oooo |
No Operand | NOP |
Field definitions:
| Symbol | Description |
|---|---|
o |
Opcode |
t |
Target operand specifier (5 bits) |
s |
Source operand specifier (6 bits) |
| b | Byte Switch |
The source field is one bit wider than the target field (6 bits vs. 5 bits) to accommodate the compact inline immediate value range, described in the Addressing Modes section below. For Double Operand instructions the 5-bit opcode occupies bits 15–11. Single Operand instructions fix bits 15–11 to 00000 and place the opcode in bits 10–7. No Operand instructions fix bits 15–4 to zero with the opcode in bits 3–0.
Branch instructions share the Single Operand layout but replace the lower portion of the source field with the four condition flag bits (C, N, Z, O) used as the branch condition. The Byte switch b is to select between the usual condition flags used by word operation and the high byte of byte operations and those set by the low byte in byte operations. 0is for used to select C, N, Z and O, while 1 selects CL, NL, ZL and OL.
Addressing Modes¶
The DWMC-16 supports a variety of addressing modes, allowing operands to refer to registers directly, to memory through pointers, to PC-relative locations, or to literal values embedded in the instruction stream. The mode for each operand is encoded in its respective field of the opcode word. Modes that require an additional value — an index offset, a relative offset, or an immediate — consume the next word in the instruction stream (PC++), extending the instruction by one word per such operand.
Register Mode — Rn¶
The operand is the value held directly in the named register. All eight registers (R0–R7) are accessible in this mode.
Symbolic:
MOV R1, R3 ; copy R3 into R1
Pointer Mode — @Rn¶
The register holds a memory address. The operand is the word at that address. Only R4–R7 may be used as pointer registers.
Symbolic:
MOV R1, @R4 ; copy the word in memory pointed to by R4 into R1
Autoincrement Mode — @Rn+¶
As Pointer Mode, but after the memory access completes the pointer register is incremented — by 1 for byte operations, or by 2 for word operations. Only R4–R7 are valid. This mode is well suited for walking forward through arrays or buffers.
Symbolic:
MOV R1, @R4+ ; copy the word at R4 into R1, then increment R4 by 2
Autodecrement Mode — @-Rn¶
As Pointer Mode, but the pointer register is decremented before the access — by 1 for byte operations, or by 2 for word operations. Only R4–R7 are valid. Combined with Autoincrement Mode, this enables efficient traversal in both directions.
Symbolic:
MOV R1, @-R4 ; decrement R4 by 2, then copy the word at R4 into R1
Index Mode — Rn(X)¶
The register holds a base address, and the next instruction word (PC++) provides a signed 16-bit offset X. The effective address is the sum of the register value and X. Only R4–R7 are valid as the base register. Both operands of a double-operand instruction may independently use Index Mode, each consuming one extension word, for a three-word instruction in total.
Symbolic:
MOV R1, R4(0x1337) ; copy the word at R4 + 0x1337 into R1
MOV R4(0x54F2), R5(0x342A) ; copy the word at R5 + 0x342A into the word at R4 + 0x54F2
Relative Mode — (X)¶
The next instruction word (PC++) provides a signed 16-bit value X. The effective address is PC + X, allowing position-independent data references relative to the current instruction.
Symbolic:
MOV R1, (10) ; copy the word at PC + 10 into R1
Relative Address Mode — @(X)¶
As Relative Mode, but the computed address is then used as a pointer — a two-level dereference. The processor first computes PC + X, reads the word stored there, and uses that word as the final address.
Symbolic:
MOV R1, @(10) ; read the word at PC + 10 as an address, then copy the word at that address into R1
Immediate Mode — X¶
The next instruction word (PC++) is the operand value itself. If Immediate Mode is specified as the write target of an instruction, the operation fails silently.
As an optimisation, values in the range −1 to 30 (0xFFFF to 0x001E) can be encoded directly within the 6-bit source field of the opcode word, avoiding any extension word.
Symbolic:
MOV R1, 0xFF54 ; load the literal value 0xFF54 into R1
Immediate Address Mode — @X¶
The next instruction word (PC++) is treated as an absolute memory address. The operand is the word stored at that address. If used as a write target, the operation fails silently.
Symbolic:
MOV R1, @0x00FF ; copy the word at absolute address 0x00FF into R1
Operand Encoding Reference¶
Each operand is encoded as a 5-bit value in the target field or a 6-bit value in the source field. The extra bit in the source field provides space for the compact inline immediate range (0x20–0x3F), which is only available as a source.
| Encoding | Mnemonic | Description |
|---|---|---|
0x00–0x07 |
Rn |
Register Mode, R0–R7 |
0x08–0x0B |
@Rn |
Pointer Mode, R4–R7 |
0x0C–0x0F |
@Rn+ |
Autoincrement Mode, R4–R7 |
0x10–0x13 |
@-Rn |
Autodecrement Mode, R4–R7 |
0x14–0x17 |
Rn(X) |
Index Mode, R4–R7; consumes one extension word |
0x18 |
— | Reserved |
0x19 |
PC |
Program Counter |
0x1A |
PSW |
Processor Status Word |
0x1B |
ISW |
Interrupt Status Word |
0x1C |
(X) |
Relative Mode — effective address is PC + X; consumes one extension word |
0x1D |
@(X) |
Relative Address Mode — dereferences address at PC + X; consumes one extension word |
0x1E |
X |
Immediate Mode — literal value in extension word; consumes one extension word |
0x1F |
@X |
Immediate Address Mode — absolute address in extension word; consumes one extension word |
0x20–0x3F |
X |
Compact Immediate — literal value −1 to 30 encoded inline; no extension word |
The compact immediate range maps encoding value E to literal value E − 0x21, covering −1 (0xFFFF) through 30 (0x001E). This range is only available in the 6-bit source field.
Stack Operations¶
The DWMC-16 has no dedicated stack instructions. Since R7/SP is a general-purpose pointer register, all pointer addressing modes apply to it directly. The assembler provides convenience macros for the most common stack operations, each expanding to a standard MOV instruction:
; Push a register onto the stack (Autodecrement Mode on SP)
.MACRO PUSH R
MOV @-SP, R
.ENDMACRO
; Pop the top of the stack into a register (Autoincrement Mode on SP)
.MACRO POP R
MOV R, @SP+
.ENDMACRO
; Read the top of the stack without modifying SP (Pointer Mode on SP)
.MACRO PEEK R
MOV R, @SP
.ENDMACRO
; Read a value at an arbitrary depth in the stack
.MACRO PICK R, n
MOV R, SP(n)
.ENDMACRO
PUSH decrements SP before writing, and POP reads then increments SP, maintaining the standard full-descending stack convention. PEEK reads the top of stack non-destructively. PICK n reads the word at offset n from the current stack pointer, allowing access to values deeper in the stack without disturbing it.
Instruction Set Reference¶
This part of this post has been streamlined with the help of Claude from the initial design I did by myself and I can prove it. :p
Conventions¶
Opcode Encoding Notation
| Symbol | Meaning |
|---|---|
o |
Opcode bit |
t |
Target operand field (5 bits) |
s |
Source operand field (6 bits) |
C |
Carry flag condition bit |
N |
Negative flag condition bit |
Z |
Zero flag condition bit |
O |
Overflow flag condition bit |
b |
Byte Switch, 0 for high byte or word, 1 for low byte |
0 |
Fixed zero bit |
Flag Notation
| Symbol | Flag | Set When |
|---|---|---|
| C | Carry | Carry or borrow out of the MSB for the word or high byte |
| N | Negative | MSB of result is 1 for the word or high byte |
| Z | Zero | Result is 0x0000 for the word or high byte |
| O | Overflow | Signed arithmetic overflow occurred for the word or high byte |
| CL | Carry Low | Carry or borrow out of the MSB for the low byte |
| NL | Negative Low | MSB of result is 1 for the low byte |
| ZL | Zero Low | Result is 0x00 for the low byte |
| OL | Overflow Low | Signed arithmetic overflow occurred for the low byte |
Flags marked - are not affected by the instruction. Flags marked * are modified by the instruction. Flags marked 0 are cleared by the instruction.
Operand Notation
| Notation | Meaning |
|---|---|
src |
Source operand value |
dst |
Destination/Target operand value |
(addr) |
Contents of memory at address addr |
← |
Assignment |
PC |
Program Counter |
SP |
Stack Pointer (R7) |
PSW |
Processor Status Word |
Double Operand Instructions¶
Opcode Format: ooooo ttttt ssssss
Branch Format: ooooo CNZO b ssssss
MOV / MOVB - Move / Move Byte¶
| Field | Word | Byte |
|---|---|---|
| Type | Double Operand | Double Operand |
| Opcode | 0x01 |
0x02 |
| Encoding | 00001 ttttt ssssss |
00010 ttttt ssssss |
| Operation | dst ← src |
dst.hi ← src.hidst.lo ← src.lo |
| Example | MOV R1, R3 |
MOVB R1, R3 |
| Flags | C: - N: * Z: * O: - | C: - N: * Z: * O: - CL: - NL: * ZL: * OL: - |
Move the contents of the source operand into the destination operand. The source operand is not modified. The Zero flag is set if the result is zero and the Negative flag reflects the MSB of the result.
MOVB operates on the two bytes of the word independently, moving the high byte of the source into the high byte of the destination and the low byte of the source into the low byte of the destination.
ADD / ADDB - Add with Carry / Add Bytes with Carry¶
| Field | Word | Byte |
|---|---|---|
| Type | Double Operand | Double Operand |
| Opcode | 0x03 |
0x04 |
| Encoding | 00011 ttttt ssssss |
00100 ttttt ssssss |
| Operation | dst ← dst + src |
dst.hi ← dst.hi + src.hidst.lo ← dst.lo + src.lo |
| Example | ADD R1, R3 |
ADDB R1, R3 |
| Flags | C: * N: * Z: * O: * | C: * N: * Z: * O: * CL: * NL: * ZL: * OL: * |
Add the source operand to the destination operand and store the result in the destination. The Carry flag is set if the addition produces a carry out of the MSB. The Overflow flag is set if a signed overflow occurs. The Zero flag is set if the result is zero and the Negative flag reflects the MSB of the result.
ADDB performs the addition independently on both bytes of the word. Each byte generates its own carry and overflow, which are OR'd together into the respective flags.
SUB / SUBB - Subtract with Carry / Subtract Bytes with Carry¶
| Field | Word | Byte |
|---|---|---|
| Type | Double Operand | Double Operand |
| Opcode | 0x05 |
0x06 |
| Encoding | 00101 ttttt ssssss |
00110 ttttt ssssss |
| Operation | dst ← dst - src |
dst.hi ← dst.hi - src.hidst.lo ← dst.lo - src.lo |
| Example | SUB R1, R3 |
SUBB R1, R3 |
| Flags | C: * N: * Z: * O: * | C: * N: * Z: * O: * CL: * NL: * ZL: * OL: * |
Subtract the source operand from the destination operand and store the result in the destination. The Carry flag reflects the borrow out of the MSB. The Overflow flag is set if a signed overflow occurs. The Zero flag is set if the result is zero and the Negative flag reflects the MSB of the result.
SUBB performs the subtraction independently on both bytes of the word. Each byte generates its own borrow and overflow, which are OR'd together into the respective flags.
MUL / MULB - Multiply / Multiply Byte¶
| Field | Word | Byte |
|---|---|---|
| Type | Double Operand | Double Operand |
| Opcode | 0x07 |
0x08 |
| Encoding | 00111 ttttt ssssss |
01000 ttttt ssssss |
| Operation | dst:src ← dst × src |
dst.hi:dst.lo ← dst.hi × src.hisrc.hi:src.lo ← dst.lo × src.lo |
| Example | MUL R1, R3 |
MULB R1, R3 |
| Flags | C: - N: * Z: * O: - | C: - N: * Z: * O: - CL: - NL: * ZL: * OL: - |
Multiply the destination operand by the source operand, producing a 32-bit result. The upper word of the result is stored in the destination operand and the lower word of the result is stored in the source operand.
MULB performs the multiplication independently on both byte pairs. The high byte of the destination is multiplied by the high byte of the source, and the low byte of the destination is multiplied by the low byte of the source. Each produces a 16-bit result; the upper byte of each result is stored in the corresponding byte of the destination and the lower byte in the corresponding byte of the source.
DIV / DIVB - Divide / Divide Byte¶
| Field | Word | Byte |
|---|---|---|
| Type | Double Operand | Double Operand |
| Opcode | 0x09 |
0x0A |
| Encoding | 01001 ttttt ssssss |
01010 ttttt ssssss |
| Operation | dst ← dst / src, src ← dst mod src |
dst.hi ← dst.hi / src.hi, src.hi ← dst.hi mod src.hidst.lo ← dst.lo / src.lo, src.lo ← dst.lo mod src.lo |
| Example | DIV R1, R3 |
DIVB R1, R3 |
| Flags | C: - N: * Z: * O: * | C: - N: * Z: * O: * CL: - NL: * ZL: * OL: * |
Divide the destination operand by the source operand. The quotient is stored in the destination operand and the remainder is stored in the source operand. The Overflow flag is set on division by zero.
DIVB performs the division independently on both bytes of the word. The high byte of the destination is divided by the high byte of the source, and the low byte of the destination is divided by the low byte of the source. The quotient and remainder for each byte are stored in the corresponding bytes of the destination and source operands respectively.
AND - Logical AND¶
| Field | Value |
|---|---|
| Type | Double Operand |
| Opcode | 0x10 |
| Encoding | 10000 ttttt ssssss |
| Operation | dst ← dst AND src |
| Example | AND R1, R3 |
| Flags | C: - N: * Z: * O: - |
Perform a bitwise AND of the source and destination operands and store the result in the destination. Each bit of the result is set only if the corresponding bit is set in both operands. The Zero flag is set if the result is zero and the Negative flag reflects the MSB of the result.
OR - Logical OR¶
| Field | Value |
|---|---|
| Type | Double Operand |
| Opcode | 0x11 |
| Encoding | 10001 ttttt ssssss |
| Operation | dst ← dst OR src |
| Example | OR R1, R3 |
| Flags | C: - N: * Z: * O: - |
Perform a bitwise OR of the source and destination operands and store the result in the destination. Each bit of the result is set if the corresponding bit is set in either or both operands. The Zero flag is set if the result is zero and the Negative flag reflects the MSB of the result.
XOR - Logical Exclusive OR¶
| Field | Value |
|---|---|
| Type | Double Operand |
| Opcode | 0x12 |
| Encoding | 10010 ttttt ssssss |
| Operation | dst ← dst XOR src |
| Example | XOR R1, R3 |
| Flags | C: - N: * Z: * O: - |
Perform a bitwise exclusive OR of the source and destination operands and store the result in the destination. Each bit of the result is set if the corresponding bits of the two operands differ. The Zero flag is set if the result is zero and the Negative flag reflects the MSB of the result.
ROT - Rotate¶
| Field | Value |
|---|---|
| Type | Double Operand |
| Opcode | 0x13 |
| Encoding | 10011 ttttt ssssss |
| Operation | if src ≥ 0: dst ← (dst << src) OR (dst >> (16 - src))if src < 0: dst ← (dst >> \|src\|) OR (dst << (16 - \|src\|)) |
| Example | ROT R1, R3 |
| Flags | C: * N: * Z: * O: - |
Rotate the destination operand by the number of bit positions given in the source operand. The direction of the rotation is determined by the sign bit of the source operand. Bits rotated out of one end of the word are reintroduced at the other end. The last bit rotated out is reflected in the Carry flag.
ROC - Rotate with Carry¶
| Field | Value |
|---|---|
| Type | Double Operand |
| Opcode | 0x14 |
| Encoding | 10100 ttttt ssssss |
| Operation | if src ≥ 0: {C, dst} ← ({C, dst} << src) [17-bit]if src < 0: {C, dst} ← ({C, dst} >> \|src\|) [17-bit] |
| Example | ROC R1, R3 |
| Flags | C: * N: * Z: * O: - |
Rotate the destination operand through the Carry flag by the number of bit positions given in the source operand. The direction of the rotation is determined by the sign bit of the source operand. The Carry flag participates in the rotation as an additional bit, extending the effective rotation width to 17 bits. The last bit rotated out is placed in the Carry flag.
SFT - Shift¶
| Field | Value |
|---|---|
| Type | Double Operand |
| Opcode | 0x15 |
| Encoding | 10101 ttttt ssssss |
| Operation | if src ≥ 0: dst ← dst << src, C ← last bit shifted outif src < 0: dst ← dst >> \|src\|, C ← last bit shifted out |
| Example | SFT R1, R3 |
| Flags | C: * N: * Z: * O: - |
Shift the destination operand by the number of bit positions given in the source operand. The direction of the shift is determined by the sign bit of the source operand. Bits shifted out are lost and vacated bit positions are filled with zeros. The last bit shifted out is placed in the Carry flag.
SB - Set Bit¶
| Field | Value |
|---|---|
| Type | Double Operand |
| Opcode | 0x18 |
| Encoding | 11000 ttttt ssssss |
| Operation | dst[src] ← 1 |
| Example | SB R1, R3 |
| Flags | C: - N: - Z: - O: - |
Set the bit in the destination operand at the bit position given by the source operand. All other bits of the destination are unaffected. No flags are modified.
RB - Reset Bit¶
| Field | Value |
|---|---|
| Type | Double Operand |
| Opcode | 0x19 |
| Encoding | 11001 ttttt ssssss |
| Operation | dst[src] ← 0 |
| Example | RB R1, R3 |
| Flags | C: - N: - Z: - O: - |
Clear the bit in the destination operand at the bit position given by the source operand. All other bits of the destination are unaffected. No flags are modified.
CMP / CMPB - Compare / Compare Bytes¶
| Field | Word | Byte |
|---|---|---|
| Type | Double Operand | Double Operand |
| Opcode | 0x1C |
0x1D |
| Encoding | 11100 ttttt ssssss |
11101 ttttt ssssss |
| Operation | dst - src(result discarded) |
dst.hi - src.hidst.lo - src.lo (results discarded) |
| Example | CMP R1, R3 |
CMPB R1, R3 |
| Flags | C: * N: * Z: * O: * | C: * N: * Z: * O: * CL: * NL: * ZL: * OL: * |
Subtract the source operand from the destination operand and update the condition flags. Neither operand is modified.
CMPB performs the comparison independently on the high and low bytes of the operands. The subtraction results are discarded. The Carry, Negative, Zero, and Overflow flags are updated from the byte comparisons by OR'ing the corresponding flags from each byte, matching the behaviour of the other byte arithmetic instructions.
BRN - Branch if Condition¶
| Field | Value |
|---|---|
| Type | Branch |
| Opcode | 0x1E |
| Encoding | 11110 CNZO b ssssss |
| Operation | if (all selected flags set) PC ← PC + src |
| Example | BRN C, (label)/ BRN CZ, (label) |
| Flags | C: - N: - Z: - O: - |
Branch to an address relative to the current PC if any of the selected condition flags are set in the PSW. Any combination of the C, N, Z, and O flags may be selected by setting the corresponding bits in the instruction word. The branch is taken if all of the specified flags are set. The source operand provides the signed offset used to compute the branch target address relative to the current PC.
The Byte Switch b is used to select between the C, N, Z and O and CL, NL, ZL and OL. C, N, Z and O are set by both word operations and by the high byte of byte operations, while CL, NL, ZL and OL are set by the low byte of byte operations only.
Any combination of flags in either flag group may be tested in a single instruction, allowing compound conditions to be evaluated without multiple branch instructions, similar to the branch mechanism of the PDP-11.
BNZD - Branch if Not Zero, Decrement¶
| Field | Value |
|---|---|
| Type | Double Operand |
| Opcode | 0x1F |
| Encoding | 11111 ttttt ssssss |
| Operation | if (dst ≠ 0) { PC ← PC + src; dst ← dst - 1 } |
| Example | BNZD R2, (label) |
| Flags | C: - N: * Z: * O: - |
Branch to an address relative to the current PC if the destination operand is not zero, then decrement the destination operand. This instruction is primarily intended for implementing counted loops, where the destination acts as a loop counter that is decremented on each iteration. When the counter reaches zero the branch is not taken and execution falls through to the following instruction. The source operand provides the signed relative branch offset.
Single Operand Instructions¶
Opcode Format: 00000 oooo 0 ssssss
NEG, NEGB - Negate (Two's Complement)¶
| Field | NEG | NEGB |
|---|---|---|
| Type | Single Operand | Single Operand |
| Opcode | 0x02 |
0x03 |
| Encoding | 00000 0010 0 ssssss |
00000 0011 0 ssssss |
| Operation | src ← \~src + 1 |
src.high ← \~src.high + 1src.low ←\~src.low + 1 |
| Example | NEG R1 |
NEGB R2 |
| Flags | C: - N: * Z: * O: - | C: - N: * Z: * O: - CL: - NL: * ZL: * OL: - |
Replace the operand with its two's complement. Every bit that was 0 becomes 1 and every bit that was 1 becomes 0, followed by a 1 being added. In NEGB, the operation is applied independently to the high and low bytes of the word.
NOT - Logical NOT¶
| Field | Value |
|---|---|
| Type | Single Operand |
| Opcode | 0x04 |
| Encoding | 00000 0100 0 ssssss |
| Operation | src ← ~src |
| Example | NOT R1 |
| Flags | C: - N: * Z: * O: - |
Replace the operand with its logical NOT. Each bit of the operand is inverted.
TRAP - Software Interrupt¶
| Field | Value |
|---|---|
| Type | Single Operand |
| Opcode | 0x09 |
| Encoding | 00000 1001 0 ssssss |
| Operation | Trigger software interrupt src |
| Example | TRAP 3 |
| Flags | C: - N: - Z: - O: - |
Trigger the software interrupt (Trap) identified by the source operand. Registers R0 through R6 are automatically saved to the stack before the trap handler is entered, and execution continues at the corresponding Trap vector. The RETI instruction must be used to return from the handler and restore the saved register contents. Up to 16 software interrupts are available, identified by values 0 through 15.
JMP - Jump¶
| Field | Value |
|---|---|
| Type | Single Operand |
| Opcode | 0x0B |
| Encoding | 00000 1011 0 ssssss |
| Operation | PC ← src |
| Example | JMP R4 / JMP @(offset) |
| Flags | C: - N: - Z: - O: - |
Jump unconditionally to the address given by the source operand. The following addressing modes may be used to specify the jump target: Register Mode (target address in register), Pointer Mode (target address in memory cell pointed to by register), Immediate Mode (jump to a fixed address), Immediate Address Mode (jump to address stored at a known memory location), Relative Mode (jump relative to PC), and Index Mode (for jump tables with a known base address). Autoincrement and Autodecrement addressing modes are not supported.
JMS - Jump to Subroutine¶
| Field | Value |
|---|---|
| Type | Single Operand |
| Opcode | 0x0C |
| Encoding | 00000 1100 0 ssssss |
| Operation | (--SP) ← PC; PC ← src |
| Example | JMS R4 / JMS @(offset) |
| Flags | C: - N: - Z: - O: - |
Jump to a subroutine at the address given by the source operand. The return address (the address of the instruction following JMS) is pushed onto the stack before the jump is taken. The same addressing modes available to JMP may be used to specify the subroutine entry point. The RET instruction is used to return from the subroutine.
No Operand Instructions¶
Opcode Format: 0000 0000 0000 oooo
NOP - No Operation¶
| Field | Value |
|---|---|
| Type | No Operand |
| Opcode | 0x00 |
| Encoding | 0000 0000 0000 0000 |
| Operation | - |
| Example | NOP |
| Flags | C: - N: - Z: - O: - |
Perform no operation. The processor advances the program counter to the next instruction. No registers, memory locations, or flags are affected. May be used for pipeline padding, timing delays, or as a placeholder during development.
WAIT - Wait for Interrupt¶
| Field | Value |
|---|---|
| Type | No Operand |
| Opcode | 0x02 |
| Encoding | 0000 0000 0000 0010 |
| Operation | Suspend until interrupt |
| Example | WAIT |
| Flags | C: - N: - Z: - O: - |
Suspend processor execution until a hardware interrupt or enabled software trap occurs. The processor enters a low-activity state and does not advance the program counter. Upon receipt of an interrupt, normal interrupt entry and handling resumes from the point at which the processor was suspended.
DMA - DMA Handover¶
| Field | Value |
|---|---|
| Type | No Operand |
| Opcode | 0x03 |
| Encoding | 0000 0000 0000 0011 |
| Operation | Yield bus to DMA controller |
| Example | DMA |
| Flags | C: - N: - Z: - O: - |
Yield control of the system bus to the DMA controller, allowing a direct memory access transfer to proceed. The processor suspends bus activity until the DMA transfer is complete, at which point normal execution resumes with the instruction following DMA.
RET - Return from Subroutine¶
| Field | Value |
|---|---|
| Type | No Operand |
| Opcode | 0x08 |
| Encoding | 0000 0000 0000 1000 |
| Operation | PC ← (SP++) |
| Example | RET |
| Flags | C: - N: - Z: - O: - |
Return from a subroutine by popping the return address from the stack into the program counter. Execution continues at the instruction following the JMS that entered the subroutine.
RETI - Return from Interrupt¶
| Field | Value |
|---|---|
| Type | No Operand |
| Opcode | 0x09 |
| Encoding | 0000 0000 0000 1001 |
| Operation | R0–R6, PC ← (SP) |
| Example | RETI |
| Flags | C: * N: * Z: * O: * |
Return from a hardware interrupt or software trap handler. The saved register contents R0 through R6 are automatically restored from the stack, and execution resumes at the address that was interrupted. All condition flags are restored from the stack to the values they held at the point the interrupt was taken.
Modules¶
Memory Management Unit¶
This part of this post has been streamlined with the help of Claude from the initial design I did by myself and I can prove it. :p
Overview¶
While the DWMC-16 is a 16 bit machine with a external 22 bit wide address bus to access up to 4 MiWord of memory, it can only access 64 kiWord directly through the internal 16 bit wide address bus.
This necessitates the use of a Memory Management Unit to manage the external address bus, controlling segments of 1 kiWord size. Overall this means 4096 segments of memory over the entire memory space and means that the lowest 10 bits of the address bus are directly connected to the address bus, while the upper six bit are translated to the extended 12 bit address bits 10 to 21.
Of all segments, segments 0 to 7, or in other words the lowest 8 kiWord of memory are fixed in the memory space, as they are home to the Monitor and BIOS and provide useful subroutines to other programs, as well as the kernel space for things like multi-tasking. This means that there are 56 segments of memory available for a process.
At the moment.
The MMU is designed around a pair of fast modern 8 bit SRAM ICs, likely 10 to 15 ns, and likely a size of 256 kibit, like the AS7C256A-10. In addition to this memory are a number of support 74LS series TTL and GAL ICs to provide quick logic for checking and fault logic and the like.
Segment Map¶
The Segment Map is a lookup table of 64 entries where each of the segments of the virtual memory available to the process if mapped into physical memory. However, segments 0 to 7 of the map are unused as those segments are hardwired to be bypassed and are only in the Segment Map to simplify the logic of the circuit.
Segment Map Entry Bit Fields
| Bits | Field | Description |
|---|---|---|
[15:4] |
Physical Segment | 12 bit physical segment address |
3 |
Present | Segment is mapped by the MMU (0 = fault) |
2 |
Writable | Write protection (0 = read only) |
1 |
User | Accessible in user mode (0 = kernel only) |
0 |
Dirty | Written since last cleared (Kernel cannot write this bit) |
Bit 0 is special, as it can only be read by the kernel, but not written. Instead it initialised as 0 by the MMU and set to 1 the first time data has been written to the memory segment.
This later allows the kernel to manage memory faster and allows it to swap out memory block from, e.g. disk access, faster, without having to perform a write back operation for changed memory segments.
To write to the Segment Map, the 16 bit value of the generated physical address and the Write Protection and User Mode for the segment are written into the the MMU_ENTRY register. This is followed by writing the segment address and set the MMU_WR bit in the MMU_CTL register.
Following the write to MMU_CTL, the content of MMU_ENTRY is written to the segment address in the currently selected context, with the Present bit automatically set and the Dirty bit automatically cleared.
Later on the Segment SRAM could be extended by a third SRAM IC to extend the capabilities of the MMU.
Context Support¶
Thanks to the size of modern SRAM, its is easily possible to provide the MMU with a fast context switching capabilities, so that multi-tasking is easy and quick and the MMU just needs to switch to a different context for a new process, rather then having to rewrite the memory translation space with new data upon the switch to another, existing context.
Since the MMU itself only uses 6 bits of the SRAM ICs address bus, this allows the MMU to provide more memory contexts than might be needed. For simplicity reasons, the MMU only provides 128 contexts, theoretically limiting the number of parallel processes.
SRAM Address Bits (AS7C256A specific)
| Bit | Used By |
|---|---|
[0:5] |
Segment Address |
[6:12] |
Context Address |
[13:14] |
Unused |
Protection and Fault Generation¶
The Protection and Fault Generation is done by a GAL that quickly compares the SRAM flags between the current control signals coming from the CPU, based on the bus cycle and the mode the CPU is in.
- Kernel/User Mode: A signal that is set by the CPU when entering a system interrupt or when the CPU enters Kernel mode, following a system call
- Write Protection: The GAL detect the Memory Write Enable signal from the CPU and compares it to the Writeable bit in the Segment Map and intercepts it if necessary.
- Presence Check: The GAL checks if the segment has been given to the process in the first place and if needed faults immediately.
If any of these faults is triggered, the GAL prevents memory access and immediately triggers the Trap 0 Interrupt, protecting the memory segment in question.
The Trap 0 interrupt then reads the MMU Fault register for the actual fault and acts accordingly.
In addition, the MMU Control Register allows the kernel to set the MMU into User Mode, which prevents any non-kernel process from accessing the control registers of the MMU and creates a fault.
This has the possibility of extension later on if needed.
Kernel Bypass¶
Since the lowest 8 segments/8 kiWord of memory are not remapped like the remaining 56 segments, any addresses falling into this area of memory by pass the MMU.
However, while the MMU is in User Mode, most write access to this area of memory is handled like a write protected sector, with the exception of certain parts of the MMIO space (from 0x0080 to 0x01FF), as to allow a user process to access external hardware.
Certain Interrupt Vectors (Interrupt 7 to Interrupt 15) can be modified by a User process to reroute interrupt handlers, by allowing the user to write new addresses to the Interrupt Vector table, which are then handled during User mode operation.
MMU Register Map¶
The MMU Register Map lives in the systems MMIO space at address 0x0020.
| Address | Register Name |
Description |
|---|---|---|
0x0020 |
MMU_CTX |
Active Context/Process ID (0 to 127/7 bits) |
0x0021 |
MMU_CTL |
MMU Control Register |
0x0022 |
MMU_ENTRY |
Read/Write Register for the MMU segment selected in MMU_CTL, see Segment Map Entry bit fields |
0x0023 |
MMU_FAULT_ADDR |
Logical Address that caused the last fault |
0x0024 |
MMU_FAULT_SEG |
Segment and Context that caused the last fault |
0x0025 |
MMU_FAULT_STATUS |
Fault Status Byte for error handling |
MMU_CTL Bit Fields
| Bits | Name | Description |
|---|---|---|
0 |
MMU_EN |
1 = MMU enabled, 0 = MMU disabled/passthrough |
1 |
MMU_WR |
1 = MMU write access |
2 |
FAULT_INT_EN |
1 = enables generation of interrupts at faults |
3 |
FAULT_HALT |
1 = halts the CPU at a fault |
4 |
USER_MODE |
1 = User Mode, 0 = Kernel Mode, automatically set to 0 by a system interrupt |
[5:9] |
Unused | |
[15:10] |
SEG_ADDR |
6 bit segment Address, used during MMU writes |
MMU_FAULT_STATUS Bit Fields
| Bits | Name | Description |
|---|---|---|
[3:0] |
FAULT_TYPE |
0000 = No Fault 0001 = Presence Check Failed 0010 = Write Protected Sector 0011 = Unauthorised Access to Kernel memory (User field in Sector Map is 0) 0100 = Unauthorised MMU Access |
[5:4] |
FAULT_ACCESS |
00 = Read 01 = Write 10 = Execute (Instruction Fetch) |
[13:6] |
Unused | |
14 |
FAULT_LATCH |
1 = fault is latched and unacknowledged |
15 |
FAULT_REPEAT |
1 = another fault happened while latch was held |
Mass Storage Module¶
The Mass Storage Module is a module that does the minimum necessary conversion of signals and additional logic to directly connect an IDE compatible hard drive to the DWMC-16 bus.
The hard drive in question will likely be a simple CF card, as they are ubiquitous and easy to purchase.
The use of CSEL lines on the modules PCB itself should allow for the use of two CF Cards, say, one for fixed storage and one for removable storage.
The disks themselves should be using FAT16 as data format, to simplify working with the CF cards on an external machine.
The CF cards can only be addressed by the kernel and they are placed into the MMIO address space after address 0x0030.
BlinkenLights Panel¶
The BlinkenLights Panel is meant to be a basic Input/Output panel for the DWMC-16, with a layout similar to those found in 70 Minicomputers, such as the PDP-11.
The panel is separated into two parts.
The BlinkenLights¶
The BlinkenLights are the base display for the DWMC-16, using 24 LEDs for the Address Bus and 16 LEDs for the Data Bus, with a set of 24 Switches for input. In addition, 10 7-segment displays show the Address and Data on the bus in a more easily readable format.
Several momentary switches allow for entering data through the Blinkenlights and control the status of the system, e.g. halt the system, start the system and single step the system.
The IO¶
The IO is a set of three convenience systems implemented on the front panel and part of the absolute base system of the DWMC-16.
The IO integrates:
- an Intel 8251A USART, for basic monitor/kernel level serial communication with the DWMC-16
- a HD44780 Alphanumeric Display, for basic and easily human readable interaction with the system
- an Intel 8279 Keyboard Controller with a 20 key matrix, for basic input capabilities for the DWMC-16
- Latches for the Data LEDs and bits 0 to 15 of the input switches, which can be activated in a flag register