# DSA Instruction Set Architecture Specification

**Status:** Draft, grounded directly in `common::isa::instructions` and `common::isa::instructions::encode`.
**Audience:** Emulator/interpreter authors, kernel writers, toolchain developers.

---

## 1. Architecture Overview

| Feature                | Detail                                                                                                                                                                                                                                                                |
|------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| Word size              | 32 bits                                                                                                                                                                                                                                                               |
| Instruction width      | 32 bits, fixed (one word per instruction)                                                                                                                                                                                                                             |
| Instruction endianness | Little-endian in memory                                                                                                                                                                                                                                               |
| Addressing mode        | Base register + signed 16-bit offset for all loads/stores/jumps                                                                                                                                                                                                       |
| Stack direction        | Downward — `push` decrements the stack pointer, `stw`s, then continues; `pop` reads then increments                                                                                                                                                                   |
| Program counter        | Updated by jump/call/ret instructions; not directly writable by ordinary instructions                                                                                                                                                                                 |
| Condition handling     | **No flags register.** Comparisons (`ieq`/`ine`/`ilt`/`ile`/`igt`/`ige`) write a 1-or-0 result into an explicit destination register, which conditional jumps (`jez`/`jnz`) then test directly. There is no implicit zero/carry flag propagated between instructions. |

> **Correction to earlier drafts:** some prior notes describe a `sts` status/flags register and carry-flag-driven branches (`jic`/`jnc` "jump if carry"). The current implementation has no flags register at all — `jic`/`jnc` exist as opcodes but their semantics are whatever the interpreter defines for them (not yet confirmed in this document); all conditional control flow actually exercised by the assembler/emulator today goes through `jez`/`jnz` testing an explicit register produced by a comparison instruction.

---

## 2. Register File

Confirmed from the `Register` enum used throughout `Instruction`'s builders and the disassembler:

| Register    | Role                                                                                                                                                                                     |
|-------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `Rg0`–`RgF` | 16 general-purpose registers. Caller-saved (not preserved across calls unless explicitly saved).                                                                                         |
| `Acc`       | Scratch register used by pseudo-instruction expansions (address computation for label-based load/store/jump). Volatile — never preserved across any instruction that touches it.         |
| `Spr`       | Stack pointer.                                                                                                                                                                           |
| `Bpr`       | Base/frame pointer.                                                                                                                                                                      |
| `Ret`       | Return-address register, written internally by `call`/read by `ret`.                                                                                                                     |
| `Zero`      | Hardwired to 0. Writes are discarded.                                                                                                                                                    |
| `Null`      | Sentinel used internally by the assembler/disassembler for "unused/unencodable" register fields. Not a real writable register — referencing it in assembly should be treated as invalid. |

Registers not independently confirmed against source in this conversation but described in earlier architecture notes (present pending confirmation against the actual `Register` enum): `Idr` (interrupt descriptor table base), `Mmr` (memory-map register), `Sts` (status), `Cir` (current instruction register), `Pcx` (program counter). Treat these as provisional until checked against `common::isa::register`.

---

## 3. Instruction Encoding

### R-type
```
| 31-26  | 25-21 | 20-16 | 15-11 | 10-6 | 5-0   |
| opcode | src1  | dest  | src2  | misc | shamt |
```
Built by `Instruction::build_r(opcode, src1, dest, src2, misc, shamt)`:
```
(opcode << 26) | (src1 << 21) | (dest << 16) | (src2 << 11) | (misc << 6) | (shamt & 0x3F)
```

### I-type
```
| 31-26  | 25-21 | 20-16 | 15-0  |
| opcode | src   | dest  | imm16 |
```
Built by `Instruction::build_i(opcode, src, dest, imm)`:
```
(opcode << 26) | (src << 21) | (dest << 16) | imm
```

### No-arg
```
| 31-26  | 25-0        |
| opcode | must be zero |
```
Built by `Instruction::build_noarg(opcode)`: `opcode << 26`.

