// 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. 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 | 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, 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. They'll be modelled together, not 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 |
//! | 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 |
//! | 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 (`Bus::builder::clocked_by`, `Error::ClockedTwice`). The runtime has no second, read-only clock for that comparison yet |
//! | 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. Cycle-level bus behaviour is 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, serde::Serialize, serde::Deserialize)]
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)
})
}
// 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;
/// A read changes nothing here, so it answers what a peek does.
fn read(
&mut self,
register: Register,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) -> Option<u64> {
self.peek(register, registers, context.now())
}
fn peek(
&self,
register: Register,
registers: &RegisterFile,
_now: VirtualNanos,
) -> 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");
}
/// A debugger's look at `PR` and `RLR` while they cross answers what a read does, the old values,
/// RM0090 rev 22 sections 21.4.2 and 21.4.3, and the new ones once they've crossed.
#[test]
fn peek_answers_what_a_read_would_and_changes_nothing() {
let mut soc = soc();
clock(&mut soc, LSI_HZ);
write(&mut soc, Register::Kr, UNLOCK_KEY);
write(&mut soc, Register::Pr, 3);
write(&mut soc, Register::Rlr, 4);
let peek = |soc: &mut Single<Iwdg>, at, register| {
let address = address(soc, register);
soc.peek(at, address, Width::W32)
.expect("the instance answers")
};
for register in [Register::Pr, Register::Rlr, Register::Sr] {
assert_eq!(
peek(&mut soc, VirtualNanos(0), register),
read(&mut soc, register),
"{register:?}"
);
}
assert_eq!(peek(&mut soc, VirtualNanos(0), Register::Pr), 0);
assert_eq!(peek(&mut soc, VirtualNanos(0), Register::Rlr), 0x0FFF);
let crossing = VirtualNanos(1_000_000_000 * CROSSING_CYCLES / LSI_HZ);
soc.advance_to(crossing);
assert_eq!(peek(&mut soc, crossing, Register::Pr), 3);
assert_eq!(peek(&mut soc, crossing, Register::Rlr), 4);
}
#[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));
}
}