Skip to content

perf: walk-free C3 systimer — lazy counter + scheduled alarms via intmatrix routing#519

Merged
w1ne merged 2 commits into
mainfrom
perf/walk-free-c3-systimer
Jul 12, 2026
Merged

perf: walk-free C3 systimer — lazy counter + scheduled alarms via intmatrix routing#519
w1ne merged 2 commits into
mainfrom
perf/walk-free-c3-systimer

Conversation

@w1ne

@w1ne w1ne commented Jul 12, 2026

Copy link
Copy Markdown
Owner

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):

  • Attach the bus-published CycleClock; the OP-update snapshot latch syncs the
    free-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_snapshot to avoid the stale-anchor
    freeze (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 live pending && INT_ENA level, so the bus can
    re-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 differential
    reference. 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_result gains a C3 arm beside B1's system_exception arm: on a
    C3 bus (esp32c3_irq_routing) it re-derives every scheduler-driven
    peripheral's live matrix level (refresh_esp32c3_sched_sources) and folds it
    into riscv_irq_lines (recompute_esp32c3_irq_lines) at the exact firing
    cycle. pend_irq_for_event (NVIC) would mis-route a matrix source ID as a
    Cortex-M exception on RISC-V, so the NVIC path stays for non-C3 buses only.
  • New persistent bus bitmap esp32c3_sched_asserted_sources: the per-cycle walk
    rebuilds esp32c3_asserted_sources from scratch each tick and skips
    scheduler-driven peripherals, so their level is kept separately (re-derived
    from matrix_irq_sources at the event path AND the walk-tick aggregation) and
    UNION-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 new
EventResult 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) — the
    REAL 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 and
    the legacy walk aggregation produce identical riscv_irq_lines for the same
    SYSTIMER level; clearing the level (INT_CLR) de-asserts the routed line. PASS.
  • oled_lab_walk_pinners_after_rtc_migration — SYSTIMER gone from the pinner
    ledger; remaining pinners exactly i2c0 / ledc / spi2 / apb_saradc / wifi_mac.
  • Full lib suite --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)

interval SYSTIMER walk SYSTIMER scheduler
1 (deploy's actual — bus not walk-deletable) 3.62 MIPS 3.46 MIPS
64 (hypothetical; forced past max_safe_tick_interval) 16.2 MIPS 6.1 MIPS

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

w1ne added 2 commits July 12, 2026 02:54
…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).
@w1ne w1ne merged commit 47bc943 into main Jul 12, 2026
2 checks passed
@w1ne w1ne deleted the perf/walk-free-c3-systimer branch July 12, 2026 01:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant