// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The serial peripheral interface of RM0433 rev 9 chapter 50, written against the `spi1` layout.
//!
//! One layout serves six instances, SPI1 to SPI6, and [`INSTANCES`] says what differs between them
//! (section 50.3's Table 422): SPI1, SPI2 and SPI3 have 16-byte FIFOs, frames and CRCs of up to 32 bits
//! and the I2S, and SPI4, SPI5 and SPI6 have 8-byte FIFOs, frames and CRCs of up to 16 bits and no I2S.
//! [`Kind`] carries those three facts, and the reserved bits they imply read zero on the narrow ones.
//! Every citation is RM0433 rev 9 unless it names another document.
//!
//! **This model is the SPI as a master and not the I2S, and not a slave.** A firmware that enables
//! the block as a slave or with `I2SMOD` set gets a peripheral that stores every write and moves
//! nothing, and the session is told once. The slave takes its clock from a wire and the I2S is a
//! second peripheral sharing the block, and neither is something the chip seam carries.
//!
//! **What the model handles and what falls to the register file.** Every register of the chapter has
//! an arm in [`Device::read`] and [`Device::write`]. The register file settles the access types the
//! model's corrections state (`TXCRC` and `RXCRC` read-only, `CSTART`, `IOLOCK` and `TSER` write-one-to-set,
//! `TXPIE` and `DXPIE` read-write) and answers every read but `RXDR` from the values the model keeps in it. The
//! model keeps `SR` current after every call that changes a flag, so `SR` reads need no arm of their
//! own. The model is everything else: the transmit and receive queues, the frame on the wire and its
//! time, the flags and what clears them, the write protection, the interrupt, the two DMA requests,
//! the CRC, and the chips on the wires.
//!
//! **The width of an access.** A data register access can carry one, two or four frames (section
//! 50.4.13). The context of a bus access carries its width, [`DeviceContext::access_width`], and
//! [`Device::read`] and [`Device::write`] hand it to [`Spi::read_data`] and [`Spi::write_data`]. Where
//! the context carries none, the model takes the access a driver casts the register to for the frame
//! size: a byte up to 8 bits, a half-word up to 16 and a word above.
//!
//! | Register or section | Behavior | Source | Test |
//! |---|---|---|---|
//! | Reset state | `CFG1` at `0x0007_0007`, `CRCPOLY` at `0x107`, `SR` at `0x1002` (`TXP` and `TXC`), the rest zero, nothing scheduled or raised | 50.11.1 to 50.11.14 | `reset_values_match_the_manual`, `the_status_an_idle_interface_computes_is_the_reset_value`, `the_registers_come_out_of_reset_with_nothing_scheduled_or_raised` |
//! | Instances | Six on one layout with Table 422's FIFO and frame sizes, Table 147's lines, Tables 124, 125 and 128's request inputs, and the kernel clock multiplexer of each | 50.3, 8.7.20, 8.7.22 | `the_instance_table_is_the_vendors_file_and_rm0433`, `the_interrupt_is_position_zero_of_each_instances_table_at_table_147s_line`, `each_fifo_holds_16_bytes_on_spi1_and_8_on_spi4` |
//! | Reserved bits | Bits the manual reserves read zero, and on the 16-bit instances so do the top bit of `DSIZE` and `CRCSIZE` and the upper half of `CRCPOLY`, `TXCRC`, `RXCRC` and `UDRDR` | 50.11.3, 50.11.10 to 50.11.13 | `reserved_bits_read_zero_and_the_sixteen_bit_instances_lose_their_upper_halves` |
//! | `CR1` `SPE` | Rising loads the transfer counts from `TSIZE` and the CRC registers with their pattern. Falling abandons the frame on the wire, flushes both FIFOs, clears `OVR`, `UDR`, `CRCE`, `CSTART` and `IOLOCK`, and leaves `EOT`, `TXTF`, `SUSP` and the rest. Refused while `MODF` stands | 50.11.1, 50.11.6, 50.4.12, 50.4.11 | `disabling_flushes_both_fifos_and_clears_what_the_manual_says`, `disabling_with_data_queued_drops_it_and_a_new_enable_starts_empty`, `a_stale_frame_event_changes_nothing`, `spe_cannot_be_set_while_modf_stands_and_modfc_lets_it_go` |
//! | `CR1` `CSTART` | Starts a master. A full-duplex or transmit master moves a frame whenever the `TxFIFO` has one, and a receive master clocks by itself. Cleared by `EOT` and by a suspension. Taken at a master with `SPE` set only | 50.11.1, 50.4.10, 50.4.11 | `a_polled_exchange_returns_the_answer_after_eight_bit_times`, `a_master_with_cstart_set_waits_for_data_and_starts_with_the_next_write`, `data_in_the_txfifo_waits_for_cstart`, `a_slave_transfers_nothing_and_is_told_so` |
//! | `CR1` `CSUSP` | Reads zero. Suspends at the end of the current frame, or at once on an idle bus, clearing `CSTART` and setting `SUSP`. A new `CSTART` follows without disabling | 50.11.1, 50.11.6 | `csusp_with_the_bus_idle_clears_cstart_and_sets_susp_at_once`, `csusp_with_a_frame_on_the_wire_waits_for_the_frame_to_end` |
//! | `CR1` `MASRX` | A receive master stops its clock when the `RxFIFO` is full and sets `SUSP`, and runs on when `SUSPC` is written. A full-duplex master takes no notice | 50.11.1, 50.4.11 | `masrx_stops_a_receiver_when_the_rxfifo_is_full_and_susp_resumes_it`, `masrx_does_not_hold_a_full_duplex_master_and_the_overrun_follows` |
//! | `CR1` `HDDIR`, `CFG2` `COMM` | Full duplex sends and keeps. A simplex transmitter sends and keeps nothing. A simplex receiver clocks by itself and sends nothing. Half duplex is a transmitter with `HDDIR` set and a receiver with it clear | 50.4.4, 50.11.4 | `a_simplex_transmitter_sends_frames_and_keeps_no_answer`, `a_simplex_receiver_clocks_by_itself_and_keeps_every_answer`, `half_duplex_follows_hddir` |
//! | `CR1` `IOLOCK` | Set while disabled and with no fault, holds `CFG2`, cleared when `SPE` falls | 50.11.1, 50.11.4 | `iolock_locks_cfg2_is_set_only_while_disabled_and_clears_when_spe_falls`, `iolock_cannot_be_set_while_modf_stands` |
//! | `CR1` `SSI`, `CFG2` `SSM`, `SSIOP`, `SSOE`, `MASTER` | A master whose slave select input is at its active level takes a mode fault: `MODF` is set, `SPE` and `IOLOCK` and `MASTER` clear. The input is `SSI` under `SSM`, and `SSIOP` chooses the level. Under `SSOE` the input is forced inactive | 50.4.7, 50.5.2, 50.11.4 | `a_master_whose_software_slave_select_is_active_takes_the_fault`, `ssiop_chooses_the_level_of_ssi_that_is_active`, `a_master_driving_nss_takes_no_fault_from_a_low_ssi` |
//! | `CR1` `CRC33_17`, `TCRCINI`, `RCRCINI` | The implied top term of a 33-bit or 17-bit polynomial, and the pattern, zero or ones, each CRC register starts from | 50.11.1, 50.5.3 | `a_seventeen_bit_polynomial_gives_crc_16_on_the_sixteen_and_thirty_two_bit_instances_alike`, `the_ones_pattern_starts_the_crc_at_all_ones` |
//! | `CR2` `TSIZE` | The frames of a transfer, counted down in `CTSIZE`. At the last, `EOT` is set and `CSTART` clears. Zero is endless | 50.11.2, 50.4.11 | `a_transfer_of_tsize_frames_ends_with_eot_after_the_last_and_clears_cstart`, `a_tsize_of_zero_is_endless_and_never_raises_eot`, `a_tsize_written_under_an_enabled_interface_is_told` |
//! | `CR2` `TSER` | Takes a write only while it holds zero. It counts toward what software may push as soon as it is accepted, so `TXTF` waits for the extension. When `TSIZE` frames are done it moves into `TSIZE`, clears, and sets `TSERF` with no `EOT`. Writing it clears `TSERF` | 50.11.2, 50.4.11, 50.11.6, 50.11.7 | `tser_extends_the_transfer_with_tserf_and_no_eot_at_the_reload`, `tser_takes_a_write_only_while_it_holds_zero`, `writing_tser_clears_tserf` |
//! | `CFG1` `MBR` | The kernel clock over two to the power of `MBR` plus one, from 2 to 256 | 50.11.3 | `a_frame_takes_its_bits_at_the_rate_mbr_chooses` |
//! | `CFG1` `DSIZE` | A frame of `DSIZE` plus one bits, never under four. A frame is aligned to a byte, a half-word or a word in the data registers, and the bits past it are ignored | 50.11.3, 50.4.8 | `a_frame_is_dsize_plus_one_bits_and_never_under_four`, `rxdr_right_aligns_a_frame_in_a_byte_a_half_word_or_a_word`, `the_bits_of_a_written_frame_past_its_size_are_ignored` |
//! | `CFG1` `FTHLV` | The packet is `FTHLV` plus one frames. `TXP`, `RXP` and `DXP` follow whole packets, and the last packet of a sized transfer asks for what is left. A packet over half the FIFO is told | 50.11.3, 50.4.11, 50.11.6 | `txp_stands_while_a_whole_packet_fits`, `rxp_waits_for_a_whole_packet_and_dxp_is_both_flags`, `the_last_packet_of_a_transfer_asks_only_for_what_is_left`, `a_packet_over_half_the_fifo_is_told_once` |
//! | `CFG1` `CRCEN`, `CRCSIZE` | The CRC is computed over the data and sent as one frame of `CRCSIZE` bits after the last, taken from the CRC's top bits and padded with zeros below. The receiver compares what comes with its own and sets `CRCE`. `EOT` follows the CRC frame | 50.11.3, 50.5.3 | `the_crc_matches_the_check_value_of_crc_8_and_the_frame_follows_the_data`, `the_receiver_crc_is_over_what_the_peer_sent_and_a_wrong_one_sets_crce`, `crcsize_takes_the_most_significant_bits_of_the_crc_and_pads_a_longer_frame_with_zeros`, `a_receiver_checks_the_peers_crc_frame_and_leaves_its_own_crc_at_the_pattern` |
//! | `CFG1` `TXDMAEN`, `RXDMAEN` | Drive the two request outputs, the only bits of `CFG1` writable with `SPE` set | 50.11.3, 50.4.14 | `the_transmit_request_follows_txp_while_txdmaen_and_spe_hold`, `the_receive_request_follows_rxp_while_rxdmaen_holds`, `cfg1_is_write_protected_while_spe_is_set_except_the_dma_enables` |
//! | `CFG1` `UDRCFG`, `UDRDET`, `UDRDR` | Slave underrun behavior. Stored, with `UDRDR` and the rest of `CFG1` held while enabled | 50.5.2, 50.11.13 | `cfg2_crcpoly_and_udrdr_are_write_protected_while_spe_is_set` |
//! | `CFG2` `LSBFRST` | The peer sees the frame with its bits reversed within the frame size, and the answer is reversed back. The CRC is shifted in the order the bits cross | 50.11.4, 50.5.3 | `lsbfrst_sends_the_least_significant_bit_first_and_reverses_the_answer`, `the_crc_is_shifted_in_transmission_order_so_lsbfrst_reflects_the_input` |
//! | `CFG2` `MIDI`, `MSSI` | `MIDI` bit times of idle between consecutive frames, and `MSSI` between `CSTART` and the first frame where the master drives `NSS` | 50.11.4, 50.4.7 | `midi_idles_the_bus_between_consecutive_frames_only`, `midi_also_holds_back_a_frame_written_inside_the_gap`, `mssi_delays_the_first_frame_when_the_master_drives_nss`, `mssi_waits_for_nothing_under_software_slave_select` |
//! | `CFG2` `CPOL`, `CPHA`, `SP`, `IOSWP`, `AFCNTR`, `SSOM` | Stored and held while enabled. The waveform and the pins are left out, below. `SP` set to the TI protocol is told | 50.11.4, 50.5.1 | `cfg2_crcpoly_and_udrdr_are_write_protected_while_spe_is_set`, `the_ti_protocol_moves_frames_as_motorola_does_and_is_told` |
//! | `IER` | Eleven enables, one at the bit of the flag it arms. `TXPIE` and `DXPIE` are also cleared by the `TXTF` event | 50.11.5, 50.7 | `every_enable_sits_at_the_bit_of_the_flag_it_arms`, `txpie_and_dxpie_are_set_and_cleared_by_software`, `txtf_stands_when_tsize_frames_have_been_pushed_and_masks_txpie_and_dxpie` |
//! | `SR` `RXP`, `TXP`, `DXP` | Whole packets in the `RxFIFO` and room for one in the `TxFIFO`, and both. `TXP` stands with `SPE` clear | 50.11.6, 50.4.11 | `rxp_waits_for_a_whole_packet_and_dxp_is_both_flags`, `txp_stands_while_a_whole_packet_fits`, `a_disabled_interface_raises_its_txp_and_txc_interrupts_under_item_2_22_4` |
//! | `SR` `RXWNE`, `RXPLVL` | Four bytes or more in the `RxFIFO`, and the frames of a residue under four bytes of up to 16 bits | 50.11.6 | `rxwne_and_rxplvl_count_the_residue_of_a_short_packet`, `rxwne_counts_bytes_whatever_the_frame_and_rxplvl_reads_zero_above_sixteen_bits` |
//! | `SR` `TXC` | With `TSIZE` zero, the `TxFIFO` empty and the bus idle. Otherwise a copy of `EOT` | 50.11.6 | `txc_follows_the_queue_and_the_bus_while_tsize_is_zero`, `eotc_clears_eot_and_with_it_txc_while_tsize_is_above_zero` |
//! | `SR` `EOT`, `TXTF` | `EOT` at the last frame of a sized transfer. `TXTF` when `TSIZE` frames have been pushed. Writes after `TXTF` are dropped | 50.11.6, 50.4.11 | `a_transfer_of_tsize_frames_ends_with_eot_after_the_last_and_clears_cstart`, `writes_after_txtf_are_dropped_and_told_once`, `redundant_writes_past_tsize_are_discarded_in_a_packed_access` |
//! | `SR` `OVR` | A frame that finds the `RxFIFO` full is dropped, the queue keeps what it had, and `OVR` is set. Cleared by `OVRC` or by disabling | 50.5.2, 50.11.6 | `a_receiver_without_masrx_overruns_and_keeps_what_it_had`, `ovrc_clears_an_overrun_and_so_does_disabling` |
//! | `SR` `MODF`, `SUSP`, `TSERF`, `CRCE` | Raised by the mode fault, a suspension, a reload from `TSER` and a CRC mismatch, and cleared by their `IFCR` bits | 50.5.2, 50.11.6 | `a_master_whose_software_slave_select_is_active_takes_the_fault`, `csusp_with_the_bus_idle_clears_cstart_and_sets_susp_at_once`, `tser_extends_the_transfer_with_tserf_and_no_eot_at_the_reload`, `the_receiver_crc_is_over_what_the_peer_sent_and_a_wrong_one_sets_crce` |
//! | `SR` `UDR`, `TIFRE` | Slave flags a master never raises. They set and clear as flags | 50.5.2, 50.11.6 | `ifcr_clears_each_flag_it_names_and_only_that_one` |
//! | `SR` `CTSIZE` | The frames of the transfer still to cross, zero for an endless one | 50.11.6 | `a_transfer_of_tsize_frames_ends_with_eot_after_the_last_and_clears_cstart`, `a_tsize_of_zero_is_endless_and_never_raises_eot` |
//! | `IFCR` | Nine clear bits for the nine flags software clears. Write-only and reads zero | 50.11.7 | `ifcr_clears_each_flag_it_names_and_only_that_one`, `ifcr_and_txdr_read_zero_and_csusp_reads_zero` |
//! | `TXDR` | Pushes frames onto the `TxFIFO`, least significant first, dropped while disabled, full or after `TXTF`, and told once on each transition | 50.11.8, 50.4.11, 50.4.13 | `a_wide_access_carries_several_frames_least_significant_first`, `each_fifo_holds_16_bytes_on_spi1_and_8_on_spi4`, `writes_after_txtf_are_dropped_and_told_once`, `a_word_write_to_txdr_with_eight_bit_frames_queues_four_frames`, `a_dma_packed_transfer_with_word_beats_reaches_eot` |
//! | `RXDR` | Pops frames off the `RxFIFO`, the oldest in the low bits, and a redundant read gives zero. A debugger's peek takes nothing | 50.11.9, 50.4.13 | `a_wide_read_takes_several_frames_and_a_redundant_one_reads_zero`, `a_word_read_of_rxdr_with_eight_bit_frames_pops_four_frames`, `a_debugger_peek_of_rxdr_shows_the_front_frame_and_takes_nothing` |
//! | The FIFOs | 16 or 8 bytes each, holding that many frames of up to 8 bits, half as many of up to 16, a third of up to 24, and a quarter above | 50.4.11, 50.3 | `each_fifo_holds_16_bytes_on_spi1_and_8_on_spi4`, `a_wider_frame_takes_more_of_the_fifo` |
//! | `CRCPOLY`, `TXCRC`, `RXCRC` | The polynomial, with its length given by its top bit, and the two registers it accumulates, read-only and kept when disabled. A new transfer starts them over | 50.11.10 to 50.11.12, 50.5.3 | `the_crc_matches_the_check_value_of_crc_8_and_the_frame_follows_the_data`, `a_new_transfer_starts_the_crc_over_and_the_registers_survive_disabling`, `txcrc_rxcrc_sr_and_rxdr_ignore_writes`, `a_polynomial_of_degree_zero_gives_a_crc_of_zero_width` |
//! | `I2SCFGR` | Stored. With `I2SMOD` set on SPI1 to SPI3 the block moves nothing and the session is told. SPI4 to SPI6 have no I2S | 50.11.14, 50.3 | `the_audio_mode_of_spi1_moves_nothing_and_means_nothing_on_spi4` |
//! | Write protection | `CFG1` but its DMA enables, `CFG2`, `CRCPOLY` and `UDRDR` take no write while `SPE` is set, and `CFG2` none while `IOLOCK` is. A dropped change is told | 50.11.3, 50.11.4, 50.11.10, 50.11.13 | `cfg1_is_write_protected_while_spe_is_set_except_the_dma_enables`, `cfg2_crcpoly_and_udrdr_are_write_protected_while_spe_is_set` |
//! | The wire | A frame takes its bits at the baud rate, carrying the remainder so a burst can't drift, and the chip is clocked as it ends | 50.11.3, 50.4.8 | `a_frame_takes_its_bits_at_the_rate_mbr_chooses`, `a_run_of_frames_does_not_drift`, `sixteen_frames_back_to_back_end_at_sixteen_frame_times` |
//! | `spi_ker_ck` | The kernel clock the part drives, followed as it changes: the next frame goes at the new rate. A frame whose time runs out with the clock stopped ends when it returns. Until the part drives one the interface runs on the HSI's 64 MHz | 50.4.1, 8.7.2, 8.7.20, 8.7.22 | `a_change_of_clock_applies_from_the_next_frame`, `a_stopped_clock_parks_the_frame_and_starts_no_other`, `a_model_nobody_wired_runs_on_the_hsi_and_says_so` |
//! | Interrupt | One line, position zero of the instance's table, high while any flag stands under its enable. `EOTIE` arms `EOT`, `SUSP` and `TXC` | 50.7, Table 423, Table 147 | `each_sticky_flag_raises_the_line_under_its_own_enable_alone`, `eotie_also_arms_txc_and_susp`, `rxpie_txpie_and_dxpie_follow_their_packet_flags`, `the_line_is_reported_only_when_its_level_changes` |
//! | DMA requests | A transmit request while `TXDMAEN` and `SPE` are set and `TXP` stands, and a receive request while `RXDMAEN` is set and `RXP` stands, or an incomplete packet is left at `EOT`. Each is a level, driven on a change only | 50.4.14 | `the_transmit_request_follows_txp_while_txdmaen_and_spe_hold`, `the_receive_request_follows_rxp_while_rxdmaen_holds`, `the_receive_request_asks_for_the_incomplete_last_packet_at_eot`, `chibios_start_leaves_the_transmit_request_standing_for_the_dma`, `a_dma_shaped_exchange_asks_for_each_frame_as_it_arrives` |
//! | 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 low. A peer attached without a select is tied to its active level | 50.4.5 | `a_request_from_the_named_pin_selects_the_peer`, `a_request_from_another_pin_or_port_is_ignored`, `a_peer_is_told_once_per_edge`, `a_peer_attached_without_a_select_is_always_selected`, `a_chip_select_toggled_by_gpio_selects_each_chip_in_turn_on_one_bus` |
//! | Contention and a silent chip | Two peers selected at once are both clocked, the line reads low and the interface counts it. A chip a board has silenced is neither clocked nor counted, and a silence asked for while its select stands waits for the deselect | not applicable | `two_peers_selected_at_once_are_contention_and_the_line_reads_low`, `a_silent_chip_is_not_on_the_bus_and_answers_again_after`, `a_silence_waits_for_the_chips_deselect`, `a_trigger_counts_the_transactions_that_end_at_a_deselect`, `an_event_a_chip_scheduled_reaches_it_at_its_instant` |
//! | ChibiOS's sequences | `spi_lld_start`, `spi_lld_exchange` with its DMA beats and its `CSUSP`, `spi_lld_wait_complete` and `spi_lld_polled_exchange`, on the bus, with a real chip model and a fake one | 50.4.10, 50.4.11, 50.4.14 | `tests/stm32h743_spi.rs` |
//! | Events, reset, snapshot | An event the model doesn't expect is ignored. A reset puts the model back and leaves the chips. The state serializes | 50.1 | `a_stale_frame_event_changes_nothing`, `a_reset_returns_the_interface_to_its_reset_state_and_leaves_the_chips_on_the_wires`, `the_model_state_round_trips_through_a_snapshot_with_a_frame_in_flight` |
//! | ES0392 rev 15 item 2.22.2 | Applicable to revision V. Setting `CSTART` within one SCK cycle of `EOT` can stall a master. The model starts the transfer and tells the session | ES0392 rev 15 | `a_start_within_one_sck_cycle_of_eot_is_told_under_item_2_22_2` |
//! | ES0392 rev 15 item 2.22.4 | Applicable to revision V. A disabled interface has `TXP` and `TXC` set and raises the interrupt if `TXPIE` or `EOTIE` is. The workaround is to keep both disabled | ES0392 rev 15 | `a_disabled_interface_raises_its_txp_and_txc_interrupts_under_item_2_22_4` |
//! | ES0392 rev 15 item 2.22.5 | Applicable to revision V. With `CRCEN` clear, `TSIZE` above zero and `CRCSIZE` under 8 bits the last data received may be corrupted. The model tells the session and corrupts nothing, since the sheet gives no value | ES0392 rev 15 | `a_sized_transfer_with_a_crc_frame_under_eight_bits_and_no_crc_is_told_under_item_2_22_5` |
//!
//! **What the model leaves out, and why.**
//!
//! | Behavior | Source | Reason |
//! |---|---|---|
//! | Slave mode: the clock from a wire, `UDR`, `UDRCFG` and `UDRDET`, `TIFRE`, the slave's TI protocol and the NSS start detection | 50.4.4, 50.4.10, 50.5.1, 50.5.2 | No wire delivers a clock, so a slave's frames have no instant to happen at. `UDR` and `TIFRE` exist as flags software can clear, and a master never raises them |
//! | The I2S | 50.8 to 50.10 | A second peripheral sharing the block. No target of the first board has an audio codec on an SPI |
//! | The TI protocol's `NSS` pulse in master mode | 50.5.1 | Frames move as in the Motorola protocol and the session is told. The pulse is a waveform on a pin |
//! | The waveform: `CPOL`, `CPHA`, `IOSWP`, the pins the peripheral keeps with `AFCNTR` | 50.4.8, 50.4.7, 50.11.4 | A pin is something the chip seam doesn't carry. The frame that crosses is the same whatever they say, and a chip attached through the seam is wired as the firmware's configuration expects |
//! | The `NSS` pin: the level a master drives under `SSOE`, the input of a master without it, and `SSOM`'s interleaved pulses | 50.4.7 | A pin is something the chip seam doesn't carry, and no board in this release wires one. A master's fault comes from `SSI` alone |
//! | The CRC frame's room in the `RxFIFO` | 50.4.11 | The manual says to allow for it when relying on `EOT` alone. The model checks the frame and never queues it, so it can't make an overrun the part would |
//! | Synchronization between the three clock domains, the first frame's start delay and the clock's last cycles | 50.4.1, 50.11.1 | The sheet states the domains are "strictly synchronized" and gives no number of cycles, so a frame starts at the access that releases it. `MASRX`'s own caveat about a frame already begun at the full signal is the same kind of gap |
//! | The kernel clock retuned under a frame on the wire | 50.4.1 | The frame finishes at the old rate's instant, where the part would clock the rest at the new one. A tree isn't retuned under a transfer |
//! | Low-power modes and `spi_wkup` | 50.6 | No power modes are modeled |
//! | The datasheet's SCK ceiling and timing | DS12110 rev 11 | The model's baud rate is whatever `MBR` and the kernel clock give. The seam carries words and no clock |
//! | ES0392 rev 15 item 2.22.1 | ES0392 rev 15 | Not applicable to this part's revision V: the summary table marks it present on revisions Y and W alone. No test reproduces what this revision does not do |
//! | ES0392 rev 15 item 2.22.3 | ES0392 rev 15 | Slave only: a corrupted first CRC bit from `UDRCFG` at a non-zero `UDRDET` |
//! | ES0392 rev 15 item 2.22.6 | ES0392 rev 15 | Applicable to revision V and pending. Disabling the interface too soon after `EOT` truncates the last SCK pulse on the wire, which is a waveform the model doesn't have, and the sheet gives no figure for too soon |
//!
//! **Where this model departs from the vendor's file.** ST types `CSTART` and `IOLOCK` of `CR1` and
//! `TSER` of `CR2` read-only, where sections 50.11.1 and 50.11.2 type all three `rs`, and `TXPIE` and
//! `DXPIE` of `IER` read-only, where section 50.11.5 types both `rs`. The part restates the first
//! three ([`FIELD_CORRECTIONS`]) as write-one-to-set, and the model clears each as hardware does. It
//! restates the last two as read-write, since ES0392 rev 15 item 2.22.4's workaround needs software to
//! clear them, and the model clears them too at the `TXTF` event. ST types
//! `TXCRC` and `RXCRC` read-write where sections 50.11.11 and 50.11.12 type every bit `r`, and the
//! part gives each [`READ_ONLY`]'s rule. The file spells `FTHLV` `FTHVL` and `DXPIE` `DPXPIE`, and
//! calls `SPI_I2SCFGR` `CGFR`, and the tables keep the file's names. It lists the fields of the
//! 32-bit instances for all six, and the model masks the 16-bit ones as Table 422 and the register
//! pages say. The register file stores a reserved bit as written, so the model masks every write to
//! the bits the instance defines.
//!
//! **Where the manual is silent and the model assumes.** Each of these is one reading, quoted.
//!
//! | Case | The page | What the model does |
//! |---|---|---|
//! | `TXPIE` and `DXPIE` written zero | Type `rs`: "Writing 0 has no effect on the bit value", and the text: "set by software and cleared by TXTF flag set event" | A zero clears them. ES0392 rev 15 item 2.22.4's workaround is to keep `TXP` interrupts disabled, which software can only do if it can clear them |
//! | `CSUSP` with no transfer under way | "In master mode, when this bit is set by software, CSTART bit is reset at the end of the current frame and communication is suspended" | Honored whenever the interface is an enabled master: `SUSP` is set and `CSTART` is clear. A firmware that waits for `SUSP` after a suspension therefore never hangs |
//! | `CSTART` with `SPE` clear | "When SPE=0 ... internal state machine is reseted" | The write is dropped and `CSTART` reads zero |
//! | `CSTART` after a completed or suspended transfer | "SPI must always be disabled after such software suspension and re-enabled before the next transaction starts" | A new `CSTART` starts a new transfer without a disable, which is what ChibiOS's `spi_lld_wait_complete` and its next `CSTART` do on the part |
//! | The frames after an overrun | "The newly received value is discarded and all data transmitted subsequently are lost" | Only the frame that found the queue full is dropped. Later ones queue if room has appeared, and `OVR` stands until cleared |
//! | `RXPLVL` with `RXWNE` set | Defined "When RXWNE=0" | Reads zero |
//! | `MASRX` in a full-duplex master | "SPI flow is suspended temporary on `RxFIFO` full condition" in "master receiver mode" | Takes effect in the two receive-only modes alone, which "master mode (full duplex or receive only with MASRX = 0)" supports |
//! | Mode fault evaluation | "Mode fault occurs when the master device has its internal slave select logic input ... pulled low" | Evaluated whenever the master's configuration or its `SSI` bit changes, with or without `SPE` |
//! | The CRC registers' initial pattern | "initialized when the CRCEN bit ... is written to 1 or when a data block is transacted completely", and "CRC calculation initialized" when `SPE` is clear | Loaded when `CRCEN` is written one, when `SPE` rises, and when new data is sampled after a CRC phase, over the polynomial's length, and kept when `SPE` falls |
//! | The bit order of the CRC | "done, bit by bit, on the sampling clock edge" | Shifted in as the bits cross, most significant first unless `LSBFRST` is set |
//! | `CTSIZE` | "The value is not quite reliable when traffic is ongoing" | The frames not yet crossed, counted down at the end of each frame, and zero for an endless transfer |
//! | When the `TxFIFO` is popped | Not stated | When a frame is committed to the wire, which is up to `MIDI` bit times before its first bit |
//!
//! **Determinism.** `two_runs_of_one_script_are_the_same_run` drives two fresh instances through a
//! sized transfer with a CRC, a chip select moving, an overrun and a clock change, and checks the
//! session's tells, the interrupt line, the request outputs, every register, the chip's record and the
//! model's whole state agree between the two. `two_runs_of_one_burst_are_the_same_run` does it for a
//! DMA burst on SPI4.
use std::collections::VecDeque;
use crate::chip::spi::Peer;
use crate::chip::{Place, Placed, Timeline};
use crate::device::{Device, DeviceContext, Level, RegisterFile, RequestChange};
use crate::snapshot::store;
use crate::soc::stm32h743::generated::spi1::{Register, cfg1, cfg2, cgfr, cr1, cr2, ier, ifcr, sr};
use crate::soc::stm32h743::peripherals::gpio::Pin;
use crate::soc::{Access, FieldSpec, Layout, Width, WriteModify};
use crate::time::{EventId, VirtualNanos};
// The interface's one interrupt, position zero of its instance's table. RM0433 rev 9 Table 147
const GLOBAL: usize = 0;
/// The receive request, this instance's request output zero, RM0433 rev 9 section 50.4.14.
///
/// The number is the model's, as an interrupt's position is. A layout shared by six instances can't
/// know which of them it is, so the part's wiring says where `spi3_rx_dma` arrives, with
/// [`INSTANCES`] giving the multiplexer input of each.
pub const RECEIVE_REQUEST: usize = 0;
/// The transmit request, this instance's request output one, section 50.4.14.
pub const TRANSMIT_REQUEST: usize = 1;
/// The kernel clock an interface runs on until the part's clock tree drives it, in hertz.
///
/// RM0433 rev 9 section 8.7.2 gives `HSIDIV` a reset value of division by one, "hsi(_ker)_ck =
/// 64 MHz", and sections 8.7.7 to 8.7.10 give the system clock switch and every bus prescaler reset
/// values that leave it at the HSI undivided. So `rcc_pclk2` and `rcc_pclk4` run at 64 MHz out of
/// reset, which is what SPI4, SPI5 and SPI6 take (`SPI45SEL` and `SPI6SEL` reset to the bus clock,
/// sections 8.7.20 and 8.7.22). SPI1, SPI2 and SPI3 reset to `pll1_q_ck`, which is off until the
/// firmware starts the PLL, so for them this is only a stand-in that lets a model on a bus of its
/// own count time. A part that wires the interface to its clock controller never uses it.
pub const DEFAULT_KERNEL_HZ: u64 = 64_000_000;
// The shortest frame, RM0433 rev 9 section 50.4.8: "If a data length of less than 4 bits is
const MIN_FRAME_BITS: u32 = 4;
const NANOS_PER_SECOND: u64 = 1_000_000_000;
const WRITE_PROTECTED: &str = "RM0433 rev 9 sections 50.11.3, 50.11.4, 50.11.10 and 50.11.13: this register is write protected while SPE is set, and the write was dropped";
const TXDR_WHILE_DISABLED: &str =
"RM0433 rev 9 section 50.4.11: a write to the TxFIFO is ignored when the SPI is disabled";
const TXDR_WHILE_FULL: &str = "RM0433 rev 9 section 50.4.11: a write to the TxFIFO is ignored when there is no room for a data frame";
const TXDR_AFTER_TXTF: &str =
"RM0433 rev 9 section 50.4.11: a write to the TxFIFO is ignored when TXTF is set";
const TSIZE_WHILE_ENABLED: &str =
"RM0433 rev 9 section 50.11.2: TSIZE must only be changed while the SPI is disabled";
const PACKET_TOO_LARGE: &str = "RM0433 rev 9 sections 50.4.11 and 50.11.3: a data packet must not exceed half of the FIFO, so two packets always fit";
const SPE_WITH_MODF: &str =
"RM0433 rev 9 section 50.5.2: hardware does not allow SPE to be set while MODF is set";
const SLAVE_NOT_MODELED: &str = "RM0433 rev 9 section 50.4.4: the slave takes its clock from a wire, and the model carries none, so a slave transfers nothing";
const I2S_NOT_MODELED: &str = "RM0433 rev 9 section 50.9: the I2S is a second peripheral sharing the block, and the model transfers nothing with I2SMOD set";
const TI_NOT_MODELED: &str = "RM0433 rev 9 section 50.5.1: frames move as in the Motorola protocol, and the NSS pulse of the TI protocol is not modeled";
// Told when a transfer is started inside the window of ES0392 rev 15 item 2.22.2.
const STALL_WINDOW: &str = "ES0392 rev 15 item 2.22.2: setting CSTART within one SCK cycle of EOT can stall a master when the system clock is much faster than SCK, and the model does not stall";
// Told when a transfer is started under ES0392 rev 15 item 2.22.5.
const CRCSIZE_CORRUPTION: &str = "ES0392 rev 15 item 2.22.5: with CRCEN clear, TSIZE above zero and CRCSIZE under 8 bits the last received data may be corrupted, and the model does not corrupt it";
/// What differs between the six instances of the layout, RM0433 rev 9 section 50.3's Table 422.
///
/// SPI1, SPI2 and SPI3 have 16-byte FIFOs, frames and CRCs of up to 32 bits, and the I2S. SPI4, SPI5
/// and SPI6 have 8-byte FIFOs, frames and CRCs of up to 16 bits, and no I2S.
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct Kind {
fifo_bytes: u8,
frame_bits: u8,
i2s: bool,
}
impl Kind {
/// SPI1, SPI2 and SPI3.
pub const FULL: Self = Self {
fifo_bytes: 16,
frame_bits: 32,
i2s: true,
};
/// SPI4, SPI5 and SPI6.
pub const BASIC: Self = Self {
fifo_bytes: 8,
frame_bits: 16,
i2s: false,
};
}
/// The kernel clock multiplexer an instance takes its `spi_ker_ck` from, RM0433 rev 9 section 8.7.
///
/// The multiplexer's other inputs are the PLL outputs, the HSI, the CSI, the HSE and `per_ck`. The
/// clock controller picks one, and the part tells the interface its rate through
/// [`Device::on_clock`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum KernelClock {
/// `SPI123SEL` of `RCC_D2CCIP1R`, section 8.7.20, for SPI1, SPI2 and SPI3. It resets to
/// `pll1_q_ck`, which is off until the PLL starts.
Spi123,
/// `SPI45SEL` of `RCC_D2CCIP1R`, section 8.7.20, for SPI4 and SPI5. It resets to the APB clock,
/// `rcc_pclk2`.
Spi45,
/// `SPI6SEL` of `RCC_D3CCIPR`, section 8.7.22, for SPI6. It resets to the APB clock,
/// `rcc_pclk4`.
Spi6,
}
/// What the part wires about one instance.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Instance {
/// The instance as the vendor's file names it.
pub name: &'static str,
/// Its FIFO size, frame size and I2S, Table 422.
pub kind: Kind,
/// The request multiplexer its two requests arrive at: SPI6 is on the second, the other five
/// on the first.
pub multiplexer: &'static str,
/// The multiplexer input [`RECEIVE_REQUEST`] drives, Tables 124, 125 and 128.
pub receive_input: u8,
/// The multiplexer input [`TRANSMIT_REQUEST`] drives, Tables 124, 125 and 128.
pub transmit_input: u8,
/// Where its kernel clock comes from.
pub kernel_clock: KernelClock,
}
/// The six instances, in order, with what a part needs to wire each: RM0433 rev 9 Table 422 for
/// the FIFO and frame sizes, Tables 124, 125 and 128 for the request inputs, and sections 8.7.20
/// and 8.7.22 for the kernel clock.
pub const INSTANCES: [Instance; 6] = [
Instance {
name: "SPI1",
kind: Kind::FULL,
multiplexer: "DMAMUX1",
receive_input: 37,
transmit_input: 38,
kernel_clock: KernelClock::Spi123,
},
Instance {
name: "SPI2",
kind: Kind::FULL,
multiplexer: "DMAMUX1",
receive_input: 39,
transmit_input: 40,
kernel_clock: KernelClock::Spi123,
},
Instance {
name: "SPI3",
kind: Kind::FULL,
multiplexer: "DMAMUX1",
receive_input: 61,
transmit_input: 62,
kernel_clock: KernelClock::Spi123,
},
Instance {
name: "SPI4",
kind: Kind::BASIC,
multiplexer: "DMAMUX1",
receive_input: 83,
transmit_input: 84,
kernel_clock: KernelClock::Spi45,
},
Instance {
name: "SPI5",
kind: Kind::BASIC,
multiplexer: "DMAMUX1",
receive_input: 85,
transmit_input: 86,
kernel_clock: KernelClock::Spi45,
},
Instance {
name: "SPI6",
kind: Kind::BASIC,
multiplexer: "DMAMUX2",
receive_input: 11,
transmit_input: 12,
kernel_clock: KernelClock::Spi6,
},
];
const fn corrected(base: &FieldSpec, access: Access, write: WriteModify) -> FieldSpec {
FieldSpec {
access,
write,
..*base
}
}
/// `CSTART`, RM0433 rev 9 section 50.11.1: type `rs`, set by software and cleared by hardware at
/// the end of the transfer or the suspension. The vendor's file types it read-only.
pub static CSTART: FieldSpec = corrected(cr1::CSTART, Access::ReadWrite, WriteModify::OneToSet);
/// `IOLOCK`, section 50.11.1: type `rs`, set by software and cleared by hardware whenever `SPE`
/// falls. The vendor's file types it read-only.
pub static IOLOCK: FieldSpec = corrected(cr1::IOLOCK, Access::ReadWrite, WriteModify::OneToSet);
/// `TSER`, section 50.11.2: type `rs`, set by software while it holds zero and cleared by hardware
/// when it is copied into `TSIZE`. The vendor's file types it read-only.
pub static TSER: FieldSpec = corrected(cr2::TSER, Access::ReadWrite, WriteModify::OneToSet);
/// `TXPIE`, section 50.11.5: type `rs`, "set by software and cleared by TXTF flag set event". The
/// vendor's file types it read-only.
pub static TXPIE: FieldSpec = corrected(ier::TXPIE, Access::ReadWrite, WriteModify::None);
/// `DXPIE`, section 50.11.5, the same rule as [`TXPIE`]. The vendor's file spells it `DPXPIE` and
/// types it read-only.
pub static DXPIE: FieldSpec = corrected(ier::DPXPIE, Access::ReadWrite, WriteModify::None);
/// The fields the part restates for every instance, as the register, the field's name in the
/// vendor's file and the corrected field.
pub static FIELD_CORRECTIONS: [(Register, &FieldSpec); 5] = [
(Register::Cr1, &CSTART),
(Register::Cr1, &IOLOCK),
(Register::Cr2, &TSER),
(Register::Ier, &TXPIE),
(Register::Ier, &DXPIE),
];
/// The registers the vendor's file types read-write where sections 50.11.11 and 50.11.12 type every
/// bit `r`. The part gives each [`WriteModify::Modify`], the rule that leaves the register as the
/// model holds it.
pub const READ_ONLY: [Register; 2] = [Register::Txcrc, Register::Rxcrc];
// Each enable of the interface's one interrupt and the flag it arms, RM0433 rev 9 Table 423. The
const INTERRUPTS: [(&FieldSpec, &FieldSpec); 11] = [
(ier::RXPIE, sr::RXP),
(ier::TXPIE, sr::TXP),
(ier::DPXPIE, sr::DXP),
(ier::EOTIE, sr::EOT),
(ier::TXTFIE, sr::TXTF),
(ier::UDRIE, sr::UDR),
(ier::OVRIE, sr::OVR),
(ier::CRCEIE, sr::CRCE),
(ier::TIFREIE, sr::TIFRE),
(ier::MODFIE, sr::MODF),
(ier::TSERFIE, sr::TSERF),
];
// it, section 50.11.7.
const CLEARS: [(&FieldSpec, &FieldSpec); 9] = [
(ifcr::EOTC, sr::EOT),
(ifcr::TXTFC, sr::TXTF),
(ifcr::UDRC, sr::UDR),
(ifcr::OVRC, sr::OVR),
(ifcr::CRCEC, sr::CRCE),
(ifcr::TIFREC, sr::TIFRE),
(ifcr::MODFC, sr::MODF),
(ifcr::TSERFC, sr::TSERF),
(ifcr::SUSPC, sr::SUSP),
];
/// 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<u32>,
held: u32,
received: Vec<u32>,
}
impl Pattern {
/// A peer answering `words`, oldest first.
#[must_use]
pub fn answering(words: impl IntoIterator<Item = u32>) -> Self {
Self {
answers: words.into_iter().collect(),
held: 0,
received: Vec::new(),
}
}
}
store!(Pattern);
impl Peer for Pattern {
fn received(&self) -> &[u32] {
&self.received
}
fn transfer(&mut self, mosi: u32, _bits: u8, _context: &mut Timeline<'_, '_>) -> u32 {
self.received.push(mosi);
if let Some(word) = self.answers.pop_front() {
self.held = word;
}
self.held
}
}
/// Which port pin selects a peer, and the level that selects it.
///
/// The port is named as the vendor names the instance, `GPIOA` to `GPIOK`, 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 `GPIOE`.
pub port: &'static str,
/// The pin of that port, 0 to 15.
pub pin: Pin,
/// The level at which the peer is selected, low for nearly every chip.
pub active: Level,
}
#[derive(Debug)]
struct Attached {
peer: Place<dyn Peer>,
select: Option<Select>,
selected: bool,
}
// Who transmits and who receives, from `COMM` and `HDDIR`, RM0433 rev 9 section 50.4.4.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Mode {
Duplex,
Transmit,
Receive,
}
impl Mode {
const fn transmits(self) -> bool {
matches!(self, Self::Duplex | Self::Transmit)
}
const fn receives(self) -> bool {
matches!(self, Self::Duplex | Self::Receive)
}
}
#[derive(Debug, Clone, Copy)]
struct Config {
bits: u32,
packet: usize,
mode: Mode,
divider: u64,
lsb_first: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
enum Phase {
Data,
// The CRC frame that follows the last data, RM0433 rev 9 section 50.5.3.
Crc,
}
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
struct Frame {
event: EventId,
word: u32,
bits: u32,
phase: Phase,
parked: bool,
}
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
struct Run {
ends: VirtualNanos,
carry: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
enum Stage {
Data,
Crc,
Over,
}
// One transfer of `TSIZE` frames, or an endless one where `TSIZE` is zero, RM0433 rev 9 section
#[derive(Debug, Clone, Copy, serde::Serialize, serde::Deserialize)]
struct Session {
remaining: u32,
to_push: u32,
endless: bool,
first: bool,
reinit: bool,
stage: Stage,
}
impl Session {
const fn idle() -> Self {
Self {
remaining: 0,
to_push: 0,
endless: true,
first: false,
reinit: false,
stage: Stage::Data,
}
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
struct Switches {
enabled: bool,
started: bool,
locked: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
enum Pause {
Running,
Requested,
Full,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
enum Dropped {
Nothing,
Disabled,
Filled,
Full,
}
const GUARDED: [Register; 4] = [
Register::Cfg1,
Register::Cfg2,
Register::Crcpoly,
Register::Udrdr,
];
/// One serial peripheral interface of an STM32H743, RM0433 rev 9 chapter 50.
#[derive(Debug)]
pub struct Spi {
kind: Kind,
clock_hz: u64,
peers: Vec<Attached>,
contentions: u32,
tx: VecDeque<u32>,
rx: VecDeque<u32>,
frame: Option<Frame>,
flags: u64,
session: Session,
switches: Switches,
pause: Pause,
run: Option<Run>,
last_end: Option<VirtualNanos>,
began_at: VirtualNanos,
eot_at: Option<VirtualNanos>,
dropped: Dropped,
interrupt: Level,
requests: [Level; 2],
shadow: [u64; GUARDED.len()],
held_cr2: u64,
masks: [u64; Register::ALL.len()],
}
store!(Spi {
values: [
clock_hz,
contentions,
tx,
rx,
frame,
flags,
session,
switches,
pause,
run,
last_end,
began_at,
eot_at,
dropped,
interrupt,
requests,
shadow,
held_cr2,
],
parts: [peers],
built: [kind, masks],
});
store!(Attached {
values: [selected],
parts: [peer],
built: [select],
});
impl Spi {
/// An interface of `kind` running on the kernel clock a part that wires nothing gives it,
/// [`DEFAULT_KERNEL_HZ`].
#[must_use]
pub fn new(kind: Kind) -> Self {
Self::with_clock(kind, DEFAULT_KERNEL_HZ)
}
/// An interface of `kind` whose kernel clock runs at `clock_hz`, which `MBR` divides, until the
/// part's clock controller drives it otherwise.
#[must_use]
pub fn with_clock(kind: Kind, clock_hz: u64) -> Self {
Self {
kind,
clock_hz,
peers: Vec::new(),
contentions: 0,
tx: VecDeque::with_capacity(usize::from(kind.fifo_bytes)),
rx: VecDeque::with_capacity(usize::from(kind.fifo_bytes)),
frame: None,
flags: 0,
session: Session::idle(),
switches: Switches::default(),
pause: Pause::Running,
run: None,
last_end: None,
began_at: VirtualNanos(0),
eot_at: None,
dropped: Dropped::Nothing,
interrupt: Level::Low,
requests: [Level::Low; 2],
shadow: GUARDED.map(|register| register.spec().reset),
held_cr2: Register::Cr2.spec().reset,
masks: defined_bits(kind),
}
}
/// 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);
self.deselect_all();
}
fn deselect_all(&mut self) {
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 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 frames 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 kernel clock rate the interface counts time with.
#[must_use]
pub const fn kernel_hz(&self) -> u64 {
self.clock_hz
}
}
// RM0433 rev 9 sections 50.11.3 and 50.11.10 to 50.11.13 reserve them.
fn defined_bits(kind: Kind) -> [u64; Register::ALL.len()] {
let mut masks = Register::ALL.map(|register| {
register
.spec()
.fields
.iter()
.fold(0, |bits, field| bits | field.mask())
});
if kind.frame_bits < 32 {
masks[Register::Cfg1.index()] &= !(top_bit(cfg1::DSIZE) | top_bit(cfg1::CRCSIZE));
for register in [
Register::Crcpoly,
Register::Txcrc,
Register::Rxcrc,
Register::Udrdr,
] {
masks[register.index()] &= u64::from(u16::MAX);
}
}
masks
}
const fn top_bit(field: &FieldSpec) -> u64 {
field.mask() & !(field.mask() >> 1)
}
fn field(spec: &FieldSpec, value: u64) -> u32 {
u32::try_from(spec.get(value)).expect("a field of at most 32 bits")
}
// and four above, RM0433 rev 9 section 50.4.11.
fn frame_bytes(bits: u32) -> usize {
usize::try_from(bits.div_ceil(8)).expect("at most four")
}
// section 50.4.8's figure 622.
const fn container_bytes(bits: u32) -> usize {
match bits {
..=8 => 1,
9..=16 => 2,
_ => 4,
}
}
const fn natural_width(bits: u32) -> Width {
match container_bytes(bits) {
1 => Width::W8,
2 => Width::W16,
_ => Width::W32,
}
}
const fn frame_mask(bits: u32) -> u32 {
u32::MAX >> (u32::BITS - bits)
}
// reads the wire, sees the bits reversed, RM0433 rev 9 section 50.11.4 bit 23.
const fn on_the_wire(word: u32, bits: u32, lsb_first: bool) -> u32 {
if lsb_first {
word.reverse_bits() >> (u32::BITS - bits)
} else {
word
}
}
// The polynomial of RM0433 rev 9 section 50.5.3: `CRCPOLY`, with the implied top term when
#[derive(Debug, Clone, Copy)]
struct Polynomial {
taps: u64,
width: u32,
}
impl Polynomial {
fn of(polynomial: u64, implied: Option<u32>) -> Self {
let value = implied.map_or(polynomial, |bits| polynomial | 2_u64.pow(bits));
let width = value.checked_ilog2().unwrap_or(0);
Self {
taps: value & (2_u64.pow(width) - 1),
width,
}
}
fn mask(self) -> u64 {
2_u64.pow(self.width) - 1
}
fn feed(self, mut crc: u64, word: u32, bits: u32, lsb_first: bool) -> u64 {
if self.width == 0 {
return 0;
}
for step in 0..bits {
let position = if lsb_first { step } else { bits - 1 - step };
let incoming = u64::from(word >> position) & 1;
let top = (crc >> (self.width - 1)) & 1;
crc = (crc << 1) & self.mask();
if top ^ incoming == 1 {
crc ^= self.taps;
}
}
crc
}
fn frame(self, crc: u64, size: u32) -> u32 {
let aligned = if size >= self.width {
crc << (size - self.width)
} else {
crc >> (self.width - size)
};
u32::try_from(aligned & u64::from(frame_mask(size))).expect("masked to a frame")
}
}
impl Spi {
fn config(registers: &RegisterFile) -> Config {
let configured = registers.get(Register::Cfg1);
let format = registers.get(Register::Cfg2);
let mode = match cfg2::COMM.get(format) {
0 => Mode::Duplex,
1 => Mode::Transmit,
2 => Mode::Receive,
_ if cr1::HDDIR.is_set(registers.get(Register::Cr1)) => Mode::Transmit,
_ => Mode::Receive,
};
Config {
bits: (field(cfg1::DSIZE, configured) + 1).max(MIN_FRAME_BITS),
packet: usize::try_from(field(cfg1::FTHVL, configured) + 1).expect("at most sixteen"),
mode,
divider: 2_u64.pow(field(cfg1::MBR, configured) + 1),
lsb_first: cfg2::LSBFRST.is_set(format),
}
}
fn flag(&self, flag: &FieldSpec) -> bool {
flag.is_set(self.flags)
}
fn raise(&mut self, flag: &FieldSpec) {
self.flags = flag.set(self.flags, 1);
}
fn lower(&mut self, flag: &FieldSpec) {
self.flags = flag.set(self.flags, 0);
}
fn capacity(&self, config: &Config) -> usize {
usize::from(self.kind.fifo_bytes) / frame_bytes(config.bits)
}
// RM0433 rev 9 section 50.11.3 bits 30:28.
fn nanos(&self, config: &Config, bits: u64) -> u64 {
bits * config.divider * NANOS_PER_SECOND / self.clock_hz.max(1)
}
// Whether the block is the I2S sharing its registers, section 50.11.14 bit 0.
fn i2s_active(&self, registers: &RegisterFile) -> bool {
self.kind.i2s && cgfr::I2SMOD.is_set(registers.get(Register::Cgfr))
}
fn polynomial(&self, registers: &RegisterFile) -> Polynomial {
let implied = cr1::CRC33_17
.is_set(registers.get(Register::Cr1))
.then_some(u32::from(self.kind.frame_bits));
Polynomial::of(registers.get(Register::Crcpoly), implied)
}
// The length of the CRC frame, `CRCSIZE` plus one and never under four, section 50.11.3.
fn crc_bits(registers: &RegisterFile) -> u32 {
(field(cfg1::CRCSIZE, registers.get(Register::Cfg1)) + 1).max(MIN_FRAME_BITS)
}
// Loads the CRC registers with the pattern `TCRCINI` and `RCRCINI` choose, section 50.11.1.
fn initialize_crc(&self, registers: &mut RegisterFile) {
let control = registers.get(Register::Cr1);
let ones = self.polynomial(registers).mask();
registers.set(
Register::Txcrc,
if cr1::TCRCI.is_set(control) { ones } else { 0 },
);
registers.set(
Register::Rxcrc,
if cr1::RCRCI.is_set(control) { ones } else { 0 },
);
}
/// Reads the frames a data register access of `width` carries off the receive queue, RM0433 rev
/// 9 section 50.4.13: the oldest frame is in the least significant bits, a frame is aligned to a
/// byte, a half-word or a word, and "redundant reads (or any incomplete data) are reading 0".
///
/// The runtime hands a model the register and not the width of the access, so
/// [`Device::read`] calls this with `natural_width`, the access a driver makes for the frame
/// size. A part whose runtime carries the width calls it with that instead.
pub fn read_data(
&mut self,
width: Width,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) -> u64 {
let container = container_bytes(Self::config(registers).bits);
let frames = (usize::try_from(width.bytes()).expect("at most eight") / container).max(1);
let mut word = 0_u64;
for index in 0..frames {
if let Some(frame) = self.rx.pop_front() {
word |= u64::from(frame) << (index * container * 8);
}
}
self.advance(registers, context);
word
}
/// Writes the frames a data register access of `width` carries onto the transmit queue, RM0433
/// rev 9 section 50.4.13: the least significant frame goes first, and "unused bits are ignored".
/// A frame is dropped where the queue has no room, the transfer has all the frames it asked
/// for, `TXTF` stands or the interface is disabled, section 50.4.11.
///
/// The runtime hands a model the register and not the width of the access, so
/// [`Device::write`] calls this with `natural_width`. A part whose runtime carries the width
/// calls it with that instead.
pub fn write_data(
&mut self,
width: Width,
value: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
registers.set(Register::Txdr, 0);
let config = Self::config(registers);
if !self.switches.enabled {
self.drop_write(Dropped::Disabled, context);
} else if self.flag(sr::TXTF) {
self.drop_write(Dropped::Filled, context);
} else {
let container = container_bytes(config.bits);
let frames =
(usize::try_from(width.bytes()).expect("at most eight") / container).max(1);
let mut accepted = false;
for index in 0..frames {
if self.tx.len() >= self.capacity(&config)
|| !(self.session.endless || self.session.to_push > 0)
{
break;
}
let shifted = value >> (index * container * 8);
let word = u32::try_from(shifted & u64::from(frame_mask(config.bits)))
.expect("masked to a frame");
self.tx.push_back(word);
accepted = true;
self.counted(registers);
}
if accepted {
self.dropped = Dropped::Nothing;
} else if self.session.endless || self.session.to_push > 0 {
self.drop_write(Dropped::Full, context);
} else {
self.drop_write(Dropped::Filled, context);
}
}
self.advance(registers, context);
}
// TXIE and DXPIE masks", section 50.11.6.
fn counted(&mut self, registers: &mut RegisterFile) {
if self.session.endless {
return;
}
self.session.to_push -= 1;
if self.session.to_push == 0 {
self.raise(sr::TXTF);
let enables = ier::TXPIE.mask() | ier::DPXPIE.mask();
registers.set(Register::Ier, registers.get(Register::Ier) & !enables);
}
}
fn drop_write(&mut self, why: Dropped, context: &mut DeviceContext<'_>) {
if self.dropped == why {
return;
}
self.dropped = why;
let reason = match why {
Dropped::Nothing => return,
Dropped::Disabled => TXDR_WHILE_DISABLED,
Dropped::Filled => TXDR_AFTER_TXTF,
Dropped::Full => TXDR_WHILE_FULL,
};
context.tell(Register::Txdr.spec().name, reason);
}
}
impl Spi {
fn refresh(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
let config = Self::config(registers);
let status = self.status(&config, registers);
registers.set(Register::Sr, status);
// Table 423: each flag under its enable. `EOTIE` arms `SUSP` and `TXC` as well.
let enables = registers.get(Register::Ier);
let pending = INTERRUPTS
.iter()
.any(|(enable, flag)| enable.is_set(enables) && flag.is_set(status))
|| (ier::EOTIE.is_set(enables) && (sr::SUSP.is_set(status) || sr::TXC.is_set(status)));
let wanted = Level::from(pending);
if wanted != self.interrupt {
self.interrupt = wanted;
context.interrupt(GLOBAL, wanted);
}
// Section 50.4.14: a request each time `TXP` is set, and each time `RXP` is, with the
// incomplete packet at the end of a transfer asked for as well.
let dma = registers.get(Register::Cfg1);
let transmit = self.switches.enabled && cfg1::TXDMAEN.is_set(dma) && sr::TXP.is_set(status);
let receive = cfg1::RXDMAEN.is_set(dma)
&& (sr::RXP.is_set(status) || (sr::EOT.is_set(status) && !self.rx.is_empty()));
for (output, wanted) in [
(RECEIVE_REQUEST, Level::from(receive)),
(TRANSMIT_REQUEST, Level::from(transmit)),
] {
if wanted != self.requests[output] {
self.requests[output] = wanted;
context.request(output, wanted);
}
}
}
// `SR` as the queues and the flags make it, section 50.11.6.
fn status(&self, config: &Config, registers: &RegisterFile) -> u64 {
let free = self.capacity(config).saturating_sub(self.tx.len());
// "Just for the last data case, the TXP event is asserted by SPI once there is enough space
// into TxFIFO to store remaining data to complete the current transfer."
let needed = if self.session.endless || self.session.to_push == 0 {
config.packet
} else {
config
.packet
.min(usize::try_from(self.session.to_push).unwrap_or(usize::MAX))
};
let transmit_room = free >= needed;
let receive_packet = self.rx.len() >= config.packet;
let word = self.rx.len() * frame_bytes(config.bits) >= 4;
let residue = if !word && config.bits <= 16 {
self.rx.len()
} else {
0
};
// `TXC` is the TxFIFO empty with nothing on the bus while `TSIZE` is zero, and a copy of
// `EOT` otherwise.
let complete = if field(cr2::TSIZE, registers.get(Register::Cr2)) == 0 {
self.tx.is_empty() && self.frame.is_none()
} else {
self.flag(sr::EOT)
};
let counted = if self.session.endless {
0
} else {
self.session.remaining
};
let mut status = self.flags;
status = sr::RXP.set(status, u64::from(receive_packet));
status = sr::TXP.set(status, u64::from(transmit_room));
status = sr::DXP.set(status, u64::from(receive_packet && transmit_room));
status = sr::TXC.set(status, u64::from(complete));
status = sr::RXWNE.set(status, u64::from(word));
status = sr::RXPLVL.set(
status,
u64::try_from(residue).expect("at most the FIFO's frames"),
);
sr::CTSIZE.set(status, u64::from(counted))
}
fn advance(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
self.pump(registers, context);
self.refresh(registers, context);
}
// Starts the next frame when a master is running and has something to move, RM0433 rev 9
fn pump(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
if self.frame.is_some()
|| !self.switches.started
|| self.pause != Pause::Running
|| self.clock_hz == 0
|| !cfg2::MASTER.is_set(registers.get(Register::Cfg2))
|| self.i2s_active(registers)
{
return;
}
let config = Self::config(registers);
let (phase, word, bits) = match self.session.stage {
Stage::Over => return,
Stage::Crc => (
Phase::Crc,
self.crc_word(&config, registers),
Self::crc_bits(registers),
),
Stage::Data => match self.next_word(&config, registers) {
Some(word) => (Phase::Data, word, config.bits),
None => return,
},
};
let ends = self.frame_end(context.now(), bits, &config, registers);
self.frame = Some(Frame {
event: context.schedule(ends),
word,
bits,
phase,
parked: false,
});
self.session.first = false;
}
// only receives, none, unless `MASRX` stops it because the `RxFIFO` is full, section 50.4.11.
fn next_word(&mut self, config: &Config, registers: &RegisterFile) -> Option<u32> {
if config.mode.transmits() {
return self.tx.pop_front();
}
if cr1::MASRX.is_set(registers.get(Register::Cr1)) && self.rx.len() >= self.capacity(config)
{
self.raise(sr::SUSP);
self.pause = Pause::Full;
return None;
}
Some(0)
}
// The CRC frame a transmitter sends, section 50.5.3.
fn crc_word(&self, config: &Config, registers: &RegisterFile) -> u32 {
if !config.mode.transmits() {
return 0;
}
self.polynomial(registers)
.frame(registers.get(Register::Txcrc), Self::crc_bits(registers))
}
// A frame that follows another at the instant it ended also waits `MIDI` bit times, section
fn frame_end(
&mut self,
now: VirtualNanos,
bits: u32,
config: &Config,
registers: &RegisterFile,
) -> VirtualNanos {
let format = registers.get(Register::Cfg2);
let midi = u64::from(field(cfg2::MIDI, format));
let (begin, carry, lead) = if let Some(run) = self.run.filter(|run| run.ends == now) {
(now, run.carry, midi)
} else {
let mut begin = now;
if let Some(last) = self.last_end {
begin = begin.max(VirtualNanos(last.0 + self.nanos(config, midi)));
}
if self.session.first && !cfg2::SSM.is_set(format) && cfg2::SSOE.is_set(format) {
let mssi = u64::from(field(cfg2::MSSI, format));
begin = begin.max(VirtualNanos(self.began_at.0 + self.nanos(config, mssi)));
}
(begin, 0, 0)
};
// The remainder is in kernel cycles, so a rate that doesn't divide the clock can't drift.
let cycles = self.clock_hz.max(1);
let numerator = (lead + u64::from(bits)) * config.divider * NANOS_PER_SECOND + carry;
let ends = VirtualNanos(begin.0 + (numerator / cycles).max(1));
self.run = Some(Run {
ends,
carry: numerator % cycles,
});
ends
}
fn cross(&mut self, outgoing: u32, bits: u32, context: &mut DeviceContext<'_>) -> u32 {
let frame = u8::try_from(bits).expect("a frame is at most 32 bits");
let mut answers = self
.peers
.iter_mut()
.filter(|attached| attached.selected)
.filter_map(|attached| {
let peer = attached.peer.answering()?;
let answer = peer.transfer(outgoing, frame, &mut Timeline::new(context));
Some(attached.peer.answered(answer) & frame_mask(bits))
});
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
}
}
}
fn finish(
&mut self,
frame: Frame,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let config = Self::config(registers);
let sent = on_the_wire(frame.word, frame.bits, config.lsb_first);
let answered = self.cross(sent, frame.bits, context);
let received = on_the_wire(answered, frame.bits, config.lsb_first);
self.frame = None;
self.last_end = Some(context.now());
match frame.phase {
Phase::Data => {
self.data_crossed((frame.word, received), &config, registers, context.now());
}
Phase::Crc => self.crc_crossed(received, &config, registers, context),
}
if self.pause == Pause::Requested {
// Section 50.11.1: "CSTART bit is reset at the end of the current frame and
// communication is suspended", and section 50.11.6: `SUSP` is set then.
self.suspended(registers);
}
self.advance(registers, context);
if self.frame.is_none() {
self.run = None;
}
}
fn data_crossed(
&mut self,
(sent, received): (u32, u32),
config: &Config,
registers: &mut RegisterFile,
now: VirtualNanos,
) {
let crc_on = cfg1::CRCEN.is_set(registers.get(Register::Cfg1));
if crc_on {
let polynomial = self.polynomial(registers);
if self.session.reinit {
self.initialize_crc(registers);
self.session.reinit = false;
}
if config.mode.transmits() {
let crc = registers.get(Register::Txcrc);
let next = polynomial.feed(crc, sent, config.bits, config.lsb_first);
registers.set(Register::Txcrc, next);
}
if config.mode.receives() {
let crc = registers.get(Register::Rxcrc);
let next = polynomial.feed(crc, received, config.bits, config.lsb_first);
registers.set(Register::Rxcrc, next);
}
}
if config.mode.receives() {
// Section 50.5.2: a frame that finds the RxFIFO full "does not overwrite the previous
// one in the RxFIFO", it "is discarded", and `OVR` is set.
if self.rx.len() < self.capacity(config) {
self.rx.push_back(received);
} else {
self.raise(sr::OVR);
}
}
if self.session.endless {
return;
}
self.session.remaining -= 1;
if self.session.remaining == 0 {
self.last_data(crc_on, registers, now);
}
}
// section 50.4.11.
fn last_data(&mut self, crc_on: bool, registers: &mut RegisterFile, now: VirtualNanos) {
let control = registers.get(Register::Cr2);
let extension = field(cr2::TSER, control);
if extension != 0 {
let reloaded = cr2::TSER.set(cr2::TSIZE.set(control, u64::from(extension)), 0);
registers.set(Register::Cr2, reloaded);
self.held_cr2 = reloaded;
self.session.remaining = extension;
self.raise(sr::TSERF);
} else if crc_on {
self.session.stage = Stage::Crc;
} else {
self.end(registers, now);
}
}
// The CRC frame has crossed: what arrived is compared with the receiver's CRC, section 50.5.3.
fn crc_crossed(
&mut self,
received: u32,
config: &Config,
registers: &mut RegisterFile,
context: &DeviceContext<'_>,
) {
if config.mode.receives() {
let expected = self
.polynomial(registers)
.frame(registers.get(Register::Rxcrc), Self::crc_bits(registers));
if received != expected {
self.raise(sr::CRCE);
}
}
self.session.reinit = true;
self.end(registers, context.now());
}
// The transfer is complete: `EOT` is set and `CSTART` clears, section 50.11.6.
fn end(&mut self, registers: &mut RegisterFile, now: VirtualNanos) {
self.raise(sr::EOT);
self.session.stage = Stage::Over;
self.eot_at = Some(now);
self.stop(registers);
}
fn stop(&mut self, registers: &mut RegisterFile) {
self.switches.started = false;
self.pause = Pause::Running;
self.last_end = None;
self.run = None;
registers.set(
Register::Cr1,
cr1::CSTART.set(registers.get(Register::Cr1), 0),
);
}
fn suspended(&mut self, registers: &mut RegisterFile) {
self.raise(sr::SUSP);
self.stop(registers);
}
}
impl Spi {
// pattern, and the session is told of what the model leaves out, RM0433 rev 9 section 50.11.1.
fn enable(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
self.switches.enabled = true;
self.reload(registers);
if cfg1::CRCEN.is_set(registers.get(Register::Cfg1)) {
self.initialize_crc(registers);
}
let name = Register::Cr1.spec().name;
if self.i2s_active(registers) {
context.tell(name, I2S_NOT_MODELED);
} else if !cfg2::MASTER.is_set(registers.get(Register::Cfg2)) {
context.tell(name, SLAVE_NOT_MODELED);
} else if cfg2::SP.get(registers.get(Register::Cfg2)) != 0 {
context.tell(name, TI_NOT_MODELED);
}
}
fn reload(&mut self, registers: &RegisterFile) {
let words = registers.get(Register::Cr2);
let size = field(cr2::TSIZE, words);
self.session = Session {
remaining: size,
to_push: size + field(cr2::TSER, words),
endless: size == 0,
stage: Stage::Data,
..self.session
};
}
// `SPE` has fallen, RM0433 rev 9 section 50.11.1: "any SPI operation is stopped and disabled,
fn disable(&mut self) {
self.switches = Switches::default();
self.frame = None;
self.tx.clear();
self.rx.clear();
for flag in [sr::OVR, sr::UDR, sr::CRCE] {
self.lower(flag);
}
self.pause = Pause::Running;
self.session = Session::idle();
self.run = None;
self.last_end = None;
self.dropped = Dropped::Nothing;
// ES0392 rev 15 item 2.22.2's workaround: disabling and enabling after EOT.
self.eot_at = None;
}
// `CSTART` has been set on an enabled master: the transfer starts, section 50.11.1.
fn begin(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
if self.session.stage == Stage::Over {
self.reload(registers);
}
self.switches.started = true;
self.session.first = true;
self.began_at = context.now();
self.last_end = None;
self.run = None;
self.pause = Pause::Running;
self.warn_of_errata(registers, context);
}
// The two items of ES0392 rev 15 section 2.22 a start can fall under, which the model tells
fn warn_of_errata(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
let config = Self::config(registers);
let name = Register::Cr1.spec().name;
if let Some(ended) = self.eot_at.take() {
let period = (config.divider * NANOS_PER_SECOND).div_ceil(self.clock_hz.max(1));
if context.now().0.saturating_sub(ended.0) < period {
context.tell(name, STALL_WINDOW);
}
}
let configured = registers.get(Register::Cfg1);
if !cfg1::CRCEN.is_set(configured)
&& field(cr2::TSIZE, registers.get(Register::Cr2)) > 0
&& Self::crc_bits(registers) < 8
{
context.tell(name, CRCSIZE_CORRUPTION);
}
}
// `CSUSP` has been written on an enabled master, section 50.11.1: the transfer suspends at the
fn suspend(&mut self, registers: &mut RegisterFile) {
if self.frame.is_some() {
self.pause = Pause::Requested;
} else {
self.suspended(registers);
}
}
// A mode fault, RM0433 rev 9 section 50.5.2: `MODF` is set, `SPE` and `IOLOCK` are cleared,
fn fault(&mut self, registers: &mut RegisterFile) {
self.disable();
self.raise(sr::MODF);
let format = cfg2::MASTER.set(registers.get(Register::Cfg2), 0);
registers.set(Register::Cfg2, format);
self.shadow[Self::slot(Register::Cfg2)] = format;
let control = registers.get(Register::Cr1);
let control = cr1::IOLOCK.set(cr1::CSTART.set(cr1::SPE.set(control, 0), 0), 0);
registers.set(Register::Cr1, control);
}
// Whether the slave select the master sees is at its active level, section 50.4.7's figure
fn check_conflict(&mut self, registers: &mut RegisterFile) {
let format = registers.get(Register::Cfg2);
if !cfg2::MASTER.is_set(format) {
return;
}
let input = if cfg2::SSM.is_set(format) {
Level::from(cr1::SSI.is_set(registers.get(Register::Cr1)))
} else {
// The pin is a wire the model doesn't carry, and under `SSOE` the input is forced
// inactive, section 50.11.4 bit 26.
return;
};
if input == Level::from(cfg2::SSIOP.is_set(format)) {
self.fault(registers);
}
}
}
impl Spi {
fn slot(register: Register) -> usize {
GUARDED
.iter()
.position(|guarded| *guarded == register)
.expect("a guarded register")
}
// Whether a write to `register` is held off, RM0433 rev 9 sections 50.11.3, 50.11.4, 50.11.10
fn held_off(&self, register: Register) -> (bool, u64) {
match register {
Register::Cfg1 => (
self.switches.enabled,
cfg1::TXDMAEN.mask() | cfg1::RXDMAEN.mask(),
),
Register::Cfg2 => (self.switches.enabled || self.switches.locked, 0),
_ => (self.switches.enabled, 0),
}
}
fn guard(
&mut self,
register: Register,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let slot = Self::slot(register);
let landed = registers.get(register) & self.masks[register.index()];
let (held, exempt) = self.held_off(register);
let kept = if held {
(self.shadow[slot] & !exempt) | (landed & exempt)
} else {
landed
};
if held && (landed ^ kept) != 0 {
context.tell(register.spec().name, WRITE_PROTECTED);
}
registers.set(register, kept);
self.shadow[slot] = kept;
}
// Whether two packets of the configuration fit in the FIFO, section 50.11.3 bits 8:5.
fn packets_fit(&self, configured: u64) -> bool {
let bits = (field(cfg1::DSIZE, configured) + 1).max(MIN_FRAME_BITS);
let packet = usize::try_from(field(cfg1::FTHVL, configured) + 1).expect("at most sixteen");
packet * frame_bytes(bits) * 2 <= usize::from(self.kind.fifo_bytes)
}
// `CR1`, RM0433 rev 9 section 50.11.1.
fn write_cr1(
&mut self,
value: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let mut control = registers.get(Register::Cr1) & self.masks[Register::Cr1.index()];
// `IOLOCK` "can be set when SPI is disabled only", and "cannot be set when the MODF bit
// is set".
if cr1::IOLOCK.is_set(control) && !self.switches.locked {
if self.switches.enabled || self.flag(sr::MODF) {
control = cr1::IOLOCK.set(control, 0);
} else {
self.switches.locked = true;
}
}
let wanted = cr1::SPE.is_set(control);
if wanted && !self.switches.enabled {
// "SPE is cleared and cannot be set when MODF error flag is active."
if self.flag(sr::MODF) {
control = cr1::SPE.set(control, 0);
context.tell(Register::Cr1.spec().name, SPE_WITH_MODF);
} else {
self.enable(registers, context);
}
} else if !wanted && self.switches.enabled {
// "IOLOCK ... is set by software and cleared by hardware whenever SPE bit is changed
// from 1 to 0", and `CSTART` is cleared with the state machine.
self.disable();
control = cr1::IOLOCK.set(cr1::CSTART.set(control, 0), 0);
}
let master = cfg2::MASTER.is_set(registers.get(Register::Cfg2));
if cr1::CSTART.is_set(value) && !self.switches.started {
// "In SPI mode, the bit is taken into account at master mode only."
if self.switches.enabled && master {
self.begin(registers, context);
} else {
control = cr1::CSTART.set(control, 0);
}
}
// `CSUSP` "reads as zero".
let suspend = cr1::CSUSP.is_set(value) && self.switches.enabled && master;
registers.set(Register::Cr1, cr1::CSUSP.set(control, 0));
if suspend {
self.suspend(registers);
}
self.check_conflict(registers);
}
// `CR2`, RM0433 rev 9 section 50.11.2: `TSER` "can be set by software when its content is
fn write_cr2(
&mut self,
value: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let landed = registers.get(Register::Cr2);
let held = cr2::TSER.get(self.held_cr2);
let mut kept = landed;
if held != 0 {
kept = cr2::TSER.set(kept, held);
} else if cr2::TSER.is_set(value) {
self.lower(sr::TSERF);
// 50.4.11's own example pushes past TSIZE before the reload, so the extension counts
// toward what software may push as soon as it is accepted.
if !self.session.endless {
self.session.to_push += field(cr2::TSER, kept);
}
}
if self.switches.enabled && cr2::TSIZE.get(kept) != cr2::TSIZE.get(self.held_cr2) {
context.tell(Register::Cr2.spec().name, TSIZE_WHILE_ENABLED);
}
registers.set(Register::Cr2, kept);
self.held_cr2 = kept;
}
// `CFG1`, section 50.11.3: the CRC registers take their pattern when `CRCEN` is written one.
fn write_cfg1(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
let slot = Self::slot(Register::Cfg1);
let before = self.shadow[slot];
self.guard(Register::Cfg1, registers, context);
let after = self.shadow[slot];
if cfg1::CRCEN.is_set(after) && !cfg1::CRCEN.is_set(before) {
self.initialize_crc(registers);
}
if self.packets_fit(before) && !self.packets_fit(after) {
context.tell(Register::Cfg1.spec().name, PACKET_TOO_LARGE);
}
}
// `CFG2`, section 50.11.4: a master whose slave select input is active takes the fault.
fn write_cfg2(&mut self, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
self.guard(Register::Cfg2, registers, context);
self.check_conflict(registers);
}
// `IFCR`, section 50.11.7: a written one clears its flag, and clearing `SUSP` lets a master
fn write_ifcr(&mut self, value: u64, registers: &mut RegisterFile) {
for (clear, flag) in CLEARS {
if clear.is_set(value) {
self.lower(flag);
}
}
if ifcr::SUSPC.is_set(value) && self.pause == Pause::Full {
self.pause = Pause::Running;
}
registers.set(Register::Ifcr, 0);
}
}
impl Device for Spi {
type Register = Register;
/// The part restarted: the registers are at their reset values, so the queues, the transfer and
/// every flag go too. The chips stay on the wires with their selects back at reset, and the
/// part tells them it restarted through [`Spi::chips_restarted`].
fn reset(&mut self) {
self.disable();
self.flags = 0;
self.eot_at = None;
self.interrupt = Level::Low;
self.requests = [Level::Low; 2];
self.shadow = GUARDED.map(|register| register.spec().reset);
self.held_cr2 = Register::Cr2.spec().reset;
self.deselect_all();
}
/// The kernel clock has changed. The frame on the wire keeps its end, so it finishes at the rate
/// it began at, and the run's carry was a fraction of the old rate and means nothing at the new
/// one. A frame whose time ran out with the clock stopped ends when the clock returns.
fn on_clock(&mut self, hz: u64, registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
self.clock_hz = hz;
if hz == 0 {
return;
}
if let Some(run) = &mut self.run {
run.carry = 0;
}
if let Some(frame) = self.frame.filter(|frame| frame.parked) {
self.finish(frame, registers, context);
} else {
self.advance(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();
}
}
}
/// Every register of RM0433 rev 9 section 50.11, by its section. `RXDR` pops the receive queue,
/// and the rest answer from the register file, which the model keeps current.
fn read(
&mut self,
register: Register,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) -> Option<u64> {
match register {
// 50.11.8, 50.11.9: the oldest frames of the RxFIFO, right aligned.
Register::Rxdr => {
let width = context
.access_width()
.unwrap_or_else(|| natural_width(Self::config(registers).bits));
Some(self.read_data(width, registers, context))
}
Register::Cr1 // 50.11.1
| Register::Cr2 // 50.11.2
| Register::Cfg1 // 50.11.3
| Register::Cfg2 // 50.11.4
| Register::Ier // 50.11.5
| Register::Sr // 50.11.6, kept current by every call that changes a flag
| Register::Ifcr // 50.11.7, write-only
| Register::Txdr // 50.11.8, write-only
| Register::Crcpoly // 50.11.10
| Register::Txcrc // 50.11.11
| Register::Rxcrc // 50.11.12
| Register::Udrdr // 50.11.13
| Register::Cgfr => None, // 50.11.14, the I2S configuration
}
}
/// `RXDR` is the front of the receive queue. Everything else reads as it stands.
fn peek(
&self,
register: Register,
_registers: &RegisterFile,
_now: VirtualNanos,
) -> Option<u64> {
(register == Register::Rxdr).then(|| self.rx.front().map_or(0, |frame| u64::from(*frame)))
}
/// Every register of RM0433 rev 9 section 50.11, by its section.
fn write(
&mut self,
register: Register,
value: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
match register {
Register::Cr1 => self.write_cr1(value, registers, context), // 50.11.1
Register::Cr2 => self.write_cr2(value, registers, context), // 50.11.2
Register::Cfg1 => self.write_cfg1(registers, context), // 50.11.3
Register::Cfg2 => self.write_cfg2(registers, context), // 50.11.4
Register::Crcpoly | Register::Udrdr => self.guard(register, registers, context), // 50.11.10, 50.11.13
Register::Ifcr => self.write_ifcr(value, registers), // 50.11.7
Register::Txdr => {
// 50.11.8, which advances the transfer itself
let width = context
.access_width()
.unwrap_or_else(|| natural_width(Self::config(registers).bits));
self.write_data(width, value, registers, context);
return;
}
Register::Ier // 50.11.5, whose reserved bits are masked below
| Register::Cgfr // 50.11.14, stored
| Register::Sr // 50.11.6, every bit type r
| Register::Rxdr // 50.11.9, type r
| Register::Txcrc // 50.11.11, type r
| Register::Rxcrc => {} // 50.11.12, type r
}
let masked = registers.get(register) & self.masks[register.index()];
registers.set(register, masked);
self.advance(registers, context);
}
fn on_event(
&mut self,
event: EventId,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
if let Some(frame) = self
.frame
.filter(|frame| frame.event == event && !frame.parked)
{
if self.clock_hz == 0 {
self.frame = Some(Frame {
parked: true,
..frame
});
} else {
self.finish(frame, registers, context);
}
}
for attached in &mut self.peers {
attached
.peer
.chip_mut()
.on_event(event, &mut Timeline::new(context));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::device::{InterruptChange, Told};
use crate::mcu::{Operate, Single};
use crate::soc::stm32h743::builder;
use crate::soc::stm32h743::generated::{self, PERIPHERALS};
const START: VirtualNanos = VirtualNanos(0);
const KERNEL_HZ: u64 = 64_000_000;
const CFG1_RESET: u64 = 0x0007_0007;
const FRAME: u64 = 2_000;
const BIT: u64 = 250;
const EVERY_BIT: u64 = 0xFFFF_FFFF;
#[derive(Debug, Default, serde::Serialize, serde::Deserialize)]
struct Chip {
answers: VecDeque<u32>,
seen: Vec<(u64, u32, u8)>,
selects: u32,
deselects: u32,
pending: Option<EventId>,
ticks: u32,
restarts: u32,
}
store!(Chip);
impl Chip {
fn answering(words: impl IntoIterator<Item = u32>) -> Self {
Self {
answers: words.into_iter().collect(),
..Self::default()
}
}
fn words(&self) -> Vec<u32> {
self.seen.iter().map(|(_, word, _)| *word).collect()
}
fn instants(&self) -> Vec<u64> {
self.seen.iter().map(|(at, _, _)| *at).collect()
}
}
impl Peer for Chip {
fn transfer(&mut self, mosi: u32, bits: u8, context: &mut Timeline<'_, '_>) -> u32 {
self.seen.push((context.now().0, mosi, bits));
self.answers.pop_front().unwrap_or(0)
}
fn select(&mut self, context: &mut Timeline<'_, '_>) {
self.selects += 1;
self.pending = Some(context.schedule(VirtualNanos(context.now().0 + 500)));
}
fn on_event(&mut self, event: EventId, _context: &mut Timeline<'_, '_>) {
if self.pending == Some(event) {
self.ticks += 1;
}
}
fn deselect(&mut self, _context: &mut Timeline<'_, '_>) {
self.deselects += 1;
}
fn restarted(&mut self, _context: &mut Timeline<'_, '_>) {
self.restarts += 1;
}
}
struct Rig {
soc: Single<Spi>,
base: u64,
now: VirtualNanos,
}
impl Rig {
fn new() -> Self {
Self::of("SPI1")
}
fn of(instance: &'static str) -> Self {
let kind = INSTANCES
.iter()
.find(|candidate| candidate.name == instance)
.expect("an instance of the part")
.kind;
Self::with(instance, Spi::with_clock(kind, KERNEL_HZ))
}
fn with(instance: &'static str, model: Spi) -> Self {
let base = PERIPHERALS
.iter()
.find(|peripheral| peripheral.name == instance)
.expect("an instance of the part")
.base;
Self {
soc: Single::new(builder(), instance, model).expect("the bus builds"),
base,
now: START,
}
}
fn master(mut self, mbr: u64) -> Self {
self.write(Register::Cfg1, cfg1::MBR.set(CFG1_RESET, mbr));
self.write(Register::Cfg2, cfg2::MASTER.mask() | cfg2::SSOE.mask());
self.set_bits(Register::Cr1, cr1::SPE.mask());
self
}
fn address(&self, register: Register) -> u64 {
self.base + u64::from(register.spec().offset)
}
fn read(&mut self, register: Register) -> u64 {
self.read_as(register, Width::W32)
}
fn read_as(&mut self, register: Register, width: Width) -> u64 {
let address = self.address(register);
self.soc
.read(self.now, address, width)
.expect("the register answers")
}
fn write(&mut self, register: Register, value: u64) {
self.write_as(register, Width::W32, value);
}
fn write_as(&mut self, register: Register, width: Width, value: u64) {
let address = self.address(register);
self.soc
.write(self.now, address, width, value)
.expect("the register takes the write");
}
fn set_bits(&mut self, register: Register, bits: u64) {
let value = self.read(register);
self.write(register, value | bits);
}
fn clear_bits(&mut self, register: Register, bits: u64) {
let value = self.read(register);
self.write(register, value & !bits);
}
fn status(&mut self) -> u64 {
self.read(Register::Sr)
}
fn has(&mut self, flag: &FieldSpec) -> bool {
flag.is_set(self.status())
}
fn push(&mut self, byte: u64) {
self.write_as(Register::Txdr, Width::W8, byte);
}
fn pop(&mut self) -> u64 {
self.read_as(Register::Rxdr, Width::W8)
}
fn start(&mut self) {
self.set_bits(Register::Cr1, cr1::CSTART.mask());
}
fn at(&mut self, nanos: u64) {
self.now = VirtualNanos(nanos);
self.soc.advance_to(self.now);
}
fn model(&mut self) -> &mut Spi {
&mut self.soc.model
}
fn chip(&mut self) -> &mut Chip {
self.soc
.model
.chip_mut::<Chip>(0)
.expect("a test chip hangs on the wires")
}
fn told(&mut self) -> Vec<Told> {
self.soc.drain_told()
}
fn levels(&mut self) -> Vec<Level> {
self.soc
.drain_interrupts()
.into_iter()
.map(|change| change.level)
.collect()
}
fn requests(&mut self) -> Vec<(usize, Level)> {
self.soc
.drain_requests()
.into_iter()
.map(|change| (change.request, change.level))
.collect()
}
fn drive(
&mut self,
input: impl FnOnce(&mut Spi, &mut RegisterFile, &mut DeviceContext<'_>),
) {
self.soc.drive(self.now, input);
}
fn pin(&mut self, port: &'static str, pin: usize, level: Level) {
let change = RequestChange {
instance: port,
request: pin,
level,
};
self.drive(|model, registers, context| model.on_request(change, registers, context));
}
fn clock(&mut self, hz: u64) {
self.drive(|model, registers, context| model.on_clock(hz, registers, context));
}
}
fn chibios_start(rig: &mut Rig, mbr: u64) {
rig.clear_bits(Register::Cr1, cr1::SPE.mask());
rig.write(Register::Cr1, cr1::MASRX.mask());
rig.write(Register::Cr2, 0);
rig.write(
Register::Cfg1,
cfg1::MBR.set(CFG1_RESET, mbr) | cfg1::RXDMAEN.mask() | cfg1::TXDMAEN.mask(),
);
rig.write(Register::Cfg2, cfg2::MASTER.mask() | cfg2::SSOE.mask());
rig.write(Register::Ier, ier::OVRIE.mask());
rig.write(Register::Ifcr, EVERY_BIT);
rig.set_bits(Register::Cr1, cr1::SPE.mask());
}
#[test]
fn reset_values_match_the_manual() {
// RM0433 rev 9 sections 50.11.1 to 50.11.14, each register's page.
let expected: [(Register, u64); 14] = [
(Register::Cr1, 0),
(Register::Cr2, 0),
(Register::Cfg1, 0x0007_0007),
(Register::Cfg2, 0),
(Register::Ier, 0),
(Register::Sr, 0x0000_1002),
(Register::Ifcr, 0),
(Register::Txdr, 0),
(Register::Rxdr, 0),
(Register::Crcpoly, 0x0000_0107),
(Register::Txcrc, 0),
(Register::Rxcrc, 0),
(Register::Udrdr, 0),
(Register::Cgfr, 0),
];
for instance in INSTANCES {
let mut rig = Rig::of(instance.name);
for (register, value) in expected {
assert_eq!(rig.read(register), value, "{} {register:?}", instance.name);
}
}
}
#[test]
fn the_status_an_idle_interface_computes_is_the_reset_value() {
// 50.11.6: TXP and TXC out of reset, from an empty TxFIFO with nothing on the bus.
let mut rig = Rig::new();
rig.write(Register::Ier, 0);
assert_eq!(rig.status(), 0x0000_1002);
}
#[test]
fn the_registers_come_out_of_reset_with_nothing_scheduled_or_raised() {
let mut rig = Rig::new();
assert_eq!(rig.soc.next_event(), None);
assert!(rig.levels().is_empty());
assert!(rig.requests().is_empty());
}
// to `TXDR`, wait for `RXP`, a byte from `RXDR`. RM0433 rev 9 sections 50.4.10 and 50.4.11.
#[test]
fn a_polled_exchange_returns_the_answer_after_eight_bit_times() {
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([0x5A]));
rig.start();
assert!(rig.has(sr::TXP), "there is room in the TxFIFO to wait for");
rig.push(0x9F);
let status = rig.status();
assert!(!sr::TXC.is_set(status), "a frame is on the bus");
assert!(
sr::TXP.is_set(status),
"and the byte left the queue for the shift register"
);
assert!(!sr::RXP.is_set(status));
rig.at(FRAME - 1);
assert!(!rig.has(sr::RXP), "one nanosecond early");
rig.at(FRAME);
let status = rig.status();
assert!(
sr::RXP.is_set(status) && sr::DXP.is_set(status),
"{status:#x}"
);
assert!(
sr::TXC.is_set(status),
"the queue is empty and the bus idle again"
);
assert_eq!(rig.pop(), 0x5A);
assert_eq!(rig.chip().seen, vec![(FRAME, 0x9F, 8)]);
assert!(!rig.has(sr::RXP), "the read emptied the RxFIFO");
}
#[test]
fn csusp_with_the_bus_idle_clears_cstart_and_sets_susp_at_once() {
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([1, 2]));
rig.start();
rig.push(0x01);
rig.at(FRAME);
rig.pop();
rig.set_bits(Register::Cr1, cr1::CSUSP.mask());
let control = rig.read(Register::Cr1);
assert!(!cr1::CSTART.is_set(control), "50.11.1: CSTART is reset");
assert!(!cr1::CSUSP.is_set(control), "50.11.1: CSUSP reads as zero");
assert!(rig.has(sr::SUSP), "50.11.6: the suspension is flagged");
rig.write(Register::Ifcr, EVERY_BIT);
assert!(!rig.has(sr::SUSP));
// And the next exchange runs the way the first did.
rig.start();
rig.push(0x02);
rig.at(2 * FRAME);
assert_eq!(rig.pop(), 2);
assert_eq!(rig.chip().seen, vec![(FRAME, 1, 8), (2 * FRAME, 2, 8)]);
}
#[test]
fn csusp_with_a_frame_on_the_wire_waits_for_the_frame_to_end() {
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([7]));
rig.start();
rig.push(0x01);
rig.push(0x02);
rig.at(FRAME / 2);
rig.set_bits(Register::Cr1, cr1::CSUSP.mask());
assert!(
cr1::CSTART.is_set(rig.read(Register::Cr1)),
"the frame isn't finished"
);
assert!(!rig.has(sr::SUSP));
rig.at(FRAME);
assert!(
!cr1::CSTART.is_set(rig.read(Register::Cr1)),
"50.11.1: at the end of the current frame"
);
assert!(rig.has(sr::SUSP));
assert_eq!(
rig.soc.next_event(),
None,
"the second byte waits in the TxFIFO"
);
assert_eq!(rig.chip().words(), vec![1]);
}
#[test]
fn a_master_with_cstart_set_waits_for_data_and_starts_with_the_next_write() {
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([1]));
rig.start();
assert_eq!(
rig.soc.next_event(),
None,
"50.4.10: CSTART alone moves nothing in full duplex"
);
rig.at(5_000);
rig.push(0x44);
assert_eq!(rig.soc.next_event(), Some(VirtualNanos(5_000 + FRAME)));
}
#[test]
fn data_in_the_txfifo_waits_for_cstart() {
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([1]));
rig.push(0x44);
assert_eq!(rig.soc.next_event(), None);
assert!(!cr1::CSTART.is_set(rig.read(Register::Cr1)));
rig.at(300);
rig.start();
assert_eq!(rig.soc.next_event(), Some(VirtualNanos(300 + FRAME)));
}
#[test]
fn a_frame_takes_its_bits_at_the_rate_mbr_chooses() {
// 50.11.3 bits 30:28: the kernel clock over two to the power of MBR plus one.
let times = [250, 500, 1_000, 2_000, 4_000, 8_000, 16_000, 32_000];
for (mbr, nanos) in (0..8).zip(times) {
let mut rig = Rig::new().master(mbr);
rig.model().attach(Chip::answering([1]));
rig.start();
rig.push(0x01);
rig.at(nanos - 1);
assert!(!rig.has(sr::RXP), "MBR {mbr}: early");
rig.at(nanos);
assert!(rig.has(sr::RXP), "MBR {mbr}: at {nanos} ns");
}
}
#[test]
fn a_frame_is_dsize_plus_one_bits_and_never_under_four() {
// 50.11.3 bits 4:0, and 50.4.8: shorter selections are forced to four bits.
let cases = [
(0, 4),
(1, 4),
(2, 4),
(3, 4),
(4, 5),
(7, 8),
(11, 12),
(15, 16),
(31, 32),
];
for (dsize, bits) in cases {
let mut rig = Rig::new();
rig.write(
Register::Cfg1,
cfg1::DSIZE.set(cfg1::MBR.set(CFG1_RESET, 3), dsize),
);
rig.write(Register::Cfg2, cfg2::MASTER.mask() | cfg2::SSOE.mask());
rig.set_bits(Register::Cr1, cr1::SPE.mask());
rig.model().attach(Chip::answering([1]));
rig.start();
rig.write_as(Register::Txdr, natural_width(bits), 1);
let end = u64::from(bits) * BIT;
rig.at(end - 1);
assert!(!rig.has(sr::RXP), "DSIZE {dsize}: early");
rig.at(end);
assert!(rig.has(sr::RXP), "DSIZE {dsize}: {bits} bits take {end} ns");
assert_eq!(rig.chip().seen[0].2, u8::try_from(bits).unwrap());
}
}
#[test]
fn a_run_of_frames_does_not_drift() {
// Five-bit frames at 32 MHz are 156.25 ns each. Four of them end at 625 ns, and a model that
// truncated each would end them at 624.
let mut rig = Rig::new();
rig.write(
Register::Cfg1,
cfg1::DSIZE.set(cfg1::MBR.set(CFG1_RESET, 0), 4),
);
rig.write(Register::Cfg2, cfg2::MASTER.mask() | cfg2::SSOE.mask());
rig.set_bits(Register::Cr1, cr1::SPE.mask());
rig.model().attach(Chip::answering([]));
for byte in 1..=4 {
rig.push(byte);
}
rig.start();
rig.at(1_000);
assert_eq!(rig.chip().instants(), vec![156, 312, 468, 625]);
}
#[test]
fn sixteen_frames_back_to_back_end_at_sixteen_frame_times() {
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([]));
for byte in 0..16 {
rig.push(byte);
}
rig.start();
rig.at(16 * FRAME);
let instants = rig.chip().instants();
assert_eq!(
instants,
(1..=16).map(|frame| frame * FRAME).collect::<Vec<_>>()
);
assert_eq!(rig.chip().words(), (0..16).collect::<Vec<u32>>());
assert_eq!(
rig.soc.next_event(),
None,
"and the clock stops with the queue empty"
);
}
#[test]
fn midi_idles_the_bus_between_consecutive_frames_only() {
// 50.11.4 bits 7:4: "minimum time delay (expressed in SPI clock cycles periods) inserted
// between two consecutive data frames". Two bit times here.
let mut rig = Rig::new();
rig.write(Register::Cfg1, cfg1::MBR.set(CFG1_RESET, 3));
rig.write(
Register::Cfg2,
cfg2::MASTER.mask() | cfg2::SSOE.mask() | cfg2::MIDI.set(0, 2),
);
rig.set_bits(Register::Cr1, cr1::SPE.mask());
rig.model().attach(Chip::answering([]));
rig.push(1);
rig.push(2);
rig.push(3);
rig.start();
rig.at(10 * FRAME);
let gap = 2 * BIT;
assert_eq!(
rig.chip().instants(),
vec![FRAME, 2 * FRAME + gap, 3 * FRAME + 2 * gap],
"the first frame has nothing before it to wait for"
);
}
#[test]
fn midi_also_holds_back_a_frame_written_inside_the_gap() {
let mut rig = Rig::new();
rig.write(Register::Cfg1, cfg1::MBR.set(CFG1_RESET, 3));
rig.write(
Register::Cfg2,
cfg2::MASTER.mask() | cfg2::SSOE.mask() | cfg2::MIDI.set(0, 4),
);
rig.set_bits(Register::Cr1, cr1::SPE.mask());
rig.model().attach(Chip::answering([]));
rig.start();
rig.push(1);
rig.at(FRAME);
rig.at(FRAME + BIT);
rig.push(2);
rig.at(10 * FRAME);
assert_eq!(
rig.chip().instants(),
vec![FRAME, FRAME + 4 * BIT + FRAME],
"four bit times after the first ended, then a frame"
);
}
#[test]
fn mssi_delays_the_first_frame_when_the_master_drives_nss() {
// 50.11.4 bits 3:0: "an extra delay ... between active edge of NSS and first data of a
// session start in master mode when SSOE is enabled". Three bit times from CSTART.
let mut rig = Rig::new();
rig.write(Register::Cfg1, cfg1::MBR.set(CFG1_RESET, 3));
rig.write(
Register::Cfg2,
cfg2::MASTER.mask() | cfg2::SSOE.mask() | cfg2::MSSI.set(0, 3),
);
rig.set_bits(Register::Cr1, cr1::SPE.mask());
rig.model().attach(Chip::answering([]));
rig.push(1);
rig.push(2);
rig.at(1_000);
rig.start();
rig.at(20_000);
assert_eq!(
rig.chip().instants(),
vec![1_000 + 3 * BIT + FRAME, 1_000 + 3 * BIT + 2 * FRAME],
"only the first frame of the transfer waits"
);
}
#[test]
fn mssi_waits_for_nothing_under_software_slave_select() {
let mut rig = Rig::new();
rig.write(Register::Cr1, cr1::SSI.mask());
rig.write(Register::Cfg1, cfg1::MBR.set(CFG1_RESET, 3));
rig.write(
Register::Cfg2,
cfg2::MASTER.mask() | cfg2::SSM.mask() | cfg2::MSSI.set(0, 3),
);
rig.set_bits(Register::Cr1, cr1::SPE.mask());
rig.model().attach(Chip::answering([]));
rig.push(1);
rig.start();
rig.at(20_000);
assert_eq!(rig.chip().instants(), vec![FRAME]);
}
#[test]
fn a_change_of_clock_applies_from_the_next_frame() {
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([]));
rig.start();
rig.push(1);
rig.at(FRAME);
rig.clock(KERNEL_HZ / 2);
rig.push(2);
rig.at(10 * FRAME);
assert_eq!(rig.chip().instants(), vec![FRAME, FRAME + 2 * FRAME]);
assert_eq!(rig.model().kernel_hz(), KERNEL_HZ / 2);
}
#[test]
fn a_stopped_clock_parks_the_frame_and_starts_no_other() {
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([9]));
rig.start();
rig.push(1);
rig.push(2);
rig.at(FRAME / 2);
rig.clock(0);
rig.at(5 * FRAME);
assert!(!rig.has(sr::RXP), "the frame can't finish without a clock");
assert_eq!(rig.soc.next_event(), None);
rig.clock(KERNEL_HZ);
assert!(rig.has(sr::RXP), "it ends the instant the clock returns");
assert_eq!(rig.chip().instants()[0], 5 * FRAME);
rig.at(5 * FRAME + FRAME);
assert_eq!(
rig.chip().words(),
vec![1, 2],
"and the queued byte follows"
);
}
#[test]
fn a_model_nobody_wired_runs_on_the_hsi_and_says_so() {
let spi = Spi::new(Kind::BASIC);
assert_eq!(spi.kernel_hz(), DEFAULT_KERNEL_HZ);
assert_eq!(
DEFAULT_KERNEL_HZ, 64_000_000,
"RM0433 rev 9 section 8.7.2: HSIDIV 00"
);
}
#[test]
fn a_stale_frame_event_changes_nothing() {
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([9]));
rig.start();
rig.push(1);
rig.at(FRAME / 2);
rig.clear_bits(Register::Cr1, cr1::SPE.mask());
rig.at(2 * FRAME);
assert!(
rig.chip().seen.is_empty(),
"the abandoned frame never reached the chip"
);
assert!(!rig.has(sr::RXP));
rig.set_bits(Register::Cr1, cr1::SPE.mask());
rig.at(3 * FRAME);
assert!(!rig.has(sr::RXP));
}
#[test]
fn a_transfer_of_tsize_frames_ends_with_eot_after_the_last_and_clears_cstart() {
// 50.4.11 "Transaction handling", 50.11.6 bit 3.
let mut rig = Rig::new();
rig.write(Register::Cr2, 3);
rig = rig.master(3);
rig.model().attach(Chip::answering([10, 11, 12]));
for byte in [1, 2, 3] {
rig.push(byte);
}
rig.start();
assert_eq!(sr::CTSIZE.get(rig.status()), 3, "50.11.6: frames remaining");
rig.at(2 * FRAME);
let status = rig.status();
assert!(!sr::EOT.is_set(status) && cr1::CSTART.is_set(rig.read(Register::Cr1)));
assert_eq!(sr::CTSIZE.get(status), 1);
rig.at(3 * FRAME - 1);
assert!(!rig.has(sr::EOT), "one nanosecond early");
rig.at(3 * FRAME);
let status = rig.status();
assert!(sr::EOT.is_set(status));
assert!(
sr::TXC.is_set(status),
"50.11.6: with TSIZE above zero TXC copies EOT"
);
assert_eq!(sr::CTSIZE.get(status), 0);
assert!(
!cr1::CSTART.is_set(rig.read(Register::Cr1)),
"50.11.1: cleared by EOT"
);
assert_eq!(rig.chip().instants(), vec![FRAME, 2 * FRAME, 3 * FRAME]);
let received: Vec<u64> = (0..3).map(|_| rig.pop()).collect();
assert_eq!(received, vec![10, 11, 12]);
}
#[test]
fn eotc_clears_eot_and_with_it_txc_while_tsize_is_above_zero() {
let mut rig = Rig::new();
rig.write(Register::Cr2, 1);
rig = rig.master(3);
rig.model().attach(Chip::answering([1]));
rig.push(1);
rig.start();
rig.at(FRAME);
assert!(rig.has(sr::EOT) && rig.has(sr::TXC));
rig.write(Register::Ifcr, ifcr::EOTC.mask());
let status = rig.status();
assert!(!sr::EOT.is_set(status));
assert!(
!sr::TXC.is_set(status),
"50.11.6: TXC \"just copies the EOT flag value including its software clearing\""
);
}
#[test]
fn a_tsize_of_zero_is_endless_and_never_raises_eot() {
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([]));
rig.start();
for byte in 0..40 {
rig.push(byte);
let next = rig.soc.next_event().expect("a frame is running").0;
rig.at(next);
rig.pop();
}
assert!(!rig.has(sr::EOT) && !rig.has(sr::TXTF));
assert!(
cr1::CSTART.is_set(rig.read(Register::Cr1)),
"50.4.11: nothing ends it but CSUSP"
);
assert_eq!(sr::CTSIZE.get(rig.status()), 0);
}
#[test]
fn txtf_stands_when_tsize_frames_have_been_pushed_and_masks_txpie_and_dxpie() {
// 50.11.6 bit 4, 50.11.5 bits 2:1.
let mut rig = Rig::new();
rig.write(Register::Cr2, 2);
rig = rig.master(3);
let both = ier::TXPIE.mask() | ier::DPXPIE.mask();
rig.write(Register::Ier, both | ier::EOTIE.mask());
rig.push(1);
assert!(!rig.has(sr::TXTF));
assert_eq!(rig.read(Register::Ier) & both, both);
rig.push(2);
assert!(rig.has(sr::TXTF), "both frames are in the TxFIFO");
let enables = rig.read(Register::Ier);
assert_eq!(enables & both, 0, "cleared by the TXTF event");
assert!(ier::EOTIE.is_set(enables), "and only those two");
}
#[test]
fn writes_after_txtf_are_dropped_and_told_once() {
let mut rig = Rig::new();
rig.write(Register::Cr2, 1);
rig = rig.master(3);
rig.push(1);
assert!(rig.has(sr::TXTF));
rig.push(2);
rig.push(3);
let told = rig.told();
assert_eq!(told.len(), 1, "once, on the transition: {told:?}");
assert_eq!(told[0].register, "TXDR");
assert_eq!(told[0].reason, TXDR_AFTER_TXTF);
rig.write(Register::Ifcr, ifcr::TXTFC.mask());
assert!(!rig.has(sr::TXTF));
}
#[test]
fn redundant_writes_past_tsize_are_discarded_in_a_packed_access() {
// 50.4.11: "only the consistent data are pushed while redundant writes are discarded".
let mut rig = Rig::new();
rig.write(Register::Cr2, 3);
rig = rig.master(3);
rig.model().attach(Chip::answering([]));
rig.drive(|model, registers, context| {
model.write_data(Width::W32, 0x0403_0201, registers, context);
});
assert!(
rig.has(sr::TXTF),
"the third frame completed the transfer's data"
);
rig.start();
rig.at(10 * FRAME);
assert_eq!(
rig.chip().words(),
vec![1, 2, 3],
"the fourth frame was dropped"
);
}
#[test]
fn tser_extends_the_transfer_with_tserf_and_no_eot_at_the_reload() {
// 50.4.11 and 50.11.2: TSIZE reloads from TSER, TSER clears, TSERF is set, EOT waits.
let mut rig = Rig::new();
rig.write(Register::Cr2, cr2_words(2, 2));
rig = rig.master(3);
rig.model().attach(Chip::answering([]));
for byte in 1..=4 {
rig.push(byte);
}
assert!(
rig.has(sr::TXTF),
"the extension counts as room to push, so TXTF waits for all four"
);
rig.start();
rig.at(2 * FRAME);
let status = rig.status();
assert!(sr::TSERF.is_set(status), "the extension was accepted");
assert!(
!sr::EOT.is_set(status),
"\"The EOT event is not raised in this case\""
);
assert!(cr1::CSTART.is_set(rig.read(Register::Cr1)));
assert_eq!(
rig.read(Register::Cr2),
cr2_words(2, 0),
"TSIZE took TSER and TSER cleared"
);
assert_eq!(sr::CTSIZE.get(status), 2);
rig.at(4 * FRAME);
assert!(rig.has(sr::EOT), "the extended transfer ends");
assert_eq!(
rig.chip().instants(),
vec![FRAME, 2 * FRAME, 3 * FRAME, 4 * FRAME]
);
}
#[test]
fn tser_takes_a_write_only_while_it_holds_zero() {
let mut rig = Rig::new();
rig.write(Register::Cr2, cr2_words(5, 3));
assert_eq!(rig.read(Register::Cr2), cr2_words(5, 3));
rig.write(Register::Cr2, cr2_words(5, 9));
assert_eq!(
rig.read(Register::Cr2),
cr2_words(5, 3),
"50.11.2: set when its content is cleared only"
);
rig.write(Register::Cr2, cr2_words(5, 0));
assert_eq!(
rig.read(Register::Cr2),
cr2_words(5, 3),
"and type rs: a zero clears nothing"
);
}
#[test]
fn writing_tser_clears_tserf() {
let mut rig = Rig::new();
rig.drive(|model, registers, context| {
model.raise(sr::TSERF);
model.advance(registers, context);
});
assert!(rig.has(sr::TSERF));
rig.write(Register::Cr2, cr2_words(0, 1));
assert!(
!rig.has(sr::TSERF),
"50.11.7: \"TSERF is also reset by writing the TSER register\""
);
}
#[test]
fn a_tsize_written_under_an_enabled_interface_is_told() {
let mut rig = Rig::new().master(3);
rig.write(Register::Cr2, 5);
let told = rig.told();
assert_eq!(told.len(), 1);
assert_eq!(told[0].register, "CR2");
assert_eq!(told[0].reason, TSIZE_WHILE_ENABLED);
rig.write(Register::Cr2, 5);
assert!(rig.told().is_empty(), "an unchanged value isn't a change");
}
impl Rig {
fn master_with(mut self, configured: u64, format: u64) -> Self {
self.write(Register::Cfg1, configured);
self.write(
Register::Cfg2,
format | cfg2::MASTER.mask() | cfg2::SSOE.mask(),
);
self.set_bits(Register::Cr1, cr1::SPE.mask());
self
}
}
fn cr2_words(size: u64, extension: u64) -> u64 {
cr2::TSER.set(cr2::TSIZE.set(0, size), extension)
}
fn cfg1_for(mbr: u64) -> u64 {
cfg1::MBR.set(CFG1_RESET, mbr)
}
#[test]
fn a_simplex_transmitter_sends_frames_and_keeps_no_answer() {
// 50.4.4: transmit-only keeps no data from the line, and 50.4.14 "RXP and OVR are never set".
let mut rig = Rig::new().master_with(cfg1_for(3), cfg2::COMM.set(0, 1));
rig.model().attach(Chip::answering(1..=40));
rig.start();
for byte in 1..=30 {
rig.push(byte);
let next = rig.soc.next_event().expect("a frame is running").0;
rig.at(next);
}
assert_eq!(rig.chip().words(), (1..=30).collect::<Vec<u32>>());
let status = rig.status();
assert!(!sr::RXP.is_set(status) && !sr::OVR.is_set(status));
assert_eq!(sr::RXPLVL.get(status), 0);
}
#[test]
fn a_simplex_receiver_clocks_by_itself_and_keeps_every_answer() {
// 50.4.4: "The clock signal is generated continuously as long as the SPI is enabled and the
// CSTART bit ... is set", with the transmitter disabled.
let mut rig = Rig::new().master_with(cfg1_for(3), cfg2::COMM.set(0, 2));
rig.model().attach(Chip::answering([11, 12, 13]));
rig.start();
rig.at(3 * FRAME);
assert_eq!(rig.chip().instants(), vec![FRAME, 2 * FRAME, 3 * FRAME]);
assert_eq!(rig.chip().words(), vec![0, 0, 0], "nothing is sent");
let received: Vec<u64> = (0..3).map(|_| rig.pop()).collect();
assert_eq!(received, vec![11, 12, 13]);
}
#[test]
fn half_duplex_follows_hddir() {
// 50.4.4: COMM 11 with HDDIR clear is a receiver and with it set a transmitter.
let mut rig = Rig::new();
rig.write(Register::Cr1, cr1::HDDIR.mask());
let mut rig = rig.master_with(cfg1_for(3), cfg2::COMM.set(0, 3));
rig.model().attach(Chip::answering([5, 6]));
rig.start();
assert_eq!(rig.soc.next_event(), None, "a transmitter waits for data");
rig.push(0x31);
rig.at(FRAME);
assert!(!rig.has(sr::RXP), "and keeps no answer");
assert_eq!(rig.chip().words(), vec![0x31]);
let mut rig = Rig::new().master_with(cfg1_for(3), cfg2::COMM.set(0, 3));
rig.model().attach(Chip::answering([5, 6]));
rig.start();
rig.at(2 * FRAME);
assert_eq!(
(rig.pop(), rig.pop()),
(5, 6),
"a receiver clocks by itself"
);
}
#[test]
fn masrx_stops_a_receiver_when_the_rxfifo_is_full_and_susp_resumes_it() {
// 50.11.1 bit 8 and 50.4.11: the clock stops on a full RxFIFO, SUSP is set, and clearing SUSP
// lets the transfer run on.
let mut rig = Rig::new();
rig.write(Register::Cr1, cr1::MASRX.mask());
let mut rig = rig.master_with(cfg1_for(3), cfg2::COMM.set(0, 2));
rig.model().attach(Chip::answering(1..=40));
rig.start();
rig.at(16 * FRAME);
let status = rig.status();
assert!(
sr::SUSP.is_set(status),
"the sixteenth frame filled the queue"
);
assert!(!sr::OVR.is_set(status), "and the next was never started");
assert!(
cr1::CSTART.is_set(rig.read(Register::Cr1)),
"the transfer is only held"
);
assert_eq!(rig.soc.next_event(), None);
let first: Vec<u64> = (0..4).map(|_| rig.pop()).collect();
assert_eq!(first, vec![1, 2, 3, 4]);
rig.at(20 * FRAME);
assert_eq!(rig.chip().seen.len(), 16, "reading alone doesn't resume it");
rig.write(Register::Ifcr, ifcr::SUSPC.mask());
assert_eq!(rig.soc.next_event(), Some(VirtualNanos(20 * FRAME + FRAME)));
rig.at(21 * FRAME);
assert_eq!(rig.chip().seen.len(), 17);
}
#[test]
fn a_receiver_without_masrx_overruns_and_keeps_what_it_had() {
// 50.5.2: the new value "does not overwrite the previous one in the RxFIFO".
let mut rig = Rig::new().master_with(cfg1_for(3), cfg2::COMM.set(0, 2));
rig.model().attach(Chip::answering(1..=40));
rig.start();
rig.at(16 * FRAME);
assert!(!rig.has(sr::OVR));
rig.at(17 * FRAME);
assert!(
rig.has(sr::OVR),
"the seventeenth frame found the queue full"
);
let kept: Vec<u64> = (0..16).map(|_| rig.pop()).collect();
assert_eq!(kept, (1..=16).collect::<Vec<u64>>());
assert_eq!(rig.pop(), 0, "and the lost frame isn't there");
}
#[test]
fn masrx_does_not_hold_a_full_duplex_master_and_the_overrun_follows() {
// 50.4.11: "This may happen in slave mode or master mode (full duplex or receive only with
// MASRX = 0)". ChibiOS sets MASRX in full duplex all the same.
let mut rig = Rig::new();
rig.write(Register::Cr1, cr1::MASRX.mask());
let mut rig = rig.master_with(cfg1_for(3), 0);
rig.model().attach(Chip::answering(1..=40));
for byte in 0..16 {
rig.push(byte);
}
rig.start();
rig.at(16 * FRAME);
assert!(!rig.has(sr::OVR) && !rig.has(sr::SUSP));
rig.push(99);
rig.at(17 * FRAME);
assert!(rig.has(sr::OVR));
assert!(!rig.has(sr::SUSP));
}
#[test]
fn ovrc_clears_an_overrun_and_so_does_disabling() {
let mut rig = Rig::new().master_with(cfg1_for(3), cfg2::COMM.set(0, 2));
rig.model().attach(Chip::answering(1..=40));
rig.start();
rig.at(17 * FRAME);
assert!(rig.has(sr::OVR));
rig.write(Register::Ifcr, ifcr::OVRC.mask());
assert!(!rig.has(sr::OVR));
rig.at(18 * FRAME);
assert!(
rig.has(sr::OVR),
"the receiver is still running into a full queue"
);
rig.clear_bits(Register::Cr1, cr1::SPE.mask());
assert!(
!rig.has(sr::OVR),
"50.11.6 bit 6: cleared when SPI is disabled"
);
}
#[test]
fn disabling_flushes_both_fifos_and_clears_what_the_manual_says() {
// 50.11.1 bit 0: "all the FIFOs content is flushed", and 50.11.6: OVR, UDR and CRCE clear
// with SPE, while the flags that need a clear of their own stay.
let mut rig = Rig::new();
rig.write(Register::Cr2, 2);
let mut rig = rig.master_with(cfg1_for(3), 0);
rig.model().attach(Chip::answering([1, 2]));
rig.push(1);
rig.push(2);
rig.start();
rig.at(2 * FRAME);
rig.drive(|model, registers, context| {
model.raise(sr::OVR);
model.raise(sr::UDR);
model.raise(sr::CRCE);
model.raise(sr::SUSP);
model.advance(registers, context);
});
assert!(rig.has(sr::EOT) && rig.has(sr::RXP) && rig.has(sr::TXTF));
rig.clear_bits(Register::Cr1, cr1::SPE.mask());
let status = rig.status();
assert!(!sr::RXP.is_set(status), "the RxFIFO was flushed");
assert_eq!(rig.pop(), 0, "RXDR \"is read zero\" with SPE=0");
assert!(!sr::OVR.is_set(status) && !sr::UDR.is_set(status) && !sr::CRCE.is_set(status));
assert!(sr::EOT.is_set(status), "an EOT still waits for its EOTC");
assert!(sr::TXTF.is_set(status));
assert!(
sr::SUSP.is_set(status),
"\"all the not cleared requests ... stay pending\""
);
assert!(sr::TXP.is_set(status));
}
#[test]
fn disabling_with_data_queued_drops_it_and_a_new_enable_starts_empty() {
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([]));
for byte in 1..=5 {
rig.push(byte);
}
rig.clear_bits(Register::Cr1, cr1::SPE.mask());
rig.set_bits(Register::Cr1, cr1::SPE.mask());
rig.start();
rig.at(10 * FRAME);
assert!(
rig.chip().seen.is_empty(),
"the TxFIFO was flushed with SPE"
);
}
#[test]
fn each_fifo_holds_16_bytes_on_spi1_and_8_on_spi4() {
// 50.3 Table 422.
for (instance, bytes) in [
("SPI1", 16),
("SPI2", 16),
("SPI3", 16),
("SPI4", 8),
("SPI5", 8),
("SPI6", 8),
] {
let mut rig = Rig::of(instance).master(3);
rig.model().attach(Chip::answering([]));
for byte in 0..24 {
rig.push(byte);
}
rig.start();
rig.at(40 * FRAME);
assert_eq!(rig.chip().seen.len(), bytes, "{instance}");
assert_eq!(
rig.told()
.iter()
.filter(|told| told.reason == TXDR_WHILE_FULL)
.count(),
1,
"{instance}: the drop is told once"
);
}
}
#[test]
fn a_wider_frame_takes_more_of_the_fifo() {
// 50.4.11: N frames of up to 8 bits, N/2 up to 16, N/3 up to 24 and N/4 above.
for (dsize, frames) in [(7, 16), (8, 8), (15, 8), (16, 5), (23, 5), (24, 4), (31, 4)] {
let mut rig = Rig::new();
rig.write(Register::Cfg1, cfg1::DSIZE.set(cfg1_for(3), dsize));
let mut rig = rig.master_with(cfg1::DSIZE.set(cfg1_for(3), dsize), 0);
rig.model().attach(Chip::answering([]));
let width = natural_width(u32::try_from(dsize + 1).unwrap());
for word in 0..20 {
rig.write_as(Register::Txdr, width, word);
}
rig.start();
rig.at(100 * 32 * BIT);
assert_eq!(rig.chip().seen.len(), frames, "DSIZE {dsize}");
}
}
#[test]
fn txp_stands_while_a_whole_packet_fits() {
// 50.4.11: "TXP ... is set by hardware when there is enough space ... to push at least one
// complete data packet". FTHLV 3 is a packet of four.
let mut rig = Rig::new().master_with(cfg1::FTHVL.set(cfg1_for(3), 3), 0);
for byte in 0..12 {
rig.push(byte);
}
assert!(rig.has(sr::TXP), "four places are left");
rig.push(12);
assert!(!rig.has(sr::TXP), "three are not a packet");
rig.model().attach(Chip::answering([]));
rig.start();
assert!(
rig.has(sr::TXP),
"the first frame left for the shift register"
);
}
#[test]
fn rxp_waits_for_a_whole_packet_and_dxp_is_both_flags() {
let mut rig = Rig::new().master_with(cfg1::FTHVL.set(cfg1_for(3), 3), 0);
rig.model().attach(Chip::answering([1, 2, 3, 4]));
for byte in 0..4 {
rig.push(byte);
}
rig.start();
for frame in 1..=4 {
rig.at(frame * FRAME);
let status = rig.status();
assert_eq!(sr::RXP.is_set(status), frame == 4, "frame {frame}");
assert_eq!(
sr::DXP.is_set(status),
sr::RXP.is_set(status) && sr::TXP.is_set(status),
"50.11.6: DXP is TXP and RXP together"
);
}
let packet: Vec<u64> = (0..4).map(|_| rig.pop()).collect();
assert_eq!(packet, vec![1, 2, 3, 4]);
assert!(!rig.has(sr::RXP) && !rig.has(sr::DXP));
}
#[test]
fn the_last_packet_of_a_transfer_asks_only_for_what_is_left() {
// 50.4.11: "Just for the last data case, the TXP event is asserted by SPI once there is
// enough space into TxFIFO to store remaining data to complete the current transfer."
let mut rig = Rig::of("SPI4");
rig.write(Register::Cr2, 6);
let mut rig = rig.master_with(cfg1::FTHVL.set(cfg1_for(3), 3), 0);
for byte in 0..4 {
rig.push(byte);
}
assert!(
rig.has(sr::TXP),
"four places for a packet of four, and two frames to come"
);
rig.push(4);
assert!(
rig.has(sr::TXP),
"three places are fewer than a packet but more than the one frame left"
);
rig.push(5);
assert!(
!rig.has(sr::TXP),
"nothing is left to ask for and the queue has two places"
);
assert!(rig.has(sr::TXTF));
}
#[test]
fn rxwne_and_rxplvl_count_the_residue_of_a_short_packet() {
// 50.11.6 bits 15:13.
let mut rig = Rig::new().master_with(cfg1::FTHVL.set(cfg1_for(3), 3), 0);
rig.model().attach(Chip::answering([1, 2, 3, 4]));
for byte in 0..4 {
rig.push(byte);
}
rig.start();
rig.at(3 * FRAME);
let status = rig.status();
assert!(!sr::RXP.is_set(status) && !sr::RXWNE.is_set(status));
assert_eq!(sr::RXPLVL.get(status), 3, "three frames of residue");
rig.at(4 * FRAME);
let status = rig.status();
assert!(
sr::RXP.is_set(status) && sr::RXWNE.is_set(status),
"four bytes are a word"
);
assert_eq!(sr::RXPLVL.get(status), 0);
for _ in 0..3 {
rig.pop();
}
let status = rig.status();
assert!(!sr::RXWNE.is_set(status));
assert_eq!(sr::RXPLVL.get(status), 1);
}
#[test]
fn rxwne_counts_bytes_whatever_the_frame_and_rxplvl_reads_zero_above_sixteen_bits() {
let mut rig =
Rig::new().master_with(cfg1::FTHVL.set(cfg1::DSIZE.set(cfg1_for(3), 15), 1), 0);
rig.model().attach(Chip::answering([1, 2]));
rig.write_as(Register::Txdr, Width::W16, 1);
rig.write_as(Register::Txdr, Width::W16, 2);
rig.start();
rig.at(16 * BIT);
let status = rig.status();
assert_eq!(sr::RXPLVL.get(status), 1, "one frame of two bytes");
assert!(!sr::RXWNE.is_set(status) && !sr::RXP.is_set(status));
rig.at(32 * BIT);
let status = rig.status();
assert!(sr::RXWNE.is_set(status), "two frames are four bytes");
assert!(sr::RXP.is_set(status));
let mut rig = Rig::new().master_with(cfg1::DSIZE.set(cfg1_for(3), 23), 0);
rig.model().attach(Chip::answering([1]));
rig.write_as(Register::Txdr, Width::W32, 1);
rig.start();
rig.at(24 * BIT);
let status = rig.status();
assert_eq!(
sr::RXPLVL.get(status),
0,
"50.11.6: \"When the frame size is greater than 16-bit\" 00"
);
assert!(!sr::RXWNE.is_set(status), "three bytes");
assert!(sr::RXP.is_set(status));
}
#[test]
fn a_packet_over_half_the_fifo_is_told_once() {
// 50.11.3 bits 8:5: "The size of the packet must not exceed 1/2 of FIFO space."
let mut rig = Rig::of("SPI4");
rig.write(Register::Cfg1, cfg1::FTHVL.set(CFG1_RESET, 3));
assert!(rig.told().is_empty(), "four of eight is half");
rig.write(Register::Cfg1, cfg1::FTHVL.set(CFG1_RESET, 4));
let told = rig.told();
assert_eq!(told.len(), 1);
assert_eq!(told[0].register, "CFG1");
assert_eq!(told[0].reason, PACKET_TOO_LARGE);
rig.write(Register::Cfg1, cfg1::FTHVL.set(CFG1_RESET, 5));
assert!(
rig.told().is_empty(),
"still too large is not a new transition"
);
}
#[test]
fn txc_follows_the_queue_and_the_bus_while_tsize_is_zero() {
// 50.11.6 bit 12.
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([]));
assert!(rig.has(sr::TXC), "empty and idle");
rig.start();
rig.push(1);
rig.push(2);
assert!(!rig.has(sr::TXC));
rig.at(FRAME);
assert!(!rig.has(sr::TXC), "a frame remains");
rig.at(2 * FRAME - 1);
assert!(!rig.has(sr::TXC), "and it is still on the wire");
rig.at(2 * FRAME);
assert!(
rig.has(sr::TXC),
"cleared by activity, raised when the queue empties and the bus idles"
);
}
#[test]
fn a_wide_access_carries_several_frames_least_significant_first() {
// 50.4.13 figure 623 and 50.11.8: four bytes of one word are four frames.
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([]));
rig.drive(|model, registers, context| {
model.write_data(Width::W32, 0x0403_0201, registers, context);
model.write_data(Width::W16, 0x0605, registers, context);
model.write_data(Width::W8, 0x07, registers, context);
});
rig.start();
rig.at(20 * FRAME);
assert_eq!(rig.chip().words(), (1..=7).collect::<Vec<u32>>());
let mut rig = Rig::new().master_with(cfg1::DSIZE.set(cfg1_for(3), 11), 0);
rig.model().attach(Chip::answering([]));
rig.drive(|model, registers, context| {
model.write_data(Width::W32, 0x0BBB_0AAA, registers, context);
});
rig.start();
rig.at(10 * 12 * BIT);
assert_eq!(
rig.chip().words(),
vec![0x0AAA, 0x0BBB],
"50.4.13: the least significant half-word first, whatever LSBFRST says"
);
}
#[test]
fn a_wide_read_takes_several_frames_and_a_redundant_one_reads_zero() {
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([1, 2, 3, 4, 5]));
for byte in 0..5 {
rig.push(byte);
}
rig.start();
rig.at(5 * FRAME);
let read = |rig: &mut Rig, width| {
let mut value = 0;
rig.drive(|model, registers, context| {
value = model.read_data(width, registers, context);
});
value
};
assert_eq!(read(&mut rig, Width::W32), 0x0403_0201);
assert_eq!(
read(&mut rig, Width::W16),
0x0000_0005,
"one frame is left for two"
);
assert_eq!(
read(&mut rig, Width::W32),
0,
"\"redundant reads (or any incomplete data) are reading 0\""
);
}
#[test]
fn rxdr_right_aligns_a_frame_in_a_byte_a_half_word_or_a_word() {
// 50.4.8 figure 622.
let cases = [
(3, Width::W8, 0xF, 0x0F),
(11, Width::W16, 0xABC, 0x0ABC),
(23, Width::W32, 0x00AB_CDEF, 0x00AB_CDEF),
];
for (dsize, width, answer, expected) in cases {
let mut rig = Rig::new().master_with(cfg1::DSIZE.set(cfg1_for(3), dsize), 0);
rig.model().attach(Chip::answering([answer]));
rig.write_as(Register::Txdr, width, 1);
rig.start();
rig.at(32 * BIT);
assert_eq!(
rig.read_as(Register::Rxdr, width),
expected,
"DSIZE {dsize}"
);
}
}
#[test]
fn the_bits_of_a_written_frame_past_its_size_are_ignored() {
// 50.11.8: "Unused bits are ignored when writing to the register."
let mut rig = Rig::new().master_with(cfg1::DSIZE.set(cfg1_for(3), 3), 0);
rig.model().attach(Chip::answering([]));
rig.push(0xFA);
rig.start();
rig.at(10 * FRAME);
assert_eq!(rig.chip().seen, vec![(4 * BIT, 0xA, 4)]);
}
#[test]
fn lsbfrst_sends_the_least_significant_bit_first_and_reverses_the_answer() {
// 50.11.4 bit 23: the peer reads the wire, so it sees the frame reversed.
let mut rig = Rig::new().master_with(cfg1_for(3), cfg2::LSBFRST.mask());
rig.model().attach(Chip::answering([0x80]));
rig.push(0x01);
rig.start();
rig.at(FRAME);
assert_eq!(rig.chip().words(), vec![0x80]);
assert_eq!(rig.pop(), 0x01);
let mut rig =
Rig::new().master_with(cfg1::DSIZE.set(cfg1_for(3), 11), cfg2::LSBFRST.mask());
rig.model().attach(Chip::answering([0x800]));
rig.write_as(Register::Txdr, Width::W16, 0x001);
rig.start();
rig.at(12 * BIT);
assert_eq!(
rig.chip().words(),
vec![0x800],
"a twelve-bit frame reverses within its twelve bits"
);
assert_eq!(rig.read_as(Register::Rxdr, Width::W16), 0x001);
}
#[test]
fn a_debugger_peek_of_rxdr_shows_the_front_frame_and_takes_nothing() {
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([0x21, 0x22]));
rig.push(0);
rig.push(0);
rig.start();
rig.at(2 * FRAME);
let address = rig.address(Register::Rxdr);
let peek = rig.soc.peek(rig.now, address, Width::W8).unwrap();
assert_eq!(peek, 0x21);
assert_eq!(rig.soc.peek(rig.now, address, Width::W8).unwrap(), 0x21);
assert_eq!(rig.pop(), 0x21);
assert_eq!(rig.pop(), 0x22);
}
#[test]
fn cfg1_is_write_protected_while_spe_is_set_except_the_dma_enables() {
// 50.11.3: "Content of this register is write protected when SPI is enabled, except TXDMAEN
// and RXDMAEN bits."
let mut rig = Rig::new();
let before = cfg1::DSIZE.set(cfg1_for(3), 11);
rig.write(Register::Cfg1, before);
assert_eq!(rig.read(Register::Cfg1), before, "writable while disabled");
rig.set_bits(Register::Cr1, cr1::SPE.mask());
rig.told();
rig.write(Register::Cfg1, cfg1_for(5) | cfg1::TXDMAEN.mask());
assert_eq!(
rig.read(Register::Cfg1),
before | cfg1::TXDMAEN.mask(),
"only the DMA enable landed"
);
rig.write(Register::Cfg1, cfg1_for(5) | cfg1::RXDMAEN.mask());
assert_eq!(rig.read(Register::Cfg1), before | cfg1::RXDMAEN.mask());
let told = rig.told();
assert_eq!(told.len(), 2, "each dropped change is told: {told:?}");
assert!(
told.iter()
.all(|entry| entry.register == "CFG1" && entry.reason == WRITE_PROTECTED)
);
rig.write(Register::Cfg1, before | cfg1::RXDMAEN.mask());
assert!(
rig.told().is_empty(),
"a write that changes nothing protected is not a fault"
);
}
#[test]
fn cfg2_crcpoly_and_udrdr_are_write_protected_while_spe_is_set() {
// 50.11.4, 50.11.10 and 50.11.13.
let mut rig = Rig::new();
rig.write(Register::Cfg2, cfg2::MASTER.mask() | cfg2::SSOE.mask());
rig.write(Register::Crcpoly, 0x1021);
rig.write(Register::Udrdr, 0x55);
rig.set_bits(Register::Cr1, cr1::SPE.mask());
rig.write(Register::Cfg2, cfg2::MASTER.mask() | cfg2::CPOL.mask());
rig.write(Register::Crcpoly, 0x07);
rig.write(Register::Udrdr, 0xAA);
assert_eq!(
rig.read(Register::Cfg2),
cfg2::MASTER.mask() | cfg2::SSOE.mask()
);
assert_eq!(rig.read(Register::Crcpoly), 0x1021);
assert_eq!(rig.read(Register::Udrdr), 0x55);
assert_eq!(rig.told().len(), 3);
rig.clear_bits(Register::Cr1, cr1::SPE.mask());
rig.write(Register::Crcpoly, 0x07);
rig.write(Register::Udrdr, 0xAA);
assert_eq!(
(rig.read(Register::Crcpoly), rig.read(Register::Udrdr)),
(0x07, 0xAA)
);
}
#[test]
fn iolock_locks_cfg2_is_set_only_while_disabled_and_clears_when_spe_falls() {
// 50.11.1 bit 16 and 50.11.4: "write protected when SPI is enabled or IOLOCK bit is set".
let mut rig = Rig::new();
rig.set_bits(Register::Cr1, cr1::IOLOCK.mask());
assert!(cr1::IOLOCK.is_set(rig.read(Register::Cr1)));
rig.write(Register::Cfg2, cfg2::CPOL.mask());
assert_eq!(
rig.read(Register::Cfg2),
0,
"IOLOCK holds CFG2 with SPE clear"
);
rig.write(Register::Cr1, cr1::IOLOCK.mask() | cr1::SPE.mask());
rig.clear_bits(Register::Cr1, cr1::SPE.mask());
assert!(
!cr1::IOLOCK.is_set(rig.read(Register::Cr1)),
"cleared \"whenever SPE bit is changed from 1 to 0\""
);
rig.write(Register::Cfg2, cfg2::CPOL.mask());
assert_eq!(rig.read(Register::Cfg2), cfg2::CPOL.mask());
rig.set_bits(Register::Cr1, cr1::SPE.mask());
rig.set_bits(Register::Cr1, cr1::IOLOCK.mask());
assert!(
!cr1::IOLOCK.is_set(rig.read(Register::Cr1)),
"\"This bit can be set when SPI is disabled only\""
);
}
#[test]
fn reserved_bits_read_zero_and_the_sixteen_bit_instances_lose_their_upper_halves() {
// 50.11.3, 50.11.10 to 50.11.13 and Table 422.
let defined = |register: Register| {
register
.spec()
.fields
.iter()
.fold(0, |bits, field| bits | field.mask())
};
let mut wide = Rig::of("SPI1");
let mut narrow = Rig::of("SPI4");
let writable = [
Register::Cr2,
Register::Cfg1,
Register::Ier,
Register::Crcpoly,
Register::Udrdr,
Register::Cgfr,
];
for rig in [&mut wide, &mut narrow] {
for register in writable {
rig.write(register, EVERY_BIT);
}
rig.write(Register::Cfg2, EVERY_BIT & !cfg2::MASTER.mask());
rig.write(
Register::Cr1,
EVERY_BIT & !(cr1::SPE.mask() | cr1::CSTART.mask()),
);
}
for rig in [&mut wide, &mut narrow] {
for register in [Register::Cr2, Register::Ier, Register::Cgfr] {
assert_eq!(rig.read(register), defined(register), "{register:?}");
}
assert_eq!(
rig.read(Register::Cfg2),
defined(Register::Cfg2) & !cfg2::MASTER.mask()
);
assert_eq!(
rig.read(Register::Cr1),
defined(Register::Cr1)
& !(cr1::SPE.mask() | cr1::CSTART.mask() | cr1::CSUSP.mask()),
"CSUSP reads zero"
);
}
assert_eq!(wide.read(Register::Cfg1), defined(Register::Cfg1));
assert_eq!(
narrow.read(Register::Cfg1),
defined(Register::Cfg1) & !(top_bit(cfg1::DSIZE) | top_bit(cfg1::CRCSIZE)),
"DSIZE and CRCSIZE lose their top bit"
);
assert_eq!(wide.read(Register::Crcpoly), EVERY_BIT);
assert_eq!(wide.read(Register::Udrdr), EVERY_BIT);
assert_eq!(narrow.read(Register::Crcpoly), 0xFFFF);
assert_eq!(narrow.read(Register::Udrdr), 0xFFFF);
}
#[test]
fn txcrc_rxcrc_sr_and_rxdr_ignore_writes() {
// 50.11.6, 50.11.9, 50.11.11 and 50.11.12 type every bit r.
let mut rig = Rig::new();
for register in [
Register::Txcrc,
Register::Rxcrc,
Register::Sr,
Register::Rxdr,
] {
rig.write(register, EVERY_BIT);
}
assert_eq!(rig.read(Register::Txcrc), 0);
assert_eq!(rig.read(Register::Rxcrc), 0);
assert_eq!(rig.read(Register::Sr), 0x1002);
assert_eq!(rig.read(Register::Rxdr), 0);
}
#[test]
fn ifcr_and_txdr_read_zero_and_csusp_reads_zero() {
let mut rig = Rig::new().master(3);
rig.write(Register::Ifcr, EVERY_BIT);
rig.push(0x5A);
assert_eq!(rig.read(Register::Ifcr), 0);
assert_eq!(rig.read(Register::Txdr), 0);
assert_eq!(rig.model().tx.len(), 1);
rig.set_bits(Register::Cr1, cr1::CSUSP.mask());
assert!(!cr1::CSUSP.is_set(rig.read(Register::Cr1)));
}
#[test]
fn ifcr_clears_each_flag_it_names_and_only_that_one() {
// 50.11.7. UDR and TIFRE are slave flags that a master never raises, so they are set here
// through the model.
let flags = [
(ifcr::EOTC, sr::EOT),
(ifcr::TXTFC, sr::TXTF),
(ifcr::UDRC, sr::UDR),
(ifcr::OVRC, sr::OVR),
(ifcr::CRCEC, sr::CRCE),
(ifcr::TIFREC, sr::TIFRE),
(ifcr::MODFC, sr::MODF),
(ifcr::TSERFC, sr::TSERF),
(ifcr::SUSPC, sr::SUSP),
];
for (clear, flag) in flags {
let mut rig = Rig::new();
rig.drive(|model, registers, context| {
for (_, raised) in flags {
model.raise(raised);
}
model.advance(registers, context);
});
let every = flags
.iter()
.fold(0, |bits, (_, raised)| bits | raised.mask());
assert_eq!(rig.status() & every, every);
rig.write(Register::Ifcr, clear.mask());
assert_eq!(
rig.status() & every,
every & !flag.mask(),
"{} clears {} alone",
clear.name,
flag.name
);
}
}
#[test]
fn a_slave_transfers_nothing_and_is_told_so() {
// The model is a master. 50.11.1: CSTART "is taken into account at master mode only".
let mut rig = Rig::new();
rig.set_bits(Register::Cr1, cr1::SPE.mask());
rig.model().attach(Chip::answering([1]));
rig.start();
assert!(!cr1::CSTART.is_set(rig.read(Register::Cr1)));
rig.push(1);
rig.at(10 * FRAME);
assert!(rig.chip().seen.is_empty());
let told = rig.told();
assert_eq!(told.len(), 1);
assert_eq!(
(told[0].register, told[0].reason),
("CR1", SLAVE_NOT_MODELED)
);
}
#[test]
fn the_audio_mode_of_spi1_moves_nothing_and_means_nothing_on_spi4() {
// 50.11.14 bit 0, and Table 422: SPI4 has no I2S.
let mut rig = Rig::new();
rig.write(Register::Cgfr, cgfr::I2SMOD.mask());
let mut rig = rig.master(3);
assert!(
rig.told()
.iter()
.any(|entry| entry.reason == I2S_NOT_MODELED)
);
rig.model().attach(Chip::answering([1]));
rig.push(1);
rig.start();
rig.at(10 * FRAME);
assert!(
rig.chip().seen.is_empty(),
"SPI1 with I2SMOD set is not an SPI"
);
let mut rig = Rig::of("SPI4");
rig.write(Register::Cgfr, cgfr::I2SMOD.mask());
assert_eq!(rig.read(Register::Cgfr), cgfr::I2SMOD.mask(), "stored");
let mut rig = rig.master(3);
rig.model().attach(Chip::answering([7]));
rig.push(1);
rig.start();
rig.at(FRAME);
assert_eq!(rig.pop(), 7);
assert!(rig.told().is_empty());
}
#[test]
fn the_ti_protocol_moves_frames_as_motorola_does_and_is_told() {
// 50.5.1. The NSS pulse is not modeled.
let mut rig = Rig::new();
rig.write(Register::Cfg1, cfg1_for(3));
rig.write(
Register::Cfg2,
cfg2::MASTER.mask() | cfg2::SSOE.mask() | cfg2::SP.set(0, 1),
);
rig.set_bits(Register::Cr1, cr1::SPE.mask());
rig.model().attach(Chip::answering([3]));
rig.push(9);
rig.start();
rig.at(FRAME);
assert_eq!(rig.pop(), 3);
assert!(
rig.told()
.iter()
.any(|entry| entry.reason == TI_NOT_MODELED)
);
}
#[test]
fn a_master_whose_software_slave_select_is_active_takes_the_fault() {
// 50.5.2 and 50.4.7 figure 618: SSI below SSIOP's level is the active level.
let mut rig = Rig::new();
rig.write(Register::Cr1, cr1::SSI.mask());
rig.write(Register::Cfg2, cfg2::MASTER.mask() | cfg2::SSM.mask());
rig.set_bits(Register::Cr1, cr1::SPE.mask());
assert!(!rig.has(sr::MODF), "SSI high is inactive");
assert!(cr1::SPE.is_set(rig.read(Register::Cr1)));
rig.clear_bits(Register::Cr1, cr1::SSI.mask());
assert!(rig.has(sr::MODF));
let control = rig.read(Register::Cr1);
assert!(
!cr1::SPE.is_set(control),
"\"The SPE bit is forced to zero\""
);
assert!(!cr1::IOLOCK.is_set(control));
assert!(
!cfg2::MASTER.is_set(rig.read(Register::Cfg2)),
"\"The MASTER bit is cleared, thus forcing the device into slave mode\""
);
}
#[test]
fn ssiop_chooses_the_level_of_ssi_that_is_active() {
// 50.11.4 bit 28: with SSIOP set, a high SSI is the active level.
let mut rig = Rig::new();
rig.write(Register::Cr1, cr1::SSI.mask());
rig.write(
Register::Cfg2,
cfg2::MASTER.mask() | cfg2::SSM.mask() | cfg2::SSIOP.mask(),
);
assert!(rig.has(sr::MODF));
}
#[test]
fn a_master_driving_nss_takes_no_fault_from_a_low_ssi() {
// 50.11.4 bit 26: with SSM clear and SSOE set the input is forced inactive.
let mut rig = Rig::new().master(3);
rig.clear_bits(Register::Cr1, cr1::SSI.mask());
assert!(!rig.has(sr::MODF));
}
#[test]
fn spe_cannot_be_set_while_modf_stands_and_modfc_lets_it_go() {
let mut rig = Rig::new();
rig.write(Register::Cfg2, cfg2::MASTER.mask() | cfg2::SSM.mask());
assert!(
rig.has(sr::MODF),
"the conflict was there when the master was configured"
);
rig.set_bits(Register::Cr1, cr1::SPE.mask());
assert!(
!cr1::SPE.is_set(rig.read(Register::Cr1)),
"50.5.2: hardware does not allow it"
);
let told = rig.told();
assert_eq!(told.len(), 1);
assert_eq!(told[0].reason, SPE_WITH_MODF);
rig.write(Register::Ifcr, ifcr::MODFC.mask());
assert!(!rig.has(sr::MODF));
rig.set_bits(Register::Cr1, cr1::SPE.mask());
assert!(cr1::SPE.is_set(rig.read(Register::Cr1)));
}
#[test]
fn iolock_cannot_be_set_while_modf_stands() {
let mut rig = Rig::new();
rig.write(Register::Cfg2, cfg2::MASTER.mask() | cfg2::SSM.mask());
rig.set_bits(Register::Cr1, cr1::IOLOCK.mask());
assert!(!cr1::IOLOCK.is_set(rig.read(Register::Cr1)));
}
#[test]
fn the_interrupt_is_position_zero_of_each_instances_table_at_table_147s_line() {
let lines = [35, 36, 51, 84, 85, 86];
for (instance, line) in INSTANCES.iter().zip(lines) {
let mut rig = Rig::of(instance.name);
rig.write(Register::Ier, ier::TXPIE.mask());
assert_eq!(
rig.soc.drain_interrupts(),
vec![InterruptChange {
instance: instance.name,
interrupt: line,
level: Level::High
}],
"{}: TXP stands out of reset",
instance.name
);
}
}
#[test]
fn every_enable_sits_at_the_bit_of_the_flag_it_arms() {
// Table 423 against the register pages of 50.11.5 and 50.11.6.
for (enable, flag) in INTERRUPTS {
assert_eq!(enable.bit, flag.bit, "{} arms {}", enable.name, flag.name);
}
assert_eq!(INTERRUPTS.len(), Register::Ier.spec().fields.len());
}
#[test]
fn each_sticky_flag_raises_the_line_under_its_own_enable_alone() {
let flags = [
(ier::EOTIE, sr::EOT),
(ier::TXTFIE, sr::TXTF),
(ier::UDRIE, sr::UDR),
(ier::OVRIE, sr::OVR),
(ier::CRCEIE, sr::CRCE),
(ier::TIFREIE, sr::TIFRE),
(ier::MODFIE, sr::MODF),
(ier::TSERFIE, sr::TSERF),
];
for (own, flag) in flags {
let mut rig = Rig::new();
// With TSIZE above zero TXC copies EOT, so EOTIE alone is quiet until EOT.
rig.write(Register::Cr2, 1);
let others = flags
.iter()
.filter(|(enable, _)| enable.name != own.name && enable.name != "EOTIE")
.fold(0, |bits, (enable, _)| bits | enable.mask());
rig.write(Register::Ier, others);
rig.drive(|model, registers, context| {
model.raise(flag);
model.advance(registers, context);
});
assert!(
rig.levels().is_empty(),
"{}: no other enable arms it",
flag.name
);
rig.write(Register::Ier, others | own.mask());
assert_eq!(
rig.levels(),
vec![Level::High],
"{} under {}",
flag.name,
own.name
);
rig.write(Register::Ifcr, EVERY_BIT);
assert_eq!(rig.levels(), vec![Level::Low], "{} cleared", flag.name);
}
}
#[test]
fn eotie_also_arms_txc_and_susp() {
// 50.11.5 bit 3: "EOT, SUSP and TXC interrupt enable", and Table 423 note 2.
let mut rig = Rig::new();
rig.write(Register::Ier, ier::EOTIE.mask());
assert_eq!(
rig.levels(),
vec![Level::High],
"TXC stands with TSIZE at zero"
);
let mut rig = Rig::new();
rig.write(Register::Cr2, 1);
rig.write(Register::Ier, ier::EOTIE.mask());
assert!(rig.levels().is_empty(), "TXC copies EOT, which is clear");
rig.drive(|model, registers, context| {
model.raise(sr::SUSP);
model.advance(registers, context);
});
assert_eq!(rig.levels(), vec![Level::High]);
rig.write(Register::Ifcr, ifcr::SUSPC.mask());
assert_eq!(rig.levels(), vec![Level::Low]);
}
#[test]
fn rxpie_txpie_and_dxpie_follow_their_packet_flags() {
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([1]));
rig.write(Register::Ier, ier::RXPIE.mask());
assert!(rig.levels().is_empty());
rig.start();
rig.push(1);
rig.at(FRAME);
assert_eq!(rig.levels(), vec![Level::High], "RXP");
rig.pop();
assert_eq!(rig.levels(), vec![Level::Low]);
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([]));
rig.write(Register::Ier, ier::TXPIE.mask());
assert_eq!(
rig.levels(),
vec![Level::High],
"TXP stands while the queue has room"
);
for byte in 0..16 {
rig.push(byte);
}
assert_eq!(rig.levels(), vec![Level::Low], "and falls when it is full");
rig.start();
assert_eq!(
rig.levels(),
vec![Level::High],
"a frame left for the shift register"
);
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([1]));
rig.write(Register::Ier, ier::DPXPIE.mask());
assert!(rig.levels().is_empty(), "TXP alone is not DXP");
rig.start();
rig.push(1);
rig.at(FRAME);
assert_eq!(rig.levels(), vec![Level::High], "DXP");
rig.pop();
assert_eq!(rig.levels(), vec![Level::Low]);
}
#[test]
fn the_line_is_reported_only_when_its_level_changes() {
let mut rig = Rig::new();
rig.write(Register::Ier, ier::TXPIE.mask());
assert_eq!(rig.levels(), vec![Level::High]);
rig.write(Register::Ier, ier::TXPIE.mask() | ier::OVRIE.mask());
rig.write(Register::Ifcr, EVERY_BIT);
assert!(rig.levels().is_empty());
}
#[test]
fn txpie_and_dxpie_are_set_and_cleared_by_software() {
// 50.11.5, and ES0392 rev 15 item 2.22.4's workaround.
let mut rig = Rig::new();
rig.write(Register::Ier, ier::TXPIE.mask() | ier::DPXPIE.mask());
assert_eq!(
rig.read(Register::Ier),
ier::TXPIE.mask() | ier::DPXPIE.mask()
);
rig.write(Register::Ier, 0);
assert_eq!(rig.read(Register::Ier), 0);
}
#[test]
fn the_transmit_request_follows_txp_while_txdmaen_and_spe_hold() {
// 50.4.14: "a series of DMA requests is triggered each time TXP is set to 1".
let mut rig = Rig::new();
rig.write(Register::Cfg1, cfg1_for(3) | cfg1::TXDMAEN.mask());
rig.write(Register::Cfg2, cfg2::MASTER.mask() | cfg2::SSOE.mask());
assert!(
rig.requests().is_empty(),
"a disabled interface takes no write"
);
rig.set_bits(Register::Cr1, cr1::SPE.mask());
assert_eq!(rig.requests(), vec![(TRANSMIT_REQUEST, Level::High)]);
rig.model().attach(Chip::answering([]));
for byte in 0..15 {
rig.push(byte);
}
assert!(rig.requests().is_empty(), "one place is still a packet");
rig.push(15);
assert_eq!(rig.requests(), vec![(TRANSMIT_REQUEST, Level::Low)]);
rig.start();
assert_eq!(rig.requests(), vec![(TRANSMIT_REQUEST, Level::High)]);
rig.write(Register::Cfg1, cfg1_for(3));
assert_eq!(
rig.requests(),
vec![(TRANSMIT_REQUEST, Level::Low)],
"TXDMAEN is writable while enabled and the request follows it"
);
}
#[test]
fn the_receive_request_follows_rxp_while_rxdmaen_holds() {
let mut rig = Rig::new();
rig.write(Register::Cfg1, cfg1_for(3) | cfg1::RXDMAEN.mask());
rig.write(Register::Cfg2, cfg2::MASTER.mask() | cfg2::SSOE.mask());
rig.set_bits(Register::Cr1, cr1::SPE.mask());
rig.model().attach(Chip::answering([1, 2]));
assert!(rig.requests().is_empty());
rig.start();
rig.push(1);
rig.push(2);
rig.at(FRAME);
assert_eq!(rig.requests(), vec![(RECEIVE_REQUEST, Level::High)]);
rig.at(2 * FRAME);
assert!(rig.requests().is_empty(), "still asked for");
assert_eq!(rig.pop(), 1);
assert!(rig.requests().is_empty(), "a second frame waits");
assert_eq!(rig.pop(), 2);
assert_eq!(rig.requests(), vec![(RECEIVE_REQUEST, Level::Low)]);
}
#[test]
fn the_receive_request_asks_for_the_incomplete_last_packet_at_eot() {
// 50.4.14: "When EOT is set at the end of transaction and last data packet is incomplete then
// DMA request is activated automatically".
let mut rig = Rig::new();
rig.write(Register::Cr2, 3);
rig.write(
Register::Cfg1,
cfg1::FTHVL.set(cfg1_for(3), 1) | cfg1::RXDMAEN.mask(),
);
rig.write(Register::Cfg2, cfg2::MASTER.mask() | cfg2::SSOE.mask());
rig.set_bits(Register::Cr1, cr1::SPE.mask());
rig.model().attach(Chip::answering([1, 2, 3]));
for byte in 0..3 {
rig.push(byte);
}
rig.start();
rig.at(2 * FRAME);
assert_eq!(
rig.requests(),
vec![(RECEIVE_REQUEST, Level::High)],
"a whole packet"
);
rig.pop();
rig.pop();
assert_eq!(rig.requests(), vec![(RECEIVE_REQUEST, Level::Low)]);
rig.at(3 * FRAME);
assert!(
rig.has(sr::EOT) && !rig.has(sr::RXP),
"one frame is not a packet"
);
assert_eq!(
rig.requests(),
vec![(RECEIVE_REQUEST, Level::High)],
"and is asked for all the same"
);
assert_eq!(rig.pop(), 3);
assert_eq!(rig.requests(), vec![(RECEIVE_REQUEST, Level::Low)]);
}
#[test]
fn the_request_outputs_are_the_models_own_numbers_zero_and_one() {
assert_eq!((RECEIVE_REQUEST, TRANSMIT_REQUEST), (0, 1));
}
#[test]
fn chibios_start_leaves_the_transmit_request_standing_for_the_dma() {
let mut rig = Rig::new();
chibios_start(&mut rig, 3);
assert_eq!(rig.requests(), vec![(TRANSMIT_REQUEST, Level::High)]);
assert_eq!(rig.read(Register::Cr1), cr1::MASRX.mask() | cr1::SPE.mask());
assert_eq!(rig.read(Register::Ier), ier::OVRIE.mask());
let configured = rig.read(Register::Cfg1);
assert!(cfg1::TXDMAEN.is_set(configured) && cfg1::RXDMAEN.is_set(configured));
assert_eq!(cfg1::FTHVL.get(configured), 0, "one-frame packets");
assert!(rig.levels().is_empty());
}
#[test]
fn a_dma_shaped_exchange_asks_for_each_frame_as_it_arrives() {
// ChibiOS's spi_lld_exchange: the streams are enabled, CSTART is set, and each frame is a
// transmit beat and a receive beat. The test plays the controller's part from the requests.
let mut rig = Rig::new();
chibios_start(&mut rig, 3);
rig.model().attach(Chip::answering([0xA1, 0xA2, 0xA3]));
rig.requests();
for byte in [0x01, 0x02, 0x03] {
rig.push(byte);
}
rig.start();
let mut received = Vec::new();
for frame in 1..=3 {
rig.at(frame * FRAME);
assert_eq!(
rig.requests().first(),
Some(&(RECEIVE_REQUEST, Level::High)),
"frame {frame}"
);
assert!(rig.has(sr::RXP));
received.push(rig.pop());
assert_eq!(rig.requests(), vec![(RECEIVE_REQUEST, Level::Low)]);
}
assert_eq!(received, vec![0xA1, 0xA2, 0xA3]);
assert_eq!(
rig.chip().seen,
vec![(FRAME, 1, 8), (2 * FRAME, 2, 8), (3 * FRAME, 3, 8)]
);
}
const SELECT: Select = Select {
port: "GPIOE",
pin: Pin::new(4).expect("a port has sixteen pins"),
active: Level::Low,
};
#[test]
fn a_request_from_the_named_pin_selects_the_peer() {
let mut rig = Rig::new().master(3);
rig.model()
.attach_selected(Chip::answering([0x11, 0x22]), SELECT);
rig.start();
rig.push(1);
rig.at(FRAME);
assert!(rig.chip().seen.is_empty(), "nothing is selected");
assert_eq!(rig.pop(), 0, "and nothing drives MISO, so it reads low");
rig.pin("GPIOE", 4, Level::Low);
assert_eq!(rig.chip().selects, 1);
rig.push(2);
rig.at(2 * FRAME);
assert_eq!(rig.chip().seen, vec![(2 * FRAME, 2, 8)]);
assert_eq!(rig.pop(), 0x11);
rig.pin("GPIOE", 4, Level::High);
assert_eq!(rig.chip().deselects, 1);
rig.push(3);
rig.at(3 * FRAME);
assert_eq!(rig.chip().words(), vec![2], "deselected again");
}
#[test]
fn a_request_from_another_pin_or_port_is_ignored() {
let mut rig = Rig::new().master(3);
rig.model().attach_selected(Chip::answering([]), SELECT);
rig.pin("GPIOE", 5, Level::Low);
rig.pin("GPIOD", 4, Level::Low);
rig.pin("SPI2", 4, Level::Low);
assert_eq!(rig.chip().selects, 0);
}
#[test]
fn a_peer_is_told_once_per_edge() {
let mut rig = Rig::new().master(3);
rig.model().attach_selected(Chip::answering([]), SELECT);
rig.pin("GPIOE", 4, Level::High);
assert_eq!(rig.chip().deselects, 0, "it was never selected");
rig.pin("GPIOE", 4, Level::Low);
rig.pin("GPIOE", 4, Level::Low);
assert_eq!(rig.chip().selects, 1);
rig.pin("GPIOE", 4, Level::High);
rig.pin("GPIOE", 4, Level::High);
assert_eq!(rig.chip().deselects, 1);
}
#[test]
fn a_peer_attached_without_a_select_is_always_selected() {
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([0x42]));
rig.start();
rig.push(7);
rig.at(FRAME);
assert_eq!(rig.pop(), 0x42);
assert_eq!(rig.chip().selects, 0, "there is no edge to be told of");
}
#[test]
fn two_peers_selected_at_once_are_contention_and_the_line_reads_low() {
let mut rig = Rig::new().master(3);
rig.model().attach_selected(Chip::answering([0xFF]), SELECT);
rig.model().attach_selected(Chip::answering([0xFF]), SELECT);
rig.pin("GPIOE", 4, Level::Low);
rig.start();
rig.push(5);
rig.at(FRAME);
assert_eq!(rig.pop(), 0, "the fight reads as silence");
assert_eq!(rig.model().contentions(), 1);
let second = rig.model().chip_mut::<Chip>(1).unwrap().words();
assert_eq!(second, vec![5], "both were clocked");
assert_eq!(rig.chip().words(), vec![5]);
}
#[test]
fn a_silent_chip_is_not_on_the_bus_and_answers_again_after() {
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([0x61, 0x62, 0x63]));
rig.start();
assert!(rig.model().silence(0, true));
rig.push(1);
rig.at(FRAME);
assert_eq!(rig.pop(), 0);
assert!(rig.chip().seen.is_empty(), "it was not clocked");
assert!(rig.model().silence(0, false));
rig.push(2);
rig.at(2 * FRAME);
assert_eq!(rig.pop(), 0x61);
assert!(!rig.model().silence(3, true), "no such chip");
}
#[test]
fn a_silence_waits_for_the_chips_deselect() {
let mut rig = Rig::new().master(3);
rig.model()
.attach_selected(Chip::answering([1, 2, 3]), SELECT);
rig.start();
rig.pin("GPIOE", 4, Level::Low);
assert!(rig.model().silence(0, true));
rig.push(1);
rig.at(FRAME);
assert_eq!(rig.chip().words(), vec![1], "a burst is never cut in half");
rig.pin("GPIOE", 4, Level::High);
rig.pin("GPIOE", 4, Level::Low);
rig.push(2);
rig.at(2 * FRAME);
assert_eq!(rig.chip().words(), vec![1], "silent from the next select");
assert!(rig.model().silence(0, false));
rig.pin("GPIOE", 4, Level::High);
rig.pin("GPIOE", 4, Level::Low);
rig.push(3);
rig.at(3 * FRAME);
assert_eq!(rig.chip().words(), vec![1, 3]);
}
#[test]
fn a_trigger_counts_the_transactions_that_end_at_a_deselect() {
let mut rig = Rig::new().master(3);
rig.model().attach_selected(Chip::answering([]), SELECT);
for _ in 0..3 {
rig.pin("GPIOE", 4, Level::Low);
rig.pin("GPIOE", 4, Level::High);
}
assert_eq!(rig.model().place_mut(0).unwrap().ended(), 3);
assert!(rig.model().place_mut(1).is_none());
}
#[test]
fn an_event_a_chip_scheduled_reaches_it_at_its_instant() {
let mut rig = Rig::new().master(3);
rig.model().attach_selected(Chip::answering([]), SELECT);
rig.pin("GPIOE", 4, Level::Low);
rig.at(499);
assert_eq!(rig.chip().ticks, 0);
rig.at(500);
assert_eq!(rig.chip().ticks, 1);
}
#[test]
fn the_chips_move_to_a_fresh_interface_with_only_a_tied_select_standing() {
let mut old = Spi::new(Kind::FULL);
old.attach_selected(Chip::answering([]), SELECT);
let mut fresh = Spi::new(Kind::FULL);
fresh.inherit(&mut old);
assert!(fresh.attached() && !old.attached());
assert!(fresh.peer().is_some());
assert_eq!(fresh.peers().count(), 1);
assert!(!fresh.peers[0].selected, "a pin is back at reset");
let mut tied = Spi::new(Kind::FULL);
tied.attach(Chip::answering([]));
let mut fresh = Spi::new(Kind::FULL);
fresh.inherit(&mut tied);
assert!(fresh.peers[0].selected);
assert!(fresh.peer_mut(0).is_some() && fresh.peer_mut(1).is_none());
}
#[test]
fn a_restart_reaches_every_chip() {
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([]));
rig.drive(|model, _, context| model.chips_restarted(context));
assert_eq!(rig.chip().restarts, 1);
}
#[test]
fn the_stand_in_peer_answers_its_script_then_repeats_the_last_and_records_what_it_got() {
let mut rig = Rig::new().master(3);
rig.model().drive_miso([0x10, 0x20]);
rig.start();
for byte in [1, 2, 3] {
rig.push(byte);
let next = rig.soc.next_event().unwrap().0;
rig.at(next);
}
let answers: Vec<u64> = (0..3).map(|_| rig.pop()).collect();
assert_eq!(answers, vec![0x10, 0x20, 0x20]);
assert_eq!(rig.model().peer().unwrap().received(), &[1, 2, 3]);
}
fn text(bytes: &[u8]) -> impl Iterator<Item = u32> + '_ {
bytes.iter().map(|byte| u32::from(*byte))
}
fn crc8(bytes: &[u8]) -> u64 {
let mut crc = 0_u8;
for byte in bytes {
crc ^= byte;
for _ in 0..8 {
crc = if crc & 0x80 == 0 {
crc << 1
} else {
(crc << 1) ^ 0x07
};
}
}
u64::from(crc)
}
fn crc16(bytes: &[u8], start: u16) -> u64 {
let mut crc = start;
for byte in bytes {
crc ^= u16::from(*byte) << 8;
for _ in 0..8 {
crc = if crc & 0x8000 == 0 {
crc << 1
} else {
(crc << 1) ^ 0x1021
};
}
}
u64::from(crc)
}
fn kermit(bytes: &[u8]) -> u64 {
let mut crc = 0_u16;
for byte in bytes {
crc ^= u16::from(*byte);
for _ in 0..8 {
crc = if crc & 1 == 0 {
crc >> 1
} else {
(crc >> 1) ^ 0x8408
};
}
}
u64::from(crc)
}
const CHECK: &[u8] = b"123456789";
fn send_check_text(rig: &mut Rig, extra_bits: u64) {
let begun = rig.now.0;
let mut bytes = CHECK.iter().copied();
for _ in 0..2 {
while rig.has(sr::TXP) {
let Some(byte) = bytes.next() else { break };
rig.push(u64::from(byte));
}
rig.start();
}
rig.at(begun + 9 * FRAME + extra_bits * BIT);
}
#[test]
fn the_crc_matches_the_check_value_of_crc_8_and_the_frame_follows_the_data() {
// 50.5.3: the CRC is sent after the last data, and "When CRC is applied, the EOT event is
// extended over the CRC frame transaction". The default polynomial 0x107 is CRC-8.
assert_eq!(crc8(CHECK), 0xF4, "the reference itself");
let mut rig = Rig::new();
rig.write(Register::Cr2, 9);
let mut rig = rig.master_with(cfg1_for(3) | cfg1::CRCEN.mask(), 0);
rig.model()
.attach(Chip::answering(text(CHECK).chain([0xF4])));
for byte in CHECK {
rig.push(u64::from(*byte));
}
rig.start();
rig.at(9 * FRAME);
assert!(!rig.has(sr::EOT), "the CRC frame is still to come");
assert!(cr1::CSTART.is_set(rig.read(Register::Cr1)));
rig.at(10 * FRAME - 1);
assert!(!rig.has(sr::EOT));
rig.at(10 * FRAME);
assert!(rig.has(sr::EOT));
assert!(!rig.has(sr::CRCE), "the peer's CRC matched");
let mut expected: Vec<u32> = text(CHECK).collect();
expected.push(0xF4);
assert_eq!(rig.chip().words(), expected);
assert_eq!(rig.read(Register::Txcrc), 0xF4);
assert_eq!(rig.read(Register::Rxcrc), 0xF4);
let kept: Vec<u64> = (0..9).map(|_| rig.pop()).collect();
assert_eq!(kept, text(CHECK).map(u64::from).collect::<Vec<_>>());
assert_eq!(rig.pop(), 0, "the CRC frame isn't left in the RxFIFO");
}
#[test]
fn the_receiver_crc_is_over_what_the_peer_sent_and_a_wrong_one_sets_crce() {
// 50.5.2: "The CRCE flag ... is set if the value received in the shift register does not
// match the receiver SPI_RXCRC value, after the last data is received".
let answers = b"ABCDEFGHI";
let mut rig = Rig::new();
rig.write(Register::Cr2, 9);
let mut rig = rig.master_with(cfg1_for(3) | cfg1::CRCEN.mask(), 0);
rig.model()
.attach(Chip::answering(text(answers).chain([0x00])));
send_check_text(&mut rig, 8);
assert_eq!(rig.read(Register::Txcrc), crc8(CHECK));
assert_eq!(rig.read(Register::Rxcrc), crc8(answers));
assert!(rig.has(sr::CRCE), "0x00 is not CRC-8 of the answers");
assert!(rig.has(sr::EOT), "and the transfer still ends");
rig.write(Register::Ifcr, ifcr::CRCEC.mask());
assert!(!rig.has(sr::CRCE));
}
#[test]
fn a_seventeen_bit_polynomial_gives_crc_16_on_the_sixteen_and_thirty_two_bit_instances_alike() {
// 50.5.3: "write 0x11021 to the SPI_CRCPOLY register for a 32-bit instance, whereas to obtain
// the full size for a 16-bit instance, write 0x1021 with the CRC33_17 bit set".
assert_eq!(crc16(CHECK, 0), 0x31C3, "the reference itself");
let sixteen_bit_crc = cfg1::CRCSIZE.set(cfg1_for(3), 15) | cfg1::CRCEN.mask();
for (instance, polynomial, control) in [
("SPI1", 0x1_1021, 0),
("SPI4", 0x1021, cr1::CRC33_17.mask()),
] {
let mut rig = Rig::of(instance);
rig.write(Register::Cr2, 9);
rig.write(Register::Cr1, control);
rig.write(Register::Crcpoly, polynomial);
let mut rig = rig.master_with(sixteen_bit_crc, 0);
rig.model()
.attach(Chip::answering(text(CHECK).chain([0x31C3])));
send_check_text(&mut rig, 16);
assert_eq!(rig.read(Register::Txcrc), 0x31C3, "{instance}");
assert_eq!(rig.read(Register::Rxcrc), 0x31C3, "{instance}");
assert!(rig.has(sr::EOT) && !rig.has(sr::CRCE), "{instance}");
assert_eq!(
rig.chip().seen[9],
(9 * FRAME + 16 * BIT, 0x31C3, 16),
"{instance}: the CRC frame is CRCSIZE bits long"
);
}
}
#[test]
fn the_ones_pattern_starts_the_crc_at_all_ones() {
// 50.11.1 bits 15:14: TCRCINI and RCRCINI.
assert_eq!(crc16(CHECK, 0xFFFF), 0x29B1, "the reference itself");
let mut rig = Rig::new();
rig.write(Register::Cr2, 9);
rig.write(Register::Cr1, cr1::TCRCI.mask() | cr1::RCRCI.mask());
rig.write(Register::Crcpoly, 0x1_1021);
rig.write(
Register::Cfg1,
cfg1::CRCSIZE.set(cfg1_for(3), 15) | cfg1::CRCEN.mask(),
);
assert_eq!(
rig.read(Register::Txcrc),
0xFFFF,
"50.11.11: initialized when CRCEN is written"
);
rig.write(Register::Cfg2, cfg2::MASTER.mask() | cfg2::SSOE.mask());
rig.set_bits(Register::Cr1, cr1::SPE.mask());
rig.model().attach(Chip::answering(text(CHECK)));
send_check_text(&mut rig, 16);
assert_eq!(rig.read(Register::Txcrc), 0x29B1);
assert_eq!(rig.read(Register::Rxcrc), 0x29B1);
}
#[test]
fn the_crc_is_shifted_in_transmission_order_so_lsbfrst_reflects_the_input() {
// 50.5.3: "The CRC computation is done, bit by bit, on the sampling clock edge". With the
// least significant bit first the register is KERMIT's with its bits reversed.
assert_eq!(kermit(CHECK), 0x2189, "the reference itself");
let mut rig = Rig::new();
rig.write(Register::Cr2, 9);
rig.write(Register::Crcpoly, 0x1_1021);
let mut rig = rig.master_with(
cfg1::CRCSIZE.set(cfg1_for(3), 15) | cfg1::CRCEN.mask(),
cfg2::LSBFRST.mask(),
);
rig.model().attach(Chip::answering([]));
send_check_text(&mut rig, 16);
let reflected = u64::from(u16::try_from(kermit(CHECK)).unwrap().reverse_bits());
assert_eq!(rig.read(Register::Txcrc), reflected);
}
#[test]
fn crcsize_takes_the_most_significant_bits_of_the_crc_and_pads_a_longer_frame_with_zeros() {
// 50.5.3: "how many the most significant bits from CRC calculation registers are
// transacted", and "the bits unused at the calculation are transacted and expected all zero
// at the end of the CRC pattern if its size is set greater than the polynomial length".
for (size, frame, bits) in [(7, 0x31, 8_u8), (15, 0x31C3, 16), (23, 0x31_C300, 24)] {
let mut rig = Rig::new();
rig.write(Register::Cr2, 9);
rig.write(Register::Crcpoly, 0x1_1021);
let mut rig =
rig.master_with(cfg1::CRCSIZE.set(cfg1_for(3), size) | cfg1::CRCEN.mask(), 0);
rig.model().attach(Chip::answering([]));
send_check_text(&mut rig, u64::from(bits));
let sent = rig.chip().seen[9];
assert_eq!((sent.1, sent.2), (frame, bits), "CRCSIZE {size}");
assert_eq!(
rig.read(Register::Txcrc),
0x31C3,
"the register holds all of it"
);
}
}
#[test]
fn a_new_transfer_starts_the_crc_over_and_the_registers_survive_disabling() {
// 50.5.3: "they are not cleared when SPI is disabled".
let mut rig = Rig::new();
rig.write(Register::Cr2, 9);
let mut rig = rig.master_with(cfg1_for(3) | cfg1::CRCEN.mask(), 0);
rig.model().attach(Chip::answering([]));
send_check_text(&mut rig, 8);
assert_eq!(rig.read(Register::Txcrc), 0xF4);
rig.clear_bits(Register::Cr1, cr1::SPE.mask());
assert_eq!(rig.read(Register::Txcrc), 0xF4, "disabling clears nothing");
rig.set_bits(Register::Cr1, cr1::SPE.mask());
rig.write(Register::Ifcr, EVERY_BIT);
assert_eq!(rig.read(Register::Txcrc), 0, "enabling starts it over");
let start = rig.now.0;
for byte in CHECK {
rig.push(u64::from(*byte));
}
rig.start();
rig.at(start + 10 * FRAME);
assert_eq!(
rig.read(Register::Txcrc),
0xF4,
"and the second run agrees with the first"
);
}
#[test]
fn a_receiver_checks_the_peers_crc_frame_and_leaves_its_own_crc_at_the_pattern() {
// 50.11.11: in a receive-only mode "a meaningless non-zero value may appear in this register
// during communication when the TCRCINI bit is set".
let mut rig = Rig::new();
rig.write(Register::Cr2, 9);
rig.write(Register::Cr1, cr1::TCRCI.mask());
let mut rig = rig.master_with(cfg1_for(3) | cfg1::CRCEN.mask(), cfg2::COMM.set(0, 2));
rig.model()
.attach(Chip::answering(text(CHECK).chain([0xF4])));
rig.start();
rig.at(10 * FRAME);
assert!(rig.has(sr::EOT) && !rig.has(sr::CRCE));
assert_eq!(rig.read(Register::Rxcrc), 0xF4);
assert_eq!(
rig.read(Register::Txcrc),
0xFF,
"nothing was sent to accumulate"
);
assert_eq!(rig.chip().words(), vec![0; 10]);
}
#[test]
fn a_polynomial_of_degree_zero_gives_a_crc_of_zero_width() {
let polynomial = Polynomial::of(0, None);
assert_eq!(polynomial.feed(0, 0xFF, 8, false), 0);
assert_eq!(polynomial.frame(0, 8), 0);
let full = Polynomial::of(0x1021, Some(16));
assert_eq!((full.width, full.taps), (16, 0x1021));
let eight = Polynomial::of(0x107, None);
assert_eq!((eight.width, eight.taps, eight.mask()), (8, 0x07, 0xFF));
}
#[test]
fn a_start_within_one_sck_cycle_of_eot_is_told_under_item_2_22_2() {
// ES0392 rev 15 item 2.22.2, present on revision V: "SPI master data transfer can stall upon
// setting the CSTART bit within one SCK cycle after the EOT event".
let run = |delay: u64| {
let mut rig = Rig::new();
rig.write(Register::Cr2, 1);
let mut rig = rig.master_with(cfg1_for(3), 0);
rig.model().attach(Chip::answering([]));
rig.push(1);
rig.start();
rig.at(FRAME + delay);
assert!(rig.has(sr::EOT));
rig.told();
rig.start();
rig.told()
};
let told = run(BIT - 1);
assert_eq!(told.len(), 1);
assert_eq!((told[0].register, told[0].reason), ("CR1", STALL_WINDOW));
assert!(
run(BIT).is_empty(),
"a whole SCK cycle later is outside the item's window"
);
assert!(run(5 * BIT).is_empty());
}
#[test]
fn a_disabled_interface_raises_its_txp_and_txc_interrupts_under_item_2_22_4() {
// ES0392 rev 15 item 2.22.4, present on revision V: with SPE clear, "TXP and TXC flags
// become set, triggering interrupt if enabled with TXPIE or EOTIE bit, respectively".
let mut rig = Rig::new();
assert!(rig.has(sr::TXP) && rig.has(sr::TXC));
rig.write(Register::Ier, ier::TXPIE.mask());
assert_eq!(rig.levels(), vec![Level::High], "TXPIE");
let mut rig = Rig::new();
rig.write(Register::Ier, ier::EOTIE.mask());
assert_eq!(rig.levels(), vec![Level::High], "EOTIE with TSIZE at zero");
let mut rig = Rig::new();
rig.write(Register::Cr2, 1);
rig.write(Register::Ier, ier::EOTIE.mask());
assert!(
rig.levels().is_empty(),
"TXC follows EOT when TSIZE is above zero"
);
let mut rig = Rig::new();
rig.write(Register::Ier, ier::OVRIE.mask());
assert!(
rig.levels().is_empty(),
"the workaround: keep both disabled while SPE is clear"
);
}
#[test]
fn a_sized_transfer_with_a_crc_frame_under_eight_bits_and_no_crc_is_told_under_item_2_22_5() {
// ES0392 rev 15 item 2.22.5, present on revision V: with CRCEN clear, TSIZE above zero and
// CRCSIZE under 00111 "the last data received in the RxFIFO may be corrupted".
let started = |tsize: u64, crcsize: u64, crc_on: bool| {
let mut rig = Rig::new();
rig.write(Register::Cr2, tsize);
let mut configured = cfg1::CRCSIZE.set(cfg1_for(3), crcsize);
if crc_on {
configured |= cfg1::CRCEN.mask();
}
let mut rig = rig.master_with(configured, 0);
rig.told();
rig.start();
rig.told()
};
let told = started(4, 3, false);
assert_eq!(told.len(), 1);
assert_eq!(
(told[0].register, told[0].reason),
("CR1", CRCSIZE_CORRUPTION)
);
assert!(
started(4, 7, false).is_empty(),
"the default size is the workaround"
);
assert!(
started(0, 3, false).is_empty(),
"an endless transfer isn't under the item"
);
assert!(started(4, 3, true).is_empty(), "nor is one with the CRC on");
}
#[test]
fn the_instance_table_is_the_vendors_file_and_rm0433() {
// Table 422 for the FIFO and frame sizes, Tables 124, 125 and 128 for the request inputs.
let wired = [
("SPI1", Kind::FULL, "DMAMUX1", 37, 38, KernelClock::Spi123),
("SPI2", Kind::FULL, "DMAMUX1", 39, 40, KernelClock::Spi123),
("SPI3", Kind::FULL, "DMAMUX1", 61, 62, KernelClock::Spi123),
("SPI4", Kind::BASIC, "DMAMUX1", 83, 84, KernelClock::Spi45),
("SPI5", Kind::BASIC, "DMAMUX1", 85, 86, KernelClock::Spi45),
("SPI6", Kind::BASIC, "DMAMUX2", 11, 12, KernelClock::Spi6),
];
assert_eq!(INSTANCES.len(), wired.len());
for (instance, (name, kind, multiplexer, receive, transmit, clock)) in
INSTANCES.iter().zip(wired)
{
assert_eq!(
(
instance.name,
instance.kind,
instance.multiplexer,
instance.receive_input,
instance.transmit_input,
instance.kernel_clock
),
(name, kind, multiplexer, receive, transmit, clock)
);
let described = PERIPHERALS
.iter()
.find(|peripheral| peripheral.name == name)
.expect("the vendor's file has the instance");
assert!(
std::ptr::eq(described.registers, generated::spi1::REGISTERS.as_slice()),
"{name} shares the layout"
);
assert_eq!(
described.interrupts.len(),
1,
"{name}: one interrupt, position zero"
);
}
}
#[test]
fn the_corrected_fields_are_what_the_part_declares_and_the_names_exist_in_the_vendors_file() {
let soc = builder().build().unwrap();
for instance in INSTANCES {
let registers = soc.registers(instance.name).unwrap();
for (register, corrected) in FIELD_CORRECTIONS {
let name = corrected.name;
assert!(
register
.spec()
.fields
.iter()
.any(|field| field.name == name),
"{register:?}.{name} is in the vendor's file"
);
let field = registers
.fields(register.index())
.iter()
.find(|field| field.name == name)
.unwrap();
assert_eq!(field, corrected, "{} {register:?}.{name}", instance.name);
assert_eq!(field.access, Access::ReadWrite);
let expected = if matches!(name, "TXPIE" | "DPXPIE") {
WriteModify::None
} else {
WriteModify::OneToSet
};
assert_eq!(field.write, expected, "{name}");
}
for register in READ_ONLY {
assert_eq!(
registers.write_rule(register.index()),
WriteModify::Modify,
"{} {}",
instance.name,
register.spec().name
);
}
}
}
#[test]
fn the_access_that_carries_one_frame_is_the_one_a_driver_casts_the_data_register_to() {
// ChibiOS's spi_lld_polled_exchange and its DMA setup, by DSIZE.
for (bits, width) in [
(4, Width::W8),
(8, Width::W8),
(9, Width::W16),
(16, Width::W16),
(17, Width::W32),
(32, Width::W32),
] {
assert_eq!(natural_width(bits), width, "{bits} bits");
}
}
#[test]
fn a_reset_returns_the_interface_to_its_reset_state_and_leaves_the_chips_on_the_wires() {
use crate::device::ResetCause;
use crate::mcu::Mcu;
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([]));
rig.model().attach_selected(Chip::answering([1]), SELECT);
rig.pin("GPIOE", 4, Level::Low);
rig.start();
for byte in 0..4 {
rig.push(byte);
}
rig.at(FRAME / 2);
rig.soc.reset(rig.now, ResetCause::Pin);
assert_eq!(
rig.soc.next_event(),
None,
"the frame in flight went with the bus"
);
rig.at(10 * FRAME);
assert_eq!(rig.status(), 0x1002);
assert_eq!(rig.read(Register::Cr1), 0);
assert_eq!(rig.read(Register::Cfg1), 0x0007_0007);
assert!(rig.model().tx.is_empty() && rig.model().rx.is_empty());
assert!(rig.model().attached());
assert!(rig.model().peers[0].selected, "a tied select stays");
assert!(
!rig.model().peers[1].selected,
"and the pin is back at reset"
);
rig.write(Register::Cfg1, cfg1_for(3));
rig.write(Register::Cfg2, cfg2::MASTER.mask() | cfg2::SSOE.mask());
rig.set_bits(Register::Cr1, cr1::SPE.mask());
rig.start();
let begun = rig.now.0;
rig.push(7);
rig.at(begun + FRAME);
assert_eq!(rig.chip().words(), vec![7], "and works again from the top");
}
#[test]
fn the_model_state_round_trips_through_a_snapshot_with_a_frame_in_flight() {
let mut rig = Rig::new().master(3);
rig.model()
.attach_selected(Chip::answering([1, 2, 3]), SELECT);
rig.pin("GPIOE", 4, Level::Low);
rig.write(Register::Ier, ier::OVRIE.mask());
rig.start();
for byte in 1..=5 {
rig.push(byte);
}
rig.at(2 * FRAME + 700);
let bytes = crate::snapshot::stored(rig.model()).expect("the model keeps");
let mut fresh = Spi::with_clock(Kind::FULL, KERNEL_HZ);
fresh.attach_selected(Chip::default(), SELECT);
crate::snapshot::load(&mut fresh, crate::snapshot::keep(bytes)).expect("and loads");
assert_eq!(format!("{fresh:?}"), format!("{:?}", rig.model()));
assert_eq!(fresh.rx.len(), 2);
assert_eq!(fresh.tx.len(), 2);
assert!(fresh.frame.is_some());
}
#[test]
fn two_runs_of_one_script_are_the_same_run() {
// A bounded transfer with a CRC, a second one chained, an overrun, a chip select moving,
// and a clock change, all in virtual time.
fn scripted() -> Rig {
let mut rig = Rig::new();
rig.write(Register::Cr2, 4);
rig.write(Register::Crcpoly, 0x1_1021);
let mut rig = rig.master_with(
cfg1::CRCSIZE.set(cfg1_for(2), 15) | cfg1::CRCEN.mask() | cfg1::RXDMAEN.mask(),
cfg2::MIDI.set(0, 1),
);
rig.write(Register::Ier, ier::EOTIE.mask() | ier::OVRIE.mask());
rig.model().attach_selected(Chip::answering(1..=20), SELECT);
rig.pin("GPIOE", 4, Level::Low);
for byte in [0x10, 0x20, 0x30, 0x40] {
rig.push(byte);
}
rig.start();
rig.at(3_000);
rig.clock(KERNEL_HZ / 4);
rig.at(40_000);
rig.pop();
rig.write(Register::Ifcr, EVERY_BIT);
rig.pin("GPIOE", 4, Level::High);
rig
}
let mut first = scripted();
let mut second = scripted();
assert_eq!(first.told(), second.told());
assert_eq!(first.levels(), second.levels());
assert_eq!(first.requests(), second.requests());
for register in Register::ALL {
assert_eq!(
first.read(register),
second.read(register),
"{register:?} disagrees between two runs of the same script"
);
}
assert_eq!(first.chip().seen, second.chip().seen);
assert_eq!(first.soc.next_event(), second.soc.next_event());
assert_eq!(
format!("{:?}", first.model()),
format!("{:?}", second.model())
);
}
#[test]
fn a_disabled_interface_takes_no_data_starts_nothing_and_asks_nothing() {
// The idle state of 50.11.1 bit 0: nothing moves, nothing is raised, nothing is scheduled.
let mut rig = Rig::new();
rig.write(
Register::Cfg1,
cfg1_for(3) | cfg1::TXDMAEN.mask() | cfg1::RXDMAEN.mask(),
);
rig.write(Register::Cfg2, cfg2::MASTER.mask() | cfg2::SSOE.mask());
rig.model().attach(Chip::answering([1]));
rig.start();
assert!(
!cr1::CSTART.is_set(rig.read(Register::Cr1)),
"a start with SPE clear is dropped"
);
rig.push(1);
rig.push(2);
let told = rig.told();
assert_eq!(told.len(), 1, "told once: {told:?}");
assert_eq!(told[0].reason, TXDR_WHILE_DISABLED);
assert!(rig.model().tx.is_empty());
assert!(rig.requests().is_empty(), "no request while disabled");
assert!(rig.levels().is_empty());
assert_eq!(rig.soc.next_event(), None);
rig.at(10 * FRAME);
assert!(rig.chip().seen.is_empty());
assert_eq!(rig.status(), 0x1002);
}
#[test]
fn a_word_read_of_rxdr_with_eight_bit_frames_pops_four_frames() {
// 50.11.9: "word-wise (32 bit access). In this case 4 data-bytes ... can be read by single
// access."
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([1, 2, 3, 4, 5]));
for byte in 0..5 {
rig.push(byte);
}
rig.start();
rig.at(5 * FRAME);
assert_eq!(rig.read_as(Register::Rxdr, Width::W32), 0x0403_0201);
assert_eq!(rig.read_as(Register::Rxdr, Width::W16), 0x0005);
assert_eq!(
rig.read_as(Register::Rxdr, Width::W32),
0,
"nothing is left"
);
}
#[test]
fn a_word_write_to_txdr_with_eight_bit_frames_queues_four_frames() {
// 50.11.8: "word-wise (32 bit access). In this case 4 data-bytes ... can be written by
// single access."
let mut rig = Rig::new().master(3);
rig.model().attach(Chip::answering([]));
rig.write_as(Register::Txdr, Width::W32, 0x0403_0201);
rig.write_as(Register::Txdr, Width::W16, 0x0605);
rig.start();
rig.at(10 * FRAME);
assert_eq!(rig.chip().words(), vec![1, 2, 3, 4, 5, 6]);
}
#[test]
fn a_dma_packed_transfer_with_word_beats_reaches_eot() {
// 50.4.14 "Data packing with DMA": PSIZE of a word with frames of 8 bits packs four to a beat.
// TSIZE of eight and packets of four are two beats each way.
let mut rig = Rig::new();
rig.write(Register::Cr2, 8);
let mut rig = rig.master_with(
cfg1::FTHVL.set(cfg1_for(3), 3) | cfg1::TXDMAEN.mask() | cfg1::RXDMAEN.mask(),
0,
);
rig.model().attach(Chip::answering(1..=8));
assert_eq!(rig.requests(), vec![(TRANSMIT_REQUEST, Level::High)]);
rig.write_as(Register::Txdr, Width::W32, 0x0403_0201);
rig.write_as(Register::Txdr, Width::W32, 0x0807_0605);
assert!(rig.has(sr::TXTF), "all eight frames are in");
rig.start();
let mut read = Vec::new();
rig.at(4 * FRAME);
assert!(rig.has(sr::RXP), "a whole packet of four");
read.push(rig.read_as(Register::Rxdr, Width::W32));
rig.at(8 * FRAME);
assert!(rig.has(sr::EOT), "EOT at the eighth frame");
read.push(rig.read_as(Register::Rxdr, Width::W32));
assert_eq!(read, vec![0x0403_0201, 0x0807_0605]);
assert_eq!(rig.chip().words(), (1..=8).collect::<Vec<u32>>());
assert!(!rig.has(sr::RXP));
}
#[test]
fn disabling_after_eot_is_the_workaround_of_item_2_22_2() {
// ES0392 rev 15 item 2.22.2: "Disable then enable SPI after each EOT event."
let mut rig = Rig::new();
rig.write(Register::Cr2, 1);
let mut rig = rig.master_with(cfg1_for(3), 0);
rig.model().attach(Chip::answering([]));
rig.push(1);
rig.start();
rig.at(FRAME + 1);
rig.clear_bits(Register::Cr1, cr1::SPE.mask());
rig.set_bits(Register::Cr1, cr1::SPE.mask());
rig.write(Register::Ifcr, EVERY_BIT);
rig.told();
rig.push(2);
rig.start();
assert!(rig.told().is_empty());
}
}