Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 54 additions & 0 deletions tools/cascade-differential/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# Cascade differential — SIL reference vs lowered ARM

DD-026 P2 splits the proof: **relay proves the loop closes in source (SIL); jess proves it
still closes after lowering.** This directory is jess's half. It is *not* a second simulator —
relay owns SIL. What jess needs is a **single discriminating vector** with a known-good answer,
so the lowered ARM can be checked against it.

## `sil_reference.py` — DONE

Runs the **same fused core module that synth lowers**, in wasmtime. One module, two backends:
any divergence is a lowering defect, not a modelling difference.

```
rate@0.7.0#tick : (param i32) -> (result i32) # canonical ABI, pointer in / pointer out
arg -> vehicle-state (14 x f32, 56 B) ++ rate-setpoint (4 x f32, 16 B)
ret -> torque-setpoint (4 x f32, 16 B)
```

**Reference vector** (deliberately asymmetric — body rates distinct on all three axes; a
symmetric or all-zero input would be a vacuous differential, reproducible by a miscompile that
drops terms):

```
state qw=1 qx=qy=qz=0 · pos 0,0,-2.5 · vel 0.1,-0.2,0.05 · w 0.30,-0.15,0.07 · innov 0
sp rx=1.0 ry=0 rz=0 thrust=0.5 (1 rad/s step about x, matching relay's step test)

TORQUE tx=1 ty=0.472507507 tz=-0.147003502 thrust=0.5
hex 3F800000 3EF1EC81 BE168816 3F000000
```

`ty` and `tz` are non-obvious functions of the rate error across three axes — that is what makes
this vector discriminating rather than decorative.

## `arm_invoke_ATTEMPT.robot` — NOT WORKING, kept for the next attempt

Invoking the same export on the lowered ARM image in Renode. **What is established:**

- entry `pulseengine:falcon-cascade/rate@0.7.0#tick` at `0x7a4`
- AAPCS: **`r0` = argument pointer** (`str.w r0, [sp, #152]`), result in `r0`
- **`r9` = globals-table base**, set by the reset handler to `0x20010100` — *verified live in
Renode*, so the export cannot be called before boot has run
- reset handler: init linear memory (53,345 B → `0x20000100`) → set `r9` → init globals →
`blx` the first export → spin at `0x16c`
- linear-memory mapping: wasm `0x2280` → ARM `0x20002380`

**The blocker:** after `emulation RunFor` reaches the spin at `0x16c`, setting `cpu PC` to the
export entry and resuming (via `RunFor` or `cpu Step`) does not execute — PC stays at `0x7a4`
and `r0` is unchanged. Tried with and without the thumb bit. This is Renode CPU-resume plumbing,
not a falcon or lowering problem.

