rigless 0.2.0

Virtual hardware-in-the-loop runtime: run unmodified firmware on an emulated MCU with every peripheral, chip model and unit of virtual time in Rust, and a CPU core of its own that translates the firmware's instructions to host code
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The power controller of RM0090 rev 22 chapter 5, written against the `pwr` layout.
//!
//! Sections 5.5.1 and 5.5.2 are the `PWR_CR` and `PWR_CSR` of the STM32F42xxx and STM32F43xxx,
//! which is this SoC. Sections 5.4.1 and 5.4.2 are the same two registers for the
//! STM32F405/407/415/417. They're numbered alike and read almost the same, and they're the wrong
//! part. Only 5.5 carries over-drive at all. Every register citation here is 5.5. The prose of 5.1
//! and 5.2 covers both parts and is cited as it stands. This is the same trap chapter 6 sets
//! against chapter 7 for the clock controller.
//!
//! | Register or section | Behavior | Source | Test |
//! |---|---|---|---|
//! | Reset state | Scale 1 selected, nothing ready, the detector off | 5.5.1, 5.5.2 | `reset_selects_scale_one_with_nothing_ready` |
//! | `CR` `VOS` | Three scales, and the regulator reaching the one selected raises `VOSRDY` | 5.5.1, 5.5.2 | `a_new_voltage_scale_becomes_ready_after_the_regulator_settles`, `a_scale_changed_mid_settling_starts_its_own` |
//! | `CR` `VOS` encoding | `00` is reserved and reads as scale 3, `01` scale 3, `10` scale 2, `11` scale 1 | 5.5.1 | `the_reserved_scale_encoding_is_scale_three` |
//! | `CSR` `VOSRDY` | Hardware's. A write can't set it, which ST's file allows | 5.5.2 | `software_cannot_set_a_flag_hardware_owns`, `a_firmware_that_only_polls_the_status_register_still_sees_the_regulator` |
//! | `CR` `ODEN` | Enabling over-drive arms `ODRDY`, and clearing it drops `ODRDY`, `ODSWEN`, and `ODSWRDY` together | 5.1.4, 5.5.1 | `over_drive_comes_up_in_two_steps`, `clearing_the_enable_takes_the_switch_with_it`, `clearing_the_over_drive_alone_takes_the_switch_bit_with_it` |
//! | `CR` `ODSWEN` | Stands as soon as it's written. The switch itself waits for `ODRDY` | 5.1.4, 5.5.1 | `the_switch_waits_for_the_over_drive_to_be_ready`, `over_drive_comes_up_in_two_steps` |
//! | The manual's entry sequence | Steps 3 to 5 of section 5.1.4, run as a firmware runs them | 5.1.4 | `the_manuals_entry_sequence_leaves_both_spin_loops` |
//! | The manual's exit sequences | Both: the two bits cleared together, and the switch cleared before the enable | 5.1.4 | `either_exit_sequence_brings_the_regulator_back` |
//! | `CR` `DBP` | Stored, and inside this peripheral it gates `BRE` | 5.5.1, 5.5.2 | `bre_is_writable_only_once_dbp_is_set` |
//! | `CSR` `BRE`, `BRR` across a system reset | Kept, since only a backup domain reset clears them. The part carries them through [`Pwr::keep_backup_regulator`] | 5.5.2, 6.1.3 | `a_reset_keeps_the_pins_and_the_backup_domain_and_nothing_else` |
//! | `CSR` `BRE`, `BRR` | Enabling the backup regulator raises `BRR`. Clearing it drops the flag, which the page does not say in as many words: bit 3 gives only what sets `BRR`, and bit 9 says that with `BRE` reset "the backup regulator is switched off", from which a regulator that is off is not a regulator that is ready | 5.5.2 | `the_backup_regulator_becomes_ready` |
//! | `CR` `PVDE`, `PLS` | The detector and its eight thresholds | 5.5.1 | `the_detector_reports_the_supply_against_its_threshold` |
//! | `CSR` `PVDO` | Set while the supply is below the threshold, with the datasheet's hysteresis, and zero whenever the detector is off | 5.5.2 | `the_detector_reports_the_supply_against_its_threshold`, `the_detector_crosses_every_rising_threshold_where_the_datasheet_puts_it`, `the_detector_holds_its_output_through_the_hysteresis`, `a_disabled_detector_reports_nothing` |
//! | `CR` `CWUF`, `CSBF` | Write one to clear `WUF` and `SBF`. Both read zero | 5.5.1, 5.5.2 | `the_clear_bits_clear_their_flags_and_read_zero` |
//! | `CSR` `UDRDY` | Only a Stop entry sets these, so the model never does, and a write cannot either | 5.5.2 | `software_cannot_set_a_flag_hardware_owns` |
//! | `CSR` `WUF`, `SBF` | Hardware's, and only the `CR` clear bits clear them | 5.5.2 | `software_cannot_set_a_flag_hardware_owns` |
//! | `CSR` `EWUP` | Stored. The pin it enables reaches no wire | 5.5.2 | `the_wake_up_pin_enable_is_stored` |
//! | `CR` `LPDS`, `PDDS`, `FPDS`, `MRUDS`, `LPUDS`, `UDEN`, `ADCDC1` | Stored. What they qualify is a mode the processor enters, not a register behavior | 5.5.1 | `the_deepsleep_configuration_is_stored` |
//!
//! **What the model leaves out, and why.**
//!
//! | Behavior | Source | Reason |
//! |---|---|---|
//! | `VOSRDY` is up shortly after reset on the part, because the regulator is always reaching for the scale `VOS` selects | 5.5.2 | A model is first called when the firmware touches the peripheral, so the settling starts at that first access, whether it is a read or a write. A firmware that never touches `PWR` at all cannot tell the difference |
//! | The instance's interrupt line | 5.1.2, RM0090 rev 22 table 63 line 1 | The detector's output is "internally connected to the EXTI line 16 and can generate an interrupt if enabled through the EXTI registers", so what the vector table calls the PWR interrupt is the EXTI raising it. This model drives no line. The EXTI is its own instance, and the part has no model for it yet, the same as for the GPIO port's pins |
//! | Stop, Standby, and under-drive themselves, and everything that only entering one can set: `WUF`, `SBF`, and `UDRDY` rising, and over-drive being disabled on a Stop entry | 5.1.4, 5.2 | The processor enters deepsleep inside the engine, which sends the runtime no event for it. The bits are stored and the clear paths work, so a firmware reading them after a reset sees the truth. This and `dbg.rs`'s own gap both wait on a low-power mode entry the runtime doesn't have yet |
//! | `VOS` is writable only while the PLL is off and the HSI or HSE is the system clock, the new value is active only once the PLL is on, and with the PLL off the regulator is at scale 3 whatever `VOS` says | 5.1.4, 5.5.1 | `PLLON` and `SWS` are the RCC's bits. A part has three ways to join two instances (a gate, a request line, and an access a model asks the runtime to make), and a model reading another instance's register inside its own call is none of them. So [`Pwr::scale`] answers the scale `VOS` selects, which is the scale the part is at once the PLL is running |
//! | The over-drive switch taking `Tod_swdis`, 20 us typical, to go back to normal mode, which is what step 4 of both exit sequences waits for | 5.1.4, DS9405 rev 13 table 23 | The model drops `ODSWRDY` inside the write that asks for it, so that wait ends at once. A simplification, and the model's own. It errs in the direction that can't hang a firmware, as the same choice does for a ready flag in chapter 6 |
//! | `CWUF` clearing `WUF` "after 2 System clock cycles", where the model clears it inside the write | 5.5.1 | The runtime counts nothing finer than the access, and no firmware can observe the gap without a cycle counter it does not have |
//! | `DBP` also protecting the backup SRAM region | 5.5.1 | `RCC_BDCR` and the RTC are covered: the part gates writes to both on this bit, which `memory_map.rs` states. `the_backup_domain_refuses_a_write_and_answers_a_read` holds `RCC_BDCR` to it and `rtc.rs` covers the RTC. A memory region is the engine's rather than the bus's |
//! | `ODEN` and `ODSWEN` require the HSI or HSE to be the system clock | 5.5.1 | `SWS` is the RCC's, and reading it here is the same thing the `VOS` row above leaves out |
//! | The system clock stalls during an over-drive switch | 5.1.4, 5.5.1 | Stalling the processor is the engine's to do, and the runtime has no way to ask it to |
//! | Over-drive and under-drive are unavailable when the regulator is bypassed, and over-drive when `VDD` is 1.8 to 2.1 V | 5.1.4 table 23 | The bypass is a pin strap the machine doesn't describe. The supply is known, so the voltage condition could be checked. It's left out because the part it would refuse is one no board here fits |
//! | ES0206 rev 24 2.2.12: over-drive and under-drive unavailable | ES0206 rev 24 | Not applicable. Table 3 marks it for revisions A and Y, and `memory_map.rs` gives `DBGMCU_IDCODE` a `REV_ID` of revision 4, 5 or B with its reason |
//! | ES0206 rev 24 2.2.9: the backup domain protection takes effect some cycles after `DBP` is programmed | ES0206 rev 24 | The gate over `RCC_BDCR` opens at the write. The runtime takes performance over cycle fidelity, and a delayed gate would mean holding an access back |
//! | ES0206 rev 24 2.2.11: a backup domain reset missed after its supply drops into a window a few mV wide | ES0206 rev 24 | The model has no `VBAT` rail and no power cycle |
//! | ES0206 rev 24 2.2.4: a wake-up source held high while `CWUF` clears masks the next wake-up from Standby | ES0206 rev 24 | Table 3 marks it on every revision. Standby isn't modeled |
//!
//! **Where this model departs from the vendor's file.** ST types `VOSRDY`, `UDRDY`, `CWUF`, and
//! `CSBF` more permissively than sections 5.5.1 and 5.5.2 do, so the model puts all four back. The
//! register file enforces the rest from the table, which stays the vendor's. ST also carries the
//! STM32F405's names for `PWR_CR` bits 11 and 10, which section 5.5.1 calls `MRUDS` and `LPUDS`,
//! and the model declares those beside it. Section 5.1.4 calls bit 17 `ODSW` where 5.5.1 calls it
//! `ODSWEN`. They're one bit.

