rigless 0.0.5

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; a fork of QEMU is built in and executes the instructions
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
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The memory map of the STM32F427 and every correction of the vendor's file against RM0090 rev 22.

use crate::soc::{Builder, Bus, Clock, Cpu, Gate, Peripheral, Region, WriteModify};

use super::generated;
use super::peripherals::tim::Counter;
use super::peripherals::{ADVANCED_TIMERS, CONTROLLERS, PORTS, TIMERS, adc, rcc};

/// The memory map from RM0090 rev 22, section 2.3.
///
/// The system region covers system memory, the OTP area, and the unique device ID and flash
/// size words at `0x1FFF_7A10` and `0x1FFF_7A22` that ChibiOS reads.
pub static MEMORY: [Region; 5] = [
    Region::flash("flash", 0x0800_0000, 2 << 20),
    Region::ram("ccm", 0x1000_0000, 64 << 10),
    Region::system("system", 0x1FFF_0000, 32 << 10),
    Region::ram("sram", 0x2000_0000, 192 << 10),
    Region::ram("backup_sram", 0x4002_4000, 4 << 10),
];

/// Interrupt lines on the STM32F427, RM0090 rev 22, table 63; ST's file names no count.
const INTERRUPT_LINES: u16 = 91;

/// Every instance whose interrupt lines ST's file gets wrong, with the lines table 63 gives it.
///
/// **Table 62 is the STM32F405's and table 63 is this part's.** They are numbered alike, printed one after
/// the other, and differ; this is the same trap chapter 6 sets against chapter 7 for the clock controller
/// and 5.5 against 5.4 for the regulator, and every line below was read from 63.
///
/// The file goes wrong in two ways. Most of table 63's lines belong to one peripheral, but nine are shared
/// by two, and for each of those ST picks **one** of the pair and leaves the other with nothing — and it
/// does not pick consistently: `TIM1_BRK_TIM9` goes to TIM1, `TIM8_UP_TIM13` to TIM13. The other way is
/// worse and there is one of it: the file gives RNG line 81, which table 63 gives the **FPU**. RNG's line
/// is 80, `HASH_RNG`, which it shares with the hash processor. A model raising 81 would raise a line that
/// is not its own, and the firmware's handler for it would run.
///
/// Two more are omitted outright rather than mis-shared, SAI1 and DMA2D, and they are here with the rest.
const INTERRUPTS: [(&str, &[u16]); 11] = [
    // Line 18, `ADC`: "ADC1, ADC2 and ADC3 global interrupts". The file gives it to ADC3 alone.
    ("ADC1", &[18]),
    ("ADC2", &[18]),
    // Lines 24, 25 and 26 are TIM1's break, update and trigger, each shared with one of TIM9, TIM10 and
    // TIM11. The file gives all three to TIM1.
    ("TIM9", &[24]),
    ("TIM10", &[25]),
    ("TIM11", &[26]),
    // Lines 43 to 46 are TIM8's four, of which three are shared. The file gives 43 and 46 to TIM8, 44 to
    // TIM13 and 45 to TIM14, so TIM8 is short of 44 and 45 and TIM12 has nothing.
    ("TIM12", &[43]),
    ("TIM8", &[43, 44, 45, 46]),
    // Line 54, `TIM6_DAC`. The file gives it to the converter alone.
    ("TIM6", &[54]),
    // Line 80, `HASH_RNG`. The file gives the hash processor 80 and the generator 81, and 81 is the FPU.
    ("RNG", &[80]),
    // Omitted outright.
    ("SAI1", &[87]),
    ("DMA2D", &[90]),
];

/// Every instance table 63 gives no line of its own, with why it has none.
///
/// Without this the check below could only say that an instance has no line, not whether that is the
/// vendor's mistake or the part's own truth. Each of these is the second.
///
/// Only that check reads it, and it sits here so the reasons are found beside [`INTERRUPTS`].
#[cfg(test)]
const NOT_THE_VENDORS_AT_ALL: &str = "a block of the processor rather than of the part, which raises no line on the part's controller";

