// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The independent watchdog of RM0090 rev 22 chapter 21, written against the `iwdg` layout.
//!
//! The whole reason this model exists rather than a register file alone: a firmware that stops
//! kicking the counter has to see the part restart, at the instant chapter 21 says it will, with
//! `RCC_CSR.IWDGRSTF` (`WDGRSTF` in ST's file) set so it can tell the reboot apart from any other.
//! Everything else here exists to make that instant right: the key sequence, the prescaler and
//! reload, and the domain-crossing delay section 21.4.4 gives their update.
//!
//! | Register or section | Behaviour | Source | Test |
//! |---|---|---|---|
//! | `KR` | `0xCCCC` starts the counter, once, from the reset value of `0xFFF`, section 21.3's own words: `RLR`'s crossing cannot complete before this, so it never holds anything else yet. `0xAAAA` reloads it from `RLR`. `0x5555` opens `PR` and `RLR` to writes. Any other value closes them. Reading answers zero. Starting also forces the LSI on (`rcc.rs`) | 21.3, 21.3.2, 21.4.1, 21.4.3 | `a_key_of_5555_opens_and_anything_else_closes_the_configuration_registers`, `a_key_of_cccc_starts_the_counter_at_its_reset_value`, `starting_twice_does_not_restart_the_count`, `a_key_of_aaaa_reloads_the_counter_and_prevents_the_reset`, `reading_the_key_register_answers_zero` |
//! | `PR` | Ignored unless open. A successful write raises `PVU`, and the value the counter uses lags until the crossing completes, which cannot happen before the counter has started, section 6.2.9: this instance has no clock until then | 21.3.2, 21.4.2, 21.4.4, 6.2.9 | `pr_is_protected_until_the_key_opens_it`, `writing_pr_raises_pvu_until_the_crossing_completes`, `a_prescaler_change_mid_count_keeps_what_was_left_rather_than_the_span_since_the_old_basis` |
//! | `RLR` | The same, with `RVU`. What it holds is what a reload loads into the counter, not what changes it on its own | 21.3.2, 21.4.3, 21.4.4 | `rlr_is_protected_until_the_key_opens_it`, `writing_rlr_raises_rvu_until_the_crossing_completes`, `a_reload_takes_the_value_the_crossing_has_completed_for` |
//! | `SR` `PVU`, `RVU` | Set by the write that starts a crossing, cleared five cycles of the raw LSI later. RM0090 rev 22 gives no exact count, and the model takes its bound | 21.4.4 | `writing_pr_raises_pvu_until_the_crossing_completes`, `writing_rlr_raises_rvu_until_the_crossing_completes` |
//! | The down-counter | Counts `PR`'s divider of the LSI, from `0xFFF` at the start key or `RLR` at a reload key, to zero, where it asks the runtime for a system reset | 21.2, 21.3, table 107 | `the_counter_resets_the_part_when_it_reaches_zero`, `the_period_matches_the_prescaler_and_reload_table_gives` |
//! | A kicked counter | A reload before the counter reaches zero replaces the remaining count and pushes the reset out, never firing the old instant | 21.3 | `a_reload_before_the_end_of_count_replaces_it_and_the_old_instant_never_fires` |
//! | A clock or prescaler change mid-count | Freezes what is left under the old rate before the new one takes effect, so neither speeds up nor slows down time already spent, ADR-0025's epoch. The prescaler's own count toward the next tick carries through, since 21.4.2 moves the divider and not that count | 6.2.9, 21.4.2, 21.4.4 | `a_prescaler_change_mid_count_keeps_what_was_left_rather_than_the_span_since_the_old_basis`, `a_prescaler_rewritten_at_every_crossing_does_not_stretch_the_count`, `a_clock_change_mid_count_keeps_what_was_left_rather_than_the_span_since_the_old_basis`, `the_order_the_hal_uses_does_not_fire_late` |
//! | `IWDG_CLK` | The instance's own clock, figure 16 (p.153): the raw LSI, gated by this instance's own enable and nothing until it has started, section 6.2.9. A crossing or the count itself cannot advance while it is nothing, and resumes where it stood once it is not | 6.2.9 | `the_counter_does_not_advance_while_its_clock_is_nothing`, `a_crossing_completes_once_the_clock_returns` |
//! | System reset | The reset RM0090 rev 22 section 6.1.1 names is a system reset, so it is [`crate::device::ResetCause::Model`] through [`crate::device::DeviceContext::request_reset`], never a call into another instance. `rcc.rs` sets `IWDGRSTF` (`WDGRSTF` in ST's file) from it | 6.1.1, 6.2.9, 6.3.21 | `the_counter_resets_the_part_when_it_reaches_zero`, and the whole-part `a_watchdog_never_kicked_resets_the_part_at_the_expected_instant` |
//!
//! **What the model leaves out, and why.**
//!
//! | Behaviour | Source | Reason |
//! |---|---|---|
//! | The hardware watchdog option starting the counter at reset without a `KR` write | 21.3.1 | RL-294. The option byte is `WDG_SW`, which `flash.rs` stores and keeps across a system reset. Starting the counter from it needs a model to act at the instant of a reset, which none can yet, so this instance starts only from `KR` |
//! | `DBG_IWDG_STOP` letting the counter run while the core is halted for debug | 21.3.3 | A halted core halts virtual time for the whole machine, ADR-0010, so nothing here can advance while it is stopped whatever the bit says. The model always behaves as the bit's other setting, freezing in debug, and a firmware reading the bit back gets whatever the register file stores for it |
//! | Half-word or word access only | 21.4 | Recorded like `tim.rs`'s own access-width row: the manual names the width every field diagram gives, 16 bits of a 32-bit word, and not the effect of a narrower one, so a byte write of `0xAA` to `KR` lands in its lane rather than as the key `0x00AA` |
//! | `KR` and `RLR` reset again on wake from Standby, `RLR` to `0xFFF`, and the counter keeps running through Stop and Standby, page 705's note and section 5.3.5 | 21.4.1, 21.4.3 | Neither low-power mode is modelled on this part yet, RL-293, which covers every one of them together rather than one register or one wake source at a time. Table 31's own IWDG-reset-wakes-Standby entry is the same gap |
//! | The 20 us minimum reset pulse and NRST held low, page 152 | 6.1.1 | The model restarts the part at the instant its own end of count reaches zero. A pulse width is a property of the physical pin this emulator does not drive at that grain, ADR-0023's own trade |
//! | ES0206 rev 24 2.8.1, 2.8.2: `RVU` and `PVU` not reset in Stop mode | ES0206 rev 24 | Stop mode is not modelled anywhere on this part yet. Nothing here can enter a mode that does not exist, RL-293 |
//! | ES0206 rev 24 2.8.3, 2.8.4: `RVU` and `PVU` not cleared when the APB clock is too slow | ES0206 rev 24 | The condition compares this instance's own clock against the bus clock a firmware reads and writes it over, and an instance runs on one clock, ADR-0025 (`Bus::builder::clocked_by`, `Error::ClockedTwice`). A second, read-only clock comparison is RL-295 |
//! | ES0206 rev 24 2.2.6: the MPU's bufferable attribute over this instance's registers | ES0206 rev 24 | A posted write from the core's own bus fabric, which this emulator does not model at that grain. ADR-0023's own trade names cycle-level bus behaviour as out of scope |
use crate::device::{Device, DeviceContext, Level, RegisterFile};
use crate::soc::stm32f427::generated::iwdg::{Register, kr, pr, rlr, sr};
use crate::time::{EventId, VirtualNanos};
/// The request this instance drives when it starts, `rcc.rs`'s to read, RM0090 rev 22 section 6.2.9.
pub const STARTED: usize = 0;
// `KR`'s three meaningful values, RM0090 rev 22 section 21.4.1.
const UNLOCK_KEY: u64 = 0x5555;
const START_KEY: u64 = 0xCCCC;
const RELOAD_KEY: u64 = 0xAAAA;
// `RLR`'s own reset value, section 21.4.3, and what the start key loads, section 21.3.
const RESET_RELOAD: u64 = 0x0FFF;
// Ticks from a freshly loaded value to zero, table 107: a load of zero is one tick, not the end
// of count already, so this is what turns a loaded value into a tick count.
const COUNT_TICK_OFFSET: u64 = 1;
// `PR`'s encoding, section 21.4.2: `/4` at zero, doubling to `/256` at six and again at seven.
fn divider(prescaler: u64) -> u64 {
4 << prescaler.min(6)
}
// Section 21.4.4's own bound: "up to 5 RC 40 kHz cycles".
const CROSSING_CYCLES: u64 = 5;
/// The independent watchdog of one STM32F42xxx, RM0090 rev 22 chapter 21.
#[derive(Debug)]
pub struct Iwdg {
unlocked: bool,
// Whether `KR` has ever carried the start key, section 5.3.5: only a reset clears this.
started: bool,
// `PR` as the register shows, restored on a refused write since the bus already landed it.
pr_shown: u64,
rlr_shown: u64,
// `PR`'s divider in the counter's own domain, lagging `pr_shown` until its crossing completes.
prescaler: u64,
reload: u64,
// The instant, the ticks left then, and how far the prescaler already was toward the next tick:
// section 21.4.2 moves the divider, not the prescaler's own count.
basis: (VirtualNanos, u64, u128),
// Section 6.2.9's own gate: zero until the tree drives it.
clock_hz: u64,
expiry: Option<EventId>,
pvu: Option<EventId>,
rvu: Option<EventId>,
}
impl Default for Iwdg {
fn default() -> Self {
Self::new()
}
}
impl Iwdg {
/// The watchdog out of reset: locked, unstarted, its own clock nothing until the tree drives it.
#[must_use]
pub fn new() -> Self {
Self {
unlocked: false,
started: false,
pr_shown: 0,
rlr_shown: RESET_RELOAD,
prescaler: 0,
reload: RESET_RELOAD,
basis: (VirtualNanos(0), RESET_RELOAD + COUNT_TICK_OFFSET, 0),
clock_hz: 0,
expiry: None,
pvu: None,
rvu: None,
}
}
// `None` while the clock is nothing.
fn tick_ns(&self) -> Option<u128> {
(self.clock_hz != 0).then(|| {
1_000_000_000_u128 * u128::from(divider(self.prescaler)) / u128::from(self.clock_hz)
})
}
// ADR-0025's epoch: carries `self.basis` forward at whatever rate held for the span to `at`, as
// the ticks left and the prescaler's progress toward the next one.
fn remaining_at(&self, at: VirtualNanos) -> (u64, u128) {
let (basis_at, remaining, progress_ns) = self.basis;
let Some(tick_ns) = self.tick_ns().map(|tick_ns| tick_ns.max(1)) else {
return (remaining, progress_ns);
};
let elapsed_ns = progress_ns + u128::from(at.0.saturating_sub(basis_at.0));
let elapsed_ticks = u64::try_from(elapsed_ns / tick_ns).unwrap_or(u64::MAX);
(
remaining.saturating_sub(elapsed_ticks),
elapsed_ns % tick_ns,
)
}
// Must run before the rate itself changes, or `remaining_at` reads the new one over the old span.
fn freeze(&mut self, at: VirtualNanos) {
let (remaining, progress_ns) = self.remaining_at(at);
self.basis = (at, remaining, progress_ns);
}
// Schedules nothing while unstarted or while the clock is nothing. `on_clock` re-derives this.
fn reschedule(&mut self, context: &mut DeviceContext<'_>) {
if !self.started {
return;
}
let (basis_at, remaining, progress_ns) = self.basis;
let Some(tick_ns) = self.tick_ns() else {
self.expiry = None;
return;
};
let span_ns = tick_ns
.saturating_mul(u128::from(remaining))
.saturating_sub(progress_ns);
let at = VirtualNanos(
basis_at
.0
.saturating_add(u64::try_from(span_ns).unwrap_or(u64::MAX)),
);
self.expiry = Some(context.schedule(at));
}
fn schedule_crossing(&self, context: &mut DeviceContext<'_>) -> Option<EventId> {
(self.clock_hz != 0).then(|| {
let span_ns =
1_000_000_000_u128 * u128::from(CROSSING_CYCLES) / u128::from(self.clock_hz);
let at = VirtualNanos(
context
.now()
.0
.saturating_add(u64::try_from(span_ns).unwrap_or(u64::MAX)),
);
context.schedule(at)
})
}
fn write_kr(&mut self, value: u64, context: &mut DeviceContext<'_>) {
let key = kr::KEY.get(value);
self.unlocked = key == UNLOCK_KEY;
match key {
START_KEY if !self.started => {
self.started = true;
self.basis = (context.now(), RESET_RELOAD + COUNT_TICK_OFFSET, 0);
context.request(STARTED, Level::High);
self.reschedule(context);
}
RELOAD_KEY if self.started => {
self.basis = (context.now(), self.reload + COUNT_TICK_OFFSET, 0);
self.reschedule(context);
}
_ => {}
}
}
fn write_pr(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
if !self.unlocked || sr::PVU.is_set(registers.get(Register::Sr)) {
registers.set(Register::Pr, self.pr_shown);
return;
}
self.pr_shown = pr::PR.get(registers.get(Register::Pr));
let status = registers.get(Register::Sr);
registers.set(Register::Sr, sr::PVU.set(status, 1));
self.pvu = self.schedule_crossing(context);
}
fn write_rlr(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
if !self.unlocked || sr::RVU.is_set(registers.get(Register::Sr)) {
registers.set(Register::Rlr, self.rlr_shown);
return;
}
self.rlr_shown = rlr::RL.get(registers.get(Register::Rlr));
let status = registers.get(Register::Sr);
registers.set(Register::Sr, sr::RVU.set(status, 1));
self.rvu = self.schedule_crossing(context);
}
}
impl Device for Iwdg {
type Register = Register;
fn read(
&mut self,
register: Register,
registers: &mut RegisterFile,
_context: &mut DeviceContext<'_>,
) -> Option<u64> {
match register {
// RM0090 rev 22 section 21.4.2 and 21.4.3: valid only once the matching flag is clear, so
// a read while a crossing is in flight answers what the counter's domain still has.
Register::Pr if sr::PVU.is_set(registers.get(Register::Sr)) => Some(self.prescaler),
Register::Rlr if sr::RVU.is_set(registers.get(Register::Sr)) => Some(self.reload),
_ => None,
}
}
fn write(
&mut self,
register: Register,
value: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
match register {
Register::Kr => self.write_kr(value, context),
Register::Pr => self.write_pr(registers, context),
Register::Rlr => self.write_rlr(registers, context),
Register::Sr => {}
}
}
fn on_event(
&mut self,
event: EventId,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
// Not `else if`: `PR` and `RLR` written together cross in one event, so both complete here,
// and `was_expiry` is read before either reschedules.
let was_expiry = self.expiry;
if Some(event) == self.pvu {
self.pvu = None;
// remaining_at reads self.prescaler, so it has to run before the line below changes it.
self.freeze(context.now());
self.prescaler = pr::PR.get(registers.get(Register::Pr));
let status = registers.get(Register::Sr);
registers.set(Register::Sr, sr::PVU.set(status, 0));
self.reschedule(context);
}
if Some(event) == self.rvu {
self.rvu = None;
self.reload = rlr::RL.get(registers.get(Register::Rlr));
let status = registers.get(Register::Sr);
registers.set(Register::Sr, sr::RVU.set(status, 0));
}
if Some(event) == was_expiry {
context.request_reset();
}
}
fn on_clock(&mut self, hz: u64, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
// remaining_at reads self.clock_hz, so it has to run before the line below changes it.
self.freeze(context.now());
self.clock_hz = hz;
if hz == 0 {
// Otherwise a crossing armed against the old rate outlives it with nothing to divide by.
self.pvu = None;
self.rvu = None;
} else {
if sr::PVU.is_set(registers.get(Register::Sr)) && self.pvu.is_none() {
self.pvu = self.schedule_crossing(context);
}
if sr::RVU.is_set(registers.get(Register::Sr)) && self.rvu.is_none() {
self.rvu = self.schedule_crossing(context);
}
}
self.reschedule(context);
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::device::ResetCause;
use crate::mcu::{Mcu, Single};
use crate::soc::stm32f427;
use crate::soc::{Layout, Width};
const LSI_HZ: u64 = 32_000;
fn soc() -> Single<Iwdg> {
Single::new(stm32f427::builder(), "IWDG", Iwdg::new()).unwrap()
}
fn address(soc: &Single<Iwdg>, register: Register) -> u64 {
soc.bus().peripheral("IWDG").expect("an instance").base + u64::from(register.spec().offset)
}
fn write(soc: &mut Single<Iwdg>, register: Register, value: u64) {
let at = address(soc, register);
soc.write(VirtualNanos(0), at, Width::W32, value)
.expect("the instance answers");
}
fn write_at(soc: &mut Single<Iwdg>, at: VirtualNanos, register: Register, value: u64) {
let address = address(soc, register);
soc.write(at, address, Width::W32, value)
.expect("the instance answers");
}
fn read(soc: &mut Single<Iwdg>, register: Register) -> u64 {
let at = address(soc, register);
soc.read(VirtualNanos(0), at, Width::W32)
.expect("the instance answers")
}
/// Clocks the model as `rcc.rs` would once the LSI is ready, without going through the tree: this
/// is what a test putting one model on a bus of its own does, `soc.rs`'s *Joining one instance to
/// another*.
fn clock(soc: &mut Single<Iwdg>, hz: u64) {
soc.drive(VirtualNanos(0), |iwdg, registers, context| {
iwdg.on_clock(hz, registers, context);
});
}
/// Unlocks, sets `PR` and `RLR`, and waits out both crossings, which is the driver's own sequence,
/// `hal_wdg_lld.c`'s `wdg_lld_start` up to its `while (wdg->SR != 0)`. Answers the instant it
/// waited to, for whatever comes next to happen at or after it.
fn configure(soc: &mut Single<Iwdg>, prescaler: u64, reload: u64) -> VirtualNanos {
write(soc, Register::Kr, UNLOCK_KEY);
write(soc, Register::Pr, prescaler);
write(soc, Register::Rlr, reload);
let crossing = VirtualNanos(1_000_000_000 * CROSSING_CYCLES / LSI_HZ);
soc.advance_to(crossing);
assert_eq!(read(soc, Register::Sr), 0, "both crossings have completed");
crossing
}
#[test]
fn every_register_resets_as_the_manual_gives_it() {
let mut soc = soc();
assert_eq!(read(&mut soc, Register::Pr), 0, "21.4.2");
assert_eq!(read(&mut soc, Register::Rlr), 0x0FFF, "21.4.3");
assert_eq!(read(&mut soc, Register::Sr), 0, "21.4.4");
}
#[test]
fn reading_the_key_register_answers_zero() {
let mut soc = soc();
write(&mut soc, Register::Kr, START_KEY);
assert_eq!(
read(&mut soc, Register::Kr),
0,
"21.4.1: write only, read 0000h"
);
}
#[test]
fn a_key_of_5555_opens_and_anything_else_closes_the_configuration_registers() {
// Peeked from the register file rather than read over the bus, which is `PVU`-gated and so
// its own test with a clock behind it: this one is only about the key sequence, 21.3.2.
let mut soc = soc();
write(&mut soc, Register::Pr, 3);
assert_eq!(soc.pair().1.get(Register::Pr), 0, "locked at reset");
write(&mut soc, Register::Kr, UNLOCK_KEY);
write(&mut soc, Register::Pr, 3);
assert_eq!(soc.pair().1.get(Register::Pr), 3, "opened by 0x5555");
write(&mut soc, Register::Kr, RELOAD_KEY);
write(&mut soc, Register::Pr, 5);
assert_eq!(
soc.pair().1.get(Register::Pr),
3,
"21.3.2: any value but 0x5555 closes it, 0xAAAA the manual's own named one"
);
}
#[test]
fn pr_is_protected_until_the_key_opens_it() {
let mut soc = soc();
clock(&mut soc, LSI_HZ);
write(&mut soc, Register::Pr, 6);
assert_eq!(read(&mut soc, Register::Pr), 0, "still locked");
}
#[test]
fn rlr_is_protected_until_the_key_opens_it() {
let mut soc = soc();
clock(&mut soc, LSI_HZ);
write(&mut soc, Register::Rlr, 1);
assert_eq!(read(&mut soc, Register::Rlr), 0x0FFF, "still locked");
}
#[test]
fn writing_pr_raises_pvu_until_the_crossing_completes() {
let mut soc = soc();
clock(&mut soc, LSI_HZ);
write(&mut soc, Register::Kr, UNLOCK_KEY);
write(&mut soc, Register::Pr, 3);
assert!(sr::PVU.is_set(read(&mut soc, Register::Sr)), "21.4.4");
assert_eq!(
read(&mut soc, Register::Pr),
0,
"the old value while it crosses"
);
let crossing = VirtualNanos(1_000_000_000 * CROSSING_CYCLES / LSI_HZ);
soc.advance_to(crossing);
assert!(!sr::PVU.is_set(read(&mut soc, Register::Sr)));
assert_eq!(read(&mut soc, Register::Pr), 3, "the new value once it has");
}
#[test]
fn writing_rlr_raises_rvu_until_the_crossing_completes() {
let mut soc = soc();
clock(&mut soc, LSI_HZ);
write(&mut soc, Register::Kr, UNLOCK_KEY);
write(&mut soc, Register::Rlr, 4);
assert!(sr::RVU.is_set(read(&mut soc, Register::Sr)), "21.4.4");
assert_eq!(
read(&mut soc, Register::Rlr),
0x0FFF,
"the old value while it crosses"
);
let crossing = VirtualNanos(1_000_000_000 * CROSSING_CYCLES / LSI_HZ);
soc.advance_to(crossing);
assert!(!sr::RVU.is_set(read(&mut soc, Register::Sr)));
assert_eq!(
read(&mut soc, Register::Rlr),
4,
"the new value once it has"
);
}
/// RM0090 rev 22 section 21.4.3 gives no order for a reload that lands while `RVU` still stands,
/// so this is the model's own reading, stated so a future reader can find and settle it: the
/// counter is in the VDD domain, and a value has not reached that domain until its own crossing
/// completes, so a reload before then still loads what was there before the write.
#[test]
fn a_reload_takes_the_value_the_crossing_has_completed_for() {
let mut soc = soc();
clock(&mut soc, LSI_HZ);
write(&mut soc, Register::Kr, UNLOCK_KEY);
write(&mut soc, Register::Rlr, 0);
assert!(
sr::RVU.is_set(read(&mut soc, Register::Sr)),
"the crossing is in flight"
);
write(&mut soc, Register::Kr, START_KEY);
write(&mut soc, Register::Kr, RELOAD_KEY);
let crossing = VirtualNanos(1_000_000_000 * CROSSING_CYCLES / LSI_HZ);
soc.advance_to(crossing);
assert!(
!sr::RVU.is_set(read(&mut soc, Register::Sr)),
"the crossing has completed"
);
let old_reload_expiry =
VirtualNanos((0x0FFF + COUNT_TICK_OFFSET) * divider(0) * 1_000_000_000 / LSI_HZ);
assert_eq!(
soc.next_event(),
Some(old_reload_expiry),
"the reload landed before RLR's own crossing completed, so it used what was there before"
);
}
#[test]
fn a_key_of_cccc_starts_the_counter_at_its_reset_value() {
let mut soc = soc();
clock(&mut soc, LSI_HZ);
write(&mut soc, Register::Kr, START_KEY);
assert_eq!(
soc.drain_requests(),
[crate::device::RequestChange {
instance: "IWDG",
request: STARTED,
level: Level::High,
}],
"6.2.9: starting asks for the LSI"
);
let expiry =
VirtualNanos((0x0FFF + COUNT_TICK_OFFSET) * divider(0) * 1_000_000_000 / LSI_HZ);
assert_eq!(
soc.next_event(),
Some(expiry),
"21.3: the reset value of 0xFFF"
);
}
#[test]
fn starting_twice_does_not_restart_the_count() {
let mut soc = soc();
clock(&mut soc, LSI_HZ);
write(&mut soc, Register::Kr, START_KEY);
let first = soc.next_event();
soc.advance_to(VirtualNanos(1_000_000));
write(&mut soc, Register::Kr, START_KEY);
assert_eq!(
soc.next_event(),
first,
"the model's own reading: nothing is left for a second 0xCCCC to set, no page says so"
);
}
#[test]
fn a_key_of_aaaa_reloads_the_counter_and_prevents_the_reset() {
let mut soc = soc();
clock(&mut soc, LSI_HZ);
let started_at = configure(&mut soc, 0, 9);
write_at(&mut soc, started_at, Register::Kr, START_KEY);
write_at(&mut soc, started_at, Register::Kr, RELOAD_KEY);
let expiry = VirtualNanos(
started_at.0 + (9 + COUNT_TICK_OFFSET) * divider(0) * 1_000_000_000 / LSI_HZ,
);
assert_eq!(soc.next_event(), Some(expiry));
assert!(soc.take_reset_request().is_none(), "not yet");
soc.advance_to(expiry);
assert!(
soc.take_reset_request().is_some(),
"the reload's own instant, not prevented"
);
}
#[test]
fn the_counter_resets_the_part_when_it_reaches_zero() {
let mut soc = soc();
clock(&mut soc, LSI_HZ);
let started_at = configure(&mut soc, 0, 0);
write_at(&mut soc, started_at, Register::Kr, START_KEY);
let expiry = soc
.next_event()
.expect("the start scheduled the end of count");
assert!(soc.take_reset_request().is_none(), "not yet");
soc.advance_to(expiry);
assert_eq!(
soc.take_reset_request(),
Some(ResetCause::Model("IWDG")),
"21.3: a reset signal is generated"
);
}
#[test]
fn a_reload_before_the_end_of_count_replaces_it_and_the_old_instant_never_fires() {
let mut soc = soc();
clock(&mut soc, LSI_HZ);
let started_at = configure(&mut soc, 0, 9);
write_at(&mut soc, started_at, Register::Kr, START_KEY);
let old_expiry = soc
.next_event()
.expect("the start scheduled the end of count");
let kick_at = VirtualNanos(old_expiry.0 - 1);
write_at(&mut soc, kick_at, Register::Kr, RELOAD_KEY);
soc.advance_to(old_expiry);
assert!(
soc.take_reset_request().is_none(),
"the old instant is superseded and fires nothing"
);
let new_expiry = soc.next_event().expect("the reload scheduled a new one");
assert!(new_expiry > old_expiry);
soc.advance_to(new_expiry);
assert!(soc.take_reset_request().is_some());
}
#[test]
fn the_counter_does_not_advance_while_its_clock_is_nothing() {
let mut soc = soc();
clock(&mut soc, LSI_HZ);
let started_at = configure(&mut soc, 0, 9);
write_at(&mut soc, started_at, Register::Kr, START_KEY);
let scheduled = soc
.next_event()
.expect("the start scheduled the end of count");
clock(&mut soc, 0);
soc.advance_to(scheduled);
assert!(
soc.take_reset_request().is_none(),
"the clock stopped before the counter reached zero, so nothing here made it happen"
);
}
#[test]
fn a_crossing_completes_once_the_clock_returns() {
let mut soc = soc();
write(&mut soc, Register::Kr, UNLOCK_KEY);
write(&mut soc, Register::Pr, 3);
assert!(
sr::PVU.is_set(read(&mut soc, Register::Sr)),
"raised with no clock to carry it"
);
soc.advance_to(VirtualNanos(1_000_000_000));
assert!(
sr::PVU.is_set(read(&mut soc, Register::Sr)),
"nothing to schedule it against yet"
);
clock(&mut soc, LSI_HZ);
let crossing = VirtualNanos(1_000_000_000 * CROSSING_CYCLES / LSI_HZ);
soc.advance_to(crossing);
assert!(
!sr::PVU.is_set(read(&mut soc, Register::Sr)),
"started once the clock did"
);
}
#[test]
fn the_period_matches_the_prescaler_and_reload_table_gives() {
// RM0090 rev 22 table 107: /32 at RL=0xFFF is 4096 ms at a 32 kHz LSI.
let mut soc = soc();
clock(&mut soc, LSI_HZ);
let started_at = configure(&mut soc, 3, 0x0FFF);
write_at(&mut soc, started_at, Register::Kr, START_KEY);
assert_eq!(
soc.next_event(),
Some(VirtualNanos(started_at.0 + 4_096_000_000))
);
}
#[test]
fn a_prescaler_of_seven_answers_the_same_divider_as_six() {
assert_eq!(
divider(7),
divider(6),
"21.4.2: seven answers /256 again, not the /512 the width allows"
);
}
#[test]
fn writing_pr_while_its_own_crossing_is_still_pending_is_ignored() {
let mut soc = soc();
clock(&mut soc, LSI_HZ);
write(&mut soc, Register::Kr, UNLOCK_KEY);
write(&mut soc, Register::Pr, 3);
assert!(
sr::PVU.is_set(read(&mut soc, Register::Sr)),
"the first write is still crossing"
);
write(&mut soc, Register::Kr, UNLOCK_KEY);
write(&mut soc, Register::Pr, 5);
let crossing = VirtualNanos(1_000_000_000 * CROSSING_CYCLES / LSI_HZ);
soc.advance_to(crossing);
assert_eq!(
read(&mut soc, Register::Pr),
3,
"21.4.2: a write while PVU stands does nothing, so the second write left no mark"
);
}
#[test]
fn writing_rlr_while_its_own_crossing_is_still_pending_is_ignored() {
let mut soc = soc();
clock(&mut soc, LSI_HZ);
write(&mut soc, Register::Kr, UNLOCK_KEY);
write(&mut soc, Register::Rlr, 4);
assert!(
sr::RVU.is_set(read(&mut soc, Register::Sr)),
"the first write is still crossing"
);
write(&mut soc, Register::Kr, UNLOCK_KEY);
write(&mut soc, Register::Rlr, 8);
let crossing = VirtualNanos(1_000_000_000 * CROSSING_CYCLES / LSI_HZ);
soc.advance_to(crossing);
assert_eq!(
read(&mut soc, Register::Rlr),
4,
"21.4.3: a write while RVU stands does nothing, so the second write left no mark"
);
}
/// The auditor's own reproduction of the blocker: starting at `/256` and moving to `/4` ten
/// seconds in leaves 2846 ticks, 355.75 ms, at the new rate, not the whole span re-counted at it.
/// The second crossing's 5 LSI cycles were already counted toward the next `/256` tick, and 21.4.2
/// moves the divider, not that count, so they carry into `/4`. The manual doesn't say what a count
/// already past its new divider does: the model takes the whole ticks it holds at once.
#[test]
fn a_prescaler_change_mid_count_keeps_what_was_left_rather_than_the_span_since_the_old_basis() {
let mut soc = soc();
clock(&mut soc, LSI_HZ);
write_at(&mut soc, VirtualNanos(0), Register::Kr, UNLOCK_KEY);
write_at(&mut soc, VirtualNanos(0), Register::Pr, 6);
let first_crossing = VirtualNanos(1_000_000_000 * CROSSING_CYCLES / LSI_HZ);
soc.advance_to(first_crossing);
write_at(&mut soc, first_crossing, Register::Kr, START_KEY);
let ten_seconds_in = VirtualNanos(first_crossing.0 + 10_000_000_000);
soc.advance_to(ten_seconds_in);
write_at(&mut soc, ten_seconds_in, Register::Kr, UNLOCK_KEY);
write_at(&mut soc, ten_seconds_in, Register::Pr, 0);
let second_crossing =
VirtualNanos(ten_seconds_in.0 + 1_000_000_000 * CROSSING_CYCLES / LSI_HZ);
soc.advance_to(second_crossing);
assert_eq!(
read(&mut soc, Register::Pr),
0,
"the second crossing completed"
);
let ticks_left = 2846;
let carried_ns = 1_000_000_000 * CROSSING_CYCLES / LSI_HZ;
let expiry = VirtualNanos(
second_crossing.0 + ticks_left * divider(0) * 1_000_000_000 / LSI_HZ - carried_ns,
);
assert_eq!(
soc.next_event(),
Some(expiry),
"355.75 ms left at /4, not the 80 001 ticks a rate applied to the whole old span gives"
);
}
/// `hal_wdg_lld.c`'s own order: start, unlock, `PR`, `RLR`, issued with no wait between them.
/// `PR`'s crossing completing after the start must not charge the new rate for time spent at
/// the old one, and the counter's own stale schedule from the start must fire as a no-op.
#[test]
fn the_order_the_hal_uses_does_not_fire_late() {
let mut soc = soc();
clock(&mut soc, LSI_HZ);
write(&mut soc, Register::Kr, START_KEY);
write(&mut soc, Register::Kr, UNLOCK_KEY);
write(&mut soc, Register::Pr, 2);
write(&mut soc, Register::Rlr, 100);
let crossing = VirtualNanos(1_000_000_000 * CROSSING_CYCLES / LSI_HZ);
soc.advance_to(crossing);
// crate::time's queue has no cancel, so the start's own stale schedule still fires and must
// be seen to do nothing before the crossing's reschedule is the only one left to check.
let stale =
VirtualNanos((0x0FFF + COUNT_TICK_OFFSET) * divider(0) * 1_000_000_000 / LSI_HZ);
soc.advance_to(stale);
assert!(soc.take_reset_request().is_none(), "superseded, not fired");
// One /4 tick and one LSI cycle elapse between the start and the crossing before /16 takes
// over, so the counter has 4095 of its original 4096 ticks left, not all of them at the new
// rate, and the cycle already counted toward the next tick carries into /16, 21.4.2.
let ticks_left_at_the_crossing = 4095;
let carried_ns = 1_000_000_000 / LSI_HZ;
let expiry = VirtualNanos(
crossing.0 + ticks_left_at_the_crossing * divider(2) * 1_000_000_000 / LSI_HZ
- carried_ns,
);
assert_eq!(
soc.next_event(),
Some(expiry),
"500 us late is a rate applied to the span since start rather than since the crossing"
);
}
/// Swapping `on_clock`'s freeze and its rate assignment passes every other test here: the
/// prescaler is untouched, so `tick_ns` reads the same divider before and after, and only a
/// change of the rate itself, not the divider, tells the two orders apart.
#[test]
fn a_clock_change_mid_count_keeps_what_was_left_rather_than_the_span_since_the_old_basis() {
let mut soc = soc();
clock(&mut soc, LSI_HZ);
let started_at = configure(&mut soc, 0, 9);
write_at(&mut soc, started_at, Register::Kr, START_KEY);
write_at(&mut soc, started_at, Register::Kr, RELOAD_KEY);
let tick_ns = 1_000_000_000 * divider(0) / LSI_HZ;
let midpoint = VirtualNanos(started_at.0 + 3 * tick_ns);
soc.advance_to(midpoint);
let doubled = LSI_HZ * 2;
soc.drive(midpoint, |iwdg, registers, context| {
iwdg.on_clock(doubled, registers, context);
});
let ticks_left = 9 + COUNT_TICK_OFFSET - 3;
let new_tick_ns = 1_000_000_000 * divider(0) / doubled;
let expiry = VirtualNanos(midpoint.0 + ticks_left * new_tick_ns);
assert_eq!(
soc.next_event(),
Some(expiry),
"7 ticks left at double the rate, not 7 ticks of the span since the old basis"
);
}
/// 21.4.2 moves the divider and not the prescaler's own count, so a crossing that changes nothing
/// costs the count nothing: a firmware rewriting `PR` at every crossing's end still expires on
/// time rather than a partial tick late each time.
#[test]
fn a_prescaler_rewritten_at_every_crossing_does_not_stretch_the_count() {
let mut soc = soc();
clock(&mut soc, LSI_HZ);
let started_at = configure(&mut soc, 0, 0x0FFF);
write_at(&mut soc, started_at, Register::Kr, START_KEY);
let expiry = VirtualNanos(
started_at.0 + (0x0FFF + COUNT_TICK_OFFSET) * divider(0) * 1_000_000_000 / LSI_HZ,
);
let crossing_ns = 1_000_000_000 * CROSSING_CYCLES / LSI_HZ;
let mut at = started_at;
while at.0 + 2 * crossing_ns < expiry.0 {
write_at(&mut soc, at, Register::Kr, UNLOCK_KEY);
write_at(&mut soc, at, Register::Pr, 0);
at = VirtualNanos(at.0 + crossing_ns);
soc.advance_to(at);
}
soc.advance_to(VirtualNanos(expiry.0 - 1));
assert!(soc.take_reset_request().is_none(), "not yet");
soc.advance_to(expiry);
assert!(
soc.take_reset_request().is_some(),
"table 107's 512 ms from the start, however many crossings came between"
);
}
/// 21.4.2: seven answers the same divider as six, not the eight the field's width allows, so a
/// write over the bus that lands as seven has to expire at the same instant six would.
#[test]
fn writing_pr_of_seven_expires_at_the_same_instant_as_six() {
let mut soc = soc();
clock(&mut soc, LSI_HZ);
let started_at = configure(&mut soc, 7, 0x0FFF);
write_at(&mut soc, started_at, Register::Kr, START_KEY);
let expiry = VirtualNanos(
started_at.0 + (0x0FFF + COUNT_TICK_OFFSET) * divider(6) * 1_000_000_000 / LSI_HZ,
);
assert_eq!(soc.next_event(), Some(expiry));
}
}