use crate::device::{Device, DeviceContext, RegisterFile};
use crate::soc::{Access, FieldSpec, ReadAction, WriteModify};
use crate::time::{EventId, VirtualNanos};

use crate::soc::stm32f427::generated::pwr::{Register, cr, csr};

/// What the manual names where ST's file names it after another part.
const fn field(name: &'static str, bit: u8, width: u8) -> FieldSpec {
    FieldSpec {
        name,
        bit,
        width,
        access: Access::ReadWrite,
        read: ReadAction::None,
        write: WriteModify::None,
    }
}

/// `MRUDS`, RM0090 rev 22 section 5.5.1 bit 11. ST's file carries the STM32F405's name, `MRLVDS`.
pub static MRUDS: FieldSpec = field("MRUDS", 11, 1);
/// `LPUDS`, RM0090 rev 22 section 5.5.1 bit 10. ST's file carries the STM32F405's name, `LPLVDS`.
pub static LPUDS: FieldSpec = field("LPUDS", 10, 1);

/// Every field this model declares because the vendor's file doesn't. See
/// [`rcc::DECLARED`](super::rcc::DECLARED).
pub static DECLARED: &[&FieldSpec] = &[&MRUDS, &LPUDS];

/// The two bits of `CSR` ST types more permissively than RM0090 rev 22 section 5.5.2 does. The
/// register file enforces the rest. Nothing in this model sets either, so putting them back is the
/// whole rule.
const CSR_HARDWARE: u64 = csr::VOSRDY.mask() | csr::UDRDY.mask();

