rigless 0.3.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
// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The serial peripheral interface of RM0090 rev 22 chapter 28, written against the `spi1` layout.
//!
//! One layout serves eight instances: SPI1 to SPI6 and the two I2S extension blocks.
//!
//! **This model is the SPI and not the I2S.** `I2SCFGR`'s `I2SMOD`, section 28.5.8 bit 11, chooses
//! which of two peripherals the register block is, and the manual gives them separate feature lists
//! and separate functional sections. With it set, this model stores every write and does nothing
//! else, so a firmware that configures an I2S gets a peripheral that does nothing rather than one
//! that pretends.
//!
//! | Register or section | Behavior | Source | Test |
//! |---|---|---|---|
//! | Reset state | `TXE` set, everything else clear, the CRC polynomial at 7, and the prescaler at 2 | 28.5.3, 28.5.5, 28.5.9 | `reset_leaves_the_transmit_buffer_empty` |
//! | `DR` | A write stores into the transmit buffer, a read answers the receive buffer and leaves the word there | 28.3.5, 28.3.10 | `a_word_written_goes_out_and_the_word_on_the_wire_comes_back`, `a_second_read_of_the_data_register_answers_the_same_word` |
//! | `SR` `TXE` | Set while the transmit buffer is free, cleared by writing `DR`, and held down while a second word waits behind the one on the wire | 28.3.5, 28.3.7 | `a_word_written_goes_out_and_the_word_on_the_wire_comes_back` |
//! | `SR` `RXNE` | Set when the shift register hands its word to the receive buffer, cleared by reading `DR` | 28.3.7 | `a_word_written_goes_out_and_the_word_on_the_wire_comes_back` |
//! | `SR` `BSY` | Set when a transfer starts, and cleared when it finishes, when the SPI is disabled, or on a mode fault. It's kept low throughout a master bidirectional reception, which is the exception the section names | 28.3.7 | `the_busy_flag_stands_for_exactly_the_transfer`, `a_master_receiving_bidirectionally_never_shows_busy` |
//! | Transfer time | A word takes its bits at the baud rate, `PCLK` over the `BR` encoding, eight or sixteen bits by `DFF`, and a run of them is timed from where the run began so the rounding can't accumulate | 28.5.1 | `a_transfer_takes_the_time_its_bits_take`, `the_frame_is_eight_bits_or_sixteen_by_dff`, `a_run_of_words_does_not_drift` |
//! | Master full duplex | The sequence begins on the write to `DR`. The word goes out and the word on the wire comes in | 28.3.3, 28.3.5 | `a_word_written_goes_out_and_the_word_on_the_wire_comes_back` |
//! | Receive only | With `RXONLY` set the sequence begins as soon as `SPE` is set, and nothing is transmitted | 28.3.5 | `receive_only_starts_itself_and_sends_nothing` |
//! | Bidirectional | `BIDIMODE` with `BIDIOE` transmits and receives nothing. Without `BIDIOE` it receives and transmits nothing | 28.3.4, 28.3.5 | `bidirectional_carries_one_direction_at_a_time` |
//! | `LSBFIRST` | The bit order on the wire, which the model applies to the word it hands out and takes in | 28.5.1 | `lsbfirst_reverses_the_word_on_the_wire` |
//! | `SR` `OVR` | A word arriving with `RXNE` still set leaves the buffer alone and sets `OVR`. A read of `DR` while it stands, then a read of `SR`, clears it | 28.3.10 | `an_unread_word_overruns_and_the_new_one_is_lost`, `an_overrun_needs_both_halves_of_its_sequence` |
//! | `SR` `MODF` | `NSS` low on a master in hardware mode, or `SSI` low on one in software mode, sets it, clears `SPE` and `MSTR`, and hardware refuses to set either again while it stands. A read or write of `SR` while it stands, then a write of `CR1`, clears it | 28.3.1, 28.3.10 | `a_mode_fault_drops_the_master_and_holds_it_down`, `software_slave_management_faults_on_its_own_bit`, `a_fault_clears_only_through_both_halves_of_its_sequence` |
//! | `NSS` | Software management through `SSM` and `SSI` leaves the pin free. Hardware management is what watches it, and `SSOE` makes it an output rather than the input a fault comes in on | 28.3.1 | `software_slave_management_faults_on_its_own_bit`, `an_output_slave_select_takes_no_fault_from_its_pin` |
//! | `SR` `CRCERR` | Set when the word received doesn't match `RXCRCR`. Cleared by writing zero, and a written one leaves it alone | 28.3.10, 28.5.3 | `a_crc_that_does_not_match_is_flagged`, `a_written_one_leaves_the_crc_flag_where_it_was` |
//! | CRC | `CRCPR` is the polynomial. Enabling `CRCEN` resets both registers, and they accumulate over the words that cross. `CRCNEXT` sends the transmit register once the buffered data has gone | 28.3.6 | `the_crc_accumulates_over_the_words_of_a_transfer`, `enabling_the_crc_resets_its_registers`, `the_crc_goes_out_after_the_data_already_buffered` |
//! | `SR` `FRE` | A frame error in TI slave mode, cleared by reading `SR` | 28.3.10 | `a_frame_error_clears_when_the_status_register_is_read` |
//! | `CR2` interrupts | `TXEIE`, `RXNEIE`, and `ERRIE` raise the instance's line, and the line follows the flags | 28.3.11 | `each_enable_raises_the_line_for_its_own_flag` |
//! | Chip select | A peer sits behind a port pin the board names: the pin's level arrives as the port's request line, the peer is selected at its active level, told of each edge, and clocked while selected, and its answer is what `MISO` carries. None selected reads `MISO` low. A peer attached without a select is tied to its active level | 28.3.1 | `a_request_from_the_named_pin_selects_the_peer`, `a_request_from_another_pin_is_ignored`, `a_peer_is_told_once_per_edge`, `a_peer_without_a_select_is_always_selected`, `nothing_selected_reads_low`, `a_select_written_by_the_firmware_reaches_the_peer` |
//! | `fPCLK` | The peripheral clock as the part's tree feeds it, followed as it changes: `BR` divides what it is now, so the next word goes at the new rate, and the fraction the run was carrying is dropped with the rate it was a fraction of | 28.5.1, 6.2 | `a_change_of_clock_applies_from_the_next_word` |
//! | Contention | Two peers selected at once: both are clocked, the line reads low, and the controller counts the word | not applicable | `two_peers_selected_at_once_is_contention` |
//! | A silent chip | A chip the board has silenced, a board condition, isn't there to the bus: it's neither clocked nor told of its select, and `MISO` reads what it reads with nothing selected. A silence the board asks for while the chip's select stands waits for the deselect, so a burst is never cut in half and a chip that comes back starts at a select edge. A part reset counts as the deselect. A tied chip has no deselect to wait for, so its silence takes effect at once | not applicable | `a_silent_chip_is_not_on_the_bus_and_answers_again_after`, `a_silence_waits_for_the_chips_deselect`, `a_silence_waiting_for_a_deselect_takes_effect_at_a_part_reset`, `a_tied_chip_goes_silent_at_once` |
//! | `CR2` DMA | `RXDMAEN` and `TXDMAEN` drive the two request outputs, each following the flag that feeds it, so a controller wired to this instance is asked for one word at a time | 28.3.9 | `each_dma_enable_drives_its_own_request_off_its_own_flag` |
//! | 28.3.8 | All four disable procedures, run as a firmware runs them, including the one that clears `SPE` with a word still on the wire | 28.3.8 | `the_manuals_disable_procedures_all_terminate` |
//! | `I2SCFGR`, `I2SPR` | Stored, and with `I2SMOD` set the block does nothing at all. `I2SPR`'s reset value is one ST's file gets wrong | 28.5.8, 28.5.9 | `the_audio_mode_is_stored_and_does_nothing`, `the_prescaler_comes_out_of_reset_at_the_value_the_manual_gives` |
//! | The reset bit | A write that leaves the interface's bit of `RCC_APB1RSTR` or `RCC_APB2RSTR` set puts it back as it came out of reset: its registers, both buffers and the word on the wire, with its line and each request the controller heard rise lowered at once. ChibiOS pulses the bit as the driver starts. The chips, their selects, `NSS` and the clock the tree drives stay | 6.3.8, 6.3.9 | `a_reset_bit_puts_its_interface_back_and_leaves_the_others_alone` |
//!
//! **What the model leaves out, and why.**
//!
//! | Behavior | Source | Reason |
//! |---|---|---|
//! | The I2S: its audio clock, its four protocols, and the `CHSIDE` and `UDR` flags, which section 28.5.3 marks not used in SPI mode | 28.4 | A second peripheral sharing the block. No board in release 0.1 has an audio codec, and the audio clock is the `PLLI2S` tap the clock controller answers and nothing yet consumes |
//! | A slave's transfer beginning when the master's clock arrives, rather than when `SPE` is set or `DR` is written | 28.3.5 | No wire delivers a clock: the peer is a method, and a method can't be a clock edge. The model begins a slave's transfer as it begins a master's, and times it from `BR`, which section 28.5.1 gives the master's baud generator alone. So a slave's frames are the right words in the right order at a rate its master doesn't choose |
//! | A word on the wire when the peripheral clock changes | 28.3.1 | It finishes at the rate it began at, where the part would clock its remaining bits at the new one: a difference of less than one word, once, and a tree isn't retuned with a word in flight |
//! | Being held at reset while the bit of `RCC_APBxRSTR` stands | 28.5, 6.3.8, 6.3.9 | The interface is reset at each write that leaves the bit set and answers as usual between two of them. Held in reset it would answer its reset values and take no write, but ChibiOS sets and clears the bit in two writes, so nothing reaches it while the bit stands |
//! | The clock phase and polarity as the shape of a waveform | 28.3.1 | `CPOL` and `CPHA` decide which edge samples and which shifts, which is visible on a wire and not in a register. They're stored, and the word that crosses is the same whatever they say |
//! | The two APB cycles between a write to `DR` and `BSY` rising, and the one SPI clock cycle a slave's `BSY` dips between continuous transfers | 28.3.7, 28.5.3 | Both are gaps narrower than the access that would observe them. The manual's own advice for the first is to wait on `TXE` and then on `BSY`, which the model carries exactly |
//! | `BSY` also meaning that the transmit buffer isn't empty | 28.5.3 | The model reads `BSY` off the word on the wire alone, so a write to `DR` with the SPI disabled leaves it low where the part would raise it |
//! | The CRC computed bit by bit in transmission order, so that `LSBFIRST` reverses what it sees | 28.3.6 | The model accumulates over the word as written. A firmware that checks a CRC against a peer sees the same answer at both ends, because both ends of this model agree. One that checks against a figure computed elsewhere with `LSBFIRST` set wouldn't |
//! | Hardware `NSS` as an output reaching a peer's select, `SSOE` | 28.3.1 | A select arrives from a port pin as a request the interface reads. The interface's own `NSS` output goes the other way, from this model to a pin, and no model drives a pin yet. The bench records it as a finding |
//! | A contention reported by the run | not applicable | The controller counts it and a test asserts the count. `DeviceContext::tell` is the channel a model names a state of its own on, and this model doesn't use it for a contention yet |
//! | A multi-master bus, and section 28.3.10's rule that `NSS` must be pulled high during the `MODF` clearing sequence | 28.3.10 | One master is all a machine describes, and the rule guards against a second one. `Spi::drive_nss` does carry the level, so a machine may raise it |
//! | Any bound on how long a receive-only transfer runs | 28.3.5 | Section 28.3.5 begins that sequence as soon as `SPE` is set and the part clocks on until it's cleared, so the model schedules a word for as long as the firmware leaves it enabled. That's faithful, and it's expensive: a word at the reset baud is about 190 ns, so an enabled receive-only SPI is some millions of events a second, and a session with one never goes idle. A firmware that means to stop reading clears `SPE`, which is what section 28.3.8 has it do |
//! | The transmit underrun of `UDR` | 28.5.3 | Section 28.5.3 says in as many words that the bit isn't used in SPI mode |
//! | ES0206 rev 24 2.12.1: `BSY` may stay high when the SPI is disabled, in three named configurations | ES0206 rev 24 | The model clears `BSY` on the disable, section 28.3.7's second cause, which is the behavior the manual documents and the erratum departs from. Its workaround for the master receive-only case is to ignore `BSY`, so a firmware written against the erratum still runs here |
//! | ES0206 rev 24 2.12.2: the clock starts on `MSTR` even with the SPI disabled, coming from a receiving slave | ES0206 rev 24 | The model starts nothing while `SPE` is clear. The erratum's effect is an anticipated clock on a wire, and a wire is what this model doesn't have |
//! | ES0206 rev 24 2.12.3: the CRC is wrong when the polynomial is even | ES0206 rev 24 | The model computes the same answer for an even polynomial as for an odd one. Modeling the erratum would mean reproducing a wrong answer whose value the sheet doesn't give |
//! | ES0206 rev 24 2.12.4: the last bit of data or CRC corrupted in master mode with delayed `SCK` feedback | ES0206 rev 24 | A propagation delay on a board's tracks at a frequency the model doesn't carry |
//! | ES0206 rev 24 2.12.5: `BSY` may stay high at the end of a slave transfer | ES0206 rev 24 | The same shape as 2.12.1 and the same reason |
//! | ES0206 rev 24 2.12.6: in I2S slave mode the external master must set the `WS` level | ES0206 rev 24 | The I2S isn't modeled |
//!
//! **Where this model departs from the vendor's file.** ST names `SR` bit 8 `TIFRFE` where
//! section 28.5.3 calls it `FRE`. The generated table stays the vendor's, and the model uses the
//! manual's name in prose. The register file enforces field access from that table, and ST types
//! every flag of `SR` read-only except `CRCERR`. It types that one read-write, where section 28.5.3
//! types it `rc_w0` (a written one has no effect), so the model puts that bit back itself. ST also
//! gives `I2SPR` a reset value of `0x000A` where section 28.5.9 gives `0x0002`, which
//! `memory_map.rs` corrects for every instance of the layout.
//!
//! **One rule the model keeps that the page doesn't state.** `CRCNEXT` clears itself once the CRC
//! word has gone out. Section 28.5.1 bit 12 types it read-write and says nothing about hardware,
//! but a bit that stood would make every word after the first a CRC word, which section 28.3.6's
//! procedure doesn't intend.

