perf: walk-free C3 systimer — lazy counter + scheduled alarms via intmatrix routing#519
Merged
Conversation
…matrix routing Migrate the ESP32-C3 SYSTIMER off the per-cycle legacy walk. - SYSTIMER model (shared esp32s3/systimer.rs): attach the bus-published CycleClock and sync the OP-update snapshot from it on read (fresh counter without the walk); re-anchor on restore to avoid the stale-anchor freeze; add matrix_irq_sources() exposing the live pending&&INT_ENA level; add force_legacy_walk() for the differential reference. Alarm scheduling (take_scheduled_events/on_event) was already exact. - C3 routing arm: the SYSTIMER alarm now fires as a scheduled event delivered through the C3 interrupt matrix. apply_event_result routes scheduler-driven peripheral levels (esp32c3_sched_asserted_sources, re-derived from matrix_irq_sources) into riscv_irq_lines beside the Cortex-M system_exception arm; the walk-tick aggregation and recompute union the walk + scheduler bitmaps so the level persists across ticks and de-asserts on INT_CLR — same level semantics as the walk. - Flip the C3 ROM-boot SYSTIMER to scheduler mode; unpin it from the C3 walk-pinner ledger. Fidelity: byte-identical serial + total_cycles + SSD1306 framebuffer between SYSTIMER-on-walk and SYSTIMER-scheduler at BOTH tick interval 1 and 64 (oled_lab_systimer_walk_on_vs_scheduler_is_byte_identical); routing identity gated at the bus level (c3_systimer_matrix_routing).
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Migrate the ESP32-C3 SYSTIMER off the per-cycle legacy walk: lazy counter,
alarms as scheduled events, IRQ delivered through the C3 interrupt matrix.
Part of the walk-free C3 campaign (follows RTC #516, SysTick B1 #518).
What moved
SYSTIMER model (
peripherals/esp32s3/systimer.rs, shared S3/C3):CycleClock; the OP-update snapshot latch syncs thefree-running counter from it on read, so a counter read is fresh even though
the per-cycle walk no longer ticks the model (advance-only, no early alarm
eval). Re-anchor on
restore_runtime_snapshotto avoid the stale-anchorfreeze (the rtc_timer C3 walk-free: bus cycle clock (read-side freshness) + RTC lazy migration + write-choke IRQ aggregation #516 resume fix).
matrix_irq_sources()— the livepending && INT_ENAlevel, so the bus canre-derive the C3 matrix source set without the walk re-emitting it every tick.
force_legacy_walk()— pins the model back on the walk for the differentialreference. Alarm scheduling (
take_scheduled_events/on_event/next_alarm_delay_cycles) was already exact and unchanged.C3 interrupt-matrix routing arm — the missing piece:
apply_event_resultgains a C3 arm beside B1'ssystem_exceptionarm: on aC3 bus (
esp32c3_irq_routing) it re-derives every scheduler-drivenperipheral's live matrix level (
refresh_esp32c3_sched_sources) and folds itinto
riscv_irq_lines(recompute_esp32c3_irq_lines) at the exact firingcycle.
pend_irq_for_event(NVIC) would mis-route a matrix source ID as aCortex-M exception on RISC-V, so the NVIC path stays for non-C3 buses only.
esp32c3_sched_asserted_sources: the per-cycle walkrebuilds
esp32c3_asserted_sourcesfrom scratch each tick and skipsscheduler-driven peripherals, so their level is kept separately (re-derived
from
matrix_irq_sourcesat the event path AND the walk-tick aggregation) andUNION-ed with the walk bitmap in the routing recompute. Level semantics
identical to the walk: a source that stops asserting drops out on the next
re-derivation (e.g. the tick after INT_CLR).
Wiring: C3 ROM-boot SYSTIMER flips to
new_with_source(scheduler);unpinned from the C3 walk-pinner ledger.
Interface shape (the routing arm)
One trait method
Peripheral::matrix_irq_sources() -> Vec<u32>(default empty),one bus bitmap, one arm in
apply_event_result. No parallel mechanism, no newEventResult field — the level is a pure function of live peripheral state,
re-derived at each choke, so assert AND de-assert are automatic and exact.
Fidelity proof (byte-exact, the non-negotiable gate)
oled_lab_systimer_walk_on_vs_scheduler_is_byte_identical(release) — theREAL esp32c3-oled-demo (SYSTIMER is the FreeRTOS tick source) run with the
SYSTIMER on the legacy walk vs scheduler-driven, at interval 1 AND interval
64, SYSTIMER the only variable: serial stream, total_cycles, and the SSD1306
framebuffer all byte-identical at each interval. Alarm fires at the same
cycle and the tick ISR is entered at the same instruction boundary, so nothing
downstream can differ. PASS.
c3_systimer_matrix_routing(CI, non-ignored) — the scheduler routing arm andthe legacy walk aggregation produce identical
riscv_irq_linesfor the sameSYSTIMER level; clearing the level (INT_CLR) de-asserts the routed line. PASS.
oled_lab_walk_pinners_after_rtc_migration— SYSTIMER gone from the pinnerledger; remaining pinners exactly
i2c0 / ledc / spi2 / apb_saradc / wifi_mac.--features jit,event-scheduler: 1857 passed, 0 failed.fmt + clippy clean. VALIDATION_STATUS regenerated (esp32s3 drift_ack 2026-07-12).
The identity claim is scheduler-mode == legacy-walk-mode at the same tick
interval (proven at 1 and 64). No approximation anywhere; nothing was left on
an "≈" — the raw counter is quantised to the same grid the legacy walk itself
uses at that interval, so it matches exactly.
Numbers (native, esp32c3-oled-demo, 50M instr, release, median of clean runs)
max_safe_tick_interval)Honest read: no throughput win on this bus yet — at interval 1 the scheduler
machinery (per-step drain) costs a hair MORE than the one saved per-cycle
SYSTIMER tick (~4% slower). The OLED bus still pins interval 1 because five real
walkers remain (i2c0/ledc/spi2/apb_saradc/wifi_mac), so interval-64 batching
can't engage yet. The win of THIS batch is structural — shrinking the C3
pinned surface 6 → 5 — a required step toward the walk-deletable bus that
unlocks batching. (interval-64 numbers are noisy under machine load and are
directional only.)
Caveats
uses; the payoff is deferred to when the remaining C3 walkers migrate and the
bus flips walk-deletable. Consistent with the per-batch cost the campaign
accepts (same as RTC C3 walk-free: bus cycle clock (read-side freshness) + RTC lazy migration + write-choke IRQ aggregation #516).
tests are unchanged (the C3 arm is guarded by
esp32c3_irq_routing).