/// The regulator's settling and the over-drive's two steps.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Ready {
    /// The backup regulator, which `BRE` starts.
    BackupRegulator = 0,
    /// The main regulator reaching the scale `VOS` selects.
    Scale = 1,
    /// The over-drive regulator coming up, which `ODEN` starts.
    OverDrive = 2,
    /// The switch to the over-drive rail, which `ODSWEN` starts.
    OverDriveSwitch = 3,
}

impl Ready {
    /// Every one, each at the index its own discriminant gives.
    const ALL: [Self; 4] = [
        Self::BackupRegulator,
        Self::Scale,
        Self::OverDrive,
        Self::OverDriveSwitch,
    ];

    /// The flag in `CSR` this one raises.
    fn flag(self) -> &'static FieldSpec {
        match self {
            Self::Scale => csr::VOSRDY,
            Self::OverDrive => csr::ODRDY,
            Self::OverDriveSwitch => csr::ODSWRDY,
            Self::BackupRegulator => csr::BRR,
        }
    }

    /// The bit that asks for this one, and the register it lives in. `VOS` is a selection rather than an
    /// enable, so the regulator is always reaching for some scale and the scale has none.
    fn enable(self) -> Option<(Register, &'static FieldSpec)> {
        match self {
            Self::Scale => None,
            Self::OverDrive => Some((Register::Cr, cr::ODEN)),
            Self::OverDriveSwitch => Some((Register::Cr, cr::ODSWEN)),
            Self::BackupRegulator => Some((Register::Csr, csr::BRE)),
        }
    }

    /// How long this one takes. DS9405 rev 13 table 23 gives the over-drive switch 45 us typical.
    /// Nothing fixes the other three, so they're the model's own, large enough for a poll loop to
    /// turn.
    fn settling(self) -> VirtualNanos {
        VirtualNanos(match self {
            Self::OverDriveSwitch => 45_000,
            Self::Scale | Self::OverDrive | Self::BackupRegulator => 10_000,
        })
    }
}

