rigless 0.1.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
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
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
// 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 std::num::{NonZeroU32, NonZeroU64};

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

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

/// The memory map from RM0090 rev 22, section 2.3, with flash in its sectors.
///
/// 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).erased_in(&SECTORS),
    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),
];

/// The flash's sectors in address order, RM0090 rev 22 table 6: in each of the two banks four of 16
/// Kbytes, one of 64 and seven of 128. The flash model times a sector's erase by its size from here.
pub(crate) static SECTORS: [EraseBlocks; 6] = [
    sectors(16 << 10, 4),
    sectors(64 << 10, 1),
    sectors(128 << 10, 7),
    sectors(16 << 10, 4),
    sectors(64 << 10, 1),
    sectors(128 << 10, 7),
];

/// `count` sectors of `bytes` each.
const fn sectors(bytes: u64, count: u32) -> EraseBlocks {
    match (NonZeroU64::new(bytes), NonZeroU32::new(count)) {
        (Some(size), Some(count)) => EraseBlocks { size, count },
        _ => panic!("a sector has bytes"),
    }
}

/// 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, but
/// `I2S2ext` and `I2S3ext`, which the part models neither of.
///
/// **Table 62 is the STM32F405's and table 63 is this part's.** They're numbered alike, printed one
/// after the other, and differ. It's the same trap chapter 6 sets against chapter 7 for the clock
/// controller and 5.5 against 5.4 for the regulator. 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, eight by two and line 18 by all three converters. For each of those ST picks **one** and
/// leaves the rest with nothing, and it doesn't pick consistently: `TIM1_BRK_TIM9` goes to TIM1,
/// `TIM8_UP_TIM13` to TIM13. The other way is worse, and there's 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 isn't 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're 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]),
];

#[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";

