Skip to content

Navigation Menu

Sign in
Sign up

feature: gust:hal read32/write32 performs REAL MMIO on STM32F100 silicon — H3 first half (AFD-109) - #252

Merged
avrabe merged 1 commit into
main from
feat/gust-hal-on-silicon
Sep 7, 2026
Merged

feature: gust:hal read32/write32 performs REAL MMIO on STM32F100 silicon — H3 first half (AFD-109) #252
avrabe merged 1 commit into
main from
feat/gust-hal-on-silicon

Conversation

@avrabe

@avrabe avrabe commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

H3, first half — on silicon

app/gust-hal-stub returns addr ^ 0x1E55_0000 for read32 and drops write32. Its own
comment says: "No silicon off-target. Real MMIO is the on-target rung, not this one." This is
that rung.

probe.wat imports gust:hal/mmio; synth lowers those imports to undefined symbols
read32 / write32 — exactly the embedder seam jess owns. The native side is two volatile
accesses:

read32: ldr r0, [r0, #0] ; bx lr
write32: str r1, [r0, #0] ; bx lr

compiled -ffixed-r9 -ffixed-r10 -ffixed-r11 (it runs between synth-lowered calls holding the
linmem base/size and globals table) and gated by verify-embedder, which inspects the emitted
code rather than trusting the flags.

Result — STM32VLDISCOVERY (STM32F100, Cortex-M3), ST-LINK/V1, fourpi bench

LEG (only the CLOCK_EN word differs) IDCODE GPIOC_ODR completion
 GPIOC clocked (baseline) 10016420 00000300 c0ffee00 OK
 clock NOT enabled (negative control) 10016420 00000000 c0ffee00 OK

IDCODE = 0x10016420DEV_ID[11:0] = 0x420, the STM32F100 medium-density value line.
That number appears nowhere in the program, and the stub would have returned
0xE0042000 ^ 0x1E55_0000 = 0xFE512000. A read that never reached the bus cannot produce it.

Why this control is stronger than the usual shape

The varied quantity is physical, not a flag jess interprets: without RCC_APB2ENR.IOPCEN
the GPIOC peripheral is unclocked and the bus drops the writes.

  • Both legs run the byte-identical flashed image; one host-written word differs.
  • IDCODE reads correctly in both legs, so the control isolates the write path rather than
    breaking everything — a control that killed both paths would only show "something broke".
  • The completion marker is in both legs: the control measured "writes dropped", not "the CPU
    never ran".
  • Result words are poisoned 0xDEADBEEF at reset, so the 0 read-back is a real zero.

build.sh also asserts both symbols are undefined in the lowered object before linking, with a CI
negative control (a probe with no gust:hal imports must be refused — and refused for that
reason
). Without it, a probe that had quietly stopped calling the HAL would still build and the
rung would measure nothing.

H3's second half — measured, not done

Against the pinned gale-nano 0.7.0 (sha256 546531952a5c...), synth 0.60.0:

cortex-m3 / m4f / m7dp exit 0, 0 skips, 4775 / 4779 / 4779 B

confirming "lowers 0 skips on all 3 cores" — but only with --embedder-data-init /
--embedder-global-init.
Without them synth correctly refuses (#1041: 11 active data
segments). My first run omitted them, exited 1 on all three cores, and reporting that as
"gale-nano does not lower" would have been a false report against a supplier. Reading the error
instead of the exit code is what caught it.

Derived residual — what jess still owes to run gale-nano on target:

symbol interface status
read32 gust:hal/mmio supplied for real by this PR
poll-task gust:os/taskdisp gale's lane (polarity measured in AFD-067)
deadline, set-deadline, slept-status, state timer / task state gated on DD-025, gale#224

Four names carry hyphens (WIT kebab-case emitted verbatim), so a C embedder cannot declare them —
GCC's __asm__("poll-task") emits an unquoted .type poll-task, %function and the assembler
rejects it. Not reported upstream as friction: jess already uses objcopy --redefine-sym for
this on the export side in cascade-invoke/build.sh. A quoted symbol in a hand-written .S also
works. Both verified so the next rung doesn't rediscover them.

Scope

  • Executed on silicon: read32 / write32 against real peripherals, Cortex-M3.
  • Not executed: gale-nano itself — it lowers and its residual is derived, but nothing has run
    it. Nothing on the RT1176; the debug adapter is in transit.

Recovery: run-on-silicon.sh verifies the backup against its recorded sha256 and refuses to
flash
on mismatch, and self-claims the ST-Link through with-device.

...con — H3 first half (AFD-109)
app/gust-hal-stub returns `addr ^ 0x1E55_0000` and drops write32. Its own comment
says "No silicon off-target. Real MMIO is the on-target rung, not this one." This
is that rung.
probe.wat imports gust:hal/mmio; synth lowers those to UNDEFINED symbols
read32/write32 — the embedder seam jess owns. The native side is two volatile
accesses, compiled -ffixed-r9/-r10/-r11 because it runs between synth-lowered
calls holding the linmem base/size and globals table, and gated by verify-embedder.
On real silicon (STM32VLDISCOVERY, ST-LINK/V1, fourpi bench):
 LEG (only the CLOCK_EN word differs) IDCODE GPIOC_ODR completion
 GPIOC clocked (baseline) 10016420 00000300 c0ffee00 OK
 clock NOT enabled (negative control) 10016420 00000000 c0ffee00 OK
IDCODE 0x10016420 -> DEV_ID 0x420, the STM32F100 medium-density value line. That
number appears nowhere in the program; the stub would have returned 0xFE512000.
The control varies a PHYSICAL quantity, not a flag jess interprets: without
RCC_APB2ENR.IOPCEN the peripheral is unclocked and the bus drops the writes. And
IDCODE reads correctly in BOTH legs, so the control isolates the WRITE path rather
than breaking everything — a control that killed both paths would only show that
something broke.
build.sh asserts both symbols are undefined in the lowered object before linking,
with a CI negative control (a probe with no gust:hal imports must be refused, and
refused for that reason) — otherwise a probe that had stopped calling the HAL would
still build and the rung would measure nothing.
H3 second half is MEASURED, not done. Against the pinned gale-nano 0.7.0 it lowers
0 skips on all 3 cores (4775/4779/4779 B) — but only with --embedder-data-init and
--embedder-global-init. My first run omitted them, exited 1 on all three cores, and
reporting that as "gale-nano does not lower" would have been a false report against
a supplier. Reading the error instead of the exit code is what caught it.
Derived residual, by symbol: read32 (supplied for real here), poll-task, deadline,
set-deadline, slept-status, state. Four carry hyphens from WIT kebab-case, so a C
embedder cannot declare them — not reported upstream, because jess already uses
objcopy --redefine-sym for this on the export side.
SCOPE: executed on silicon = read32/write32 against real peripherals on Cortex-M3.
Not executed = gale-nano itself, and nothing on the RT1176.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
avrabe merged commit c7d95b7 into main Sep 7, 2026
9 checks passed
avrabe deleted the feat/gust-hal-on-silicon branch September 7, 2026 02:55
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

1 participant

AltStyle によって変換されたページ (->オリジナル) /