/// The regulator's output scale, RM0090 rev 22 section 5.5.1 bits 15:14.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Scale {
    /// The highest, which over-drive extends. It's the reset value.
    One,
    /// The middle scale.
    Two,
    /// The lowest, and what `00` selects as well as `01`.
    Three,
}

/// The detector's thresholds in millivolts, one pair per `PLS` level, named as DS9405 rev 13 table 22
/// names them: the supply's rising edge then its falling one. `PVDO` follows the supply the other way
/// round, so the rising figure is where it clears.
const PVD_THRESHOLDS_MV: [(u32, u32); 8] = [
    (2140, 2040),
    (2300, 2190),
    (2450, 2350),
    (2600, 2510),
    (2760, 2660),
    (2930, 2840),
    (3030, 2930),
    (3140, 3030),
];

/// The power controller of one STM32F42xxx, RM0090 rev 22 chapter 5.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
pub struct Pwr {
    supply_mv: u32,
    pending: [Option<EventId>; Ready::ALL.len()],
    shadow: Shadow,
}

impl Default for Pwr {
    fn default() -> Self {
        Self::new()
    }
}

impl Pwr {
    /// The power controller of a bare part, with no rail on it until a board regulates one through
    /// [`Pwr::set_supply`]. A rail is a point a board picks inside the range DS9405 rev 13 table 17
    /// allows the part, so the part doesn't pick one for it.
    #[must_use]
    pub fn new() -> Self {
        Self {
            supply_mv: 0,
            pending: [None; Ready::ALL.len()],
            shadow: Shadow::default(),
        }
    }

    /// The board's supply rail has moved: the one analog input of chapter 5, and the only thing outside
    /// the register file that can change `PVDO`, RM0090 rev 22 section 5.5.2 bit 2.
    pub fn set_supply(&mut self, millivolts: u32, registers: &mut RegisterFile) {
        self.supply_mv = millivolts;
        self.refresh_detector(registers);
        self.store_shadow(registers);
    }

    /// The supply rail, in millivolts.
    #[must_use]
    pub const fn supply(&self) -> u32 {
        self.supply_mv
    }

    /// The scale `VOS` selects, RM0090 rev 22 section 5.5.1, where `00` is reserved and selects scale 3.
    /// Section 5.1.4's rule that the PLL off means scale 3 whatever is selected needs to read the RCC's
    /// own register, which is the row the table above leaves out.
    #[must_use]
    pub fn scale(registers: &RegisterFile) -> Scale {
        match cr::VOS.get(registers.get(Register::Cr)) {
            0b10 => Scale::Two,
            0b11 => Scale::One,
            _ => Scale::Three,
        }
    }
}