use std::collections::VecDeque;

use crate::chip::spi::{Pattern, Peer};
use crate::chip::{Place, Placed, Timeline};
use crate::device::{Device, DeviceContext, Level, RegisterFile, RequestChange};
use crate::snapshot::store;
use crate::time::{EventId, Run, VirtualNanos};

use crate::soc::stm32f427::generated::spi1::{Register, cr1, cr2, i2scfgr, sr};
use crate::soc::stm32f427::peripherals::gpio::Pin;

/// The SPI's one interrupt, position zero of its instance's table.
const GLOBAL: usize = 0;

/// The receive request, this instance's request output zero, RM0090 rev 22 section 28.3.9.
///
/// The number is the model's, as an interrupt's position is. A layout shared by eight instances
/// can't know which of them it is, so the part's wiring is what says where `SPI3_RX` arrives.
pub const RECEIVE_REQUEST: usize = 0;

/// The transmit request, this instance's request output one, section 28.3.9.
pub const TRANSMIT_REQUEST: usize = 1;

/// How many transmitted words the model keeps for a test to look at.
const REMEMBERED: usize = 64;

/// One serial peripheral interface of an STM32F42xxx, RM0090 rev 22 chapter 28.
#[derive(Debug)]
pub struct Spi {
    clock_hz: u64,
    peers: Vec<Attached>,
    contentions: u32,
    outgoing: VecDeque<u16>,
    transmit: Option<u16>,
    receive: u16,
    shifting: Option<Shift>,
    run: Option<Run>,
    nss: Level,
    interrupt: Level,
    requests: [Level; 2],
    fault_sequence: bool,
    overrun_sequence: bool,
    crc_error: bool,
    control: u64,
}

/// A word on its way across the wire.
#[derive(Clone, Copy, Debug, serde::Serialize, serde::Deserialize)]
struct Shift {
    event: EventId,
    word: u16,
    crc_phase: bool,
}

impl Spi {
    /// The SPI of an instance whose peripheral bus runs at `clock_hz`, which is what `BR` divides,
    /// until the part's tree drives it otherwise.
    #[must_use]
    pub fn with_clock(clock_hz: u64) -> Self {
        Self {
            clock_hz,
            peers: Vec::new(),
            contentions: 0,
            outgoing: VecDeque::new(),
            transmit: None,
            receive: 0,
            shifting: None,
            run: None,
            nss: Level::High,
            interrupt: Level::Low,
            requests: [Level::Low; 2],
            fault_sequence: false,
            overrun_sequence: false,
            crc_error: false,
            control: 0,
        }
    }

    /// Hangs an integrated circuit on this interface's wires with its chip select tied to the level
    /// that selects it, which is what a bench does and what a board with one chip on a bus may do.
    /// Attaching again replaces what was there, selected peers included: one chip tied low is the
    /// whole bus.
    pub fn attach(&mut self, peer: impl Peer) {
        let peer: Box<dyn Peer> = Box::new(peer);
        self.peers.clear();
        self.peers.push(Attached {
            peer: Place::new(peer),
            select: None,
            selected: true,
        });
    }