Decoding accessors (confirmed from `decode.rs`):
- `opcode()` = bits 31–26
- `src1_checked()` = bits 25–21 → `Register`
- `dest_checked()` = bits 20–16 → `Register`
- `src2_checked()` = bits 15–11 → `Register`
- `misc_checked()` = bits 10–6 → `Register`
- `shamt()` = bits 5–0 → `u8`
- `imm16()` = bits 15–0 → `u16`

Register fields decode via `Register::from_u8`, falling back to `Register::Null` on an unrecognized 5-bit value (never a hard decode failure).

---

## 4. Opcode Table

Authoritative values from `common::isa::instructions::Opcode`:

| Value | Mnemonic | Type  | Category       |
| ----- | -------- | ----- | -------------- |
| 0x00  | NOP      | noarg | Control        |
| 0x01  | MOV      | R     | Move           |
| 0x02  | CMOV     | R     | Move           |
| 0x03  | LDB      | I     | Load           |
| 0x04  | LDBS     | I     | Load (signed)  |
| 0x05  | LDH      | I     | Load           |
| 0x06  | LDHS     | I     | Load (signed)  |
| 0x07  | LDW      | I     | Load           |
| 0x08  | STB      | I     | Store          |
| 0x09  | STH      | I     | Store          |
| 0x0A  | STW      | I     | Store          |
| 0x0B  | LLI      | I     | Immediate load |
| 0x0C  | LUI      | I     | Immediate load |
| 0x0D  | IEQ      | R     | Comparison     |
| 0x0E  | INE      | R     | Comparison     |
| 0x0F  | ILT      | R     | Comparison     |
| 0x10  | ILE      | R     | Comparison     |
| 0x11  | IGT      | R     | Comparison     |
| 0x12  | IGE      | R     | Comparison     |
| 0x13  | JMP      | I     | Jump           |
| 0x14  | JEZ      | I     | Jump           |
| 0x15  | JNZ      | I     | Jump           |
| 0x16  | JIC      | I     | Jump           |
| 0x17  | JNC      | I     | Jump           |
| 0x18  | AND      | R     | Bitwise        |
| 0x19  | NAND     | R     | Bitwise        |
| 0x1A  | OR       | R     | Bitwise        |
| 0x1B  | NOR      | R     | Bitwise        |
| 0x1C  | XOR      | R     | Bitwise        |
| 0x1D  | XNOR     | R     | Bitwise        |
| 0x1E  | NOT      | R     | Bitwise        |
| 0x1F  | ADD      | R     | Arithmetic     |
| 0x20  | SUB      | R     | Arithmetic     |
| 0x21  | SHL      | R     | Shift          |
| 0x22  | SHR      | R     | Shift          |
| 0x23  | ADDI     | I     | Arithmetic     |
| 0x24  | SUBI     | I     | Arithmetic     |
| 0x25  | PUSH     | R     | Stack          |
| 0x26  | POP      | R     | Stack          |
| 0x27  | CALL     | I     | Control flow   |
| 0x28  | RET      | noarg | Control flow   |
| 0x29  | INT      | I     | System         |
| 0x2A  | IRET     | noarg | System         |
| 0x2B  | ACS      | R     | Atomic         |
| 0x2C  | HLT      | noarg | System         |

`Opcode::from_u8` accepts values `0..=0x2B` (i.e. `<= Hlt as u8`); anything higher decodes as an invalid instruction. There is no `ACS`/atomic-compare-swap opcode in the current enum — if one is planned, it is not yet implemented and should not be assumed present by tooling.

---

## 5. Instruction Semantics

All arithmetic wraps modulo 2³² unless noted.

### 5.1 Move
| Mnemonic               | Fields                           | Effect                                     |
|------------------------|----------------------------------|--------------------------------------------|
| `mov(src, dest)`       | R: src1=src, dest=dest           | `dest ← src`                               |
| `cmov(src, dest, cmp)` | R: src1=src, dest=dest, src2=cmp | If `cmp ≠ 0`, `dest ← src`; else unchanged |

