Metadata-Version: 2.4
Name: rime-ecp5
Version: 0.1.0
Summary: RIME: resident management environment for the Lattice ECP5 IcePi Zero, synthesized with nosis (no yosys)
Author-email: CharlesCNorton <machineelv@gmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/CharlesCNorton/rime
Project-URL: Repository, https://github.com/CharlesCNorton/rime
Project-URL: Issues, https://github.com/CharlesCNorton/rime/issues
Keywords: fpga,ecp5,lattice,icepi,nosis,nextpnr,bitstream,jtag,flash
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Scientific/Engineering :: Electronic Design Automation (EDA)
Classifier: Operating System :: OS Independent
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pyserial<4,>=3.5
Requires-Dist: nosis>=0.1
Provides-Extra: dev
Requires-Dist: pytest; extra == "dev"
Requires-Dist: pytest-timeout; extra == "dev"
Requires-Dist: pytest-cov; extra == "dev"
Requires-Dist: hypothesis; extra == "dev"
Requires-Dist: ruff; extra == "dev"
Requires-Dist: mypy; extra == "dev"
Requires-Dist: pre-commit; extra == "dev"
Dynamic: license-file

# RIME

RIME is the **Resident IcePi Management Environment**.

It is the resident control plane for the [IcePi Zero](https://github.com/cheyao/icepi-zero): a persistent boot image that manages flash, SDRAM, and SD card operations over a UART protocol. The host-side CLI handles build, inspect, install, verify, doctor, janitor, compose, and reload workflows.

The short version:

- Lattice ECP5-25F: 24,288 LUTs, 56 block RAMs, 28 multipliers.
- RIME makes it dependable — safe flash updates with write-unlock gating, verified readback, one CLI for everything.
- Synthesized by [nosis](https://github.com/CharlesCNorton/nosis), a pure-Python SystemVerilog synthesizer; placed and packed with the OSS CAD Suite toolchain.
- Two firmware images:

| Image | LUTs | BRAMs | Purpose |
|-------|------|-------|---------|
| **RIME** | ~5,966 (24%) | 0 | Flash + SDRAM service, SD install engine, auto-recovery FSM |
| **EMBER** | 9,534 (39%) | 2 | Hardware TRNG: 192 ring oscillators, AES-128-CBC, NIST health tests |

RIME includes an app shell with LED heartbeat, SD card read/write, firmware-mediated SD bundle install (`sd_install_engine.sv`), host-mediated staged install, inline-key write gating on destructive operations, length-prefixed responses with CRC-8 integrity, and a compositor system for stackable FPGA modules with N-way composition. Auto-recovery from SD is wired and silicon-verified (arm → boot → install → verify full cycle). EMBER's entropy subsystem is available as a composable module (EMBER-LITE).

Examples below assume the current working directory is the repository root. Board identity, layout, and toolchain paths resolve through command-line flags, environment variables, or optional local configuration rather than fixed host paths.

## Hardware

RIME targets the IcePi Zero board:

| Component | Part | Detail |
|-----------|------|--------|
| FPGA | Lattice ECP5U-25F | 24,288 LUTs, 56 block RAMs, 28 multipliers |
| Package | CABGA256 | 256-ball BGA |
| Flash | Winbond W25Q128 | 16 MB QSPI NOR, 64 KB erase sectors |
| SDRAM | Winbond W9825G6KH | 32 MB SDR, x16, 512 columns, 8192 rows, 4 banks |
| SD slot | microSD | SPI mode, SDHC/SDXC only (SDSC not supported) |
| USB bridge | FTDI FT231X | UART (VCP driver) or JTAG (WinUSB driver) |
| Clock | 50 MHz oscillator | Divided to 25 MHz system clock in fabric |
| I/O | 5 LEDs, 2 buttons | Active-low button[0] is reset |

The [upstream schematic](https://github.com/cheyao/icepi-zero) uses a generic Micron MT48LC16M16A2P KiCad symbol but the BOM populates a Winbond W9825G6KH (LCSC C62246). Both are pin-compatible 256 Mbit SDRAMs in TSOP-II 54 with identical organization: 8192 rows, 512 columns, 4 banks, 16-bit data. The SDRAM controller addresses the full 32 MB.

### SDRAM

The full 32 MB SDRAM is operational. All 13 row address bits are verified functional on silicon, including row-boundary writes.

The `install` command auto-detects full SDRAM and uses the staged upload path: stream the entire bitstream into SDRAM over UART, then commit to flash at SPI speed with on-board verify. See [`docs/THROUGHPUT.md`](docs/THROUGHPUT.md) for current reference-silicon timings (regenerated by `scripts/bench_report.py`).

## Why RIME Exists

IcePi Zero is a portable FPGA board with flash, SD, and direct host connectivity. RIME exists to turn it from "interesting FPGA hardware" into "a board you can iterate on, deploy to, and recover with confidence":

- rewrite flash safely, gated behind a write-unlock handshake
- verify readback instead of trusting writes blindly
- install from SD through the resident service or via autonomous boot-time recovery
- inspect board state, flash state, and SD state from one coherent interface
- compose multiple hardware modules into a single bitstream from the command line

## Who It Is For

- firmware and FPGA iteration on a board that may need repeated updates
- portable or bench workflows where SD-based staging is useful
- setups where you want one operator surface for build, inspect, install, verify, compose, and reload

Less of a fit:

- one-off SRAM loads with no interest in persistence
- generic FPGA bring-up on non-IcePi targets

## Resource Footprint

### RIME service image

| Resource | Used | Available | Utilization |
|----------|------|-----------|-------------|
| LUT4 | ~5,966 | 24,288 | 24% |
| DFF | ~3,500 | 24,288 | 14% |
| DP16KD (block RAM) | 0 | 56 | 0% |
| MULT18X18D | 0 | 28 | 0% |

The service provides 27 commands covering flash read/write/erase (with UNLOCK gating), SDRAM read/write/stream/commit/verify, SD card read/write/CRC/install, raw SDRAM word access, app shell identity/uptime, and diagnostics. It enters service mode via the app shell (phase 4 → phase 5).

The boot image includes the SD install engine (`sd_install_engine.sv`) for firmware-mediated bundle installs and the auto-recovery FSM (`auto_recovery.sv`) for autonomous SD-to-flash restore on boot.

### EMBER — Entropy Measurement and Board-level Environmental Randomness

| Resource | Used | Available | Utilization |
|----------|------|-----------|-------------|
| LUT4 | 9,534 | 24,288 | 39% |
| DFF | 1,959 | 24,288 | 8% |
| DP16KD (block RAM) | 2 | 56 | 3% |

EMBER is a hardware true random number generator. 192 ring oscillators in three thermally isolated paths (64 per path), majority-voted, Von Neumann debiased, LFSR whitened, then AES-128-CBC conditioned. NIST SP 800-90B health tests run continuously. 60-second thermal warmup.

EMBER is a standalone experiment image. When loaded, it takes 100% of the FPGA.

### Compositor Modules

The compositor (`icepi/compose.py`) combines RIME-I (a RISC-V soft CPU) with up to 16 hardware modules into a single bitstream. Each module declares its resource budget and interfaces in a `module.json` manifest. The compositor validates that the combined budget fits the ECP5, assigns each module a unique address region (0x30-0x3F), generates the top-level wrapper, and builds through the standard pipeline.

```
python icepi_helper.py compose anvil cairn scry --clean
```

Modules that declare a `snoop` interface requirement (like SCRY) get bus observation ports wired automatically.

Current modules (LUT4 counts from per-module `synth_ecp5 -noflatten`, refreshed by `scripts/verify_manifest_luts.py`; integration may shift these by ±15%):

| Module | Type | ~LUTs | Description |
|--------|------|-------|-------------|
| **RIME-I** | CPU | ~4,050 | RV32I base integer (37 instructions + FENCE), multi-cycle, 14 KB BRAM |
| **SLAB** | Memory | ~1,300/KB | Stackable LUT Allocated Block. 1 KB distributed LUT RAM tile |
| **SCRY** | Debug | ~1,000 | Circular trace buffer. Passively records CPU bus transactions |
| **SIFT** | Filter | ~3,200 | 256-bit Bloom filter, 3 hash functions |
| **MARK** | Security | ~250 | Free-running LFSR sampled on demand. Timing-dependent fingerprint |
| **DICE** | Compute | ~300 | 256-cycle stochastic multiply/add |
| **GLYPH** | Crypto | ~500 | GF(2^8) multiply, inverse, exponentiation |
| **PACK** | Compress | ~500 | RLE compressor with 32-entry output FIFO |
| **PARSE** | Match | ~500 | 8-state configurable hardware NFA |
| **TAPER** | Math | ~250 | Saturating add, signed multiply, min/max/abs |
| **MOSS** | Spatial | ~1,200 | 8x8 Game of Life with toroidal wrapping |
| **AUGUR** | Probability | ~600 | 4-chain Metropolis-Hastings MCMC sampler |
| **ANVIL** | Checksum | ~50 | CRC-32 accelerator (IEEE 802.3) |
| **LATCH** | Timer | ~400 | Watchdog timer + event counter with timestamp |
| **HEDGE** | Control | ~250 | Token bucket rate limiter |
| **EPOCH** | Clock | ~450 | Real-time clock synthesizer from 25 MHz sys_clk |
| **CAIRN** | Stack | ~2,000 | 16-deep 32-bit Forth-style stack machine |
| **WEAVE** | Serial ALU | ~550 | Bit-serial 32-cycle add/sub/mul, 1-cycle logic |
| **FLUX** | PID | ~550 | Fixed-point PID controller with anti-windup |
| **ORACLE** | Interpolator | ~1,800 | 64-entry lookup table with linear interpolation |
| **DELTA** | Diff | ~500 | Byte-stream XOR differencing engine |
| **VIGIL** | ECC | ~150 | Hamming(7,4) SEC encoder/decoder |
| **GRAIL** | Merkle | ~4,000 | 8-leaf CRC-32 Merkle tree hasher |
| **SENTRY** | Protection | ~400 | Memory protection unit. 4 address ranges with R/W permissions |
| **TIDE** | Waveform | ~250 | DDS waveform generator. Square/saw/triangle/sine |
| **RUNE** | Font | ~510 | 8x8 bitmap font renderer. Full printable ASCII (32-126) |
| **VAULT** | Crypto | ~150 | AES-128 single-round (SubBytes+AddRoundKey, correct FIPS 197 S-box) |
| **PRISM** | Color | ~100 | RGB to grayscale/min/max/saturation/invert |
| **CHORD** | Audio | ~570 | 4-voice DDS synthesizer. Square/saw/tri/sine per voice |
| **QUILL** | Buffer | ~550 | 4-channel 8-byte circular FIFO |
| **EMBER-LITE** | TRNG | ~124 | 8 ring oscillators (LUT4 primitives), Von Neumann debiased |
| **AXIOM** | Tokenizer | ~300 | JSON token scanner with nesting depth tracking |
| **SIGMA** | Checksum | ~50 | Fletcher-16 streaming checksum |
| **BLOOM** | Bitops | ~30 | Popcount, CLZ, CTZ, bit reverse, parity |
| **FORGE** | Hash | ~800 | Simplified single-word iterative hash (not a standard SHA-256 digest). Uses correct SHA-256 K constants, Ch, Maj, and Σ functions but processes one 32-bit word per invocation instead of a 512-bit message block — not interoperable with SHA-256 |
| **FLOCK** | Simulation | ~100 | 4-agent boids with cohesion, separation, and alignment forces |
| **MORTAR** | Matrix | ~50 | 2x2 signed 8-bit matrix multiply |
| **LACE** | Address | ~50 | Linear Address from Coordinate Encoding. Z-order bit interleave (2D + 3D) |
| **FLIP** | Transform | ~200 | Fast Logical Interstitial Permutator. 8x8 and 32x32 bit-matrix transpose |
| **APEX** | Data structure | ~700 | Asynchronous Priority Evaluation matriX. 16-entry hardware priority queue |
| **HALO** | Buffer | ~200 | Hardware Assisted Loop Orchestrator. 64-entry ring buffer with watermark |
| **HAMMER** | Matching | ~250 | Hardware Accelerated Matching and Measurement Engine Register. 256-bit Hamming distance |
| **CHURN** | Hash | ~300 | Continuous Hash Updating and Rolling Node. 32-byte Rabin-Karp rolling hash |
| **HEAT** | Profiling | ~300 | Hardware Execution Activity Tracker. 16-page memory access heatmap (snoop) |
| **ECHO** | Debug | ~300 | Event Capture and Hardware Observer. 16-entry cycle-stamped bus logger (snoop) |
| **LOGOS** | Math | ~500 | Logarithmic Operations and General Optimization System. Log-domain multiply/divide/sqrt |
| **ORBIT** | Trig | ~600 | Optimized Rotation and Basic Iteration Toolkit. 20-iteration CORDIC |
| **MIRROR** | Lookup | ~1,000 | Memory Indexing and Rapid Resolution Object Router. 16-entry masked pseudo-TCAM |
| **MOUNT** | Crypto | ~2,500 | Modular Operation Utility for Number Theory. 256-bit Montgomery multiplier |
| **DIVIDE** | Math | ~400 | Direct Integer Variable-length Iterative Division Engine. 32-bit unsigned divide |
| **CRANK** | Math | ~400 | Cyclic Register Arithmetic Numerical Kernel. 32x32 to 64-bit iterative multiply |
| **SIEVE** | Bitops | ~700 | Selective Indexed Extract and Variable Embed. Bit-field extract and deposit |
| **TALLY** | DSP | ~400 | Triggered Accumulating Linear Logic sYnthesizer. 4-channel multiply-accumulate |
| **PROOF** | Crypto | ~650 | Protected Read-Once Operational Fence. Constant-time 32-byte comparison |
| **ETCH** | Crypto | ~600 | Encrypted Transform with Cyclic Hashing. XTEA 64-round Feistel block cipher |
| **SEED** | Crypto | ~500 | Secure Entropy-Enhanced Derivation. FNV-1a nonce generator |
| **NOTCH** | Control | ~250 | Noise-Opposing Transition and Contact Handler. 8-channel debounce filter |
| **PHASE** | Control | ~150 | Periodic Hardware Accumulation and Signal Encoder. Quadrature decoder |
| **TEMPO** | Control | ~250 | Timed Event Measurement and Period Observer. Frequency and period counter |
| **CODEC** | Codec | ~200 | Compact Ordered Data Encoding/Decoding Coprocessor. Base64 encode/decode |
| **RANK** | Sort | ~2,400 | Rapid Associative Numerical Keyed sorter. 8-element parallel sorting network |
| **TRAP** | Debug | ~350 | Triggered Response to Address Predicate. 4-address hardware breakpoint (snoop) |
| **GAUGE** | Profiling | ~250 | General Aggregated Utilization and Granular Estimator. Bus bandwidth counter (snoop) |
| **DEPTH** | Debug | ~350 | Dynamic Execution Profile and Thread Height tracker. Stack pointer tracker (snoop) |
| **CELL** | Spatial | ~700 | Configurable Elementary Local Logic automaton. 64-cell 1D Wolfram cellular automaton |
| **SPARK** | ML | ~300 | Simple Perceptron with Activation and Responsive Kernel. 8-input single-layer perceptron |
| **HAZE** | Procgen | ~150 | Hardware Approximation of Zoned Entropy. 2D gradient noise via hash and bilinear interpolation |
| **CLASP** | Sync | ~50 | Contested Lock with Atomic Set Protocol. 8-slot atomic test-and-set mutex |
| **GPIO** | I/O | ~150 | General Purpose Input/Output. 16 software-driven pins with direction and edge detect |
| **PULSE** | I/O | ~250 | Programmable Unified Logic for Signal Emission. 4-channel 16-bit PWM generator |
| **SPOKE** | I/O | ~100 | Serial Peripheral Output/Kernel Exchange. SPI master with internal loopback (no external pins) |
| **WIRE** | I/O | ~50 | Wired Interconnect for Register Exchange. I2C master FSM with internal loopback (no external pins) |
| **FERRY** | DMA | ~1,500 | Fast External Register Relay Engine. 16-word internal scratchpad copy engine |
| **ARBOR** | IRQ | ~150 | Arbitrated Routing for Board-level Ordered Requests. 16-source priority interrupt controller |
| **PYLON** | IPC | ~250 | Paired Yield-Linked Output Node. 8-deep 32-bit FIFO mailbox |
| **DRUM** | Sequencer | ~1,000 | Driven Register Utility for Microsequencing. 16-instruction programmable sequencer |

**RIME-I** is a multi-cycle RV32I soft CPU. 5-state FSM, 38 instructions (37 base integer + FENCE). Memory is 14 KB of block RAM (7 DP16KD) with firmware initialized via `initial` blocks and `$readmemh`. The RV32I assembler (`gen_firmware.py`) covers the full ISA.

See `modules/` for manifests, HDL sources, and test scripts.

## Quick Start

Minimum host requirements:

- Python 3.11 or newer
- [nosis](https://github.com/CharlesCNorton/nosis) installed into the same Python (`python -m nosis` must resolve) — the synthesizer
- [OSS CAD Suite](https://github.com/YosysHQ/oss-cad-suite-build) providing `nextpnr-ecp5`, `ecppack`, and `openFPGALoader`

Install the package (runtime deps only):

```
python -m pip install -e .
```

For development (pytest, hypothesis, ruff, mypy, pre-commit):

```
python -m pip install -e ".[dev]"
pre-commit install
```

### First Success Path

```
python icepi_helper.py status
python icepi_helper.py doctor
python icepi_helper.py build rime --clean
python icepi_helper.py install rime --slot boot --reload --build
```

### Board Modes

The IcePi Zero shares one USB-C port between UART (serial communication) and JTAG (bitstream loading). Only one mode is active at a time.

- **UART mode**: all RIME commands. Normal operating mode.
- **JTAG mode**: `openFPGALoader` for SRAM loads and QSPI bootstrap. Only needed for first-time setup, disaster recovery, or standalone experiment bitstreams.

```
python icepi_admin.py uart     # switch to UART
python icepi_admin.py jtag     # switch to JTAG
```

The UART-only workflow avoids driver switching entirely.

## Service Mode

RIME boots to an app shell that responds to HELLO, PING, UPTIME, and IDENTITY. Service commands are gated until `ENTER_SERVICE` switches to service mode. `EXIT_SERVICE` returns to app mode.

Destructive operations (ERASE64, PROGRAM16) carry an inline 4-byte `RIME` key prefix (`0x52 0x49 0x4D 0x45`) in every request payload. There is no stateful unlock or expiry timer. The legacy `UNLOCK` command (0x03) is retained as a benign no-op for backwards compatibility.

See `PROTOCOL.md` for the full command specification.

## Update Paths

### Full SDRAM-Staged Update (default)

Stream the entire bitstream into SDRAM in 4 KB blocks over UART, then commit to flash at SPI speed with on-board verify. See [`docs/THROUGHPUT.md`](docs/THROUGHPUT.md) for current measured timings.

### Chunked SDRAM Update (fallback)

For boards with row-aliased SDRAM, repeatedly stream 1 KB to SDRAM word 0 and commit that chunk to flash. Slower than full staging but robust against SDRAM row failures. `upload_bitstream()` automatically falls back to this path if the staged path fails.

### SD Bundle Install

Stage a RIME bundle to raw SD space, then `sd-install <lba>` installs it through the resident service. The install engine (`sd_install_engine.sv`) parses the bundle header, erases flash sectors, and programs from SD — all on-board.

### Auto-Recovery

The boot image includes an auto-recovery FSM that checks SD LBA 1 for an armed control block on every boot. If armed, it initializes SD, reads the control block, validates the checksum, and installs the staged bundle to flash. Any UART byte during recovery aborts to the app shell (the operator is never locked out).

```
python icepi_helper.py sd-auto-arm rime --slot boot --build
```

### Bootstrap / Recovery

```
python icepi_admin.py flash-qspi rime
```

JTAG path. For when the resident service is absent or suspect.

## Flash Layout

`config/icepi-layout.json` defines named flash slots, aliases, and bundle geometry.

| Slot | Offset | Size | Purpose |
|------|--------|------|---------|
| `boot` | 0x000000 | 1 MB | Resident boot image |
| `backup` | 0x100000 | 1 MB | Secondary firmware slot |
| `staging` | 0x200000 | 1 MB | Alternate firmware slot |
| `scratch` | 0x300000 | 13 MB | Data, manifests, experiments |

## Composing Modules

```
# Validate resource budget without building
python icepi_helper.py compose anvil cairn scry --validate-only

# Compose and build
python icepi_helper.py compose anvil cairn scry --clean
```

The compositor assigns each module an address region (0x30-0x3F) and generates a complete SoC wrapper with RIME-I, BRAM, UART, and N module instantiations.

## Configuration

Board selection and tool resolution come from command-line flags, environment variables, or `config/board.local.json`.

| Variable | Purpose |
|----------|---------|
| `ICEPI_ROOT` | Repository root override |
| `ICEPI_LAYOUT_FILE` | Path to flash layout JSON |
| `ICEPI_BOARD_CONFIG` | Path to local board identity JSON |
| `ICEPI_USB_SERIAL` | USB serial number filter |
| `ICEPI_BAUD` | UART baud rate (default 115200) |
| `ICEPI_OSS_CAD_ROOT` | OSS CAD Suite root directory |
| `ICEPI_PYTHON` | Python interpreter override |

## Build System

`nosis` → `nextpnr-ecp5` → `ecppack`. The build system auto-detects module dependencies by scanning instantiated names in project sources. Per-project `build.json` provides custom nextpnr flags.

```
python icepi_helper.py build --list
python icepi_helper.py build rime --clean
python icepi_helper.py build ember --clean
```

## Testing

RIME has two regression suites that cover different failure domains and neither subsumes the other.

**Board regression** (UART):

```
python tests/regression.py           # single CRC through every subsystem
python tests/regression.py --verbose  # show per-step accumulator detail
```

Threads a CRC-32 accumulator through: app shell round-trip, protocol core, DEBUG state verification, CRC-8 frame integrity, JEDEC 3x, flash write/verify, stale-read regression, deployed-bitstream match, SDRAM round-trip, row isolation, 64-chunk fill, flash/SDRAM crosstalk, bank isolation, row boundary, 9-bit column stress, row errata scan, stream 16/64/32K, timeout recovery, 1024-byte chunked commit with host and on-board verify, SD read/write/CRC, SD_INSTALL bundle round-trip, auto-recovery arm/disarm, SD_CRC32_RANGE, staged upload with wipe_slot, address clamping, SET_WATCHDOG, inline-key rejection, 8-byte error frames, RAW_WRITE row boundary, throughput bench, and janitor cleanup. Output: one hex value (`CHAIN:XXXXXXXX`). If any subsystem fails, the chain diverges and the value is wrong.

**Themed composition regressions** (JTAG):

Seven themed composition images, each with a verification firmware that exercises every module in the image and prints labelled readbacks for host-side comparison against a Python predictor.

| Image | Modules | Regression |
|-------|---------|------------|
| compute (Gauntlet) | divide, crank, sieve, tally, taper, weave, bloom, mortar, logos, flip, lace, orbit, mount, glyph | `gauntlet.py` |
| crypto (Seal) | vault, forge, vigil, grail, anvil, sigma, emberlite, mark, sentry, churn, hammer, proof, etch, seed | `seal.py` |
| control (Servo) | tide, chord, flux, oracle, epoch, latch, hedge, notch, phase, tempo | `servo.py` |
| data (Ingest) | apex, axiom, cairn, codec, delta, halo, mirror, pack, parse, quill, rank, rune, sift | `ingest.py` |
| observe (Profile) | scry, echo, heat, epoch, latch, prism, trap, gauge, depth | `profile.py` |
| sim (Evolve) | augur, cell, dice, emberlite, epoch, flock, flux, haze, moss, oracle, spark | `evolve.py` |
| peripheral | clasp, gpio, pulse, spoke, wire, ferry, arbor, pylon, drum | `peripheral.py` |

```
python modules/compositions/gauntlet.py --gen-only    # generate firmware + sim
```

To run on silicon, generate the composition top.sv via `icepi.compose.compose()` then build with `icepi_helper.py build compositions --clean` and flash via JTAG.

**Module torture sweep** (offline, no hardware):

```
python modules/torture_sweep.py
```

Discovers every `test_*_torture.py` under `modules/`, calls each `gen()`, and verifies the firmware fits in the 1024-word BRAM. Any new module with a torture test is automatically included.

**Offline Python tests** (no hardware):

```
pip install -e ".[dev]"
python -m pytest tests/ --ignore=tests/regression.py -q
ruff check .
```

## Repository Surface

| File | Role |
|------|------|
| `icepi_helper.py` | CLI entry point: 40+ subcommands |
| `icepi/commands/` | Command implementations |
| `icepi_admin.py` | Admin wrapper: build, flash, driver switching, reload |
| `icepi/flash_service.py` | Board discovery, UART transport, protocol client |
| `icepi/compose.py` | N-way compositor: resource budgeting, top.sv generation |
| `icepi/build.py` | FPGA build pipeline: source collection, synthesis, P&R |
| `icepi/layout.py` | Flash layout loading, image planning |
| `icepi/bundle.py` | RIME bundle format: parse, validate, build |
| `icepi/sd.py` | SD partitions, FAT32 filesystem, auto-recovery control |
| `icepi/models.py` | Domain model dataclasses |
| `icepi/tools.py` | Path resolution, tool discovery, utilities |
| `PROTOCOL.md` | UART protocol specification |
| `config/icepi-layout.json` | Flash slot map, aliases, bundle geometry |
| `firmware/images/rime/top.sv` | RIME top module (clock, reset, SDRAM, SPI, SD, install engine, auto-recovery) |
| `firmware/images/rime/rime_service.sv` | UART protocol FSM (27 commands) |
| `firmware/images/rime/rime_service_defs.svh` | Extracted command IDs, capability flags, FSM state encodings |
| `firmware/images/ember/top.sv` | EMBER: hardware TRNG |
| `firmware/core/flash_spi_master.sv` | QSPI flash transaction engine |
| `firmware/core/sdram_controller.sv` | SDR SDRAM controller |
| `firmware/core/sdram_bridge.sv` | 16-byte bulk SDRAM bridge |
| `firmware/core/sd_spi_master.sv` | SD card SPI engine |
| `firmware/core/sd_install_engine.sv` | Firmware-mediated SD bundle installer |
| `firmware/core/auto_recovery.sv` | Boot-time auto-recovery FSM |
| `modules/rime-i/rime_i_core.sv` | RV32I CPU core |
| `modules/rime-i/gen_firmware.py` | RV32I assembler (38 instructions) |
| `modules/rime-i/test_isa.py` | ISA torture test (37 instructions, single hash) |
| `modules/compositor_template.py` | Shared top.sv generator for module tests |
| `modules/compositor_test.py` | Shared test runner (build, JTAG flash, serial read) |
| `modules/torture_gen.py` | Hash-based torture test generator framework |
| `tests/regression.py` | Board regression: CRC chain through every subsystem |
| `modules/compositions/{gauntlet,seal,servo,ingest,profile,evolve,peripheral}.py` | Seven themed composition silicon regressions |
| `modules/compositions/verify.py` | Host verifier: build, flash, read, compare against Python predictor |
| `modules/torture_sweep.py` | Offline torture sweep over all module tests |
| `tests/test_protocol.py` | Offline protocol tests (MockSerial + Hypothesis) |
| `tests/test_*.py` | Offline Python tests |

## Architecture

See [`docs/ARCHITECTURE.md`](docs/ARCHITECTURE.md) for the layer diagram, data paths, FSM state table, bus muxing, module bus protocol, and test suite overview.

## Contributing

See [`CONTRIBUTING.md`](CONTRIBUTING.md) for setup, module creation, register interface, and code style.

## Known Limitations

- The `$signed() >>> shamt` operator has a history of miscompiling for ECP5. The CPU uses explicit sign-extension masks instead.
- BRAM INITVAL propagation via `$readmemh` works for `initial` blocks but not for runtime loading. The `ecpbram` patch path exists for firmware-only updates without re-synthesis.

The `(* keep *)` annotations in `top.sv` and `rime_service.sv` are retained as a backstop against constant-folding of signals involved in combinational cycles; the primary fix was removing `svc_reset_req` and `wdog_reset` from the `rst` wire (which created a feedback cycle through `rime_service`). CRC-8 frame integrity is computed inside the service FSM to avoid a similar feedback cycle through the top-level inject chain.

## License

MIT.