    /// Hangs an integrated circuit behind a chip select, which is what a board does to a part.
    ///
    /// The select is a port pin's level, and it arrives here as the port's request line.
    /// The board names the port, the pin, and the level that selects, and the part's description
    /// makes this interface a reader of requests. A second chip on the same wires is a second call.
    pub fn attach_selected(&mut self, peer: impl Peer, select: Select) {
        let peer: Box<dyn Peer> = Box::new(peer);
        self.peers.push(Attached {
            peer: Place::new(peer),
            select: Some(select),
            selected: false,
        });
    }

    /// Takes the chips off `from`'s wires and hangs them on these, with only a tied select still
    /// selected: the port pins the others hang on are back at reset, driving nothing. A silence that
    /// was waiting for a deselect has had one.
    pub fn inherit(&mut self, from: &mut Self) {
        self.peers = std::mem::take(&mut from.peers);
        for attached in &mut self.peers {
            attached.selected = attached.select.is_none();
            attached.peer.release();
        }
    }

    /// Stops the nth chip on these wires answering, or lets it answer again: a board condition,
    /// [`Place::silence`]. A chip whose select stands keeps answering until it's deselected, so a
    /// burst is never cut in half and a chip that comes back always starts at a select edge. A tied
    /// chip is never deselected, so its silence takes effect at once. Answers whether a chip hangs
    /// there.
    #[must_use]
    pub fn silence(&mut self, nth: usize, silent: bool) -> bool {
        let Some(attached) = self.peers.get_mut(nth) else {
            return false;
        };
        if attached.selected && attached.select.is_some() {
            attached.peer.silence_at_release(silent);
        } else {
            attached.peer.silence(silent);
        }
        true
    }

    /// Whether anything is on the other end of the wires.
    #[must_use]
    pub fn attached(&self) -> bool {
        !self.peers.is_empty()
    }

    /// The first chip on these wires, to ask what it saw. [`Spi::peers`] has every chip.
    #[must_use]
    pub fn peer(&self) -> Option<&dyn Peer> {
        self.peers.first().map(|attached| attached.peer.chip())
    }

    /// Every chip on these wires, in the order they were hung.
    pub fn peers(&self) -> impl Iterator<Item = &dyn Peer> {
        self.peers.iter().map(|attached| attached.peer.chip())
    }

    /// The nth chip on these wires, in the order they were hung.
    pub fn peer_mut(&mut self, nth: usize) -> Option<&mut dyn Peer> {
        self.peers
            .get_mut(nth)
            .map(|attached| attached.peer.chip_mut())
    }

    /// The nth chip's place on these wires, for a trigger to count and a one-shot fault to reach.
    pub fn place_mut(&mut self, nth: usize) -> Option<&mut dyn Placed> {
        self.peers
            .get_mut(nth)
            .map(|attached| -> &mut dyn Placed { &mut attached.peer })
    }

    /// The nth chip on these wires as the type it was hung as, to drive what it measures. `None`
    /// where nothing hangs there or another chip does.
    ///
    /// The chip lives on the wires because a transfer has to reach it synchronously, and the board
    /// that hung it is what asks for it back. The type is the board's own fact, not a lookup.
    pub fn chip_mut<P: Peer>(&mut self, nth: usize) -> Option<&mut P> {
        self.peers.get_mut(nth)?.peer.chip_as()
    }

    /// Tells every chip on these wires that the part restarted, at the instant `context` gives.
    pub(crate) fn chips_restarted(&mut self, context: &mut DeviceContext<'_>) {
        for attached in &mut self.peers {
            attached
                .peer
                .chip_mut()
                .restarted(&mut Timeline::new(context));
        }
    }

    /// How many words went out with more than one peer selected.
    #[must_use]
    pub fn contentions(&self) -> u32 {
        self.contentions
    }

    /// Hangs a [`Pattern`] peer answering `words`: sugar for [`Spi::attach`] that most tests want.
    pub fn drive_miso(&mut self, words: impl IntoIterator<Item = u32>) {
        self.attach(Pattern::answering(words));
    }

    /// The words the SPI has most recently shifted out, oldest first.
    // ceiling: REMEMBERED words; a record of every word sent would grow while the bus stays busy.
    #[must_use]
    pub fn shifted_out(&self) -> Vec<u16> {
        self.outgoing.iter().copied().collect()
    }

    /// The level on the `NSS` pin, which RM0090 rev 22 section 28.3.1 gives two jobs: the mode
    /// fault of section 28.3.10 on a master managing it in hardware, and that section's frame error
    /// on a TI slave.
    pub fn drive_nss(
        &mut self,
        level: Level,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) {
        let was = self.nss;
        self.nss = level;
        if was == level || level == Level::High {
            return;
        }
        let cr1_value = registers.get(Register::Cr1);
        if cr1::MSTR.is_set(cr1_value) {
            // Section 28.3.1: the fault is entered "When configured in master mode with NSS
            // configured as an input (MSTR=1 and SSOE=0)", and section 28.3.1 again: with software
            // management "the external NSS pin remains free for other application uses", so the pin
            // says nothing there.
            if !cr1::SSM.is_set(cr1_value) && !cr2::SSOE.is_set(registers.get(Register::Cr2)) {
                self.fault(registers, context);
            }
            return;
        }
        if cr2::FRF.is_set(registers.get(Register::Cr2)) && self.shifting.is_some() {
            let status = registers.get(Register::Sr);
            registers.set(Register::Sr, sr::TIFRFE.set(sr::BSY.set(status, 0), 1));
            self.shifting = None;
            self.run = None;
            self.refresh_signals(registers, context);
        }
    }

    /// The master mode fault of RM0090 rev 22 section 28.3.10: `MODF` is set, `SPE` is cleared,
    /// "which blocks all output from the device", and `MSTR` is cleared, "thus forcing the device
    /// into slave mode".
    fn fault(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        let cr1_value = registers.get(Register::Cr1);
        registers.set(Register::Cr1, cr1::MSTR.set(cr1::SPE.set(cr1_value, 0), 0));
        let status = registers.get(Register::Sr);
        registers.set(Register::Sr, sr::MODF.set(sr::BSY.set(status, 0), 1));
        self.shifting = None;
        self.run = None;
        self.fault_sequence = false;
        self.store_control(registers);
        self.refresh_signals(registers, context);
    }
}

/// A register of this layout read back as the word it is. RM0090 rev 22 section 28.5 gives every
/// one of them sixteen bits, so the mask can't lose anything the part could have stored.
fn stored(registers: &RegisterFile, register: Register) -> u16 {
    u16::try_from(registers.get(register) & u64::from(u16::MAX)).expect("masked to sixteen bits")
}

/// `BR`: `fPCLK` over two to the encoding plus one, RM0090 rev 22 section 28.5.1 bits 5:3.
fn baud_divider(encoding: u64) -> u64 {
    2 << encoding
}

impl Spi {
    /// The bit rate on the wire, RM0090 rev 22 section 28.5.1.
    fn baud_hz(&self, registers: &RegisterFile) -> u64 {
        self.clock_hz / baud_divider(cr1::BR.get(registers.get(Register::Cr1)))
    }

    /// Eight bits, or sixteen with `DFF` set, RM0090 rev 22 section 28.5.1 bit 11.
    fn frame_bits(registers: &RegisterFile) -> u64 {
        if cr1::DFF.is_set(registers.get(Register::Cr1)) {
            16
        } else {
            8
        }
    }

    /// When the word starting now finishes. The nanoseconds a word takes rarely divide evenly, so
    /// the remainder is carried. Dropping it would drift for as long as the bus stayed busy.
    fn ends(&mut self, now: VirtualNanos, registers: &RegisterFile) -> VirtualNanos {
        let baud = self.baud_hz(registers).max(1);
        let bits = Self::frame_bits(registers);
        let run = self.run.get_or_insert(Run::at(now));
        if run.extend(u128::from(bits), baud) == 0 {
            run.ends.0 += 1;
        }
        run.ends
    }

    /// The word masked to the frame, so a firmware that writes sixteen bits into an eight-bit frame
    /// sends what the wire can carry.
    fn framed(registers: &RegisterFile, word: u16) -> u16 {
        if Self::frame_bits(registers) == 16 {
            word
        } else {
            word & 0xFF
        }
    }

    /// The word as it travels, which `LSBFIRST` reverses, RM0090 rev 22 section 28.5.1 bit 7.
    fn on_the_wire(registers: &RegisterFile, word: u16) -> u16 {
        if !cr1::LSBFIRST.is_set(registers.get(Register::Cr1)) {
            return word;
        }
        let bits = u32::try_from(Self::frame_bits(registers)).expect("eight or sixteen");
        word.reverse_bits() >> (16 - bits)
    }

    /// Whether the block is an SPI at all, or the I2S sharing its registers, section 28.5.8 bit 11.
    fn is_spi(registers: &RegisterFile) -> bool {
        !i2scfgr::I2SMOD.is_set(registers.get(Register::I2scfgr))
    }

    /// Whether a transfer puts a word on the wire, RM0090 rev 22 section 28.3.5.
    fn transmits(registers: &RegisterFile) -> bool {
        let value = registers.get(Register::Cr1);
        if cr1::BIDIMODE.is_set(value) {
            cr1::BIDIOE.is_set(value)
        } else {
            !cr1::RXONLY.is_set(value)
        }
    }

    /// Whether a transfer takes a word off the wire, RM0090 rev 22 section 28.3.5.
    fn receives(registers: &RegisterFile) -> bool {
        let value = registers.get(Register::Cr1);
        if cr1::BIDIMODE.is_set(value) {
            !cr1::BIDIOE.is_set(value)
        } else {
            true
        }
    }

    /// Whether `BSY` shows this transfer at all. RM0090 rev 22 section 28.3.7 keeps it low through
    /// a master's bidirectional reception, which is the one exception it names.
    fn busy_shows(registers: &RegisterFile) -> bool {
        let value = registers.get(Register::Cr1);
        !(cr1::MSTR.is_set(value) && cr1::BIDIMODE.is_set(value) && !cr1::BIDIOE.is_set(value))
    }

    /// One word of `bits` across the wire, both ways at once: what a peer is given is what went out,
    /// and what the selected one answers is what arrives. Nothing selected means nothing drives `MISO`, and
    /// the line reads low.
    ///
    /// It's called for every word the clock carries, whether or not the controller is listening. A
    /// chip is clocked by the wire and not by the controller's opinion of the direction, so a run
    /// in the transmit-only bidirectional mode of section 28.3.4 leaves the peer as many words
    /// further on as it was sent. Every selected peer is clocked for the same reason, and two
    /// selected at once is a contention. The silicon leaves the line indeterminate. This answers
    /// low, so a fight looks like the silence of a chip that isn't there, and the word is counted. A
    /// silent chip isn't there, so it's neither clocked nor counted.
    fn cross(&mut self, outgoing: u16, bits: u8, context: &mut DeviceContext<'_>) -> u16 {
        let mut answers = self
            .peers
            .iter_mut()
            .filter(|attached| attached.selected)
            .filter_map(|attached| {
                let peer = attached.peer.answering()?;
                let answer = peer.transfer(u32::from(outgoing), bits, &mut Timeline::new(context));
                let crossed = attached.peer.answered(answer) & u32::from(u16::MAX);
                Some(u16::try_from(crossed).expect("masked to a word"))
            });
        match (answers.next(), answers.next()) {
            // ceiling: a silent or absent chip reads 0; upgrade when a board pulls MISO up
            (None, _) => 0,
            (Some(answer), None) => answer,
            (Some(_), Some(_)) => {
                answers.for_each(drop);
                self.contentions += 1;
                0
            }
        }
    }
}

/// The CRC of RM0090 rev 22 section 28.3.6: the polynomial of `CRCPR` over the words of a transfer,
/// eight bits wide for an eight-bit frame and sixteen for a sixteen-bit one.
fn accumulate(crc: u16, word: u16, polynomial: u16, bits: u64) -> u16 {
    let top = 1 << (bits - 1);
    let mask = if bits == 16 { u16::MAX } else { 0xFF };
    let mut value = crc;
    for position in (0..bits).rev() {
        let bit = (word >> position) & 1;
        let carry = (value & top) != 0;
        value = (value << 1) & mask;
        if carry ^ (bit == 1) {
            value ^= polynomial & mask;
        }
    }
    value
}

impl Spi {
    /// Starts a transfer when one is due, RM0090 rev 22 section 28.3.5: on the write to `DR`, or as
    /// soon as `SPE` is set where nothing is transmitted.
    fn start(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        if self.shifting.is_some() || self.clock_hz == 0 || !Self::is_spi(registers) {
            return;
        }
        let cr1_value = registers.get(Register::Cr1);
        if !cr1::SPE.is_set(cr1_value) {
            return;
        }
        let armed = cr1::CRCEN.is_set(cr1_value) && cr1::CRCNEXT.is_set(cr1_value);
        let mut crc_phase = armed;
        let word = if Self::transmits(registers) {
            match self.transmit.take() {
                // Section 28.3.6: "If data are present in the TX buffer, the CRC value is
                // transmitted only after the transmission of the data byte." The procedure has a
                // firmware set CRCNEXT as it writes the last word, so the bit waits for the buffer
                // to drain rather than eating it.
                Some(word) => {
                    crc_phase = false;
                    word
                }
                None if armed => stored(registers, Register::Txcrcr),
                None => return,
            }
        } else {
            0
        };
        let status = registers.get(Register::Sr);
        let busy = u64::from(Self::busy_shows(registers));
        registers.set(Register::Sr, sr::BSY.set(sr::TXE.set(status, 1), busy));
        let at = self.ends(context.now(), registers);
        self.shifting = Some(Shift {
            event: context.schedule(at),
            word,
            crc_phase,
        });
    }

    /// One word has crossed.
    fn finish(
        &mut self,
        shift: Shift,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) {
        let bits = Self::frame_bits(registers);
        let polynomial = stored(registers, Register::Crcpr);
        let crc_on = cr1::CRCEN.is_set(registers.get(Register::Cr1));

        let on_the_wire = Self::on_the_wire(registers, shift.word);
        if Self::transmits(registers) {
            if self.outgoing.len() == REMEMBERED {
                self.outgoing.pop_front();
            }
            self.outgoing.push_back(on_the_wire);
            if crc_on && !shift.crc_phase {
                let crc = stored(registers, Register::Txcrcr);
                let next = accumulate(crc, shift.word, polynomial, bits);
                registers.set(Register::Txcrcr, u64::from(next));
            }
        }

        let frame = u8::try_from(bits).expect("eight or sixteen");
        let answered = self.cross(on_the_wire, frame, context);
        let mut status = registers.get(Register::Sr);
        if Self::receives(registers) {
            let word = Self::framed(registers, Self::on_the_wire(registers, answered));
            if sr::RXNE.is_set(status) {
                // Section 28.3.10: the buffer keeps what it had and the new word is lost.
                status = sr::OVR.set(status, 1);
            } else {
                self.receive = word;
                status = sr::RXNE.set(status, 1);
                if shift.crc_phase {
                    // Section 28.3.6: the word against the CRC phase is the peer's CRC, checked
                    // here. The page still has it reach the buffer, since "the CRC must be read in
                    // the SPI_DR register to clear the RXNE flag".
                    self.crc_error = word != stored(registers, Register::Rxcrcr);
                    status = sr::CRCERR.set(status, u64::from(self.crc_error));
                } else if crc_on {
                    let crc = stored(registers, Register::Rxcrcr);
                    let next = accumulate(crc, word, polynomial, bits);
                    registers.set(Register::Rxcrcr, u64::from(next));
                }
            }
        }
        if shift.crc_phase {
            let cr1_value = registers.get(Register::Cr1);
            registers.set(Register::Cr1, cr1::CRCNEXT.set(cr1_value, 0));
        }
        registers.set(Register::Sr, sr::BSY.set(status, 0));
        self.shifting = None;
        self.start(registers, context);
        if self.shifting.is_none() {
            self.run = None;
        }
        self.store_control(registers);
        self.refresh_signals(registers, context);
    }

    /// Raises the instance's line while an enabled flag stands, RM0090 rev 22 section 28.3.11, and
    /// drives the two request outputs of section 28.3.9 off the same two flags.
    ///
    /// The model keeps the level it last drove on each, so a write that changes nothing reports
    /// nothing. A controller acknowledges a request by making the access that clears the flag
    /// behind it, which is section 10.3.2's handshake read from this end.
    fn refresh_signals(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
        let status = registers.get(Register::Sr);
        let control = registers.get(Register::Cr2);
        for (output, enable, flag) in [
            (RECEIVE_REQUEST, cr2::RXDMAEN, sr::RXNE),
            (TRANSMIT_REQUEST, cr2::TXDMAEN, sr::TXE),
        ] {
            let wanted = Level::from(enable.is_set(control) && flag.is_set(status));
            if wanted != self.requests[output] {
                self.requests[output] = wanted;
                context.request(output, wanted);
            }
        }
        let errors = sr::OVR.mask() | sr::MODF.mask() | sr::CRCERR.mask() | sr::TIFRFE.mask();
        let wanted = if (cr2::TXEIE.is_set(control) && sr::TXE.is_set(status))
            || (cr2::RXNEIE.is_set(control) && sr::RXNE.is_set(status))
            || (cr2::ERRIE.is_set(control) && status & errors != 0)
        {
            Level::High
        } else {
            Level::Low
        };
        if wanted == self.interrupt {
            return;
        }
        self.interrupt = wanted;
        context.interrupt(GLOBAL, wanted);
    }

    fn store_control(&mut self, registers: &RegisterFile) {
        self.control = registers.get(Register::Cr1);
    }
}

/// Which port pin selects a peer, and the level that selects it.
///
/// The port is named as the vendor names the instance, because that's how the port's request line
/// names itself. The pin is the request's number.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Select {
    /// The port instance, such as `GPIOD`.
    pub port: &'static str,
    /// The pin of that port.
    pub pin: Pin,
    /// The level at which the peer is selected, low for nearly every chip.
    pub active: Level,
}

/// A chip on the wires, and whether its select stands.
#[derive(Debug)]
struct Attached {
    peer: Place<dyn Peer>,
    /// `None` is a select tied to its active level.
    select: Option<Select>,
    selected: bool,
}

store!(Spi {
    values: [
        clock_hz,
        contentions,
        outgoing,
        transmit,
        receive,
        shifting,
        run,
        nss,
        interrupt,
        requests,
        fault_sequence,
        overrun_sequence,
        crc_error,
        control,
    ],
    parts: [peers],
    built: [],
});

store!(Attached {
    values: [selected],
    parts: [peer],
    built: [select],
});

impl Device for Spi {
    type Register = Register;

    /// The peripheral clock has changed. The word on the wire keeps its end. The run's carry was a
    /// fraction of the old rate and means nothing at the new one.
    fn on_clock(&mut self, hz: u64, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        self.clock_hz = hz;
        // RM0090 rev 22 figure 16: an interface whose enable is clear has no clock and shifts nothing.
        // ceiling: a word shifting when the clock stops is dropped, not frozen; upgrade when a
        // firmware gates an interface mid-word and waits for the rest of it.
        if hz == 0 {
            self.shifting = None;
            self.run = None;
            let status = registers.get(Register::Sr);
            registers.set(Register::Sr, sr::BSY.set(status, 0));
            return;
        }
        if let Some(run) = &mut self.run {
            run.drop_carry();
        }
        // A word waiting in the buffer, or a receive-only master, starts once the clock is back.
        self.start(registers, context);
        self.refresh_signals(registers, context);
    }

    /// A port announcing a pin's level. The peers behind that pin follow it. A level a peer already
    /// stands at isn't an edge, so it's told nothing, and neither is a silent one. A silence waiting
    /// on the select takes effect at the deselect.
    fn on_request(
        &mut self,
        change: RequestChange,
        _registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) {
        for attached in &mut self.peers {
            let Some(select) = attached.select else {
                continue;
            };
            if select.port != change.instance || select.pin.index() != change.request {
                continue;
            }
            let selected = change.level == select.active;
            if selected == attached.selected {
                continue;
            }
            attached.selected = selected;
            if selected {
                attached.peer.begin();
            }
            if let Some(peer) = attached.peer.answering() {
                if selected {
                    peer.select(&mut Timeline::new(context));
                } else {
                    peer.deselect(&mut Timeline::new(context));
                }
            }
            if !selected {
                attached.peer.release();
            }
        }
    }

