rigless 0.0.4

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; needs the rigless-sidecar package from rigless-labs/qemu
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
// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: 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. RL-116 is the I2S.
//!
//! | Register or section | Behaviour | 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; and 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 cannot 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 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 does not 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; 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; the line follows the flags | 28.3.11 | `each_enable_raises_the_line_for_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` |
//!
//! **What the model leaves out, and why.**
//!
//! | Behaviour | 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, RL-116. 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 cannot 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. A slave's frames are therefore the right words in the right order at a rate its master does not choose |
//! | Clock gating: the SPI answering the bus only once `RCC_APBxENR` enables it, and being held at reset by `RCC_APBxRSTR` | 28.5, 6.3.5 to 6.3.14 | The bus does not consult another model, RL-112 |
//! | The DMA request lines of `RXDMAEN` and `TXDMAEN` | 28.3.9 | The bits are stored, and the model raises no request because nothing could answer one: a DMA model cannot reach memory across the protocol boundary. A firmware driving its SPI by DMA waits on a transfer-complete flag in the DMA that this model cannot help with, so its wait moves rather than ends |
//! | 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 are 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 is not 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 would not |
//! | 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; the rule guards against a second one. `Spi::set_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 is cleared, so the model schedules a word for as long as the firmware leaves it enabled. That is faithful and it is 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 is not 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 behaviour 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 does not 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. Modelling the erratum would mean reproducing a wrong answer whose value the sheet does not 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 does not 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 is RL-116 |
//!
//! **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`, which it types 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 does not 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 does not intend.

use std::collections::VecDeque;
use std::num::NonZeroU64;

use crate::device::{Device, DeviceContext, Level, RegisterFile};
use crate::time::{EventId, VirtualNanos};

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

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

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

/// An integrated circuit on a serial interface's wires: what a board hangs off an MCU.
///
/// The interface is full duplex, RM0090 rev 22 section 28.3.3: a word crosses each way at the same
/// time, so a peer answers in the same call it is given. Both words are as they travel, after
/// `LSBFIRST` of section 28.5.1 bit 7 has had its say, because that is what is on the wire.
pub trait Peer: std::fmt::Debug + Send {
    /// The word this peer puts on `MISO` while `mosi` crosses on the controller's line.
    fn transfer(&mut self, mosi: u16) -> u16;

    /// Every word this peer has been sent, oldest first, or nothing where it keeps no log.
    ///
    /// This is the far end's own record, and it is the only thing that can say a stream arrived. The
    /// controller's [`Spi::shifted_out`] is what it believes it sent, which a model that never reaches
    /// its peer would fill just as happily.
    fn received(&self) -> &[u16] {
        &[]
    }
}

/// A peer that answers a known sequence, repeats its last word once that drains, and records every
/// word it was sent.
///
/// It is what a bench hangs on an interface with no chip model for it yet. It answers something known
/// and **different** from what it was given, so a controller shifting its own transmission back is
/// caught rather than passed, which a loopback would not do. And what it records is what says the far end
/// of the bus got the stream, rather than the controller's own record of what it believes it sent.
#[derive(Debug, Default)]
pub struct Pattern {
    answers: VecDeque<u16>,
    held: u16,
    received: Vec<u16>,
}

impl Pattern {
    /// A peer answering `words`, oldest first.
    #[must_use]
    pub fn answering(words: impl IntoIterator<Item = u16>) -> Self {
        Self {
            answers: words.into_iter().collect(),
            held: 0,
            received: Vec::new(),
        }
    }
}

impl Peer for Pattern {
    fn received(&self) -> &[u16] {
        &self.received
    }

    fn transfer(&mut self, mosi: u16) -> u16 {
        self.received.push(mosi);
        if let Some(word) = self.answers.pop_front() {
            self.held = word;
        }
        self.held
    }
}

/// One serial peripheral interface of an STM32F42xxx, RM0090 rev 22 chapter 28.
#[derive(Debug)]
pub struct Spi {
    clock_hz: NonZeroU64,
    peer: Option<Box<dyn Peer>>,
    outgoing: VecDeque<u16>,
    transmit: Option<u16>,
    receive: u16,
    shifting: Option<Shift>,
    run: Option<Run>,
    nss: Level,
    interrupt: Level,
    fault_sequence: bool,
    overrun_sequence: bool,
    crc_error: bool,
    control: u64,
}

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