#[cfg(test)]
const NO_LINE_OF_ITS_OWN: [(&str, &str); 26] = [
    (crate::cpu::armv7m::DWT, NOT_THE_VENDORS_AT_ALL),
    ("GPIOA", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
    ("GPIOB", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
    ("GPIOC", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
    ("GPIOD", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
    ("GPIOE", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
    ("GPIOF", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
    ("GPIOG", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
    ("GPIOH", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
    ("GPIOI", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
    ("GPIOJ", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
    ("GPIOK", PINS_REACH_THE_CORE_THROUGH_THE_EXTI),
    ("OTG_FS_HOST", ONE_PERIPHERAL_IN_SEVERAL_BLOCKS),
    ("OTG_FS_DEVICE", ONE_PERIPHERAL_IN_SEVERAL_BLOCKS),
    ("OTG_FS_PWRCLK", ONE_PERIPHERAL_IN_SEVERAL_BLOCKS),
    ("OTG_HS_HOST", ONE_PERIPHERAL_IN_SEVERAL_BLOCKS),
    ("OTG_HS_DEVICE", ONE_PERIPHERAL_IN_SEVERAL_BLOCKS),
    ("OTG_HS_PWRCLK", ONE_PERIPHERAL_IN_SEVERAL_BLOCKS),
    ("Ethernet_MMC", ONE_PERIPHERAL_IN_SEVERAL_BLOCKS),
    ("Ethernet_PTP", ONE_PERIPHERAL_IN_SEVERAL_BLOCKS),
    ("Ethernet_DMA", ONE_PERIPHERAL_IN_SEVERAL_BLOCKS),
    (
        "C_ADC",
        "the three converters' common registers; line 18 is theirs, section 13.13.17",
    ),
    ("CRC", NOTHING_IN_TABLE_63),
    ("SYSCFG", NOTHING_IN_TABLE_63),
    ("DBG", NOTHING_IN_TABLE_63),
    (
        "IWDG",
        "it resets the part rather than interrupting it, section 21.3",
    ),
];

#[cfg(test)]
const PINS_REACH_THE_CORE_THROUGH_THE_EXTI: &str = "a port's pins reach the core through the EXTI, which is its own instance and holds lines 6 to 10, \
     23 and 40; section 12.2";
#[cfg(test)]
const ONE_PERIPHERAL_IN_SEVERAL_BLOCKS: &str = "ST's file splits one peripheral into several blocks and the line belongs to the whole of it, which \
     is the block the file gives it to";
#[cfg(test)]
const NOTHING_IN_TABLE_63: &str = "table 63 gives it no line at all";

/// The CPU, with the interrupt count the reference manual gives.
pub static CPU: Cpu = Cpu {
    interrupt_lines: INTERRUPT_LINES,
    ..generated::CPU
};

/// The three registers a port resets differently, in the order of [`PORT_RESETS`]'s values: RM0090 rev 22
/// sections 8.4.1, 8.4.3 and 8.4.4. Every other register of section 8.4 resets to zero on every port.
const CONFIGURED: [&str; 3] = ["MODER", "OSPEEDR", "PUPDR"];

/// What each port resets [`CONFIGURED`] to, RM0090 rev 22 sections 8.4.1, 8.4.3 and 8.4.4, read from the page.
///
/// Ports A and B wake up carrying pins the part has already assigned: port A the debug pins of section 8.3.1,
/// at very high speed with their pulls, and port B one more of them. Every other port wakes up with all three
/// registers clear.
///
/// **Section 8.4.3 gives port A `0x0C00_0000` and ST's file gives `0x0000_0000`**, one more reset value the
/// vendor gets wrong and the first of them nothing had ever read expecting a value.
///
/// Written for all eleven rather than for the two that differ from the `gpioa` table, so that a port is never
/// right by inheriting a value belonging to port A; `every_port_comes_out_of_reset_as_the_manual_gives_it`
/// reads all thirty-three back.
const PORT_RESETS: [(&str, [u64; CONFIGURED.len()]); PORTS.len()] = [
    ("GPIOA", [0xA800_0000, 0x0C00_0000, 0x6400_0000]),
    ("GPIOB", [0x0000_0280, 0x0000_00C0, 0x0000_0100]),
    ("GPIOC", [0, 0, 0]),
    ("GPIOD", [0, 0, 0]),
    ("GPIOE", [0, 0, 0]),
    ("GPIOF", [0, 0, 0]),
    ("GPIOG", [0, 0, 0]),
    ("GPIOH", [0, 0, 0]),
    ("GPIOI", [0, 0, 0]),
    ("GPIOJ", [0, 0, 0]),
    ("GPIOK", [0, 0, 0]),
];

/// Every instance, with the two interrupt lines ST's file omits filled in, every port pointed at the one
/// layout they share, and every timer at the one its pair shares; see [`PORTS`] and [`TIMERS`].
///
/// The processor's own blocks come last and are not the vendor's: ST's file describes what ST built
/// around the core and stops at the core's boundary, so the data watchpoint unit is in neither the file
/// nor the reference manual. It is the part's all the same — this part carries a Cortex-M4 and that core
/// carries the unit — so it is placed here with the rest, from `cpu::armv7m`, which is where a second
/// part of the same family reaches it without a copy. ADR-0024.
fn instances() -> impl Iterator<Item = Peripheral> {
    generated::PERIPHERALS
        .iter()
        .map(|&peripheral| match peripheral.name {
            name if corrected(name).is_some() => Peripheral {
                interrupts: corrected(name).expect("just matched"),
                ..peripheral
            },
            name if PORTS.contains(&name) => Peripheral {
                registers: &generated::gpioa::REGISTERS,
                ..peripheral
            },
            name => match timer(name) {
                Some(counter) => Peripheral {
                    registers: counter.layout(),
                    ..peripheral
                },
                None => peripheral,
            },
        })
        .chain(std::iter::once(crate::cpu::armv7m::PERIPHERAL))
}

/// The lines table 63 gives this instance, where ST's file does not give it those.
fn corrected(name: &str) -> Option<&'static [u16]> {
    INTERRUPTS
        .iter()
        .find(|(instance, _)| *instance == name)
        .map(|(_, lines)| *lines)
}

/// The timer of [`TIMERS`] this instance is, if it is one.
fn timer(name: &str) -> Option<Counter> {
    TIMERS
        .iter()
        .find(|timer| timer.instance == name)
        .map(|timer| timer.counter)
}

/// Every timer: the manual types every flag of each one's status register `rc_w0`, cleared by writing zero and
/// left alone by writing one, and ST's file carries no write rules at all. RM0090 rev 22 sections 17.4.5 (TIM1
/// and TIM8), 18.4.5 (TIM2 to TIM5), 19.4.4 (TIM9 and TIM12), 19.5.3 (TIM10, TIM11, TIM13, TIM14), and 20.4.4
/// (TIM6 and TIM7).
const EVERY_TIMER: [&str; 14] = [
    "TIM1", "TIM2", "TIM3", "TIM4", "TIM5", "TIM6", "TIM7", "TIM8", "TIM9", "TIM10", "TIM11",
    "TIM12", "TIM13", "TIM14",
];

/// Every instance of the `SPI1` layout, whose prescaler register ST's file gets wrong; see `builder`.
const SPIS: [&str; 8] = [
    "SPI1", "SPI2", "SPI3", "SPI4", "SPI5", "SPI6", "I2S2ext", "I2S3ext",
];

/// The clock enable each modelled instance answers behind, RM0090 rev 22 section 2.1.11: "After each
/// device reset, all peripheral clocks are disabled (except for the SRAM and flash memory interface).
/// Before using a peripheral you have to enable its clock in the `RCC_AHBxENR` or `RCC_APBxENR`
/// register."
///
/// The bit is named rather than numbered, so its position comes from the generated table, or from the
/// model that declares what ST's file leaves out; ports J and K are the second kind, RL-133. The enable
/// registers are sections 6.3.10, 6.3.13, and 6.3.14, and the name is always the instance's with `EN`
/// after it, which `every_clock_gate_is_named_for_the_instance_it_gates` holds this table to.
///
/// The clock controller is the one modelled instance not here, and cannot be: it is what holds every one
/// of these bits, so gating it on one of its own would be a gate that could never open. Section 2.1.11
/// excepts nothing else, the power controller included.
const CLOCK_GATES: [(&str, &str, &str); 40] = [
    ("ADC1", "APB2ENR", "ADC1EN"),
    ("ADC2", "APB2ENR", "ADC2EN"),
    ("ADC3", "APB2ENR", "ADC3EN"),
    // Section 13.13.15 puts the common block at the first converter's base plus 0x300, inside its
    // decode; the block answers with the first converter's clock.
    // ceiling: a firmware clocking the second or third converter alone is refused `CCR`; upgrade when
    // a gate can be an OR of enables.
    ("C_ADC", "APB2ENR", "ADC1EN"),
    ("USART1", "APB2ENR", "USART1EN"),
    ("USART2", "APB1ENR", "USART2EN"),
    ("USART3", "APB1ENR", "USART3EN"),
    ("USART6", "APB2ENR", "USART6EN"),
    ("UART4", "APB1ENR", "UART4EN"),
    ("UART5", "APB1ENR", "UART5EN"),
    ("UART7", "APB1ENR", "UART7ENR"),
    ("UART8", "APB1ENR", "UART8ENR"),
    ("PWR", "APB1ENR", "PWREN"),
    ("TIM2", "APB1ENR", "TIM2EN"),
    ("TIM3", "APB1ENR", "TIM3EN"),
    ("TIM4", "APB1ENR", "TIM4EN"),
    ("TIM5", "APB1ENR", "TIM5EN"),
    ("TIM1", "APB2ENR", "TIM1EN"),
    ("TIM8", "APB2ENR", "TIM8EN"),
    ("SPI1", "APB2ENR", "SPI1EN"),
    ("SPI2", "APB1ENR", "SPI2EN"),
    ("SPI3", "APB1ENR", "SPI3EN"),
    ("SPI4", "APB2ENR", "SPI4EN"),
    ("SPI5", "APB2ENR", "SPI5EN"),
    ("SPI6", "APB2ENR", "SPI6EN"),
    ("DMA1", "AHB1ENR", "DMA1EN"),
    ("DMA2", "AHB1ENR", "DMA2EN"),
    ("GPIOA", "AHB1ENR", "GPIOAEN"),
    ("GPIOB", "AHB1ENR", "GPIOBEN"),
    ("GPIOC", "AHB1ENR", "GPIOCEN"),
    ("GPIOD", "AHB1ENR", "GPIODEN"),
    ("GPIOE", "AHB1ENR", "GPIOEEN"),
    ("GPIOF", "AHB1ENR", "GPIOFEN"),
    ("GPIOG", "AHB1ENR", "GPIOGEN"),
    ("GPIOH", "AHB1ENR", "GPIOHEN"),
    ("GPIOI", "AHB1ENR", "GPIOIEN"),
    ("GPIOJ", "AHB1ENR", "GPIOJEN"),
    ("GPIOK", "AHB1ENR", "GPIOKEN"),
    ("OTG_FS_GLOBAL", "AHB2ENR", "OTGFSEN"),
    ("SDIO", "APB2ENR", "SDIOEN"),
];

/// The instances whose enable the vendor does not spell for them, and what it spells instead.
///
/// Every other enable in section 6.3 is the instance's name with `EN` after it, which is what
/// `every_clock_gate_is_named_for_the_instance_it_gates` holds [`CLOCK_GATES`] to. The USB core is the one
/// that breaks it, and for a reason worth writing down rather than loosening the check over: ST's file
/// splits one peripheral into four blocks — `OTG_FS_GLOBAL`, `OTG_FS_HOST`, `OTG_FS_DEVICE` and
/// `OTG_FS_PWRCLK` — where the clock controller gates the whole of it on one bit, section 6.3.11 bit 7,
/// and calls that bit `OTGFSEN`. So there is no name the convention could have produced. The other three
/// blocks join this table as they are modelled, against the same bit.
///
/// Only the check reads it, and it sits beside [`CLOCK_GATES`] so the exception is found where the rule is.
#[cfg(test)]
const SPELLED_OTHERWISE: [(&str, &str); 4] = [
    ("C_ADC", "ADC1EN"),
    ("OTG_FS_GLOBAL", "OTGFSEN"),
    // Section 6.3.13 names bits 30 and 31 `UART7EN` and `UART8EN`; ST's file spells both with a
    // trailing R, and the generated table keeps the vendor's spelling.
    ("UART7", "UART7ENR"),
    ("UART8", "UART8ENR"),
];

/// The part's tables and every correction to them. [`Stm32f427::new`](super::Stm32f427::new) builds
/// the bus from this and puts the models on its own fields; this is here for a test that wants one
/// model on a bus of its own.
pub fn builder() -> Builder {
    // Section 6.3.23 drops bit 26 of PLLI2SQ in ST's file. Sections 6.3.15, 6.3.18, and 6.3.19 give every
    // LPEN bit set out of reset, and ST leaves clear exactly the peripherals this part adds over the
    // STM32F405; 6.3.19's printed value carries a typo its own bit map settles. RM0090 rev 22.
    let builder = Bus::builder(CPU, &MEMORY, instances())
        .reset("RCC", "PLLI2SCFGR", 0x2400_3000)
        // DMA2D, SRAM3, GPIOK, and GPIOJ.
        .reset("RCC", "AHB1LPENR", 0x7EEF_97FF)
        // UART8 and UART7.
        .reset("RCC", "APB1LPENR", 0xF6FE_C9FF)
        // LTDC, SAI1, SPI6, SPI5, and SPI4.
        .reset("RCC", "APB2LPENR", 0x0477_7F33)
        // Section 38.6.1 gives this family 0x419 and the STM32F405 the only other identifier it names,
        // 0x413; ST says 0x6411, which is neither, with a reserved nibble set. It leaves the revision to
        // the product and lists 0x1007 as revision 1, which is taken because ES0206 rev 24 section 2.2.12
        // has no over-drive on the revision A that ST's value claims, and this SoC models over-drive.
        .reset("DBG", "DBGMCU_IDCODE", 0x1007_0419);
    // Section 30.6.1 prints USART_SR's reset value with a digit too many and ST read it literally, which
    // leaves TXE clear; its own bit map gives 0x0000_00C0, so section 30.3.2's first polled write ends.
    let builder = super::peripherals::SERIAL_PORTS
        .iter()
        .fold(builder, |builder, port| {
            builder.reset(port.instance, "SR", 0x0000_00C0)
        });
    // Section 28.5.9 and table 129 give SPI_I2SPR 0x0002; ST's file says 0x000A.
    let builder = SPIS.iter().fold(builder, |builder, instance| {
        builder.reset(instance, "I2SPR", 0x0000_0002)
    });
    let builder = EVERY_TIMER.iter().fold(builder, |builder, timer| {
        builder.write_modify(timer, "SR", WriteModify::ZeroToClear)
    });
    // Section 18.4.12 gives `TIMx_ARR` a reset value of all ones and table 100 prints it bit by bit, with
    // the high half "TIM2 and TIM5 only, reserved on the other timers"; ST's file says zero for all four,
    // which is the one value 18.4.12 says blocks the counter. The width is the instance's, ADR-0022.
    let builder = TIMERS.iter().fold(builder, |builder, timer| {
        builder.reset(timer.instance, "ARR", timer.counter.mask())
    });
    // Section 17.4.12 gives the advanced-control timers' `TIMx_ARR` a reset value of `0xFFFF`; ST's file
    // says zero for both.
    let builder = ADVANCED_TIMERS.iter().fold(builder, |builder, timer| {
        builder.reset(timer.instance, "ARR", timer.counter.mask())
    });
    // Section 34.16.2 gives `OTG_FS_GRSTCTL` a reset value of `0x8000 0000`, bit 31, which is `AHBIDL`:
    // "Indicates that the AHB master state machine is in the Idle condition." ST's file says
    // `0x2000_0000`, bit 29, which the section reserves and which leaves `AHBIDL` clear. A driver's first
    // act is to wait for that bit, so the vendor's value is a boot that never starts.
    let builder = builder.reset("OTG_FS_GLOBAL", "FS_GRSTCTL", 0x8000_0000);
    // Sections 8.4.1, 8.4.3, and 8.4.4 give ports A and B reset values of their own, which is the only
    // way the eleven ports differ and so the only reason ST's file splits one layout into three.
    let mut builder = builder;
    for (port, values) in PORT_RESETS {
        for (register, value) in CONFIGURED.iter().zip(values) {
            builder = builder.reset(port, register, value);
        }
    }
    // Section 6.3.10 gives `RCC_AHB1ENR` eleven port enables and ST's file declares nine, so the two the
    // model declares are what the last two ports' gates resolve against; RL-133.
    for field in [&rcc::GPIOJEN, &rcc::GPIOKEN] {
        builder = builder.declare("RCC", "AHB1ENR", field);
    }
    for (instance, register, field) in CLOCK_GATES {
        builder = builder.gate(instance, Gate::by("RCC", register, field));
    }
    // Section 5.5.1 bit 8: "In reset state, the RCC_BDCR register, the RTC registers (including the backup
    // registers), and the BRE bit of the PWR_CSR register, are protected against parasitic write access."
    // It is write protection and not the register going dark, so reads are left alone; section 6.3.20 has a
    // firmware read `LSERDY` back while the domain is still shut.
    builder = builder.gate_writes("RCC", &["BDCR"], Gate::by("PWR", "CR", "DBP"));
    builder = builder.gate_writes("RTC", &[], Gate::by("PWR", "CR", "DBP"));
    builder = builder.reads_requests("RTC");
    // A serial interface reads the levels of the port pins the board hangs its chips' selects on, which
    // arrive as the ports' request lines; which pin means what is the board's to say, ADR-0023.
    for instance in super::peripherals::SERIAL_INTERFACES {
        builder = builder.reads_requests(instance);
    }
    // Section 6.2's figure 16: which output of the tree each instance that keeps time runs on. The tree
    // drives a rate as a clock the runtime carries, ADR-0025, and the reader re-bases at the change. A
    // serial interface or port runs on the peripheral clock of the bus its enable is on; a timer of chapter 18
    // on the APB1 timer clock; the card interface on the 48 MHz domain; the processor's counter on HCLK.
    builder = builder.clocked_by(crate::cpu::armv7m::DWT, Clock::output("RCC", rcc::HCLK));
    for (instance, register, _) in CLOCK_GATES {
        let serial = super::peripherals::SERIAL_INTERFACES.contains(&instance)
            || super::peripherals::SERIAL_PORTS
                .iter()
                .any(|port| port.instance == instance);
        if !serial {
            continue;
        }
        let output = if register == "APB1ENR" {
            rcc::PCLK1
        } else {
            rcc::PCLK2
        };
        builder = builder.clocked_by(instance, Clock::output("RCC", output));
    }
    for timer in &TIMERS {
        builder = builder.clocked_by(timer.instance, Clock::output("RCC", rcc::APB1_TIMERS));
    }
    // Section 6.2: the advanced-control timers run on the APB2 timer clock, and section 17.3.12 has the
    // clock security system's failure reach their break circuits, which the tree drives as a request.
    for timer in &ADVANCED_TIMERS {
        builder = builder.clocked_by(timer.instance, Clock::output("RCC", rcc::APB2_TIMERS));
        builder = builder.reads_requests(timer.instance);
    }
    builder = builder.clocked_by("SDIO", Clock::output("RCC", rcc::PLL48));
    builder = builder.clocked_by(
        super::peripherals::ADC_COMMON,
        Clock::output("RCC", rcc::PCLK2),
    );
    builder = builder.reads_requests(super::peripherals::ADC_COMMON);
    for instance in super::peripherals::CONVERTERS {
        builder = builder.clocked_by(
            instance,
            Clock::output(super::peripherals::ADC_COMMON, adc::ADCCLK),
        );
    }
    builder = builder.reads_requests(super::peripherals::CONVERTERS[0]);
    // Chapter 10: the two controllers read every request the part's peripherals drive, and each knows from
    // its own wiring which of them are meant for it.
    for controller in &CONTROLLERS {
        builder = builder.reads_requests(controller.instance);
    }
    builder
}

#[cfg(test)]
mod tests {
    use crate::soc::{Layout, RegisterSpec, Width};

    use super::generated::spi1;
    use super::*;
    use crate::mcu::Mcu;
    use crate::soc::stm32f427::peripherals::usart;
    use crate::time::VirtualNanos;

    #[test]
    fn the_bus_carries_every_instance() {
        let soc = builder().build().unwrap();
        // The vendor's instances, and the processor's own blocks that ST's file does not describe.
        assert_eq!(
            soc.peripherals().count(),
            generated::PERIPHERALS.len() + 1,
            "every instance of the file, and the data watchpoint unit"
        );
        assert!(soc.peripheral(crate::cpu::armv7m::DWT).is_some());
        assert_eq!(soc.cpu().interrupt_lines, INTERRUPT_LINES);
        assert!(
            soc.memory()
                .iter()
                .any(|region| region.name == "backup_sram")
        );
    }

    /// RM0090 rev 22 section 30.6.1's bit map, against ST's reading of its typo'd reset value. A polled
    /// write waits for TXE before it writes, section 30.3.2, so with the vendor's value that wait never
    /// ends on any of the eight instances.
    #[test]
    fn every_uart_comes_out_of_reset_ready_to_be_written_to() {
        use super::generated::{uart4, usart6};

        let soc = builder().build().unwrap();
        // The two layouts put TXE and TC in the same places, which is what the shared value asserts.
        assert_eq!(usart6::sr::TXE.bit, uart4::sr::TXE.bit);
        assert_eq!(usart6::sr::TC.bit, uart4::sr::TC.bit);
        let expected = usart6::sr::TXE.mask() | usart6::sr::TC.mask();
        assert_eq!(expected, 0x0000_00C0, "30.6.1: TXE is bit 7 and TC bit 6");

        for port in crate::soc::stm32f427::peripherals::SERIAL_PORTS {
            let instance = port.instance;
            let registers = soc
                .registers(instance)
                .unwrap_or_else(|| panic!("{instance} is an instance"));
            let value = match port.kind {
                usart::Kind::Uart => registers.get(uart4::Register::Sr),
                usart::Kind::Usart => registers.get(usart6::Register::Sr),
            };
            assert_eq!(value, expected, "30.6.1: {instance} out of reset");
        }
    }

    /// Every correction reaches the part, and the generated tables stay the vendor's.
    #[test]
    fn the_lines_the_vendor_gets_wrong_are_corrected() {
        let soc = builder().build().unwrap();
        for (instance, lines) in INTERRUPTS {
            assert_eq!(
                soc.peripheral(instance)
                    .map(|peripheral| peripheral.interrupts),
                Some(lines),
                "{instance} answers on the lines table 63 gives it"
            );
        }
        assert!(
            generated::SAI1.interrupts.is_empty() && generated::RNG.interrupts == [81],
            "the generated table is the vendor's, untouched"
        );
    }

    /// The generator's own line for RNG is the FPU's, which is the sharpest of the corrections and the one
    /// worth a check of its own: nothing else in this part answers on a line belonging to something else.
    #[test]
    fn the_generator_is_not_given_a_line_that_belongs_to_the_core() {
        let soc = builder().build().unwrap();
        assert_eq!(
            soc.peripheral("RNG")
                .map(|peripheral| peripheral.interrupts),
            Some(&[80][..]),
            "table 63 line 80 is `HASH_RNG` and line 81 is `FPU`"
        );
        assert_eq!(
            soc.peripheral("HASH")
                .map(|peripheral| peripheral.interrupts),
            Some(&[80][..]),
            "and the hash processor shares it, which is why one number serves two instances"
        );
    }

    /// Every instance is accounted for: it has a line from the vendor, or one this part corrects, or a
    /// written reason for having none.
    ///
    /// This is what makes the two tables above exhaustive rather than a sample. Without it an instance the
    /// vendor leaves empty is indistinguishable from one that is empty because the part has nothing to
    /// raise, and the second is the case for twenty-five of them.
    #[test]
    fn every_instance_has_a_line_or_a_reason_for_having_none() {
        let soc = builder().build().unwrap();
        let unexplained: Vec<&str> = soc
            .peripherals()
            .filter(|peripheral| peripheral.interrupts.is_empty())
            .map(|peripheral| peripheral.name)
            .filter(|name| !NO_LINE_OF_ITS_OWN.iter().any(|(empty, _)| empty == name))
            .collect();
        assert!(
            unexplained.is_empty(),
            "{} instance(s) answer on no line and nothing says why; add each to NO_LINE_OF_ITS_OWN with \
             its reason, or to INTERRUPTS with the line table 63 gives it: {unexplained:?}",
            unexplained.len()
        );
        for (instance, reason) in NO_LINE_OF_ITS_OWN {
            let peripheral = soc
                .peripheral(instance)
                .unwrap_or_else(|| panic!("{instance} is an instance"));
            assert!(
                peripheral.interrupts.is_empty(),
                "{instance} is on the list of instances with no line and has {:?}: {reason}",
                peripheral.interrupts
            );
        }
    }

    /// No two instances are given the same line unless table 63 gives it to both, and the ones it does are
    /// the nine shared vectors. A correction that copied a line to the wrong instance would show here.
    #[test]
    fn a_line_is_shared_only_where_the_manual_shares_it() {
        use std::collections::BTreeMap;

        let soc = builder().build().unwrap();
        let mut owners: BTreeMap<u16, Vec<&str>> = BTreeMap::new();
        for peripheral in soc.peripherals() {
            for line in peripheral.interrupts {
                owners.entry(*line).or_default().push(peripheral.name);
            }
        }
        let shared: BTreeMap<u16, Vec<&str>> = owners
            .into_iter()
            .filter(|(_, instances)| instances.len() > 1)
            .collect();
        // Table 63: 18 `ADC`, 24 `TIM1_BRK_TIM9`, 25 `TIM1_UP_TIM10`, 26 `TIM1_TRG_COM_TIM11`, 35 the
        // serial interface the two extended blocks of section 28.4.2 share, 43 `TIM8_BRK_TIM12`, 44
        // `TIM8_UP_TIM13`, 45 `TIM8_TRG_COM_TIM14`, 54 `TIM6_DAC`, 80 `HASH_RNG`.
        let expected: BTreeMap<u16, Vec<&str>> = BTreeMap::from([
            (18, vec!["ADC1", "ADC2", "ADC3"]),
            (24, vec!["TIM1", "TIM9"]),
            (25, vec!["TIM1", "TIM10"]),
            (26, vec!["TIM1", "TIM11"]),
            (35, vec!["I2S2ext", "SPI1", "I2S3ext"]),
            (43, vec!["TIM12", "TIM8"]),
            (44, vec!["TIM13", "TIM8"]),
            (45, vec!["TIM8", "TIM14"]),
            (54, vec!["TIM6", "DAC"]),
            (80, vec!["HASH", "RNG"]),
        ]);
        for (line, instances) in &shared {
            let mut held: Vec<&str> = instances.clone();
            held.sort_unstable();
            let mut want = expected
                .get(line)
                .unwrap_or_else(|| {
                    panic!("line {line} is shared by {instances:?} and should not be")
                })
                .clone();
            want.sort_unstable();
            assert_eq!(held, want, "line {line}");
        }
        assert_eq!(
            shared.len(),
            expected.len(),
            "every shared line, and no other"
        );
    }

    /// One layout per pair, which is what lets one model answer for all four, ADR-0022's rule a second
    /// time. Within the wide pair the two tables ST's file emits differ in one field name and nothing
    /// else — `tim.rs` records which name the bound instance carries — and `register_tables.rs` still
    /// audits every one of them as the vendor wrote them.
    #[test]
    fn each_pair_of_timers_answers_from_the_one_layout_it_shares() {
        let soc = builder().build().unwrap();
        let of = |name: &str| {
            soc.peripheral(name)
                .unwrap_or_else(|| panic!("{name} is an instance"))
        };
        let (tim2, tim5) = (of("TIM2"), of("TIM5"));
        let (tim3, tim4) = (of("TIM3"), of("TIM4"));
        assert!(
            std::ptr::eq(tim2.registers, tim5.registers),
            "the two wide timers answer from one layout"
        );
        assert!(
            std::ptr::eq(tim3.registers, tim4.registers),
            "and the two narrow timers from one of their own"
        );
        assert!(
            !std::ptr::eq(tim2.registers, tim3.registers),
            "which is not the wide pair's: table 100 gives the narrow pair no option register"
        );
        // RM0090 rev 22 section 2.3: the two sit a kilobyte and a half apart, so this is the instance
        // rather than the layout that came with it.
        assert_eq!(tim5.base, 0x4000_0C00);
        assert_eq!(
            tim5.interrupts.len(),
            1,
            "and it keeps its own interrupt, which is what makes it a tick rather than a counter"
        );
        assert_ne!(tim2.interrupts, tim5.interrupts);
    }

    /// The whole of why TIM5 is bound: ChibiOS runs its scheduler on it, so its update has to reach the
    /// core on TIM5's own line. One model answers for both timers and takes the interrupt it raises from
    /// the instance it is answering for, so this is what would catch the two sharing a number.
    #[test]
    fn each_timer_raises_the_interrupt_of_the_instance_it_answers_for() {
        use super::generated::rcc::{Register as Rcc, apb1enr};
        use super::generated::tim2::{self, cr1, dier};
        use crate::soc::Width;

        let mut soc = crate::soc::stm32f427::Stm32f427::new().unwrap();
        let at = VirtualNanos(0);
        let enables = generated::RCC.base + u64::from(Rcc::Apb1enr.spec().offset);
        soc.write(at, enables, Width::W32, apb1enr::TIM5EN.mask())
            .expect("nothing gates the clock controller");

        let register =
            |which: tim2::Register| generated::TIM5.base + u64::from(which.spec().offset);
        // A period of ten counts at the reset bus clock, which is the shortest that still has one.
        soc.write(at, register(tim2::Register::Arr), Width::W32, 9)
            .expect("TIM5 answers once its clock stands");
        soc.write(
            at,
            register(tim2::Register::Dier),
            Width::W32,
            dier::UIE.mask(),
        )
        .expect("writable");
        soc.write(
            at,
            register(tim2::Register::Cr1),
            Width::W32,
            cr1::CEN.mask(),
        )
        .expect("writable");
        soc.advance_to(VirtualNanos(1_000));

        let raised = soc.drain_interrupts();
        assert_eq!(raised.len(), 1, "one update, on one line: {raised:?}");
        assert_eq!(raised[0].instance, "TIM5");
        // RM0090 rev 22 table 63, the vector table: TIM5 is position 50 and TIM2 is 28. Read from the
        // page rather than from the table the code uses, so this says the two agree.
        assert_eq!(
            raised[0].interrupt, 50,
            "a model raising the instance it was written against rather than the one it is answering \
             for would put this on TIM2's 28"
        );
    }

    /// What makes [`TIMERS`] safe to add a name to. The part points each timer at the layout its pair
    /// shares, which is only sound while the vendor's own table for that instance is the same shape. So
    /// this holds each against the one it is pointed at, register for register and field for field, and
    /// allows exactly one register to differ — the option register of the wide pair, which `tim.rs`
    /// records as the price of the binding. The narrow pair is held to the letter.
    #[test]
    fn every_timer_the_part_binds_is_the_shape_it_is_bound_to() {
        for timer in TIMERS {
            let reference = timer.counter.layout();
            let vendor = generated::PERIPHERALS
                .iter()
                .find(|peripheral| peripheral.name == timer.instance)
                .unwrap_or_else(|| panic!("{} is an instance", timer.instance))
                .registers;
            assert_eq!(
                vendor.len(),
                reference.len(),
                "{} carries {} registers where its layout carries {}",
                timer.instance,
                vendor.len(),
                reference.len()
            );
            let differing: Vec<&str> = vendor
                .iter()
                .zip(reference.iter())
                .filter(|(had, want)| {
                    had.offset != want.offset
                        || had.fields.len() != want.fields.len()
                        || had.fields.iter().zip(want.fields.iter()).any(|(one, two)| {
                            one.name != two.name || one.bit != two.bit || one.width != two.width
                        })
                })
                .map(|(had, _)| had.name)
                .collect();
            let allowed: &[&str] = match timer.counter {
                Counter::Wide => &["OR"],
                Counter::Narrow => &[],
            };
            assert!(
                differing.iter().all(|register| allowed.contains(register)),
                "{} differs from its layout in {differing:?}, and only {allowed:?} may",
                timer.instance
            );
        }
    }

    /// Section 18.4.12 and table 100: `TIMx_ARR` resets to all ones, in the width the instance counts in,
    /// and ST's file says zero for all four — which is the one value 18.4.12 says blocks the counter. A
    /// firmware that starts a timer without writing `ARR` is what this is for.
    #[test]
    fn every_timer_reloads_from_all_ones_at_reset() {
        let soc = builder().build().unwrap();
        for timer in TIMERS {
            let value = soc
                .registers(timer.instance)
                .unwrap_or_else(|| panic!("{} is an instance", timer.instance))
                .get(generated::tim2::Register::Arr);
            assert_eq!(
                value,
                timer.counter.mask(),
                "18.4.12: {} out of reset",
                timer.instance
            );
        }
    }

    /// One layout for the eleven ports, which is what lets one model answer for all of them; ADR-0022.
    /// ST's own file still splits it, and `register_tables.rs` still audits all three tables it emits.
    #[test]
    fn every_port_answers_from_the_one_layout_they_share() {
        let soc = builder().build().unwrap();
        let mut layouts: Vec<*const RegisterSpec> = PORTS
            .iter()
            .map(|port| {
                soc.peripheral(port)
                    .unwrap_or_else(|| panic!("{port} is an instance"))
                    .registers
                    .as_ptr()
            })
            .collect();
        layouts.sort_unstable();
        layouts.dedup();
        assert_eq!(
            layouts.len(),
            1,
            "the ports answer from {} layouts",
            layouts.len()
        );
        assert!(
            !std::ptr::eq(generated::GPIOB.registers, generated::GPIOA.registers),
            "the generated tables are the vendor's, untouched"
        );
    }

    /// RM0090 rev 22 sections 8.4.1, 8.4.3, and 8.4.4 read back through the bus, on every port. Sharing a
    /// layout means every port would otherwise wake up carrying port A's debug pins, so this is what says
    /// each correction was written rather than assumed.
    ///
    /// The values are written here the way those three sections write them — two named ports, and
    /// "0x0000 0000 for other ports" — rather than read out of [`PORT_RESETS`], which is the table that
    /// feeds the builder. Holding the corrections against themselves would pass whatever they said, so
    /// this is the second witness, and it is what catches a row of that table being wrong.
    #[test]
    fn every_port_comes_out_of_reset_as_the_manual_gives_it() {
        use super::generated::gpioa::Register;

        /// Sections 8.4.1, 8.4.3, and 8.4.4, from the page. Every port but these resets all three to zero.
        const NAMED_BY_THE_MANUAL: [(&str, [u64; 3]); 2] = [
            ("GPIOA", [0xA800_0000, 0x0C00_0000, 0x6400_0000]),
            ("GPIOB", [0x0000_0280, 0x0000_00C0, 0x0000_0100]),
        ];

        let configured = [Register::Moder, Register::Ospeedr, Register::Pupdr];
        let named: Vec<&str> = configured
            .iter()
            .map(|register| register.spec().name)
            .collect();
        assert_eq!(
            named, CONFIGURED,
            "the corrections name these three, in this order"
        );

        let soc = builder().build().unwrap();
        for port in PORTS {
            let values = NAMED_BY_THE_MANUAL
                .iter()
                .find(|(named, _)| *named == port)
                .map_or([0, 0, 0], |(_, values)| *values);
            let registers = soc
                .registers(port)
                .unwrap_or_else(|| panic!("{port} is an instance"));
            for (register, expected) in configured.iter().zip(values) {
                assert_eq!(
                    registers.get(*register),
                    expected,
                    "8.4: {port}.{} out of reset",
                    register.spec().name
                );
            }
        }
    }

    /// Section 8.4.3 gives port A `0x0C00_0000`, the debug pins at very high speed, and ST's file gives zero.
    /// The value above is the correction; this is what says the vendor's table it corrects is still wrong,
    /// so the correction is deleted when a vendor revision fixes it rather than left to shadow a right value.
    #[test]
    fn the_vendor_file_still_understates_port_a_output_speed() {
        let vendor = generated::gpioa::REGISTERS
            .iter()
            .find(|spec| spec.name == "OSPEEDR")
            .expect("port A carries OSPEEDR");
        assert_eq!(vendor.reset, 0x0000_0000, "ST's file, untouched");
    }

    #[test]
    fn instances_derived_from_a_sibling_share_its_table() {
        let soc = builder().build().unwrap();
        let (spi1, spi2) = (
            soc.peripheral("SPI1").unwrap(),
            soc.peripheral("SPI2").unwrap(),
        );
        assert!(std::ptr::eq(spi1.registers, spi2.registers));
        assert_eq!(spi2.base, 0x4000_3800);
    }

    /// An instance answers behind its clock and not before, RM0090 rev 22 section 2.1.11, and what it
    /// answers once the clock stands is its reset value.
    ///
    /// Both halves matter and neither says much alone: a gate that never opens passes the first, and a
    /// part with no gate at all passes the second.
    #[test]
    fn a_register_answers_its_reset_value_once_its_clock_stands_and_not_before() {
        use super::generated::rcc::{self, apb2enr};

        let mut soc = crate::soc::stm32f427::Stm32f427::new().unwrap();
        let status = generated::SPI1.base + u64::from(spi1::Register::Sr.spec().offset);
        assert_eq!(
            soc.read(VirtualNanos(0), status, Width::W32),
            Err(crate::soc::Fault::Gated {
                instance: "SPI1",
                register: "SR",
                by: Gate::by("RCC", "APB2ENR", "SPI1EN"),
            }),
            "2.1.11: all peripheral clocks are disabled after reset"
        );

        let enables = generated::RCC.base + u64::from(rcc::Register::Apb2enr.spec().offset);
        soc.write(VirtualNanos(0), enables, Width::W32, apb2enr::SPI1EN.mask())
            .expect("the clock controller is what holds every enable, so nothing gates it");
        assert_eq!(
            soc.read(VirtualNanos(0), status, Width::W32),
            Ok(0x2),
            "SPI_SR resets with TXE set, RM0090 28.5.3"
        );
        assert_eq!(spi1::cr1::SPE.bit, 6);
    }

    /// Every gate the part declares names a bit the part carries, and names it for the instance it gates.
    ///
    /// Section 2.1.11 leaves the name of a peripheral's enable to each register's own section, and every
    /// one of them spells it the instance's name with `EN` after it. That is a convention rather than a
    /// rule, so this is what would catch it being broken by a part whose vendor spells one differently.
    #[test]
    fn every_clock_gate_is_named_for_the_instance_it_gates() {
        let soc = builder().build().expect("the part assembles");
        for (instance, register, field) in CLOCK_GATES {
            let expected = SPELLED_OTHERWISE
                .iter()
                .find(|(gated, _)| *gated == instance)
                .map_or_else(
                    || format!("{instance}EN"),
                    |(_, spelling)| (*spelling).into(),
                );
            assert_eq!(
                field, expected,
                "{instance} is gated by a bit not named for it"
            );
            assert!(
                soc.peripheral(instance).is_some(),
                "{instance} is gated and is not an instance"
            );
            assert!(
                generated::rcc::REGISTERS
                    .iter()
                    .any(|spec| spec.name == register),
                "{register} is not a register of the clock controller"
            );
        }
    }

    /// The backup domain's write protection is write protection: section 5.5.1 bit 8 says the registers
    /// "are protected against parasitic write access", and section 6.3.20 has a firmware read `LSERDY`
    /// back while the domain is still shut.
    #[test]
    fn the_backup_domain_refuses_a_write_and_answers_a_read() {
        use super::generated::rcc;

        let mut soc = crate::soc::stm32f427::Stm32f427::new().unwrap();
        let backup = generated::RCC.base + u64::from(rcc::Register::Bdcr.spec().offset);
        assert_eq!(
            soc.read(VirtualNanos(0), backup, Width::W32),
            Ok(0),
            "the register reads while the domain is shut"
        );
        assert!(matches!(
            soc.write(VirtualNanos(0), backup, Width::W32, rcc::bdcr::LSEON.mask()),
            Err(crate::soc::Fault::Gated { .. })
        ));

        let control = generated::PWR.base + u64::from(generated::pwr::Register::Cr.spec().offset);
        let enables = generated::RCC.base + u64::from(rcc::Register::Apb1enr.spec().offset);
        soc.write(
            VirtualNanos(0),
            enables,
            Width::W32,
            rcc::apb1enr::PWREN.mask(),
        )
        .expect("the clock controller holds the regulator's enable");
        soc.write(
            VirtualNanos(0),
            control,
            Width::W32,
            generated::pwr::cr::DBP.mask(),
        )
        .expect("the regulator answers once its clock stands");
        assert!(
            soc.write(VirtualNanos(0), backup, Width::W32, rcc::bdcr::LSEON.mask())
                .is_ok(),
            "5.5.1: DBP enables write access to these registers"
        );
    }
}