mirror of
https://github.com/ruvnet/RuView
synced 2026-08-09 20:21:43 +00:00
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>
This commit is contained in:
@@ -24,7 +24,9 @@ extern crate alloc;
|
||||
use alloc::boxed::Box;
|
||||
use core::ffi::c_void;
|
||||
|
||||
mod weights;
|
||||
mod window;
|
||||
use weights::{WeightBlobView, WeightLoadError};
|
||||
use window::FrameRing;
|
||||
|
||||
// ---- ESP-IDF compatible error codes ---------------------------------------
|
||||
@@ -87,8 +89,35 @@ pub extern "C" fn ruv_temporal_init(
|
||||
if out_ctx.is_null() || input_dim == 0 || window_len == 0 || n_classes == 0 {
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
// Phase 5: deserialize weights blob; Phase 4 just records the size.
|
||||
let _ = (weights, weights_len);
|
||||
|
||||
// Optional weights blob: when caller passes a non-NULL pointer,
|
||||
// parse and validate it. Caller can pass NULL during the Phase 4/5
|
||||
// bring-up window when the kernel forward isn't actually consuming
|
||||
// weights yet — we just want the parse path itself proven on the
|
||||
// device. Once Phase 5 unblocks and the kernel is wired, Phase 6
|
||||
// makes a non-NULL weights argument required.
|
||||
if !weights.is_null() && weights_len > 0 {
|
||||
// SAFETY: caller asserts the buffer covers `weights_len` bytes
|
||||
// and outlives this call. Borrowed-slice parse — no copy.
|
||||
let buf = unsafe { core::slice::from_raw_parts(weights, weights_len) };
|
||||
match WeightBlobView::parse(buf) {
|
||||
Ok(view) => {
|
||||
// Sanity-check that the blob's declared shape matches
|
||||
// the runtime arguments. A blob with input_dim=32 in
|
||||
// a context configured for input_dim=16 is a deploy bug
|
||||
// we want to catch at init() not at first classify().
|
||||
if view.header.input_dim as u32 != input_dim
|
||||
|| view.header.n_classes as u32 != n_classes
|
||||
{
|
||||
return ESP_ERR_INVALID_ARG;
|
||||
}
|
||||
// Phase 5+: stash view into the context for the kernel
|
||||
// to consume. For now the parse itself is the proof
|
||||
// that the format crossed the host/firmware boundary.
|
||||
}
|
||||
Err(e) => return weights::weight_load_err_to_esp(&e),
|
||||
}
|
||||
}
|
||||
|
||||
let ring = match FrameRing::new(window_len as usize, input_dim as usize) {
|
||||
Some(r) => r,
|
||||
|
||||
Reference in New Issue
Block a user