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
101 changes: 101 additions & 0 deletions crates/core/src/bus/tick.rs
Original file line number Diff line number Diff line change
Expand Up @@ -756,3 +756,104 @@ impl SystemBus {
(interrupts, costs)
}
}

#[cfg(test)]
mod walk_free_campaign {
//! Pins the walk-free STM32 campaign's *remaining surface* on the L476
//! nokia5110-invaders bus as it is actually executed (`from_config` +
//! `configure_cortex_m`, exactly how every e2e/capture harness builds it).
//! The bus is built with any hand `walk_deleted` flag stripped, so the
//! assertion reflects only what the models themselves prove — not a manifest
//! override.
//!
//! The walk-forcing set is `needs_legacy_walk() && !uses_scheduler()` — the
//! exact predicate `derive_walk_deletable` negates (see this module's parent).
//! uart×6 / spi×3 are already event-migrated (`uses_scheduler()==true`) so
//! they carry the default `needs_legacy_walk()==true` but do NOT force the
//! walk; they are correctly excluded here.
//!
//! Why `configure_cortex_m`: the Cortex-M core installs the *real* SCB and
//! NVIC (the chip descriptor only carries inert placeholders for those ids)
//! and appends DWT (CYCCNT), which is not in the descriptor at all. SCB's
//! `tick()` drains software-pended exceptions and DWT's advances CYCCNT — both
//! real walk work. Omitting this step would hide SCB's real tick and DWT
//! entirely and under-report the surface, so the runtime-faithful bus is the
//! honest one to pin.
//!
//! After batch **B0** (Class-A inert sweep) the forcing set is the plan's 21
//! Class-B instances still awaiting scheduler migration, PLUS the core DWT
//! (a lazy-read CYCCNT counter the plan calls out separately as the purest
//! read-sync case). Each later batch (SysTick+SCB, timers, DMA, …) migrates a
//! slice onto the scheduler, flipping its `uses_scheduler()`, so this expected
//! set shrinks batch by batch. Keep it in lockstep with the plan's inventory.

use crate::bus::SystemBus;
use crate::system::cortex_m::configure_cortex_m;
use labwired_config::{ChipDescriptor, SystemManifest};
use std::path::PathBuf;

/// Walk-forcing ids on the runtime invaders bus after B0:
/// plan Class-B: systick + 11 timers + 2 dma + 3 i2c + adc + exti + scb + bxcan (21)
/// + core DWT (added by `configure_cortex_m`, lazy-read CYCCNT walker) = 22.
const EXPECTED_WALK_FORCING: &[&str] = &[
"systick", "tim1", "tim2", "tim3", "tim4", "tim5", "tim6", "tim7", "tim8", "tim15",
"tim16", "tim17", "dma1", "dma2", "i2c1", "i2c2", "i2c3", "adc1", "exti", "scb", "can1",
"dwt",
];

fn invaders_bus_walk_stripped() -> SystemBus {
let system_path = PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.join("../../examples/nokia5110-invaders-lab/system.yaml");
let mut manifest = SystemManifest::from_file(&system_path).expect("load invaders manifest");
// Construct WITHOUT the lab's hand `walk_deleted: true`: the campaign
// surface must come from the models, not the manifest escape hatch.
manifest.walk_deleted = None;
let chip_path = system_path.parent().unwrap().join(&manifest.chip);
let chip = ChipDescriptor::from_file(&chip_path).expect("load l476 chip");
let mut bus = SystemBus::from_config(&chip, &manifest).expect("build invaders bus");
// Install the real SCB/NVIC and the core DWT — the executed bus.
let _ = configure_cortex_m(&mut bus);
bus
}

#[test]
fn b0_remaining_walk_forcing_set_is_exactly_class_b() {
let bus = invaders_bus_walk_stripped();

let mut forcing: Vec<&str> = bus
.peripherals
.iter()
.filter(|p| p.dev.needs_legacy_walk() && !p.dev.uses_scheduler())
.map(|p| p.name.as_str())
.collect();
forcing.sort_unstable();

let mut expected: Vec<&str> = EXPECTED_WALK_FORCING.to_vec();
expected.sort_unstable();

assert_eq!(
forcing,
expected,
"walk-forcing set (needs_legacy_walk && !uses_scheduler) drifted from the \
B0 Class-B inventory.\n got ({}): {:?}\n expected ({}): {:?}\n\
A model newly (un)marked `needs_legacy_walk()` or migrated to the scheduler \
must update EXPECTED_WALK_FORCING to match the campaign's remaining surface.",
forcing.len(),
forcing,
expected.len(),
expected,
);
}

#[test]
fn b0_does_not_flip_walk_deletion() {
// Class B still forces the walk, so B0 provably changes zero runtime
// behaviour: the bus is NOT walk-deletable yet.
let bus = invaders_bus_walk_stripped();
assert!(
!bus.derive_walk_deletable(),
"invaders bus became walk-deletable after B0, but Class-B walkers remain — \
B0 must be pure honest bookkeeping with no behaviour change"
);
}
}
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/comp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ impl Default for Comp {
}