Next attempt should try: driving the call from the image itself (a small harness export that
calls `tick` with a fixed pointer and stores the result at a known address, so no PC redirect is
needed) — that sidesteps the resume issue entirely and is closer to how the on-target test will
have to work anyway.
42 changes: 42 additions & 0 deletions tools/cascade-differential/arm_invoke_ATTEMPT.robot
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
*** Settings ***
Resource ${RENODEKEYWORDS}
*** Test Cases ***
Invoke rate tick on ARM
Execute Command mach create "inv"
Execute Command machine LoadPlatformDescription @/Volumes/Home/git/pulseengine/jess/hardware/renode/pixhawk6xrt.repl
Execute Command sysbus LoadELF @/Volumes/Home/git/pulseengine/jess/hardware/renode/cascade-m7/falcon-cascade-m7.elf
# boot: init linmem, set r9, init globals, then spin at 0x16c
Execute Command emulation RunFor "0.05"
${pc}= Execute Command cpu PC
Log To Console BOOTED_PC=${pc}
${r9}= Execute Command cpu GetRegister 9
Log To Console R9=${r9}
# write the SAME argument vector the wasmtime reference used, at linmem+0x2280
# vehicle-state: qw..innovation (14 f32), then rate-setpoint (4 f32)
Execute Command sysbus WriteDoubleWord 0x20002380 0x3F800000
Execute Command sysbus WriteDoubleWord 0x20002384 0x00000000
Execute Command sysbus WriteDoubleWord 0x20002388 0x00000000
Execute Command sysbus WriteDoubleWord 0x2000238C 0x00000000
Execute Command sysbus WriteDoubleWord 0x20002390 0x00000000
Execute Command sysbus WriteDoubleWord 0x20002394 0x00000000
Execute Command sysbus WriteDoubleWord 0x20002398 0xC0200000
Execute Command sysbus WriteDoubleWord 0x2000239C 0x3DCCCCCD
Execute Command sysbus WriteDoubleWord 0x200023A0 0xBE4CCCCD
Execute Command sysbus WriteDoubleWord 0x200023A4 0x3D4CCCCD
Execute Command sysbus WriteDoubleWord 0x200023A8 0x3E99999A
Execute Command sysbus WriteDoubleWord 0x200023AC 0xBE19999A
Execute Command sysbus WriteDoubleWord 0x200023B0 0x3D8F5C29
Execute Command sysbus WriteDoubleWord 0x200023B4 0x00000000
Execute Command sysbus WriteDoubleWord 0x200023B8 0x3F800000
Execute Command sysbus WriteDoubleWord 0x200023BC 0x00000000
Execute Command sysbus WriteDoubleWord 0x200023C0 0x00000000
Execute Command sysbus WriteDoubleWord 0x200023C4 0x3F000000
# call rate#tick(0x2280) — wasm-relative pointer, return to the spin
Execute Command cpu SetRegister 0 0x2280
Execute Command cpu LR 0x16D
Execute Command cpu PC 0x7A4
Execute Command cpu Step 200000
${pc2}= Execute Command cpu PC
Log To Console AFTER_PC=${pc2}
${r0}= Execute Command cpu GetRegister 0
Log To Console RET_R0=${r0}
57 changes: 57 additions & 0 deletions tools/cascade-differential/sil_reference.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
#!/usr/bin/env python3
"""SIL reference for the falcon rate loop — the number the LOWERED ARM must match.

Runs the SAME fused core module that synth lowers, in wasmtime. That is the point:
one module, two backends. Any divergence is a lowering defect, not a modelling
difference — which is exactly what jess owes under DD-026 P2 (relay proves the loop
closes in source; jess proves it still closes after lowering).

Canonical ABI of the export, from the WIT + the lowered signature:
rate@0.7.0#tick : (param i32) -> (result i32)
arg ptr -> vehicle-state (14 x f32, 56 B) followed by rate-setpoint (4 x f32, 16 B)
ret ptr -> torque-setpoint (4 x f32, 16 B)
"""
import struct, sys
from wasmtime import Store, Module, Instance

MODULE = sys.argv[1] if len(sys.argv) > 1 else ".scratch/v1341/casc_new.loom.wasm"
EXPORT = "pulseengine:falcon-cascade/rate@0.7.0#tick"

# One deliberately non-symmetric test vector. Symmetric or all-zero inputs are a
# vacuous differential: they can be reproduced by a miscompile that drops terms.
VEHICLE_STATE = [
1.0, 0.0, 0.0, 0.0, # qw qx qy qz (level attitude)
0.0, 0.0, -2.5, # pos n e d
0.1, -0.2, 0.05, # vel n e d
0.30, -0.15, 0.07, # wx wy wz <- distinct body rates, all three axes
0.0, # innovation
]
RATE_SETPOINT = [1.0, 0.0, 0.0, 0.5] # 1 rad/s about x, matching relay's step test


def main():
store = Store()
inst = Instance(store, Module.from_file(store.engine, MODULE), [])
mem = inst.exports(store)["memory"]
tick = inst.exports(store)[EXPORT]

# scratch above the module's own static data
heap_base = inst.exports(store)["__heap_base"].value(store)
argp = (heap_base + 0xF) & ~0xF

buf = struct.pack("<14f", *VEHICLE_STATE) + struct.pack("<4f", *RATE_SETPOINT)
mem.write(store, buf, argp)

retp = tick(store, argp)
tx, ty, tz, thrust = struct.unpack("<4f", mem.read(store, retp, retp + 16))

print(f" module {MODULE}")
print(f" arg ptr 0x{argp:08X} (heap_base 0x{heap_base:08X})")
print(f" ret ptr 0x{retp:08X}")
print(f" torque tx={tx:.9g} ty={ty:.9g} tz={tz:.9g} thrust={thrust:.9g}")
print(f" raw hex {' '.join(f'{w:08X}' for w in struct.unpack('<4I', mem.read(store, retp, retp+16)))}")
return 0


if __name__ == "__main__":
sys.exit(main())
Loading