263 lines
13 KiB
Markdown
263 lines
13 KiB
Markdown
# 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]`<br>`self.ac = val & 0xFFF`<br>`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`<br>`if self.memory[target_addr] == 0: PC = (PC + 1) & 0xFFF` |
|
||
| `3` | `0o3` | **DCA** | Deposit and Clear AC | `self.memory[target_addr] = self.ac`<br>`self.ac = 0` |
|
||
| `4` | `0o4` | **JMS** | Jump to Subroutine | `self.memory[target_addr] = self.pc`<br>`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.
|