Metadata-Version: 2.4
Name: setnex-sim
Version: 0.6.1
Summary: Setnex ISA balanced-ternary processor simulator
Project-URL: Homepage, https://codeberg.org/setnex-org/setnex-sim
Project-URL: Repository, https://codeberg.org/setnex-org/setnex-sim
Project-URL: Issues, https://codeberg.org/setnex-org/setnex-sim/issues
Author-email: Eric Tellier <eric@setnex.org>
License-Expression: MIT
Keywords: balanced-ternary,ternary
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Science/Research
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Scientific/Engineering :: Mathematics
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: tritlib>=1.2.1
Provides-Extra: dev
Requires-Dist: pytest; extra == 'dev'
Requires-Dist: pytest-cov; extra == 'dev'
Description-Content-Type: text/markdown

# setnex-sim
Python simulator for the [Setnex ISA](https://setnex.org) — an open balanced ternary instruction set architecture.
Implements the full fetch/decode/execute cycle for a 27-trit processor:
27 general-purpose registers, 5 configurable ternary logic modes (LMODE), and fixed-length R/I/J/U/B instructions.
Built on [tritlib](https://codeberg.org/setnex/tritlib).

## Status
v0.6 — fully functional simulator with assembler, CLI toolchain, and nested-exception handling. 52 opcodes, 13 CSRs (main + frame-2 bank for nested traps), 100 % test coverage on non-CLI modules.

Implemented:
- Decoder (R, I, J, U, B formats)
- ALU (ADD, SUB, MUL, DIV, MOD, NEG, TAND, TOR, TNOT, TIMPL, CONS, ACONS, TSHIFT, TCMP, TGET, TSET, TSIGN, TABS, TMIN, TMAX)
- Ternary floating-point unit (FADD, FSUB, FMUL, FDIV, FCMP, FCVT) — T26F format (§7)
- Configurable ternary logic via LMODE (Kleene, Łukasiewicz, Heyting, RM3, Bochvar)
- Registers (27 GPR + CSR: PC, LMODE, FLAGS, EPC, ECAUSE, EVEC, STATUS, ESAVE, ETVAL, EPC2, ECAUSE2, ESAVE2, ETVAL2)
- Architectural exception dispatch: EXC_DIV0, EXC_FAULT, EXC_ILLEGAL, EXC_ECALL_U/H/D, EXC_OVERFLOW (§8); **nested exceptions** up to depth 2 (§8.2); machine-check reset on triple fault
- Sparse word-addressed memory
- CPU fetch/decode/execute loop with ternary FLAGS (sign, carry)
- Branches and jumps (BEQ, BNE, BLT, BGT, BLE, BGE, BF, BRT3, JMP, JMPA, CALL)
- System instructions (CSRR, CSRW, CSRX, ECALL, HCALL, DBGBRK, IRET, TSEL)
- Text assembler (`setnex-asm`): `.sasm` → `.tern`, label resolution, pseudo-instructions
- Runner (`setnex-run`): execute `.tern` files with register initialisation and ternary exit status

## Install
```bash
pip install setnex-sim
```
Or from source:
```bash
git clone https://codeberg.org/setnex/setnex-sim
cd setnex-sim
pip install -e ".[dev]"
```

## CLI usage

### Assembler
```bash
setnex-asm program.sasm -o program.tern
```

CSR operations accept symbolic names (case-insensitive) as an alternative to numeric addresses:

```
CSRR t0, FLAGS        ; equivalent to `CSRR t0, 3`
CSRW t0, EVEC         ; equivalent to `CSRW t0, 6`
```

Recognised names: `PC`, `LMODE`, `FLAGS`, `EPC`, `ECAUSE`, `EVEC`, `STATUS`, `ESAVE`, `ETVAL`, `EPC2`, `ECAUSE2`, `ESAVE2`, `ETVAL2` (addresses 1..13, §2.2).

### Runner
```bash
setnex-run program.tern --set a0=5 --print a0
```

Options:
- `--set REG=VAL` — initialise a register before execution (repeatable)
- `--print REG` — print a register after HALT (default: `a0`)
- `--verbose` — print all registers and cycle count

Exit status follows the ternary convention via `a1` at HALT:
- `a1 < 0` (N) → shell exit 1 (error)
- `a1 = 0` (Z) → shell exit 0 (indeterminate)
- `a1 > 0` (P) → shell exit 0 (success)

### Example programs

Example programs are provided in `fixtures/`:

**`sum.sasm`** : sum of integers 1..N (validates the full pipeline: LI, CMP, BF, ADD, ADDI, JMP):
```bash
setnex-asm fixtures/sum.sasm -o sum.tern
setnex-run sum.tern --set a0=5 --print a0
# a0 = 15
```

**`count_p.sasm`** : count P trits in `a0` using TGET and BRT3:
```bash
setnex-asm fixtures/count_p.sasm -o count_p.tern
setnex-run count_p.tern --set a0=13 --print a0
# a0 = 3  (13 = +++ in balanced ternary — three P trits)
```

**`modsum.sasm`** : sum with sign driven by symmetric Euclidean `i mod 3 ∈ {−1, 0, +1}`,
dispatched natively by BRT3 — a computation that has no natural binary equivalent:
```bash
setnex-asm fixtures/modsum.sasm -o modsum.tern
setnex-run modsum.tern --set a0=6 --print a0
# a0 = -2
```

**`sign_check.sasm`** : looks for a zero between a0 and a1, returns a1 = P if found, Z if absent, and  a0 the results if a1 == P
```bash
setnex-asm fixtures/sign_check.sasm -o sign_check.tern
setnex-run sign_check.tern --set a0=3 a1=7 --print a0 --print a1
# a0 = 3
# a1 = 0
setnex-run sign_check.tern --set a0=-3 a1=7 --print a0 --print a1
# a0 = 0
# a1 = 1
```

**`sum_saturated.sasm`** : iterated saturating add (`ADDS`), counting iterations until `t1 = t1 + t0` reaches T27 saturation. Uses `TMIN` for the all-P saturation test and `BRT3` for the three-way loop dispatch — the native balanced-ternary idiom for bounded accumulation:
```bash
setnex-asm fixtures/sum_saturated.sasm -o sum_saturated.tern
setnex-run sum_saturated.tern --set a0=1 a1=0 --print a0
# a0 = iterations to saturate adding 1 to 0
```

**`fdiv.sasm`** : divides `a0` by `a1` in TFP (T26F), traps via `ECALL` to a minimal handler if the result is `∞` (division by zero in floating-point produces infinity, detected through `FLAGS.carry = P`). Illustrates the exception path: handler installation via `CSRW t0, EVEC`, trap on condition, handler advances `EPC` and returns via `IRET`. v0.5-compatible: `ECALL` with the default flavor tag (Z) produces `ECAUSE = EXC_ECALL_U = 0`, same as v0.5's `EXC_ECALL`:
```bash
setnex-asm fixtures/fdiv.sasm -o fdiv.tern
setnex-run fdiv.tern --set a0=10 a1=2 --print a0 --print a1
# a0 = 5 (finite), a1 = 1, exit 0
setnex-run fdiv.tern --set a0=10 a1=0 --print a0 --print a1
# a0 = 0, a1 = -1, exit 1 (handler trapped on ∞)
```

## Exception handling

v0.6 implements the full §8 exception contract including nested exceptions (§8.2) and the three `ECALL` flavors. Eight architectural causes are wired:

| Code | Name | Trigger | `ETVAL` |
|------|------|---------|---------|
| −13 | `EXC_DIV0` | `DIV`/`MOD` with `rs2 = 0` | 0 |
| −11 | `EXC_FAULT` | `LOAD`/`STORE` on unmapped address, or fetch from unmapped PC | faulting address |
| −10 | `EXC_ILLEGAL` | reserved opcode (−20, −19, +14..+40) or unrecognised word | raw instruction word |
| 0 | `EXC_ECALL_U` | `ECALL` (user syscall flavor, `imm17[0] = Z`) | 0 (call number in `a7`) |
| +1 | `EXC_ECALL_H` | `HCALL` (hypercall flavor, `imm17[0] = P`) | 0 |
| +2 | `EXC_ECALL_D` | `DBGBRK` (debug-trap flavor, `imm17[0] = N`) | 0 |

Entry uses the bank selected by `STATUS.depth`:
- `depth = Z` (no frame active): save to main bank (`ESAVE`/`EPC`/`ECAUSE`/`ETVAL`), force `mode = ie = N`, set `depth = P`, jump to `EVEC`.
- `depth = P` (one frame active): save to frame-2 bank (`ESAVE2`/`EPC2`/`ECAUSE2`/`ETVAL2`), set `depth = N`; `mode`/`ie` already N. Main bank preserved.
- `depth = N` (both frames in use): **triple fault** — machine-check reset.

Return via `IRET` atomically restores PC and STATUS from the bank matching the current `depth` (P → main bank, N → frame-2 bank). Since `ESAVE`/`ESAVE2` captured the pre-entry `depth` value, the `STATUS ← ESAVE*` write implicitly decrements `depth` to its prior state — no separate depth manipulation.

**Minimal handler pattern** (install at `EVEC`, do work, advance `EPC`, return):

```asm
    LI    t0, handler
    CSRW  t0, EVEC           ; install handler
    ; ... user program, eventually triggers a trap ...
handler:
    ; ... handler body (read ECAUSE, ETVAL, a7 for syscall number, …) ...
    CSRR  t0, EPC
    ADDI  t0, t0, 1          ; resume at instruction after the trap
    CSRW  t0, EPC
    IRET
```

**Machine-check reset** — a third synchronous fault while both frames are in use (`STATUS.depth = N`) is a triple fault: the processor restarts from power-on state (`PC ← 0`, all CSRs and GPRs cleared). A single nested fault (fault inside an outer handler) is handled cleanly by the frame-2 bank and does *not* reset.

## Python API
```python
from setnex_sim.cpu import CPU
from setnex_sim.assembler import encode_R, encode_I

cpu = CPU()
program = [
    encode_I(-24, rd=1, rs1=0, imm=5),   # LI r1, 5
    encode_I(-24, rd=2, rs1=0, imm=3),   # LI r2, 3
    encode_R(-40, rd=3, rs1=1, rs2=2),   # ADD r3, r1, r2
    encode_R(0,   rd=0, rs1=0, rs2=0),   # HALT
]
cpu.load(program)
cpu.run()
print(int(cpu.regs[3]))  # 8
```

## Setnex ISA
The full ISA specification is available at [setnex.org](https://setnex.org) and on
[Codeberg](https://codeberg.org/setnex/setnex-isa) — Apache 2.0, patent-free.

## Licence
MIT — Copyright 2026 Eric Tellier