### 5.2 Load
| Mnemonic                  | Fields | Effect                                                                  |
|---------------------------|--------|-------------------------------------------------------------------------|
| `ldb(src, dest, offset)`  | I      | `dest ← zero_extend(byte at [src + sext(offset)])`                      |
| `ldbs(src, dest, offset)` | I      | Same, sign-extended                                                     |
| `ldh(src, dest, offset)`  | I      | `dest ← zero_extend(halfword at [src + sext(offset)])` (2-byte aligned) |
| `ldhs(src, dest, offset)` | I      | Same, sign-extended                                                     |
| `ldw(src, dest, offset)`  | I      | `dest ← word at [src + sext(offset)]` (4-byte aligned)                  |

### 5.3 Store
| Mnemonic                 | Fields | Effect                                                  |
|--------------------------|--------|---------------------------------------------------------|
| `stb(src, dest, offset)` | I      | `[dest + sext(offset)] ← src & 0xFF`                    |
| `sth(src, dest, offset)` | I      | `[dest + sext(offset)] ← src & 0xFFFF` (2-byte aligned) |
| `stw(src, dest, offset)` | I      | `[dest + sext(offset)] ← src` (4-byte aligned)          |

Note the field naming convention: for stores, the instruction's `dest` field holds the **base address** register, and `src` holds the **value** being written — this mirrors the load encoding's field layout even though the semantic roles are reversed. This matters when hand-assembling or reading raw encodings.

### 5.4 Immediate Load
| Mnemonic           | Fields      | Effect                                      |
|--------------------|-------------|---------------------------------------------|
| `lli(dest, imm16)` | I: src=Zero | `dest ← imm16` (zero-extended into 32 bits) |
| `lui(dest, imm16)` | I: src=Zero | `dest ← (imm16 << 16) \| (dest & 0xFFFF)`   |

To build a full 32-bit constant: `lli` first (sets the low half, clearing the register), then `lui` (sets the high half, preserving the low half `lli` just wrote). `Instruction::load_imm32(dest, imm)` produces this pair directly.

### 5.5 Comparison
All of the form `(sr1, sr2, dest)`, R-type, `dest ← (condition) ? 1 : 0`:

| Mnemonic | Condition             |
|----------|-----------------------|
| `ieq`    | `sr1 == sr2`          |
| `ine`    | `sr1 != sr2`          |
| `ilt`    | `sr1 < sr2` (signed)  |
| `ile`    | `sr1 <= sr2` (signed) |
| `igt`    | `sr1 > sr2` (signed)  |
| `ige`    | `sr1 >= sr2` (signed) |

### 5.6 Jump / Branch
| Mnemonic                 | Fields                 | Effect                                                            |
|--------------------------|------------------------|-------------------------------------------------------------------|
| `jmp(addr, offset)`      | I: src=Zero, dest=addr | `PCX ← addr + sext(offset)`, unconditional                        |
| `jez(cmp, addr, offset)` | I: src=cmp, dest=addr  | If `cmp == 0`: `PCX ← addr + sext(offset)`                        |
| `jnz(cmp, addr, offset)` | I: src=cmp, dest=addr  | If `cmp != 0`: `PCX ← addr + sext(offset)`                        |
| `jic(addr, offset)`      | I: src=Zero, dest=addr | Encoded; semantics not exercised by the current assembler/codegen |
| `jnc(addr, offset)`      | I: src=Zero, dest=addr | Encoded; semantics not exercised by the current assembler/codegen |

**Absolute vs. relative:** when `addr` is `Zero`, the effective target is just `offset` — this is how the assembler encodes absolute jumps to resolved symbol addresses today. Any other `addr` register makes the jump base-relative to that register's runtime value (e.g. an instruction-pointer-relative jump, as used internally by interpreters written in DSA assembly, such as the Brainfuck interpreter example). Because `offset` is a 16-bit field, **statically resolved absolute jump targets are currently capped at 0xFFFF** — a real constraint worth being aware of as programs grow past 64KB of combined text.

### 5.7 Bitwise
All `(sr1, sr2, dest)` R-type except `not`:

