7 filters — linear Kalman, matrix EKF, quaternion AHRS — each proven (Lean, sorryAx-free), simulated as a working filter on the real Forge-emitted kernels, and run on silicon (Verilog/Verilator bit-exact). One math source per kernel; no numpy in any estimator.
The flagship. A scalar Kalman filter carrying state across measurements (P ← P·r/(P+r) — a division in the feedback path), proven MMSE-optimal and fixed-point-error-bounded, running bit-exact and tracking at 100 MHz on a real Arty A7-100T.
32 compiled targets
kalman_recursive_mmse ✓ clean — each recursive step's posterior mean is MMSE-optimal (conjugacy self-composition) axiom trailkalman_variance_recursion_fixed_point ✓ clean — the Q16.16 variance recursion stays within a proven bound of the exact recursion over N steps axiom trailkalman_estimate_recursion_coupled ✓ clean — the estimate recursion's fixed-point error is bounded — the gain error derived from the variance error axiom trail| quantity | result | |
|---|---|---|
| filter RMS vs measurement RMS | 0.071 / 0.325 ratio 0.22 < 1 | ✓ |
| estimate converges to truth | |2.4812 − 2.5| = 0.0188 < 0.10 | ✓ |
| posterior variance shrinks | 1.000 → 0.01538 < 0.05 | ✓ |
on a real Arty A7-100T at 100 MHz, whole-design — eml_reciprocal +1.102 ns, kalman_update_1d +0.587 ns, kalman_filter_step +0.866 ns. all three anchors close 100 MHz WHOLE-DESIGN (0 failing endpoints, DRC clean); 170 passes bit-exact to the golden recursion, the estimate tracks and P shrinks 1.0 → 0.015
The capstone: measurement update + Joseph covariance update, looped with (x,P) state feedback, all through shared MACs + one reciprocal. It tracks a target with the covariance shrinking, and the WHOLE recursive filter closes 100 MHz on a real Arty A7-100T in 32 DSP48s — 13% of the device, less than a third of what the unrolled measurement update alone cost. Scope (graded honestly): the two halves are machine-checked (forward-error bound + MMSE-optimality + Joseph PSD); the composition into 'implemented ≈ optimal' is ARGUED, not yet a Lean theorem; and the shared scheduler/control is verified BIT-EXACT (301 silicon frames, Verilator shared==unrolled), not proven.
15 compiled targets
kalman2_state_update_fxerr ✓ clean — the fixed-point measurement update x'=x+K(z-x) is forward-error bounded through the inverse axiom trailmatrix2_posterior_mean_mmse ✓ clean — the posterior-mean estimate is MMSE-optimal (trace loss, per-component) axiom trailkalman2_joseph_psd ✓ clean — the Joseph covariance step keeps P symmetric PSD for any gain — so recursing it is safe axiom trail| quantity | result | |
|---|---|---|
| estimate converges to target | |err| = 0.0532 < 0.10 | ✓ |
| covariance trace shrinks monotonically | 0.4000 → 0.0244 monotone ↓ yes | ✓ |
| final trace(P) vs initial | 0.0244 / 0.4000 < 0.1000 | ✓ |
on a real Arty A7-100T at 100 MHz, whole-design — kalman2d_filter +0.715 ns. 32 DSP48s for the WHOLE recursive filter (24 reciprocal + two shared MACs) = 13% of the device, vs 100 for the unrolled measurement update ALONE; 8/8 trajectory steps bit-exact, 247 cycles/step, trace(P) 0.39 → 0.06.
The numerically-robust covariance step P+ = (I-K)P(I-K)^T + K R K^T — four 2x2 matrix products — time-multiplexed through ONE shared MAC. Proven to stay PSD for any gain (so recursing it is safe), and the whole four-product update costs 4 DSP48s on real fabric at 100 MHz (vs ~128 unrolled).
14 compiled targets
kalman2_joseph_psd ✓ clean — P+ = (I-K)P(I-K)^T + K R K^T stays symmetric positive-semidefinite for ANY gain K axiom trailfxerr_dot2_fullwidth ✓ clean — the full-width scheduled dot's forward error is a single rounding (<= 2^-FRAC per dot) axiom trail| quantity | result | |
|---|---|---|
| P+ stays PSD over 500 random (P,R,K) | all PSD = PSD | ✓ |
| P+ off-diagonal symmetry (max |P01-P10|) | 6.10e-05 < 1e-3 | ✓ |
on a real Arty A7-100T at 100 MHz, whole-design — joseph2_update +1.350 ns. 4 DSP48s for the whole four-product update (vs ~128 unrolled); 6/6 cases bit-exact; closes 100 MHz after registering the shared operand mux.
A matrix multiply time-multiplexed through ONE shared multiplier instead of unrolling to N*K*P of them. Bit-identical to the unrolled matmul (so no separate proof — the win is structural and measured): on real fabric the whole product costs 4 DSP48s, and the SAME 4 for a 2x2 and a 3x3 — flat in matrix size.
14 compiled targets
| quantity | result | |
|---|---|---|
| shared-MAC matmul vs exact real product (max cell error) | 2.01e-05 < 1e-3 | ✓ |
on a real Arty A7-100T at 100 MHz, whole-design — matmul2_pipeline (2x2) +1.693 ns. 4 DSP48s for the 2x2 AND the 3x3 — flat in matrix size (vs ~32 / ~108 unrolled, an 8x / 27x collapse); 6/6 cases bit-exact over UART.
A textbook predict→update Kalman filter — cuts position noise and recovers the unmeasured velocity from position measurements alone.
32 compiled targets
kalman2d_cov_predict_psd ✓ clean — the predicted covariance F·P·Fᵀ+Q stays positive-semidefinite axiom trailkalman2d_joseph_psd ✓ clean — the Joseph covariance update preserves PSD for any gain axiom trail| quantity | result | |
|---|---|---|
| filter RMS vs measurement RMS | 0.525 / 0.934 ratio 0.56 < 0.6 | ✓ |
| recovered velocity (never measured) | 0.516 ≈ 0.5 | ✓ |
| max |filter − NumPy-KF| | 1.1e-16 < 1e-9 | ✓ |
kalman2d_predict.cov_predict — Verilog/Verilator bit-exact
A nonlinear Extended Kalman Filter — range-only trilateration to two beacons; the gain K = P·Hᵀ·(H·P·Hᵀ+R)⁻¹ exercises the matrix-inverse operator.
100 compiled targets
ekf_range_nonneg ✓ clean — the range measurement √((lx-x)²+(ly-y)²) is ≥ 0 axiom trailkalman2d_innovation_cov_psd ✓ clean — the innovation covariance H·P·Hᵀ+R stays positive-semidefinite axiom trailkalman2d_matrix_joseph_psd ✓ clean — the Joseph covariance update A·P·Aᵀ+W preserves PSD for any gain axiom trail| quantity | result | |
|---|---|---|
| EKF position RMSE vs dead-reckoning | 0.479 / 2.993 ratio 0.16 < 0.6 | ✓ |
| max |EKF − NumPy-EKF| | 3.6e-15 < 1e-9 | ✓ |
complementary_filter.fuse, weighted_fusion.fuse3, kalman2d_update_matrix.update_cov — Verilog/Verilator bit-exact
A complementary attitude filter fusing a biased gyroscope with an accelerometer; every quaternion op is a shipped kernel and the state is re-normalised onto the unit sphere each step.
18 compiled targets
quat_normalize_unit ✓ clean — ‖normalize(q)‖² = 1 — the state stays a unit quaternion (sorryAx-free) axiom trail| quantity | result | |
|---|---|---|
| Mahony attitude error vs gyro-only | 2.38° / 18.44° ratio 0.13 < 0.3 | ✓ |
| max |‖q‖ − 1| over the run | 2.2e-16 < 1e-12 | ✓ |
quat_mul, quat_rotate_vec — Verilog/Verilator bit-exact