/// What a write must not change, kept because a model sees the value written and not the one before it.
/// `the_shadow_starts_at_the_reset_values` holds its starting point against the register file.
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct Shadow {
    cr: u64,
    csr: u64,
}

impl Default for Shadow {
    fn default() -> Self {
        Self {
            cr: 0x0000_C000,
            csr: 0,
        }
    }
}

impl Device for Pwr {
    type Register = Register;

    /// The model answers no read. It's here because the regulator is always reaching for the scale
    /// `VOS` selects, so a firmware that only polls `PWR_CSR` must still see `VOSRDY` rise.
    fn read(
        &mut self,
        _register: Register,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) -> Option<u64> {
        self.follow_settling(registers, context);
        self.store_shadow(registers);
        None
    }

    fn write(
        &mut self,
        register: Register,
        written: u64,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) {
        match register {
            Register::Cr => self.write_cr(written, registers, context),
            Register::Csr => self.write_csr(registers, context),
        }
        self.store_shadow(registers);
    }

    fn on_event(
        &mut self,
        event: EventId,
        registers: &mut RegisterFile,
        _context: &mut DeviceContext<'_>,
    ) {
        // An instant fires once per instance, so one event names every settling that lands at that
        // instant. A model keeps the ids it scheduled and ignores any other, as there's no cancel.
        let named: Vec<usize> = (0..self.pending.len())
            .filter(|&index| self.pending[index] == Some(event))
            .collect();
        for index in named {
            // A settling that has landed owns its slot no longer. [`Pwr::follow_settling`] clears
            // it too, on the path where a flag is dropped rather than raised, which is the one this
            // doesn't reach.
            self.pending[index] = None;
            let which = Ready::ALL[index];
            let csr_value = registers.get(Register::Csr);
            registers.set(Register::Csr, which.flag().set(csr_value, 1));
            self.store_shadow(registers);
        }
    }
}

impl Pwr {
    /// The part has restarted from a system reset with the backup regulator as `csr_before` had it,
    /// RM0090 rev 22 sections 5.5.2 and 6.1.3.
    pub fn keep_backup_regulator(&mut self, csr_before: u64, registers: &mut RegisterFile) {
        registers.restore(Register::Csr, csr::BRE.mask() | csr::BRR.mask(), csr_before);
        self.store_shadow(registers);
    }

    fn store_shadow(&mut self, registers: &RegisterFile) {
        self.shadow = Shadow {
            cr: registers.get(Register::Cr),
            csr: registers.get(Register::Csr),
        };
    }

    /// Whether the thing a settling belongs to is asked for and able to start: the switch needs `ODRDY` as
    /// well as its own bit, RM0090 rev 22 section 5.5.1 bit 17, and the other two need only their bit.
    fn is_started(registers: &RegisterFile, which: Ready) -> bool {
        let asked = which
            .enable()
            .is_none_or(|(register, enable)| enable.is_set(registers.get(register)));
        asked
            && (which != Ready::OverDriveSwitch || csr::ODRDY.is_set(registers.get(Register::Csr)))
    }