    /// `DR` answers the receive buffer and clears `RXNE`, RM0090 rev 22 section 28.3.5, leaving the
    /// word there as section 28.3.10 requires. Reading `SR` clears `FRE`. It's the second half of
    /// the sequence that clears `OVR`, and the first half of the one that clears `MODF`.
    fn read(
        &mut self,
        register: Register,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) -> Option<u64> {
        let status = registers.get(Register::Sr);
        match register {
            Register::Dr => {
                registers.set(Register::Sr, sr::RXNE.set(status, 0));
                // Step one of section 28.3.10's overrun sequence counts only while `OVR` stands.
                self.overrun_sequence = sr::OVR.is_set(status);
                self.refresh_signals(registers, context);
                Some(u64::from(self.receive))
            }
            Register::Sr => {
                let mut cleared = sr::TIFRFE.set(status, 0);
                if self.overrun_sequence {
                    cleared = sr::OVR.set(cleared, 0);
                    self.overrun_sequence = false;
                }
                // "Make a read or write access to the SPI_SR register while the MODF bit is set."
                self.fault_sequence = sr::MODF.is_set(status);
                registers.set(Register::Sr, cleared);
                self.refresh_signals(registers, context);
                Some(status)
            }
            _ => None,
        }
    }

    /// `DR` is the receive buffer. `SR` reads as it stands.
    fn peek(
        &self,
        register: Register,
        _registers: &RegisterFile,
        _now: VirtualNanos,
    ) -> Option<u64> {
        (register == Register::Dr).then_some(u64::from(self.receive))
    }

    fn write(
        &mut self,
        register: Register,
        value: u64,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) {
        match register {
            Register::Dr => {
                let status = registers.get(Register::Sr);
                registers.set(Register::Sr, sr::TXE.set(status, 0));
                let written =
                    u16::try_from(value & u64::from(u16::MAX)).expect("masked to sixteen bits");
                self.transmit = Some(Self::framed(registers, written));
                self.start(registers, context);
            }
            Register::Cr1 => self.write_cr1(registers, context),
            Register::Sr => self.write_sr(value, registers, context),
            _ => {}
        }
        self.refresh_signals(registers, context);
    }

    fn on_event(
        &mut self,
        event: EventId,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) {
        if let Some(shift) = self.shifting.filter(|shift| shift.event == event) {
            self.finish(shift, registers, context);
        }
        for attached in &mut self.peers {
            attached
                .peer
                .chip_mut()
                .on_event(event, &mut Timeline::new(context));
        }
    }

    /// Everything a reset clears goes back as built. The chips stay on the wires, each selected or not
    /// as its port pin holds it, and so does the level on `NSS`. The clock stays at the rate the tree
    /// drives.
    fn reset(&mut self) {
        *self = Self {
            peers: std::mem::take(&mut self.peers),
            nss: self.nss,
            ..Self::with_clock(self.clock_hz)
        };
    }

    /// The interface's reset line, RM0090 rev 22 sections 6.3.8 and 6.3.9: the interface goes back as
    /// a reset puts it, and each request the controller heard rise falls at once.
    fn reset_line(&mut self, _registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        context.lower_requests(&self.requests);
        self.reset();
    }
}

impl Spi {
    /// `CR1`: the mode fault's hold on the enable and its clearing sequence, the CRC registers
    /// `CRCEN` resets, the fault a cleared `SSI` raises, and the start of a receive-only transfer.
    fn write_cr1(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
        let status = registers.get(Register::Sr);
        if sr::MODF.is_set(status) {
            // Section 28.3.10: "hardware does not allow the setting of the SPE and MSTR bits while
            // the MODF bit is set", and an access to SR while it stands, then this write, clears
            // it.
            let value = registers.get(Register::Cr1);
            registers.set(Register::Cr1, cr1::MSTR.set(cr1::SPE.set(value, 0), 0));
            if self.fault_sequence {
                registers.set(Register::Sr, sr::MODF.set(status, 0));
                self.fault_sequence = false;
            }
            self.store_control(registers);
            self.refresh_signals(registers, context);
            return;
        }
        let value = registers.get(Register::Cr1);
        // Section 28.3.6: enabling the CRC "resets the CRC registers (SPI_RXCRCR and SPI_TXCRCR)".
        if cr1::CRCEN.is_set(value) && !cr1::CRCEN.is_set(self.control) {
            registers.set(Register::Rxcrcr, 0);
            registers.set(Register::Txcrcr, 0);
        }
        self.store_control(registers);
        // Section 28.3.10: the pin faults, and so does "SSI bit low (in NSS software mode)".
        if cr1::MSTR.is_set(value) && cr1::SSM.is_set(value) && !cr1::SSI.is_set(value) {
            self.fault(registers, context);
            return;
        }
        if !cr1::SPE.is_set(value) {
            // Section 28.3.7: BSY "is cleared ... when the SPI is disabled". The word already on
            // the wire still lands: section 28.3.8's receive-only procedure has a firmware clear
            // SPE and then wait for the last RXNE, which it couldn't do if this dropped the word.
            registers.set(Register::Sr, sr::BSY.set(status, 0));
            return;
        }
        self.start(registers, context);
    }

    /// `SR`: every flag is hardware's but `CRCERR`, which section 28.5.3 types `rc_w0`. ST's file
    /// types it read-write, so a written one reaches the register file and the model must put it
    /// back.
    fn write_sr(
        &mut self,
        written: u64,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) {
        let status = registers.get(Register::Sr);
        if sr::CRCERR.is_set(written) {
            registers.set(
                Register::Sr,
                sr::CRCERR.set(status, u64::from(self.crc_error)),
            );
        } else {
            self.crc_error = false;
            registers.set(Register::Sr, sr::CRCERR.set(status, 0));
        }
        // "Make a read or write access to the SPI_SR register while the MODF bit is set."
        self.fault_sequence = sr::MODF.is_set(status);
        self.refresh_signals(registers, context);
    }
}