docs(v4.0.0): DATASHEET-F18.md -- the machine: registers, stacks, memory, instruction word, opcodes, control unit, faults, I/O
As built in the golden model (v4/src), with what is not yet as intended listed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
952df0f44a
commit
1ed56b93b4
@@ -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.
|
||||
Reference in New Issue
Block a user