/// A run of words: when the word in flight ends, and the fraction of a nanosecond carried from the ones
/// before it. Carrying the remainder rather than counting the bits keeps both numbers small, where a
/// running bit count would overflow the arithmetic after a few minutes of a busy bus.
#[derive(Clone, Copy, Debug)]
struct Run {
    ends: VirtualNanos,
    carry: u64,
}

impl Spi {
    /// The SPI of an instance whose peripheral bus runs at `clock_hz`, which is what `BR` divides.
    #[must_use]
    pub fn with_clock(clock_hz: NonZeroU64) -> Self {
        Self {
            clock_hz,
            peer: None,
            outgoing: VecDeque::new(),
            transmit: None,
            receive: 0,
            shifting: None,
            run: None,
            nss: Level::High,
            interrupt: Level::Low,
            fault_sequence: false,
            overrun_sequence: false,
            crc_error: false,
            control: 0,
        }
    }

    /// Hangs an integrated circuit on this interface's wires, which is what a board does to a part.
    ///
    /// One peer, because a second needs a chip select to tell them apart and nothing carries pin
    /// levels between models yet, RL-112. Attaching again replaces what was there.
    pub fn attach(&mut self, peer: impl Peer + 'static) {
        self.peer = Some(Box::new(peer));
    }

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

    /// The chip on these wires, to ask what it saw.
    #[must_use]
    pub fn peer(&self) -> Option<&dyn Peer> {
        self.peer.as_deref()
    }

    /// Hangs a [`Pattern`] peer answering `words`. Sugar for [`Spi::attach`], and what most tests want.
    pub fn drive_miso(&mut self, words: impl IntoIterator<Item = u16>) {
        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 for as long as the bus stayed 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_interrupt(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_interrupt(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 cannot 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.get() / 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 rather than dropped, which 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 {
            ends: now,
            carry: 0,
        });
        let numerator = bits * 1_000_000_000 + run.carry;
        run.carry = numerator % baud;
        run.ends = VirtualNanos(run.ends.0 + (numerator / baud).max(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 that shares 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 across the wire, both ways at once: what the peer is given is what went out, and what
    /// it answers is what arrives. Nothing attached means nothing drives `MISO`, and the line reads low.
    ///
    /// It is 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.
    fn cross(&mut self, outgoing: u16) -> u16 {
        match self.peer.as_mut() {
            Some(peer) => peer.transfer(outgoing),
            None => 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::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 answered = self.cross(on_the_wire);
        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_interrupt(registers, context);
    }

    /// Raises the instance's line while an enabled flag stands, RM0090 rev 22 section 28.3.11. The model
    /// keeps the level it last drove, so a write that changes nothing reports nothing.
    fn refresh_interrupt(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
        let status = registers.get(Register::Sr);
        let control = registers.get(Register::Cr2);
        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;
        match wanted {
            Level::High => context.raise(GLOBAL),
            Level::Low => context.lower(GLOBAL),
        }
    }

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

impl Device for Spi {
    type Register = Register;

    /// `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` and is the second half of the
    /// sequences that clear `OVR` and `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 the overrun stands.
                self.overrun_sequence = sr::OVR.is_set(status);
                self.refresh_interrupt(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_interrupt(registers, context);
                Some(status)
            }
            _ => None,
        }
    }

    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_interrupt(registers, context);
    }

    fn on_event(
        &mut self,
        event: EventId,
        registers: &mut RegisterFile,
        context: &mut DeviceContext<'_>,
    ) {
        // A model keeps the id it last scheduled and ignores any other; there is no cancel.
        let Some(shift) = self.shifting.filter(|shift| shift.event == event) else {
            return;
        };
        self.finish(shift, registers, context);
    }
}

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, is what 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_interrupt(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 fault comes from "SSI bit low (in NSS software mode)" as well as the pin.
        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 could not do if the word were dropped here.
            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 has to 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_interrupt(registers, context);
    }
}