wifi-densepose/firmware/esp32-csi-node/components/ruv_temporal
ruv 3a5fe5e0de feat(firmware): mirror weight-blob parser into ruv_temporal (#513)
Closes the format contract on the firmware side. Source-only — Phase 5
toolchain blocker still prevents actually compiling, but when it
unblocks this is one less thing to write under time pressure.

- src/weights.rs — no_std mirror of v2/.../weights.rs. Same magic
  ('RVNE'), same version 1, same CRC32-IEEE polynomial (matches the C
  side in temporal_task.c). Bit-for-bit lockstep with the host: a
  blob produced by host WeightBlob::serialize() parses here as a
  WeightBlobView byte-for-byte.

  Borrowed-slice parse design: the firmware loader receives weights
  via mmap'd EMBED_FILES or NVS read into a heap buffer. The parser
  takes &[u8] with no copy — view fields point into the caller's
  buffer. Caller is responsible for keeping the buffer alive for the
  view's lifetime.

  Loader errors map to esp_err_t-style codes via
  weight_load_err_to_esp() so the C ABI can surface specific failure
  modes (ESP_ERR_INVALID_ARG for magic/version/size, ESP_ERR_INVALID_CRC
  for corruption, ESP_ERR_INVALID_SIZE for shape validation failures).

- src/lib.rs — ruv_temporal_init now optionally validates a non-NULL
  weights blob. NULL pointer is still allowed during the Phase 4/5
  bring-up window (kernel forward isn't actually consuming weights
  yet), but when caller passes a real blob we parse + sanity-check
  declared dims against runtime arguments. Catches deploy bugs at
  init() rather than at first classify() — the firmware Tmr Svc work
  in v0.6.4 taught us that classify-time crashes are the worst kind.

- README.md — Phase 6 marked done (verified by 8MB firmware build with
  feature off in commit 7994af822). Added module map table covering
  lib.rs / window.rs / weights.rs / ruv_temporal.h / shim.c.

What's deliberately NOT in this commit:
  - Cross-compile validation. Same toolchain blocker as before.
  - Kernel-side wiring of weights into the forward pass. That's
    Phase 6+ of the firmware roadmap — once the kernel is wired,
    weights become a required arg, not an optional one.
  - Tests on the firmware side. They'd need build-std working to run;
    16/16 host tests cover the format end-to-end via the lockstep
    polynomial.

Co-Authored-By: claude-flow <ruv@ruv.net>
2026-05-08 11:53:19 -04:00
..
.cargo feat(firmware): scaffold ruv_temporal ESP-IDF Rust component (ADR-095 Phase 4, #513) 2026-05-08 09:44:01 -04:00
include feat(firmware): scaffold ruv_temporal ESP-IDF Rust component (ADR-095 Phase 4, #513) 2026-05-08 09:44:01 -04:00
src feat(firmware): mirror weight-blob parser into ruv_temporal (#513) 2026-05-08 11:53:19 -04:00
CMakeLists.txt feat(firmware): wire temporal_task.c + Kconfig + ruv_temporal component (Phase 6, #513) 2026-05-08 11:28:11 -04:00
Cargo.lock feat(firmware): scaffold ruv_temporal ESP-IDF Rust component (ADR-095 Phase 4, #513) 2026-05-08 09:44:01 -04:00
Cargo.toml feat(firmware): scaffold ruv_temporal ESP-IDF Rust component (ADR-095 Phase 4, #513) 2026-05-08 09:44:01 -04:00
README.md feat(firmware): mirror weight-blob parser into ruv_temporal (#513) 2026-05-08 11:53:19 -04:00
rust-toolchain.toml feat(firmware): scaffold ruv_temporal ESP-IDF Rust component (ADR-095 Phase 4, #513) 2026-05-08 09:44:01 -04:00
shim.c feat(firmware): scaffold ruv_temporal ESP-IDF Rust component (ADR-095 Phase 4, #513) 2026-05-08 09:44:01 -04:00

README.md

ruv_temporal — ESP32-S3 on-device temporal head

ESP-IDF component implementing ADR-095 (#513). The Rust staticlib at src/lib.rs wraps ruvllm_sparse_attention (vendored at vendor/ruvector/crates/ruvllm_sparse_attention) and exposes a narrow C ABI declared in include/ruv_temporal.h.

Status

Phase Scope State
4 — Scaffold Cargo.toml, src/{lib.rs,window.rs,weights.rs}, include/ruv_temporal.h, CMakeLists.txt, .cargo/config.toml Done.
5 — Cross-compile cargo +esp build --release --target xtensa-esp32s3-none-elf produces libruv_temporal.a. Blocked — see below.
6 — Wire from edge_processing.c FreeRTOS task on Core 1, queue from adaptive_controller fast loop, push() in fast tick, classify() at 1 Hz, emit 0xC5110007 packet. Done in main/temporal_task.c (no-op shim path verified by 8MB firmware build with feature off).
7 — COM8 validation Flash 8MB build with CONFIG_CSI_TEMPORAL_HEAD_ENABLED=y, soak ≥5 min, check no Tmr Svc / task_wdt overflow. Pending board reattach.

Module map

File Purpose
src/lib.rs C ABI: ruv_temporal_init / push / classify / destroy / kernel_self_test
src/window.rs FrameRing rolling buffer used by ruv_temporal_push
src/weights.rs Loader-side mirror of host wifi_densepose_temporal::weights. Parses the .rvne blob format (magic RVNE, version 1, FP32/FP16, CRC32-IEEE). Bit-exact with the host crate; a blob produced by the host's WeightBlob::serialize() parses here byte-for-byte.
include/ruv_temporal.h Public C header consumed by main/temporal_task.c
shim.c Empty C shim for idf_component_register

Phase 5 blocker — esp toolchain rust-src bug

The system esp toolchain at C:\Users\ruv\.rustup\toolchains\esp has no precompiled core for xtensa-esp32s3-none-elf. It requires -Z build-std=core,alloc, but the bundled rust-src snapshot (esp channel, nightly 2025-09-16) hits two known bugs when build-std compiles core:

  1. library/portable-simd/crates/core_simd/src/simd/ptr/mut_ptr.rsCopy trait and size_of not in scope, ~16,000 errors.
  2. library/core itself — "cannot resolve a prelude import", "attributes starting with rustc are reserved", concat! macro not found.

These are upstream Rust nightly snapshot regressions, not anything this component is doing wrong. The fix is to refresh the esp toolchain to a newer nightly:

C:/Users/ruv/.cargo/bin/espup.exe install
# (re-source export-esp.ps1 / export-esp.sh after install)

espup install pulls the latest pinned esp Rust + LLVM. It is a ~1.5 GB download and ~5-10 min install. That step lands in the next loop iteration of #513 implementation work.

Build (once Phase 5 unblocks)

From this directory:

cargo +esp build --release --target xtensa-esp32s3-none-elf

Output: target/xtensa-esp32s3-none-elf/release/libruv_temporal.a.

ESP-IDF's idf.py build will pick this up via CMakeLists.txtadd_custom_command runs the cargo build before idf_component_register consumes the static library.

C ABI summary

esp_err_t ruv_temporal_init(const uint8_t *weights, size_t wlen,
                            uint32_t input_dim, uint32_t window_len,
                            uint32_t n_classes,
                            ruv_temporal_ctx_t **out_ctx);
esp_err_t ruv_temporal_push(ruv_temporal_ctx_t *ctx, const float *frame);
esp_err_t ruv_temporal_classify(ruv_temporal_ctx_t *ctx,
                                float *logits, uint32_t n_classes);
void      ruv_temporal_destroy(ruv_temporal_ctx_t *ctx);
esp_err_t ruv_temporal_kernel_self_test(void);

Threading: caller is responsible. Per ADR-095 §3.3, the firmware will spawn a single dedicated FreeRTOS task that owns the context and serialises all calls — push() and classify() are not internally synchronised.