diff --git a/docs/v4.0.0/DATASHEET-F18.md b/docs/v4.0.0/DATASHEET-F18.md new file mode 100644 index 00000000..04189a2f --- /dev/null +++ b/docs/v4.0.0/DATASHEET-F18.md @@ -0,0 +1,317 @@ +# StarForth v4 F18 Machine — Datasheet + +The machine: registers, stacks, memory, instruction word, opcodes, control unit, faults and I/O. +This sheet says what the machine **is**. Nothing above the machine is here: no FORTH, no +interpreter, no compiler, no dictionary. Those are source, in the capsule's trailer +(`MACHINE.md`). + +**Source of truth.** The golden model in C: `v4/src/` and `v4/include/v4/`, as built on +2026-10-15. The FPGA machine will match it instruction for instruction (`JUSTIFICATION.md` §10, +step 3: Verilator co-simulation in lockstep). Every statement here is checked against the model's +code, cited by file. Where the model is not yet what was intended, the sheet says so (§13). + +Design history: `DECOMPOSITION.md` §1 (the ISA) and §3 (decisions D-1 to D-19). Where the two +differ, the model and this sheet follow the later ruling. + +--- + +## 1. Summary + +| | | +|---|---| +| Architecture | Two-stack machine derived from Chuck Moore's F18 (GA144 node) | +| Instruction set | 32 opcodes, 5 bits each | +| Instruction word | 32 bits: six 5-bit slots + 2 spare bits, at every cell width | +| Cell (data) width | 32 or 64 bits, a build parameter | +| Addressing | Word-addressed. An address counts cells. | +| Registers | P, A, B; T, S (top of the data stack); R (top of the return stack) | +| Data stack | T, S + ring. 10 deep on a mesh node, 32 on the host node. Counted and guarded. | +| Return stack | R + ring. 9 deep on a mesh node, 32 on the host node. Counted and guarded. | +| Memory | RAM, a build parameter (1024 words mesh, 16384 host) | +| I/O | Memory-mapped: ports, console, stack-depth registers, error register | +| Faults | 6 kinds, vectored through a 6-word table | +| Side effects | Per-opcode heat, per-call-target heat, anti-clock. No instruction cost. | + +## 2. Build parameters + +| Parameter | Symbol | Values | Where | +|---|---|---|---| +| Cell width | `V4_CELL_BITS` | 32, 64 | `cell.h` | +| RAM words | `V4_NODE_WORDS` | default 1024; host node 16384 | `node.h`, `v4/Makefile` `HOST_WORDS` | +| Data ring | `V4_DATA_RING` | default 8 (depth 10); host 30 (depth 32) | `stack.h`, `v4/Makefile` | +| Return ring | `V4_RET_RING` | default 8 (depth 9); host 31 (depth 32) | `stack.h`, `v4/Makefile` | +| Ports | `V4_PORTS` | default 8 | `node.h` | + +The host node is the same machine with more memory and deeper stacks (D-17). Nothing else +differs. + +## 3. Registers + +| Reg | Width | Holds | Visible to code through | +|---|---|---|---| +| **P** | cell | Program counter: the address of the next instruction word, or of the next `@p`/`!p` cell | `@p`, `!p`, `ex`, branches, `;` | +| **A** | cell | Address register; also the low half of the `+*` product | `a`, `a!`, `@`, `@+`, `!`, `!+`, `+*` | +| **B** | cell | Address register | `b!`, `@b`, `!b` | +| **T** | cell | Top of the data stack | every data opcode | +| **S** | cell | Second on the data stack | `over`, `+`, `and`, `xor`, `+*` | +| **R** | cell | Top of the return stack | `;`, `ex`, `call`, `next`, `unext`, `push`, `pop` | +| **I** | 32 bits | The instruction word being executed, and the slot within it | not visible. Held by the control unit (`exec.c`). | + +T and S are the top two places of the data stack, and R is the top place of the return stack. +They are not copies of stack cells (`node.h`, `stack.h`). There is no stack pointer register +and no flags register. Conditions are tested on T directly (`if`, `-if`). + +## 4. Stacks + +**Data stack** (`v4_dstack`, `stack.h`): T, S, then a ring of `V4_DATA_RING` cells. +- A push moves S into the ring, T into S, and the new value into T. +- A pop does the reverse. + +**Return stack** (`v4_rstack`): R, then a ring of `V4_RET_RING` cells, the same way. + +**Counted and guarded (D-16).** Each stack counts what it holds, from 0 to its depth. Before +every opcode the control unit checks two things: +- the stacks hold what the opcode takes, including a T or S it only reads; +- the stacks have room for what it leaves. + +If either check fails, the opcode does nothing and the machine faults (§9). The ring's wrap is +never seen by a running program. + +**Not addressable.** There is no address for any stack cell. A program sees only the two depth +registers (§10.3). + +## 5. Memory + +- **Word-addressed (D-1).** An address names a cell, so `CELLS` is the identity. + - A byte address is 4 × word + 0..3. That is a software convention used by the block format + and strings. The machine itself has no byte access. +- **RAM:** addresses 0 to `V4_NODE_WORDS − 1`. +- **Out of range (D-14).** Every address a running program uses is checked before it is used: + - P, for an instruction fetch, an `@p` fetch or a `!p` store; + - A, for `@ @+ ! !+`; + - B, for `@b !b`. + + An address outside RAM is an address fault (§9). No access is made, and A and B are left + unchanged. +- **I/O addresses** (§10) sit inside the address space. A fetch from or store to one goes to + the device, not to RAM. The exceptions are NODE-ERROR, which writes RAM and then traps, and + the console receive addresses, where a store goes to RAM. +- **ROM.** Designed, not built: `MACHINE.md` §2, and plan Task 1. It is a read-only range at + `V4_NODE_WORDS` and above. It can be fetched and executed. A store there is an address fault. + A reset keeps it. + +## 6. Instruction word + +``` + 31 27 26 22 21 17 16 12 11 7 6 2 1 0 ++--------+--------+--------+--------+--------+--------+----+ +| slot 0 | slot 1 | slot 2 | slot 3 | slot 4 | slot 5 | xx | ++--------+--------+--------+--------+--------+--------+----+ +``` + +- **32 bits at every cell width (D-9).** On a 64-bit machine the instruction word is the low 32 + bits of the cell, and the high half is ignored. Compiled code is identical at 32 and 64. + Literals and data are a full cell. +- **Slots execute left to right.** +- **A branch** (`jump call next if -if`) takes its target from every bit to the right of its + slot, the 2 spare bits included. The target replaces the same low bits of P, and the bits of + P above them are kept. Branches are page-relative, as on the F18. + +| Branch in slot | Address bits | Reach | +|---|---|---| +| 0 | 27 | 128M words | +| 1 | 22 | 4M words | +| 2 | 17 | 128K words | +| 3 | 12 | 4K words | +| 4, 5 | 7, 2 | outside the spec. The model does not trap it; it applies the 7- or 2-bit target. | + +- **The spare bits** are never executed. They are address bits for a branch in any slot. +- **The low bits of a non-branch slot** are ignored by that opcode. + +## 7. Opcodes + +"Takes" and "leaves" are what the control unit checks before the opcode runs (§4). "d" is the +data stack, "r" is the return stack. + +| Op | Name | Effect | Takes | Leaves room for | +|---|---|---|---|---| +| 00 | `;` | P ← R, pop R | r 1 | | +| 01 | `ex` | swap P and R | r 1 | | +| 02 | `jump a` | P ← a | | | +| 03 | `call a` | push P to R; P ← a; +1 heat of a | | r 1 | +| 04 | `unext` | if R ≠ 0: R ← R−1, and the same word runs again from slot 0, with no refetch and P unchanged. Else pop R and go on to the next slot. | r 1 | | +| 05 | `next a` | if R ≠ 0: R ← R−1, P ← a. Else pop R. | r 1 | | +| 06 | `if a` | if T = 0: P ← a. T is not popped. | d 1 | | +| 07 | `-if a` | if T ≥ 0 (sign bit clear): P ← a. T is not popped. | d 1 | | +| 08 | `@p` | push the cell at P; P ← P+1 | | d 1 | +| 09 | `@+` | push the cell at A; A ← A+1 | | d 1 | +| 0A | `@b` | push the cell at B | | d 1 | +| 0B | `@` | push the cell at A | | d 1 | +| 0C | `!p` | cell at P ← T, pop; P ← P+1 | d 1 | | +| 0D | `!+` | cell at A ← T, pop; A ← A+1 | d 1 | | +| 0E | `!b` | cell at B ← T, pop | d 1 | | +| 0F | `!` | cell at A ← T, pop | d 1 | | +| 10 | `+*` | if A bit 0: T ← T+S (the carry out is not kept, D-3). Then T:A shift right one bit: T keeps its sign bit, T's bit 0 goes into A's top bit. S is not popped. | d 2 | | +| 11 | `2*` | T ← T << 1 | d 1 | | +| 12 | `2/` | T ← T >> 1, arithmetic | d 1 | | +| 13 | `inv` | T ← ~T (F18 `-`) | d 1 | | +| 14 | `+` | T ← S + T, pop. Wraps. | d 2 | | +| 15 | `and` | T ← S & T, pop | d 2 | | +| 16 | `xor` | T ← S ^ T, pop (F18 `or`) | d 2 | | +| 17 | `drop` | pop T | d 1 | | +| 18 | `dup` | push T | d 1 | d 1 | +| 19 | `pop` | pop R, push it onto the data stack | r 1 | d 1 | +| 1A | `over` | push S | d 2 | d 1 | +| 1B | `a` | push A | | d 1 | +| 1C | `nop` | nothing | | | +| 1D | `push` | pop T, push it onto R | d 1 | r 1 | +| 1E | `b!` | B ← T, pop | d 1 | | +| 1F | `a!` | A ← T, pop | d 1 | | + +**Arithmetic.** All arithmetic wraps, two's complement, at the cell width. There is no carry +flag, no overflow flag and no trap. + +**Names.** Two names differ from Moore's: `inv` is F18 `-`, and `xor` is F18 `or`. No opcode +name is a FORTH-79 word name (`opcode.h`). + +## 8. Control unit + +One step (`v4_exec_step_word`, `exec.c`): + +1. **Stopped or blocked?** If the machine has stopped (§9) or is blocked at a port or at Do + (§10.4), nothing happens. +2. **Resume.** If the machine was blocked part-way through a word and has since been served, it + continues from the saved word and slot. A fault while it was blocked cancels the resume. +3. **Fetch.** + - P is checked against memory (§5). + - If P is a port, the word is whatever the neighbour writes there, and P does not advance. + This is how a newborn node runs code sent to it. + - Otherwise the word is the cell at P, and P ← P+1. +4. **Slots, from 0 (or the resumed slot) to 5.** For each: + - Check the stacks (§4). Then run the opcode. Then count +1 heat for the opcode and +1 on the + anti-clock (§11). An opcode that faults does not retire. A raised error retires (§9). + - If the opcode was a fetch from a port with nothing written yet, the machine blocks before + the opcode, and it will resume at this slot. + - If it faulted, the rest of the word is abandoned, and P is the handler. + - If it stored to a port or to Do, the machine blocks after the opcode, and it will resume at + the next slot. + - If it was `unext` with R ≠ 0, the same word runs again from slot 0. + - If it was a branch, `;` or `ex`, the word ends. Nothing to the right of a branch executes, + taken or not. +5. The step returns the number of opcodes retired. + +**`@p` and `!p` in an `unext` loop** walk the cells after the loop word, because P is not moved +by the restart. This is the F18's behaviour. + +## 9. Faults + +| Kind | Code | Cause | +|---|---|---| +| Address | 0 | An address outside memory (§5). Also a store to a read-only port address (§10.4). | +| Data overflow | 1 | A push onto a full data stack | +| Data underflow | 2 | The data stack holds less than the opcode takes | +| Return overflow | 3 | A push onto a full return stack | +| Return underflow | 4 | The return stack holds less than the opcode takes | +| Raised | 5 | A non-zero store to NODE-ERROR (§10.5) | + +**On a fault** (`v4_node_fault`, `node.c`): +- The kind and, for an address fault, the address are recorded, and the fault count goes up. +- The return stack is emptied. The data stack is emptied too, except for a raised error, which + keeps it. +- P ← fault table + kind. + +The **fault table** is 6 words of RAM, one per kind, each normally a jump to that kind's +handler. A handler does not return to the faulting program. + +**With no fault table** the machine **stops**: no further step does anything until it is reset +or a table is attached. + +## 10. Memory-mapped I/O + +Each device is placed at an address by whoever owns the machine. In the model this is done by an +`attach` call (`node.h`). Fixed addresses are D-4, deferred to the mesh and the RTL (§13). +`v4_node_reset` detaches every device. + +### 10.1 Console transmit — CONSOLE-TX +A store sends the low 8 bits as one character. RAM is not written. A fetch reads RAM. + +### 10.2 Console receive — CONSOLE-RX, CONSOLE-STATUS +- A fetch from **STATUS** gives −1 if a character is pending and 0 if none is. It changes + nothing. +- A fetch from **RX** takes the next character, 0–255, or gives −1 if there is none. +- Neither address blocks. A program that must wait polls STATUS. +- A store to either is an ordinary RAM store. +- An instruction or literal fetch at these addresses always reads RAM. + +### 10.3 Stack depth — DSTACK-DEPTH, RSTACK-DEPTH (D-16) +- A fetch gives that stack's depth. For the data stack, the fetch's own push is not counted. +- A store empties that stack. For the data stack, the value stored is taken off first. + +### 10.4 Ports (`DECOMPOSITION.md` §6, `MESH.md` 4.1, 7d.3) +At the port base address: + +| Offset | Name | Fetch | Store | +|---|---|---|---| +| 0 … PORTS−1 | port k | Blocks until the neighbour on k writes, then gives that word | Blocks until the neighbour on k has taken the word | +| PORTS | any | Blocks until a neighbour on any port writes | address fault | +| PORTS+1 | which | The port the last "any" read came from | address fault | +| PORTS+2 | writers | bit k: the neighbour on k is waiting to write here | — | +| PORTS+3 | readers | bit k: the neighbour on k is waiting to read. Bit PORTS+k: port k has anything on it at all. | — | +| PORTS+4 | Wire | what was stored | the port the next operation is about | +| PORTS+5 | A | what was stored | an operand | +| PORTS+6 | B | what was stored | an operand | +| PORTS+7 | Do | the operation number | **Blocks** until the operation is done (whole messages) | +| PORTS+8 | How | how the last operation went | address fault | +| PORTS+9 | Have | bit k: a message is waiting on wire k | address fault | + +A read port also works as an execution source: if P is a port, the machine executes what +arrives (§8, step 3). A newborn node (`v4_node_born`) has P at "any" and blocks there, empty, +until a neighbour sends it code. + +### 10.5 Error register — NODE-ERROR (D-18) +- A store of a non-zero value writes RAM, then raises a fault of kind 5. The value stored is + recorded as the fault address. +- A store of 0 is an ordinary store, which is how the flag is cleared. +- Not attached, it is plain RAM. + +## 11. Side effects: heat and the anti-clock + +Not instructions. They cost nothing (`DECOMPOSITION.md` §1.4, `heat.h`): +- Every retired opcode adds 1 to that opcode's heat counter, `HEAT-OP[0..31]`, and 1 to the + **anti-clock**. +- Every `call` adds 1 to its target's heat counter, `HEAT-CALL[target]`. +- Counters are 64 bits at every cell width (`JUSTIFICATION.md` §5). + +They are not yet memory-mapped: the §7 addresses wait on D-4. + +## 12. Reset and birth + +**`v4_node_reset`:** +- P, A and B are set to 0. +- Both stacks are emptied, and every RAM cell is set to 0. +- Every device is detached, and the fault table is detached. + +**`v4_node_born(base)`:** a reset, then ports at `base`, then P = `base + PORTS` ("any"). The +machine blocks there, empty, until it is sent code. + +## 13. Not yet as intended + +These are reported, not hidden: + +1. **Per-call-target heat** is the model's interim structure: `heat.h` says "in the meantime", + and D-6 is deferred. It is a fixed 4096-entry table, and a call to a target at address 4096 + or above is not counted. This is the stand-in recorded 2026-10-05. It is not the intended mechanism, + and it is not to be extended. +2. **Device addresses are attached by the owner, not fixed.** D-4 (the memory map) is deferred. + The FPGA needs fixed addresses. +3. **The ROM range** is designed (`MACHINE.md`) and not built. +4. **`stack.h`'s opening comment** still says there is no overflow detection "and there never + will be". D-16 revised that, and the header carries a revision note above it. The code + counts and guards (§4). The comment's first paragraph is out of date. +5. **Branches in slots 4 and 5** are outside the spec but not trapped (§6). + +## 14. What is not in the machine + +No FORTH, no interpreter, no compiler, no assembler-in-hardware, no dictionary, no names, no +vocabularies, no boot policy. The ROM (`MACHINE.md` §2) is F18 code the machine runs. It is not +part of this sheet's hardware. Everything above the opcodes arrives as source.