    /// `CR`: the scale, the over-drive pair, the detector, and the two clear bits.
    fn write_cr(
        &mut self,
        written: u64,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) {
        // RM0090 rev 22 section 5.5.1 bits 3 and 2: CSBF and CWUF clear their flags and are always read
        // as zero. ST's file types them plain read-write and gives them no rule, so the model applies it.
        let mut csr_value = registers.get(Register::Csr);
        if cr::CSBF.is_set(written) {
            csr_value = csr::SBF.set(csr_value, 0);
        }
        if cr::CWUF.is_set(written) {
            csr_value = csr::WUF.set(csr_value, 0);
        }
        registers.set(Register::Csr, csr_value);
        let cr_value = registers.get(Register::Cr);
        let cleared = cr::CWUF.set(cr::CSBF.set(cr_value, 0), 0);
        registers.set(Register::Cr, cleared);

        // RM0090 rev 22 section 5.5.1 bit 17 names two things that clear ODSWEN, and "must only be set
        // when ODRDY is set" is a rule on software rather than an interlock. Refusing the write instead
        // would hang a firmware that sets both bits in one word and then polls both flags.
        let cr_value = registers.get(Register::Cr);
        if !cr::ODEN.is_set(cr_value) {
            registers.set(Register::Cr, cr::ODSWEN.set(cr_value, 0));
        }

        if cr::VOS.get(self.shadow.cr) != cr::VOS.get(registers.get(Register::Cr)) {
            // A new scale isn't reached yet, section 5.1.4, so the flag drops and the regulator
            // settles.
            let csr_value = registers.get(Register::Csr);
            registers.set(Register::Csr, csr::VOSRDY.set(csr_value, 0));
            self.pending[Ready::Scale as usize] = None;
        }
        self.follow_settling(registers, context);
        self.refresh_detector(registers);
    }

    /// `CSR`: the backup regulator behind `DBP`, the under-drive flags, and the flags hardware owns.
    fn write_csr(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        registers.restore(Register::Csr, CSR_HARDWARE, self.shadow.csr);
        // RM0090 rev 22 section 5.5.2 bit 9: "The DBP bit of the PWR_CR register must be set before BRE
        // can be written." Both bits are this peripheral's, so this is the one half of the backup domain's
        // write protection the model can enforce without reading another instance.
        if !cr::DBP.is_set(registers.get(Register::Cr)) {
            registers.restore(Register::Csr, csr::BRE.mask(), self.shadow.csr);
        }
        self.follow_settling(registers, context);
    }

    /// Arms or drops each settling: one that is asked for and not yet ready gets its instant scheduled,
    /// and one that is no longer asked for loses its flag.
    fn follow_settling(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        for (index, which) in Ready::ALL.iter().enumerate() {
            let flag = which.flag();
            let csr_value = registers.get(Register::Csr);
            if !Self::is_started(registers, *which) {
                registers.set(Register::Csr, flag.set(csr_value, 0));
                self.pending[index] = None;
                continue;
            }
            if !flag.is_set(csr_value) && self.pending[index].is_none() {
                self.pending[index] =
                    Some(context.schedule(VirtualNanos(context.now().0 + which.settling().0)));
            }
        }
    }

    /// `PVDO`: set while the supply sits below the threshold `PLS` selects, and cleared once it climbs back
    /// over the rising figure. RM0090 rev 22 section 5.5.2 bit 2, DS9405 rev 13 table 22.
    fn refresh_detector(&self, registers: &mut RegisterFile) {
        let cr_value = registers.get(Register::Cr);
        let csr_value = registers.get(Register::Csr);
        if !cr::PVDE.is_set(cr_value) {
            // Section 5.5.2: the output "is valid only if PVD is enabled by the PVDE bit", and is zero
            // after a reset until it is.
            registers.set(Register::Csr, csr::PVDO.set(csr_value, 0));
            return;
        }
        // PLS is three bits wide, so the table has a row for every value it can take.
        let level = usize::try_from(cr::PLS.get(cr_value)).expect("three bits fit a usize");
        let (rising_mv, falling_mv) = PVD_THRESHOLDS_MV[level];
        let below = if csr::PVDO.is_set(csr_value) {
            self.supply_mv < rising_mv
        } else {
            self.supply_mv < falling_mv
        };
        registers.set(Register::Csr, csr::PVDO.set(csr_value, u64::from(below)));
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::soc::stm32f427;

    /// The shadow's starting point is the manual's reset values, written out so that the first write of a
    /// run has something true to compare against. If the table or a correction ever moves, this fails
    /// rather than silently letting that first write through.
    #[test]
    fn the_shadow_starts_at_the_reset_values() {
        let mut soc = crate::mcu::Single::new(stm32f427::builder(), "PWR", Pwr::new()).unwrap();
        let registers = soc.registers();
        let shadow = Shadow::default();
        assert_eq!(shadow.cr, registers.get(Register::Cr));
        assert_eq!(shadow.csr, registers.get(Register::Csr));
    }
}