# DEC PDP-8/L Emulation Core Instruction Set Architecture
This document provides a detailed reference of the PDP-8/L instruction set implemented in the ILDEC emulation core.
---
## 1. Architectural Overview & State
The emulator models a simplified 12-bit DEC PDP-8/L processor. CPU registers and state are defined as follows:
* **`memory`**: An array of 4,096 12-bit words (a single 4K memory field).
* **`pc` (Program Counter)**: 12-bit register holding the address offset of the next instruction.
* **`ac` (Accumulator)**: 12-bit register used for arithmetic, logic, and I/O.
* **`link` (Link Register)**: 1-bit register functioning as a carry/overflow indicator, logically positioned to the left of the Accumulator for shifts.
* **sr` (Switch Register)**: 12-bit register representing front panel switches, queried via operate instructions.
* **Program Interrupt Registers**:
* `ie` (Interrupt Enable Flag): 1-bit register controlling the interrupt system.
* `ion_delay` (Interrupt On Delay): Counter managing the 1-instruction latency for enabling interrupts.
* **Peripheral Buffers & Flags**:
* `kbb` (Keyboard Buffer) & `kbf` (Keyboard Flag)
* `chb` (Character Buffer) & `chf` (Character Flag)
---
## 2. Memory Addressing System
Memory is organized into **32 pages of 128 words each** (4,096 words total).
Memory Reference Instructions (MRIs) contain a 9-bit address specification block. The bits are mapped as follows:
```
3 4 5 6 7 8 9 10 11
+---+---+---+---+---+---+---+---+---+
| I | Z | 7-bit Page Offset |
+---+---+---+---+---+---+---+---+---+
```
* **Bit 3 (`I` - Indirect Address Bit)**:
* `0`: Direct addressing.
* `1`: Indirect addressing.
* **Bit 4 (`Z` - Sector/Page Select Bit)**:
* `0`: Page 0 (addresses `0o0000` to `0o0177`).
* `1`: Current Page (the page containing the instruction being executed).
* **Bits 5–11 (Page Offset)**: 7-bit relative address (`0` to `127`).
### Target Address Resolution Flow
1. **Base Address Offset Calculation**:
* If `Z = 1`: The 5 high-bits of the current `PC` are combined with the 7-bit offset:
$$\text{Base Target} = (\text{PC} \ \& \ \text{0xF80}) \ | \ \text{Offset}$$
* If `Z = 0`: The base target is within Page 0:
$$\text{Base Target} = \text{Offset}$$
2. **Effective Address Calculation**:
* **Direct Addressing (`I = 0`)**:
$$\text{Effective Address} = \text{Base Target}$$
* **Indirect Addressing (`I = 1`)**:
* The pointer address is the base target:
$$\text{Pointer Address} = \text{Base Target}$$
* **Auto-Indexing**: If the base target is in the range `8` to `15` (`0o0010` to `0o0017` in octal), the pointer word is incremented by 1 (modulo 4096) and written back to memory:
$$\text{Memory}[\text{Pointer Address}] = (\text{Memory}[\text{Pointer Address}] + 1) \ \& \ \text{0xFFF}$$
* The 12-bit target pointer offset is read from the pointer address:
$$\text{Effective Address} = \text{Memory}[\text{Pointer Address}]$$
---
## 3. Instruction Set Details
Every instruction is 12 bits wide. The top 3 bits (bits 0–2) specify the opcode.
```
0 1 2 3 4 5 6 7 8 9 10 11
+---+---+---+---+---+---+---+---+---+---+---+---+
| Opcode | Rest of Instruction |
+---+---+---+---+---+---+---+---+---+---+---+---+
```
---
### 3.1. Memory Reference Instructions (Opcodes 0–5)
These instructions perform operations involving memory locations.
| Opcode | Octal | Mnemonic | Description | Py-8-S Emulation Core Logic |
| :---: | :---: | :--- | :--- | :--- |
| `0` | `0o0` | **AND** | Logical AND | `self.ac &= self.memory[target_addr]` |
| `1` | `0o1` | **TAD** | Two's Complement Add | `val = self.ac + self.memory[target_addr]`
`self.ac = val & 0xFFF`
`self.link ^= (val >> 12)` *(Carry toggles Link)* |
| `2` | `0o2` | **ISZ** | Increment and Skip if Zero | `self.memory[target_addr] = (self.memory[target_addr] + 1) & 0xFFF`
`if self.memory[target_addr] == 0: PC = (PC + 1) & 0xFFF` |
| `3` | `0o3` | **DCA** | Deposit and Clear AC | `self.memory[target_addr] = self.ac`
`self.ac = 0` |
| `4` | `0o4` | **JMS** | Jump to Subroutine | `self.memory[target_addr] = self.pc`
`self.pc = (target_addr + 1) & 0xFFF` |
| `5` | `0o5` | **JMP** | Jump | `self.pc = target_addr & 0xFFF` |
---
### 3.2. Input/Output Transfer (IOT) Instructions (Opcode 6)
IOT instructions control and transfer data to/from peripheral devices. The instruction format is:
* **Device Code (`dev`)**: Bits 3–8 (6 bits, range 0–63).
* **Sub-operation (`op`)**: Bits 9–11 (3 bits, range 0–7).
#### Device 0: Interrupt Control (Program Interrupt System)
Manages the CPU's Program Interrupt (PI) system.
* **`6001` (ION - Interrupt Turn On)**: Enables the program interrupt system. The interrupt system is enabled after the execution of the instruction following ION.
* **`6002` (IOF - Interrupt Turn Off)**: Disables the program interrupt system immediately.
* **`6003` (SRQ - Skip on Interrupt Request)**: Skips the next instruction if a device interrupt request is pending:
`if self.kbf or self.chf: PC = PC + 1`
* **`6004` (GTF - Get Flags)**: Loads various processor flags into the Accumulator:
* AC Bit 0: Link Register (`self.link`)
* AC Bit 2: Interrupt Request (`self.kbf or self.chf`)
* AC Bit 4: Interrupt Enable Flip-Flop (`self.ie`)
* All other bits are cleared to `0`.
* **`6005` (RTF - Restore Flags)**: Restores flags from the Accumulator:
* Link is restored from AC Bit 0: `self.link = (self.ac >> 11) & 1`
* Enables interrupts with a one-instruction delay (equivalent to `ION`).
* **`6007` (CAF - Clear All Flags)**: Resets the processor and I/O status:
* Clears Accumulator and Link: `self.ac = self.link = 0`
* Clears I/O device buffers/flags: `self.kbf = self.kbb = self.chf = self.chb = 0`
* Disables the program interrupt system: `self.ie = self.ion_delay = 0`
#### Device 3: Keyboard / Reader
Interfaces with the standard keyboard/character input.
* **Micro-programmed Keyboard Operations (bits 9, 10, 11)**:
* **Bit 11** (`op & 1`): Skip on Keyboard Flag.
* **Bit 10** (`(op >> 1) & 1`): Clear Accumulator and Keyboard Flag.
* **Bit 9** (`op >> 2`): OR Keyboard Buffer into the Accumulator.
Combined, these bits form the standard Keyboard instructions:
* **`6031` (KSF - Keyboard Skip on Flag)**: `if self.kbf: PC = PC + 1`
* **`6032` (KCC - Keyboard Clear Flag)**: `self.ac = self.kbf = 0`
* **`6034` (KRS - Keyboard Read Static)**: `self.ac |= self.kbb`
* **`6036` (KRB - Keyboard Read Buffer)**: `self.ac = self.kbf = 0` followed by `self.ac |= self.kbb`
#### Device 4: Teleprinter / Punch
Interfaces with the standard teleprinter/character output.
* **`6040` (Custom IOT: Force Teleprinter Flag)**:
Forces the teleprinter flag to high (`chf = 1`).
* **Micro-programmed Teleprinter Operations (bits 9, 10, 11)**:
* **Bit 11** (`op & 1`): Skip on Teleprinter Flag.
* **Bit 10** (`(op >> 1) & 1`): Clear Teleprinter Flag.
* **Bit 9** (`op >> 2`): Load Accumulator into Character Buffer, trigger write callback, and set Teleprinter Flag.
Combined, these bits form the standard Teleprinter instructions:
* **`6041` (TSF - Teleprinter Skip on Flag)**: `if self.chf: PC = PC + 1`
* **`6042` (TCF - Teleprinter Clear Flag)**: `self.chf = 0`
* **`6044` (TPC - Teleprinter Print Character)**: `self.chb = self.ac; self.chf = 1; self.outc(self.ac)`
* **`6046` (TLS - Teleprinter Load Sequence)**: `self.chf = 0; self.chb = self.ac; self.chf = 1; self.outc(self.ac)`
---
### 3.3. Operate Instructions (Opcode 7)
Operate (OPR) instructions are register manipulation commands. They do not reference memory addresses. The lower 9 bits function as micro-programmed instruction controls.
> [!NOTE]
> If the lower 9 bits are `0` (`7000`) or `0o401` (`7401`), the instruction is treated as a `NOP` (No Operation) and returns immediately.
#### Sub-group Determination
* **Bit 3** (MSB of the instruction's lower 9 bits):
* If `0`: **Group 1** (Register manipulation, complements, and shifts).
* If `1`: **Group 2** (Status testing, skips, and halt).
* *Note: Group 3 (EAE/MQ register operations) is not supported.*
---
#### Group 1 Operate Instructions (Bit 3 = 0)
Used for bitwise operations, clearing registers, incrementing, and rotations.
```
3 4 5 6 7 8 9 10 11
+---+---+---+---+---+---+---+---+---+
| 0 |CLA|CLL|CMA|CML|RAR|RAL|BSW|IAC|
+---+---+---+---+---+---+---+---+---+
```
##### Bit Mapping & Function
* **Bit 4 (`CLA`)**: Clear Accumulator.
* **Bit 5 (`CLL` / `b5`)**: Clear Link.
* **Bit 6 (`CMA` / `b6`)**: Complement Accumulator.
* **Bit 7 (`CML` / `b7`)**: Complement Link.
* **Bit 8 (`RAR` / `b8`)**: Rotate Accumulator and Link Right.
* **Bit 9 (`RAL` / `b9`)**: Rotate Accumulator and Link Left.
* **Bit 10 (`BSW` / `b10`)**: Byte Swap / Rotate Command Modifier.
* If `RAR` or `RAL` is active: Specifies double rotation (rotate 2 positions).
* If neither `RAR` nor `RAL` is active: Swaps the high and low 6-bit halves of `AC`:
`self.ac = ((self.ac << 6) | (self.ac >> 6)) & 0xFFF`
* **Bit 11 (`IAC` / `b11`)**: Increment Accumulator.
##### Execution Order
Group 1 micro-operations are executed sequentially in the following order:
1. **Clear**:
* If `CLA` is `1`: `self.ac = 0`
* If `CLL` is `1`: `self.link = 0`
2. **Complement**:
* If `CMA` is `1`: `self.ac ^= 0xFFF`
* If `CML` is `1`: `self.link ^= 1`
3. **Increment**:
* If `IAC` is `1`:
`self.ac, self.link = (self.ac + 1) & 0xFFF, self.link ^ ((self.ac + 1) >> 12)` *(Carry toggles Link)*
4. **Rotation / Swap**:
* If `RAR` is `1`: Circular right rotate `b10 + 1` times of the 13-bit combined register `[Link, AC]`.
* If `RAL` is `1`: Circular left rotate `b10 + 1` times of the 13-bit combined register `[Link, AC]`.
* If `BSW` is `1` and neither `RAR` nor `RAL` is active: Swaps the high and low 6-bit halves of `AC`:
`self.ac = ((self.ac << 6) | (self.ac >> 6)) & 0xFFF`
---
#### Group 2 Operate Instructions (Bit 3 = 1, Bit 11 = 0)
Used for conditional skips, input register integration, and program termination.
```
3 4 5 6 7 8 9 10 11
+---+---+---+---+---+---+---+---+---+
| 1 |CLA|SMA|SZA|SNL|REV|OSR|HLT| 0 |
+---+---+---+---+---+---+---+---+---+
```
##### Bit Mapping & Function
* **Bit 4 (`CLA`)**: Clear Accumulator.
* **Bit 5 (`SMA` / `b5`)**: Skip on Minus Accumulator (`AC & 0x800` is set / `AC > 0x7FF`).
* **Bit 6 (`SZA` / `b6`)**: Skip on Zero Accumulator (`AC == 0`).
* **Bit 7 (`SNL` / `b7`)**: Skip on Non-Zero Link (`Link == 1`).
* **Bit 8 (`REV` / `b8`)**: Reverse skip condition sense (OR group if `0`, AND group if `1`).
* **Bit 9 (`OSR` / `b9`)**: OR AC with Switch Register.
* **Bit 10 (`HLT` / `b10`)**: Halt processor execution.
##### Condition Evaluation & Skip Logic
1. **Or Group (`REV = 0`)**:
Skips the next instruction if *any* of the enabled conditions are true:
$$\text{Skip} \iff (\text{SMA} \land \text{AC} < 0) \lor (\text{SZA} \land \text{AC} == 0) \lor (\text{SNL} \land \text{Link} \ne 0)$$
2. **And Group (`REV = 1`)**:
Skips the next instruction if *none* of the enabled conditions are true. This evaluates to:
$$\text{Skip} \iff (\neg\text{SMA} \lor \text{AC} \ge 0) \land (\neg\text{SZA} \lor \text{AC} \ne 0) \land (\neg\text{SNL} \lor \text{Link} == 0)$$
##### Execution Order
Group 2 micro-operations execute sequentially in the following order:
1. **Skip Condition**: Evaluate conditions and increment `PC` if met:
`if any_cond != b8: self.pc = (self.pc + 1) & 0xFFF`
2. **Clear**:
* If `CLA` is `1`: `self.ac = 0`
3. **OR Switch Register**:
* If `OSR` is `1`: `self.ac |= self.sr`
4. **Halt**:
* If `HLT` is `1`: `self.halted = True`
---
## 4. Emulation & Support Control Mechanisms
### 4.1. Loader format
Octal tape images are loaded with the `load_oct` function. The loader parses files containing lines of `addr: value` configurations, storing values directly in standard memory locations. In this simplified core, all loaded addresses are masked with `0xFFF` to restrict them to the 4K word field limit.
### 4.2. Program Interrupt Mechanism
When the program interrupt system is enabled (`self.ie == 1`) and a device interrupt request is active (either `self.kbf == 1` or `self.chf == 1`), the CPU executes the interrupt sequence at the start of an instruction step:
1. Interrupts are disabled: `self.ie = 0`
2. The current Program Counter (`self.pc`) is saved to memory location `0`.
3. The Program Counter is set to `1` (`self.pc = 1`), branching control to the interrupt handler.