// 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 | 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. 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` |
//!
//! **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. 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 by `RCC_APBxRSTR` | 28.5, 6.3.8, 6.3.9 | The enable half of this is the part's: an instance whose `RCC_APBxENR` bit is clear doesn't answer, which `memory_map.rs` states as a gate. Being held at reset is a third behaviour beside the gate and the request line the runtime gives the part, and it isn't one of them |
//! | 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 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 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. Modelling 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 modelled |
//!
//! **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::any::Any;
use std::collections::VecDeque;
use crate::chip::spi::Peer;
use crate::chip::{Place, Placed};
use crate::device::{Device, DeviceContext, Level, RegisterFile, RequestChange};
use crate::snapshot::store;
use crate::time::{EventId, 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;
/// A peer that answers a known sequence, repeats its last word once that drains, and records every
/// word it was sent.
///
/// It's 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. A loopback wouldn't catch it. 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, serde::Serialize, serde::Deserialize)]
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(),
}
}
}
store!(Pattern);
impl Peer for Pattern {
fn received(&self) -> &[u16] {
&self.received
}
fn transfer(&mut self, mosi: u16, _context: &DeviceContext<'_>) -> 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: 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,
}
/// 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, serde::Serialize, serde::Deserialize)]
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,
/// 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> {
let peer: &mut dyn Any = self.peers.get_mut(nth)?.peer.chip_mut();
peer.downcast_mut()
}
/// 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 = 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 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 {
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 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 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, context: &DeviceContext<'_>) -> u16 {
let mut answers = self
.peers
.iter_mut()
.filter(|attached| attached.selected)
.filter_map(|attached| {
let answer = attached.peer.answering()?.transfer(outgoing, context);
let crossed = attached.peer.answered(u32::from(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 answered = self.cross(on_the_wire, 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.carry = 0;
}
// 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(context);
} else {
peer.deselect(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<'_>,
) {
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, 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);
}
}