| Mnemonic         | Effect                 |
|------------------|------------------------|
| `and`            | `dest ← sr1 & sr2`     |
| `nand`           | `dest ← !(sr1 & sr2)`  |
| `or`             | `dest ← sr1 \| sr2`    |
| `nor`            | `dest ← !(sr1 \| sr2)` |
| `xor`            | `dest ← sr1 ^ sr2`     |
| `xnor`           | `dest ← !(sr1 ^ sr2)`  |
| `not(src, dest)` | `dest ← !src`          |

### 5.8 Arithmetic
| Mnemonic                 | Fields | Effect                     |
|--------------------------|--------|----------------------------|
| `add(sr1, sr2, dest)`    | R      | `dest ← sr1 + sr2`         |
| `sub(sr1, sr2, dest)`    | R      | `dest ← sr1 - sr2`         |
| `addi(sr1, dest, imm16)` | I      | `dest ← sr1 + sext(imm16)` |
| `subi(sr1, dest, imm16)` | I      | `dest ← sr1 - sext(imm16)` |

### 5.9 Shift
| Mnemonic                         | Fields                                            | Effect                                     |
|----------------------------------|---------------------------------------------------|--------------------------------------------|
| `shl(src, rshamt, dest, ishamt)` | R: src1=src, dest=dest, src2=rshamt, shamt=ishamt | `dest ← src << (rshamt + ishamt)`          |
| `shr(src, rshamt, dest, ishamt)` | R                                                 | `dest ← src >> (rshamt + ishamt)`, logical |

`ishamt` is a literal encoded in the instruction (6 bits, asserted `< 64` at build time); `rshamt` is an additional runtime register value added to it. In practice, current codegen only ever populates the literal (`ishamt`) form with `rshamt = Zero`.

### 5.10 Stack
| Mnemonic    | Fields      | Effect                       |
|-------------|-------------|------------------------------|
| `push(reg)` | R: src1=reg | `SPR ← SPR - 4; [SPR] ← reg` |
| `pop(reg)`  | R: dest=reg | `reg ← [SPR]; SPR ← SPR + 4` |

### 5.11 Function Call / Return
| Mnemonic             | Fields                 | Effect                                             |
|----------------------|------------------------|----------------------------------------------------|
| `call(addr, offset)` | I: src=Zero, dest=addr | Pushes return context; `PCX ← addr + sext(offset)` |
| `ret()`              | noarg                  | Pops return context; `PCX ← saved return address`  |

Exact push/pop mechanics of `call`/`ret` (whether the return address goes through the explicit stack or the dedicated `Ret` register) are implemented in the interpreter and not independently re-derived here — confirm against the emulator's execution code if implementing a second interpreter.

### 5.12 System
| Mnemonic    | Fields                                  | Effect                  |
|-------------|-----------------------------------------|-------------------------|
| `int(code)` | I: imm16 holds `code` (asserted `< 64`) | Software interrupt      |
| `irt()`     | noarg                                   | Return from interrupt   |
| `hlt()`     | R, all-zero operands                    | Halts instruction fetch |
| `nop()`     | noarg                                   | No effect               |

Exact interrupt dispatch mechanics (IDT layout, privilege transition) are not covered here — this is architecture the emulator's interrupt controller module defines, not something re-derived from the instruction builders alone.

### 5.13 Data Pseudo-word
`Instruction::data(value)` constructs a raw `u32` word with no opcode semantics — used internally by the assembler pipeline as a placeholder for data words prior to the current data/text section split. Should not appear in the TEXT section of a well-formed program under the current `DsoBinary`/`DseExecutable` pipeline (see the DSE Executable spec) — data belongs exclusively in the DATA section now.

---

## 6. Known Gaps / Open Questions

- `jic`/`jnc` are encoded but no confirmed semantics have been exercised in this codebase — treat as reserved/unimplemented until the interpreter's handling is confirmed.
- The full `Register` enum (specifically any privileged/system registers) has not been independently confirmed in this conversation — the table in §2 lists only registers seen in actual builder/disassembler code.
- Interrupt/IDT layout and `call`/`ret`'s exact stack mechanics live in the interpreter, not the instruction encoder, and should be documented separately once confirmed against that source.