/// 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's 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 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, and line 18 is theirs, section 13.12",
    ),
    ("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.
///
/// ES0206 rev 24, 2.1, gives three errata of the Cortex-M4F core, and Table 3 marks each on every
/// revision. The core models none of them.
///
/// | Erratum | Source | Reason |
/// |---|---|---|
/// | Interrupted loads to SP can cause erroneous behavior, Arm ID 752770 | ES0206 rev 24, 2.1.1 | Not modelled. The core takes an interrupt only between instructions, so none lands in a load's data phase and no load runs twice |
/// | VDIV or VSQRT instructions might not complete correctly when very short ISRs are used, Arm ID 776924 | ES0206 rev 24, 2.1.2 | Not modelled. The core retires a VDIV or VSQRT whole, its register and `FPSCR` written, before it takes any exception, so there's no window for a short ISR to return in |
/// | Store immediate overlapping exception return operation might vector to incorrect interrupt, Arm ID 838869 | ES0206 rev 24, 2.1.3 | Not modelled. A store completes inside its instruction, and an exception return acknowledges the interrupt it chains to and reads that one's vector in one step, so no buffered store comes between them |
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 lines table 63 gives the eleven instances of [`INTERRUPTS`], 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 aren't 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's the part's all the same, since this part carries a Cortex-M4
/// and that core carries the unit. So it's placed here with the rest, from `cpu::armv7m`, which is
/// where a second part of the same family reaches it without a copy.
fn instances() -> impl Iterator<Item = Peripheral> {
    generated::PERIPHERALS
        .iter()
        .map(|&peripheral| Peripheral {
            size: spanned(&peripheral),
            ..peripheral
        })
        .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 blocks section 2.3's Table 1 gives the three converters, the Ethernet MAC, USB OTG HS and
/// FS and the FMC's registers, each as the address past its end and the instances ST's file splits
/// it into, in address order. ST's file stops an instance short of the next, at its last register
/// or `0x400` on, which leaves the rest of the block out of the memory map a debugger reads. Each
/// instance here runs on to the next one's base, and the last to the block's end, so between them
/// they cover it.
const BLOCKS: [(u64, &[&str]); 5] = [
    (0x4001_2400, &["ADC1", "ADC2", "ADC3", "C_ADC"]),
    (
        0x4002_9400,
        &[
            "Ethernet_MAC",
            "Ethernet_MMC",
            "Ethernet_PTP",
            "Ethernet_DMA",
        ],
    ),
    (
        0x4008_0000,
        &[
            "OTG_HS_GLOBAL",
            "OTG_HS_HOST",
            "OTG_HS_DEVICE",
            "OTG_HS_PWRCLK",
        ],
    ),
    (
        0x5004_0000,
        &[
            "OTG_FS_GLOBAL",
            "OTG_FS_HOST",
            "OTG_FS_DEVICE",
            "OTG_FS_PWRCLK",
        ],
    ),
    (0xA000_1000, &["FMC"]),
];

/// The bytes `peripheral` takes: up to the next instance of its block in [`BLOCKS`], or as ST's
/// file sizes it.
fn spanned(peripheral: &Peripheral) -> u64 {
    let base = |name: &str| {
        generated::PERIPHERALS
            .iter()
            .find(|instance| instance.name == name)
            .map(|instance| instance.base)
    };
    BLOCKS
        .iter()
        .find_map(|&(end, names)| {
            let at = names.iter().position(|&name| name == peripheral.name)?;
            let next = names.get(at + 1).map_or(Some(end), |&name| base(name))?;
            Some(next - peripheral.base)
        })
        .unwrap_or(peripheral.size)
}

/// The lines table 63 gives this instance, where ST's file doesn't 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.
/// The enable registers are sections 6.3.10, 6.3.11, 6.3.13, and 6.3.14. The name is the instance's
/// with `EN` after it, but for the few `SPELLED_OTHERWISE` lists, and
/// `every_clock_gate_is_named_for_the_instance_it_gates` holds this table to that.
///
/// The clock controller isn't here, and can't be: it's what holds every one of these bits, so
/// gating it on one of its own would be a gate that could never open. The backup domain, the debug
/// component, the independent watchdog and the processor's cycle counter aren't here either. The
/// first is write-protected rather than gated, sections 5.5.1 and 26.3.5, the watchdog runs on the
/// LSI with no bus enable, section 6.2.9, and the other two are on the core's own private peripheral
/// bus. Section 2.1.11 excepts the flash memory interface with the SRAM and nothing else,
/// the power controller included.
const CLOCK_GATES: [(&str, &str, &str); 43] = [
    ("I2C1", "APB1ENR", "I2C1EN"),
    ("I2C2", "APB1ENR", "I2C2EN"),
    ("I2C3", "APB1ENR", "I2C3EN"),
    ("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"),
];

/// Every instance that keeps time, with the output of the tree its clock comes from, section 6.2's
/// figure 16. A serial interface or port runs on the peripheral clock of the bus its enable is on,
/// every inter-integrated circuit interface on PCLK1 since all three enables are on `APB1ENR`,
/// section 6.3.13, a timer of chapter 18 on the APB1 timer clock and an advanced-control timer on
/// APB2's, and the card interface on the 48 MHz domain. Each runs through its own enable in
/// [`CLOCK_GATES`], [`rcc::Rcc::new`].
fn clocked() -> Vec<(&'static str, usize)> {
    let mut clocked = Vec::new();
    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 {
            let source = if register == "APB1ENR" {
                rcc::PCLK1
            } else {
                rcc::PCLK2
            };
            clocked.push((instance, source));
        }
    }
    clocked.extend(
        super::peripherals::I2C_INTERFACES
            .iter()
            .map(|&instance| (instance, rcc::PCLK1)),
    );
    clocked.extend(
        TIMERS
            .iter()
            .map(|timer| (timer.instance, rcc::APB1_TIMERS)),
    );
    clocked.extend(
        ADVANCED_TIMERS
            .iter()
            .map(|timer| (timer.instance, rcc::APB2_TIMERS)),
    );
    clocked.push(("SDIO", rcc::PLL48));
    clocked
}

/// What [`rcc::Rcc::new`] builds each instance's own clock from, for [`clocked`]: its source, and
/// the register and field of its enable.
pub(super) fn clock_enables() -> Vec<(usize, &'static str, &'static str)> {
    clocked()
        .into_iter()
        .map(|(instance, source)| {
            let (_, register, field) = CLOCK_GATES
                .iter()
                .find(|(gated, ..)| *gated == instance)
                .expect("every instance that keeps time has an enable");
            (source, *register, *field)
        })
        .collect()
}

/// The instances whose enable the vendor doesn't 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. Three cases
/// break it. The converters' common block answers behind `ADC1EN`, the first converter's. ST's file
/// spells UART7 and UART8's bits with a trailing R. And the USB core breaks it 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's no name the convention could have produced. The other three blocks join this table
/// as they're 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. Section 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 0x411, which is neither. Bits 15:12 above it are reserved with no
        // value the section or Table 314 gives, and ST's own 0x6 there is kept rather than zeroed,
        // since nothing on the shelf says zero and ArduPilot's own IDCODE constants, hal_lld.c and
        // iofirmware.cpp, carry the same nibble.
        //
        // The section leaves the revision to the product. ES0206 rev 24 Table 3 marks seven items,
        // 2.2.13, 2.2.14, 2.3.2, 2.3.3, 2.3.4, 2.3.7 and 2.3.8, applicable on revision 1 alone among
        // 1, 3, 4, 5 and B, and not applicable from revision 3 on. This SoC models none of them, but
        // 2.2.14 corrupts flash bank 2 reads with PA12 in alternate function, and PA12's alternate
        // function is `OTG_FS_DP`, so a faithful revision 1 would corrupt what any firmware using USB
        // full speed reads from bank 2.
        // Item 2.2.12 marks over-drive unavailable on revisions A and Y alone, so 3, 4, 5 and B all
        // carry the over-drive this SoC models, and the four errata this SoC's models reproduce,
        // 2.4.2, 2.7.1, 2.7.3 and 2.7.4, are marked alike across every column of Table 3 and choose
        // nothing among them. 0x2003 is revision 4, 5 or B, the latest ST names and the one that
        // carries over-drive without also carrying the seven revision-1-only items.
        .reset("DBG", "DBGMCU_IDCODE", 0x2003_6419);
    // Section 30.6.1 prints USART_SR's reset value with a digit too many and ST read it literally,
    // which leaves TXE clear. The section's 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 27.6.6's SR1 needs `rc_w0` on its error flags where ST's file gives plain read-write,
    // `i2c.rs`'s own note on why.
    let builder = super::peripherals::I2C_INTERFACES
        .iter()
        .fold(builder, |builder, instance| {
            builder.write_modify(instance, "SR1", 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.
    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);
    // 3.9.2 gives LATENCY four bits where ST's file transcribes the STM32F405's three, 3.9.1.
    // CR's LOCK/STRT and OPTCR's OPTLOCK/OPTSTRT are plain read-write in the SVD where 3.9.8 and
    // 3.9.10 type them write-one-to-set.
    let builder = builder
        .field("FLASH", "ACR", "LATENCY", flash::LATENCY)
        .field("FLASH", "CR", "LOCK", flash::LOCK)
        .field("FLASH", "CR", "STRT", flash::STRT)
        .field("FLASH", "OPTCR", "OPTLOCK", flash::OPTLOCK)
        .field("FLASH", "OPTCR", "OPTSTRT", flash::OPTSTRT);
    // 3.9.6 gives this family's FLASH_SR an RDERR at bit 8, where ST's file transcribes the
    // STM32F405's shorter FLASH_SR, 3.9.5, which has none.
    let builder = builder.declare("FLASH", "SR", &flash::RDERR);
    // Section 3.9.6: every flag but BSY clears on a written one. ST's file carries no write rules.
    let builder = builder.write_modify("FLASH", "SR", WriteModify::OneToClear);
    // 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.
    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's 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.
    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, and the reader re-bases at the
    // change. Each in `clocked` runs on a clock of its own, through its enable. The processor's
    // counter runs on HCLK.
    builder = builder.clocked_by(crate::cpu::armv7m::DWT, Clock::output("RCC", rcc::HCLK));
    // Section 3.5.1: LATENCY is held against HCLK and the supply, table 12. Not gated: section
    // 2.1.11 excepts the flash memory interface from the clock tree, the same as the SRAM.
    builder = builder.clocked_by("FLASH", Clock::output("RCC", rcc::HCLK));
    for (nth, (instance, _)) in clocked().into_iter().enumerate() {
        builder = builder.clocked_by(instance, Clock::output("RCC", rcc::Rcc::gated(nth)));
    }
    // Section 17.3.12 has the clock security system's failure reach the advanced-control timers'
    // break circuits, which the tree drives as a request.
    for timer in &ADVANCED_TIMERS {
        builder = builder.reads_requests(timer.instance);
    }
    // Section 6.2.9: the independent watchdog forces the LSI on when it starts, a request line this
    // instance drives and the clock controller reads, and then runs on the LSI itself.
    builder = builder.reads_requests("RCC");
    builder = builder.clocked_by("IWDG", Clock::output("RCC", rcc::IWDG_CLK));
    // Any converter's enable clocks the converters' common block, so it isn't gated on one of them.
    // ceiling: the common block runs on PCLK2 whatever the enables say; upgrade when a clock can be
    // gated by an OR of enables.
    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 doesn't 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");
        }
    }

    /// Section 2.3's Table 1 gives the converters `0x4001_2000` to `0x4001_23FF`, the Ethernet MAC
    /// `0x4002_8000` to `0x4002_93FF`, USB OTG HS `0x4004_0000` to `0x4007_FFFF`, USB OTG FS
    /// `0x5000_0000` to `0x5003_FFFF` and the FMC's registers `0xA000_0000` to `0xA000_0FFF`, and
    /// the instances in each cover the block end to end, where ST's file stops each short of the
    /// next.
    #[test]
    fn each_block_takes_what_table_1_gives_it() {
        let soc = builder().build().unwrap();
        for (start, end, names) in [
            (
                0x4001_2000,
                0x4001_2400,
                &["ADC1", "ADC2", "ADC3", "C_ADC"][..],
            ),
            (
                0x4002_8000,
                0x4002_9400,
                &[
                    "Ethernet_MAC",
                    "Ethernet_MMC",
                    "Ethernet_PTP",
                    "Ethernet_DMA",
                ],
            ),
            (
                0x4004_0000,
                0x4008_0000,
                &[
                    "OTG_HS_GLOBAL",
                    "OTG_HS_HOST",
                    "OTG_HS_DEVICE",
                    "OTG_HS_PWRCLK",
                ],
            ),
            (
                0x5000_0000,
                0x5004_0000,
                &[
                    "OTG_FS_GLOBAL",
                    "OTG_FS_HOST",
                    "OTG_FS_DEVICE",
                    "OTG_FS_PWRCLK",
                ],
            ),
            (0xA000_0000, 0xA000_1000, &["FMC"]),
        ] {
            let mut at = start;
            for name in names {
                let peripheral = soc.peripheral(name).unwrap();
                assert_eq!(
                    peripheral.base, at,
                    "{name} starts where the one before ends"
                );
                at += peripheral.size;
            }
            assert_eq!(at, end, "{names:?} end where Table 1's block does");
        }
        assert_eq!(
            generated::ADC1.size,
            0x51,
            "the generated table is the vendor's"
        );
    }

    /// 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. Line 35 is the one other line an instance answers on that isn't its
    /// own. Table 63 gives it to SPI1 alone, and ST's file also gives it to `I2S2ext` and `I2S3ext` by
    /// deriving them from SPI1, where section 28.4.2 pairs them with I2S2 and I2S3. Nothing here
    /// corrects that yet.
    #[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
            );
        }
    }

    /// Two instances share a line only where table 63 gives it to both, which is nine shared vectors,
    /// or where ST's file does and nothing here corrects it yet, which is line 35. Table 63 gives that
    /// line to SPI1 alone, and ST's file also gives it to `I2S2ext` and `I2S3ext` by deriving them from
    /// SPI1, where section 28.4.2 pairs them with I2S2 and I2S3. So the map below holds ten. 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`, 43
        // `TIM8_BRK_TIM12`, 44 `TIM8_UP_TIM13`, 45 `TIM8_TRG_COM_TIM14`, 54 `TIM6_DAC`, 80 `HASH_RNG`.
        // And 35, which table 63 gives SPI1 alone. ST's file also gives it to `I2S2ext` and `I2S3ext`
        // by deriving them from SPI1, where section 28.4.2 pairs them with I2S2 and I2S3, and nothing
        // here corrects that yet.
        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, the ports' rule a
    /// second time. Within the wide pair the two tables ST's file emits differ in one field name
    /// and nothing else, and `tim.rs` records which name the bound instance carries.
    /// `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 three kilobytes 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's pointed at, register for register and field
    /// for field. It 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. 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.
    /// 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's 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's 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"
        );
    }
}