impl crate::Peripheral for Comp {
// Inert walk: comparator register bank; tick() is the trait-default no-op.
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, offset: u64) -> SimResult<u8> {
let reg = offset & !3;
let byte = (offset % 4) as u32;
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/crc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,11 @@ impl Default for Crc {
}

impl crate::Peripheral for Crc {
// Inert walk: register bank (CRC computed on write); tick() is the trait-default no-op.
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, offset: u64) -> SimResult<u8> {
let reg = offset & !3;
let byte = (offset % 4) as u32;
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/dac.rs
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,11 @@ impl Dac {
}

impl crate::Peripheral for Dac {
// Inert walk: register bank with no time-driven state; tick() is the trait-default no-op.
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, offset: u64) -> SimResult<u8> {
let reg = offset & !3;
let byte = (offset % 4) as u32;
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/dbgmcu.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,11 @@ impl Dbgmcu {
}

impl crate::Peripheral for Dbgmcu {
// Inert walk: read-only ID / register bank; tick() is the trait-default no-op.
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, offset: u64) -> SimResult<u8> {
let reg = offset & !3;
let byte = (offset % 4) as u32;
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/flash.rs
Original file line number Diff line number Diff line change
Expand Up @@ -689,6 +689,11 @@ impl Default for Flash {
}

impl crate::Peripheral for Flash {
// Inert walk: tick() is the trait-default no-op; H5 erase/bank-swap ops drain via requires_cycle_accurate/drain_pending_op per instruction, never the walk.
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, offset: u64) -> SimResult<u8> {
let reg = offset & !3;
let byte = (offset % 4) as u32;
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/fmc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ impl Default for Fmc {
}

impl crate::Peripheral for Fmc {
// Inert walk: external-memory-controller register bank; tick() is the trait-default no-op.
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, offset: u64) -> SimResult<u8> {
let reg = offset & !3;
let byte = (offset % 4) as u32;
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/gpio.rs
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,11 @@ impl GpioPort {
}

impl crate::Peripheral for GpioPort {
// Inert walk: pure register + pad bank; pin edges are surfaced by the bus GPIO-diff pass, not tick().
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, offset: u64) -> SimResult<u8> {
let reg_offset = offset & !3;
let byte_offset = (offset % 4) as u32;
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/iwdg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ impl Default for Iwdg {
}

impl crate::Peripheral for Iwdg {
// Inert walk: stub register bank that does not count down today; tick() is the trait-default no-op.
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, offset: u64) -> SimResult<u8> {
let reg = offset & !3;
let byte = (offset % 4) as u32;
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/lptim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,11 @@ impl Default for Lptim {
}

impl crate::Peripheral for Lptim {
// Inert walk: register bank with no time-driven state; tick() is the trait-default no-op.
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, offset: u64) -> SimResult<u8> {
let reg = offset & !3;
let byte = (offset % 4) as u32;
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/nvic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ impl Nvic {
}

impl Peripheral for Nvic {
// Inert walk: enable/pending register bank; the NVIC pend+enable scan runs in the bus tick loop over shared state, not this peripheral's tick() (the trait-default no-op).
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, offset: u64) -> SimResult<u8> {
let reg_idx = (offset / 4) as usize;
let byte_offset = (offset % 4) as usize;
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/pwr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,11 @@ impl Default for Pwr {
}

impl crate::Peripheral for Pwr {
// Inert walk: register bank (voltage scaling resolves in the write path); tick() is the trait-default no-op.
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, offset: u64) -> SimResult<u8> {
let reg = offset & !3;
let byte = (offset % 4) as u32;
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/quadspi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,11 @@ impl Default for Quadspi {
}

impl crate::Peripheral for Quadspi {
// Inert walk: register bank with no time-driven state; tick() is the trait-default no-op.
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, offset: u64) -> SimResult<u8> {
let reg = offset & !3;
let byte = (offset % 4) as u32;
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/rcc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1043,6 +1043,11 @@ impl Rcc {
}

impl crate::Peripheral for Rcc {
// Inert walk: clock-control register bank; tick() is the trait-default no-op.
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, offset: u64) -> SimResult<u8> {
let reg_offset = offset & !3;
let byte_offset = (offset % 4) as u32;
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/rng.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ impl Default for Rng {
}

impl crate::Peripheral for Rng {
// Inert walk: register bank (output computed on read); tick() is the trait-default no-op.
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, _offset: u64) -> SimResult<u8> {
// RNG.DR has side-effects (drains a word) so a const-self byte
// read isn't ideal. Most firmware does word reads; for byte-level
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/rtc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,11 @@ impl Default for Rtc {
}

impl crate::Peripheral for Rtc {
// Inert walk: register bank; tick() is the trait-default no-op (the counter-timer RTC is rtc_v3, a separate Class-B type).
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, offset: u64) -> SimResult<u8> {
let reg = offset & !3;
let byte = (offset % 4) as u32;
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/sai.rs
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@ impl Sai {
}

impl crate::Peripheral for Sai {
// Inert walk: register bank with no time-driven state; tick() is the trait-default no-op.
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, offset: u64) -> SimResult<u8> {
let reg = offset & !3;
let byte = (offset % 4) as u32;
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/sdmmc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,11 @@ impl Default for Sdmmc {
}

impl crate::Peripheral for Sdmmc {
// Inert walk: register bank with no time-driven state; tick() is the trait-default no-op.
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, offset: u64) -> SimResult<u8> {
let reg = offset & !3;
let byte = (offset % 4) as u32;
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/tsc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,11 @@ impl Tsc {
}

impl crate::Peripheral for Tsc {
// Inert walk: register bank with no time-driven state; tick() is the trait-default no-op.
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, offset: u64) -> SimResult<u8> {
let reg = offset & !3;
let byte = (offset % 4) as u32;
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/usb_otg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,11 @@ impl Default for UsbOtg {
}

impl crate::Peripheral for UsbOtg {
// Inert walk: register bank with no time-driven state; tick() is the trait-default no-op.
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, offset: u64) -> SimResult<u8> {
let reg = offset & !3;
let byte = (offset % 4) as u32;
Expand Down
5 changes: 5 additions & 0 deletions crates/core/src/peripherals/wwdg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,11 @@ impl Default for Wwdg {
}

impl crate::Peripheral for Wwdg {
// Inert walk: stub register bank that does not count down today; tick() is the trait-default no-op.
fn needs_legacy_walk(&self) -> bool {
false
}

fn read(&self, offset: u64) -> SimResult<u8> {
let reg = offset & !3;
let byte = (offset % 4) as u32;
Expand Down
Loading