// Copyright (c) 2026 Rigless Labs.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! The DMA request multiplexers of RM0433 rev 9 chapter 17, `DMAMUX1` and `DMAMUX2`, written against
//! the `dmamux1` and `dmamux2` layouts.
//!
//! The two multiplexers are one design built twice, section 17.3.1: `DMAMUX1` has sixteen channels, one
//! for each stream of `DMA1` and `DMA2`, and `DMAMUX2` has eight, one for each channel of the BDMA. The
//! layouts differ in the width of the id fields and the number of channels, and not in what the
//! registers do, so one model, [`Multiplexer`], serves both and takes the layout as its parameter.
//! [`Dmamux1`] and [`Dmamux2`] are the two. Every citation is RM0433 rev 9 unless it names another
//! document.
//!
//! # How a request travels
//!
//! Nothing here is the runtime's, so this is the convention the three kinds of model agree on. The part
//! names the multiplexer a reader of requests with `reads_requests`, and names each controller one too.
//!
//! - **A peripheral drives a request output.** It publishes the numbers of its outputs
//! (`usart::RECEIVE_REQUEST`, `spi::`, `i2c::`, the timer's `UPDATE_REQUEST` and its siblings), and the
//! multiplexer finds the input an output is wired to in its table of `Wire` rows, `DMAMUX1_REQUESTS`
//! for the first and `DMAMUX2_REQUESTS` for the second, which are Table 124 and Table 128 read the
//! other way round. A level stands until the access that serves it.
//! - **The multiplexer drives one output a channel.** Its output `n` is the level of channel `n`'s
//! request, high from the instant a selected request stands to the instant it is served or withdrawn.
//! A controller listens to the channels section 17.3.2 connects it to: `DMA1` stream `n` to channel
//! `n`, `DMA2` stream `n` to channel `8 + n`, BDMA channel `n` to `DMAMUX2` channel `n`.
//! - **A controller acknowledges.** Section 15.3.4 has it acknowledge a request as soon as it reaches
//! the peripheral, and a request is served when it deasserts, section 17.4.4. The controller's own
//! request output `n` is the acknowledge of its stream or channel `n`, a pulse, high and low at the
//! instant of the access. The multiplexer counts those, which is the only way it can count a request
//! it generated itself.
//! - **The multiplexer acknowledges the peripheral.** Sections 17.1 and 17.4.4 have it route the
//! controller's acknowledge to the peripheral, and a transfer that doesn't touch the peripheral's own
//! registers is the case that needs it. Output `2 * channels + id` is a pulse, high and low, at the
//! instant a request is served on a channel selecting input `id`. A peripheral hears it at
//! `Device::on_request` and releases that request: the timers do, through `Tim::acknowledge`.
//! - **The multiplexer drives its events.** Output `channels + n` is the event of channel `n`, a pulse,
//! section 17.4.4. Where Table 126, 127, 129 or 130 names `dmamux_evtx` as an input, the wire is the
//! multiplexer's own output.
//! - **Synchronization and trigger inputs** arrive the same way. The only wired ones are the
//! multiplexer's own events. The sources outside it are listed in the left-out table, since no model
//! drives them.
//!
//! **What the model handles and what falls to the register file.** Every register of the chapter has an
//! arm in [`Device::write`], and the model overrides no read, so the register file answers every read from
//! the values the model keeps in it. The channel and generator configuration registers are the file's,
//! and the model reads them at the moments they matter. `DMAMUX_CSR` and `DMAMUX_RGSR` are the model's
//! to set and the file's to hold. `DMAMUX_CFR` and `DMAMUX_RGCFR` take their write in the model, which
//! clears the flags and the register.
//!
//! | Register or section | Behavior | Source | Test |
//! |---|---|---|---|
//! | Layouts | `CxCR` for each channel, then `CSR` and `CFR`, then `RGxCR` for the eight generators, then `RGSR` and `RGCFR`, in both multiplexers, the first with sixteen channels and the second with eight | 17.6.1 to 17.6.12 | `both_layouts_run_channels_then_status_then_generators_then_status` |
//! | Flags | `SOFx` and `CSOFx` at bit `x` of `CSR` and `CFR`, `OFx` and `COFx` at bit `x` of `RGSR` and `RGCFR` | 17.6.3 to 17.6.6, 17.6.9 to 17.6.12 | `every_flag_sits_at_its_channels_bit` |
//! | `DMAREQ_ID` | The channel's output follows the level of the input it selects. Zero selects none, and an id nothing is wired to never goes high | 17.4.4, 17.6.1, 17.6.2 | `a_selected_request_reaches_its_channel_and_follows_the_level`, `a_request_nothing_selects_reaches_no_channel`, `changing_the_selected_request_moves_the_output` |
//! | One request, two channels | Two channels may select one id, and both follow it. The manual's caution is for a firmware to keep the two streams from running at once | 17.4.4 | `two_channels_selecting_one_request_both_follow_it` |
//! | `DMAREQ_ID` 1 to 8 | The input is a request generator's output, `dmamux_req_gen0` to `dmamux_req_gen7` | 17.3.2, 17.3.3, Tables 124 and 128 | `a_trigger_edge_starts_gnbreq_plus_one_generated_requests` |
//! | The request table | Every row of `DMAMUX1_REQUESTS` and `DMAMUX2_REQUESTS` names the resource Table 124 or 128 prints at its id, each id once | 17.3.2, 17.3.3, Tables 124 and 128 | `every_request_wire_names_the_resource_the_manual_prints`, `the_published_tables_hold_each_id_once_and_name_instances_the_part_has` |
//! | `SE`, `SPOL`, `SYNC_ID` | With `SE` set, a pending request is connected to the output on the edge `SPOL` names of the input `SYNC_ID` selects. `SPOL` 01 is rising, 10 falling, 11 either, 00 none | 17.4.4, 17.6.1, 17.6.2 | `synchronization_connects_a_pending_request_on_the_selected_edge`, `spol_selects_the_edge` |
//! | A synchronization with nothing pending | An event with no request standing is discarded, and the next request is not connected until another event | 17.4.4 | `a_synchronization_event_with_no_request_pending_is_discarded` |
//! | `NBREQ` | After a synchronization event `NBREQ` plus one requests are forwarded, each counted as it is served, and then the input is disconnected | 17.4.4, 17.6.1 | `nbreq_plus_one_requests_pass_after_a_synchronization_event` |
//! | `EGE` | An event pulse when the counter reloads, which is after `NBREQ` plus one served requests, and after each served request when `NBREQ` is zero | 17.4.4, 17.6.1 | `event_generation_pulses_when_the_counter_reloads`, `an_event_follows_each_served_request_when_nbreq_is_zero` |
//! | Events as inputs | The events of channels 0 to 2 of `DMAMUX1` and 0 to 5 or 6 of `DMAMUX2` are inputs 0 to 2 and 0 to 5 or 6 of the synchronizations and the triggers | 17.3.2, 17.3.3, Tables 126, 127, 129, 130 | `a_channels_event_is_the_synchronization_input_of_its_number` |
//! | Synchronization and trigger tables | Every row names the resource the manual prints at its input. A source with no model to drive it is not wired and is listed below | Tables 126, 127, 129, 130 | `every_synchronization_and_trigger_wire_names_the_resource_the_manual_prints` |
//! | `SOF`, `SOIE`, `CSOF` | A synchronization event while the counter is running sets `SOFx`. It raises the interrupt while `SOIE` is set and the flag stands. `CSOFx` clears it | 17.4.4, 17.5, 17.6.3, 17.6.5 | `a_second_synchronization_event_before_the_underrun_sets_sof_and_the_interrupt`, `csof_clears_the_flag_and_lowers_the_interrupt` |
//! | Request generator | A trigger edge, the one `GPOL` names of the input `SIG_ID` selects, starts `GNBREQ` plus one requests on a generator enabled with `GE`. Each is served when the controller acknowledges it, and the generator stops after the last until the next trigger | 17.4.5, 17.6.7, 17.6.8 | `a_trigger_edge_starts_gnbreq_plus_one_generated_requests`, `gpol_selects_the_trigger_edge` |
//! | `OF`, `OIE`, `COF` | A trigger before the counter has run out, on an enabled generator, sets `OFx`. It raises the interrupt while `OIE` is set and the flag stands. `COFx` clears it | 17.4.5, 17.5, 17.6.9, 17.6.11 | `a_trigger_before_the_generator_has_finished_is_an_overrun`, `cof_clears_the_flag_and_the_interrupt_follows_oie` |
//! | `GE` cleared | Stops a generator mid-run and withdraws its request | 17.4.5, 17.6.7 | `clearing_ge_stops_the_generator` |
//! | The interrupt | One line for every flag, position zero of the instance's table: 102 on `DMAMUX1` and 128 on `DMAMUX2`. It stands while any flag stands with its enable set | 17.5, Table 132, Table 147 | `the_overrun_interrupt_stands_while_a_flag_and_its_enable_do` |
//! | The acknowledge to the peripheral | A request served on a channel is a pulse on output `2 * channels + id`, which the peripheral hears and releases its request for: `Tim::acknowledge` for a timer whose transfer never touches it | 17.1, 17.4.4 | `the_multiplexer_acknowledges_the_selected_peripheral_request`, `a_stream_paced_by_a_timer_whose_ports_do_not_touch_it_completes_its_block` |
//! | Read-only and write-only registers | `CSR` and `RGSR` take no write. `CFR` and `RGCFR` read zero | 17.6.3 to 17.6.12, ES0392 rev 15 2.5.5 | `the_status_registers_take_no_write_and_the_clear_registers_read_zero` |
//! | The second multiplexer | Eight channels and five-bit ids, `DMAMUX2` drives the BDMA | 17.3.1, 17.3.3, Table 123 | `the_second_multiplexer_routes_five_bit_ids_through_eight_channels` |
//! | Reset | A system reset drops every level, connection and counter. `DMA1RST` or `DMA2RST` resets `DMAMUX1`, and `BDMARST` `DMAMUX2`, the same way, with each channel the controller heard rise lowered at once | 8.4.3, 8.7.29, 8.7.31, 17.6 | `a_reset_forgets_levels_connections_and_counters`, `a_reset_line_lowers_each_channel_at_once` |
//! | Determinism and the snapshot | Two runs of one script are one run, and the state serializes mid-synchronization | none | `two_runs_of_one_script_are_the_same_run`, `the_model_state_round_trips_through_a_snapshot` |
//! | ES0392 rev 15 2.5.4 | Applicable to revision V, marked `A`. A write to `CxCR` that sets `SE` while `SPOL` was and stays nonzero and changes `DMAREQ_ID` and `SYNC_ID` can route the request selected before the write. The model tells the session of the write, which is the condition the workaround avoids | ES0392 rev 15 | `a_write_that_sets_se_and_changes_both_ids_without_writing_spol_is_told` |
//! | ES0392 rev 15 2.5.1, 2.5.2, 2.5.3 | Applicable to revision V, marked `N`, with no workaround. Each is two events inside one AHB clock cycle. See the table below | ES0392 rev 15 | none |
//!
//! **What the model leaves out, and why.**
//!
//! | Behavior | Source | Reason |
//! |---|---|---|
//! | The two-cycle stability filter on an edge, and the three cycles of masking after a write to `CxCR` or `RGxCR` | 17.4.4, 17.4.5 | Both are counted in AHB clock cycles of the multiplexer, and a level here holds for an instant and not a number of cycles. Every edge a source drives is taken as stable, and a write masks nothing |
//! | A one-cycle event pulse against that filter | 17.4.4 | A one-cycle event is shorter than the two cycles an edge has to hold, and the manual still has events drive the synchronization and trigger inputs of Tables 126, 127, 129 and 130. The model delivers a pulse as a rising edge and then a falling one, so a polarity of either edge sees two events from one |
//! | `SOF` and `OF` set when a flag is cleared at the same instant | ES0392 rev 15 2.5.1, 2.5.3 | The overrun of another channel during the write that clears a flag is lost. The condition is two events in one AHB clock cycle, and the model has no cycle to put them in |
//! | A trigger event on the critical instant of the last generated request | ES0392 rev 15 2.5.2 | The flag is lost and the next trigger issues one request, for `GNBREQ` above one. The critical instant is "the clock cycle at the very end of the trigger overrun condition", a cycle the model doesn't have |
//! | The wrong request routed by a `CxCR` write that coincides with a synchronization event | ES0392 rev 15 2.5.4 | The coincidence is a synchronization event "exactly two AHB clock cycles before" the write. The model tells the write that makes the condition possible, which is the workaround's whole point |
//! | `NBREQ` and `GNBREQ` write protection | 17.4.4, 17.6.1, 17.4.5 | The manual says `GNBREQ` has "no hardware write protection" and says `NBREQ` "can be written by software only when both" `SE` and `EGE` are low without saying whether anything stops it. A write to `CxCR` restarts the channel's count here, so a value written mid-run takes effect cleanly |
//! | Requests the part does not drive | Tables 124 and 128 | `HR_REQ(1)` to `HR_REQ(6)`, ids 95 to 100, which the manual does not map to a timer unit of the high-resolution timer, and `cryp_in_dma`, `cryp_out_dma` and `hash_in_dma`, ids 76 to 78, which this part's file has no instance for. `SDMMC1` and `SDMMC2` have no request in either table, because each moves its data with its own internal DMA, whose registers section 55.10 gives |
//! | Synchronization and trigger sources nothing drives | Tables 126, 127, 129, 130 | `lptim1_out`, `lptim2_out` and `lptim3_out`, `extit0`, `TIM12_TRGO` (the timer drives its requests 0 to 6 and no trigger output), the wakeups `lpuart_rx_wkup`, `lpuart_tx_wkup`, `lptim2_wkup`, `lptim3_wkup`, `i2c4_wkup` and `spi6_wkup`, the alert and interrupt lines `lptim4_ait`, `lptim5_ait`, `i2c4_event_it`, `spi6_it`, `lpuart1_it_t`, `lpuart1_it_r`, `adc3_it`, `adc3_awd1`, `bdma_ch0_it` and `bdma_ch1_it`, the comparator outputs `comp1_out` and `comp2_out`, `rtc_wkup`, and the `syscfg_exti0_mux` and `syscfg_exti2_mux` lines. A part adds a row to the tables when a source drives one |
//! | A write to a register when the instance is not clocked | 8.7 | The part gates the instance through the bus, which is `memory_map.rs` |
//!
//! **Manual ambiguities, and what the model took.** Section 17.6.3 says `SOFx` is set when a
//! synchronization event occurs "while the DMA request counter value is lower than NBREQ", where section
//! 17.4.4 and section 17.6.4 say "before the request counter underrun". For `NBREQ` zero the first reading
//! can never be true, and the counter holds `NBREQ` until a request is served, so the model takes the
//! second: an overrun is a synchronization event that finds the channel connected and counting. Section
//! 17.6.10 says `OFx` is set when a new trigger event occurs, where section 17.6.9 and section 17.4.5
//! add "before the request counter underrun". The model takes the second. Section 17.6.2 and section 17.6.8
//! point `SYNC_ID` and `SIG_ID` of `DMAMUX2` at Table 129 and Table 128, where the synchronization table is
//! Table 130 and the trigger table is Table 129, so the model uses Table 130 for `SYNC_ID`. Table 125 is
//! Table 124 printed again under another caption. Sections 17.6.1 and 17.6.2 give `DMAREQ_ID` seven and
//! five bits, and ES0392 rev 15 2.5.4 calls the fields `DMAREQ_ID[5:0]`, which is neither layout's, and
//! `SYNC_ID[4:0]`, which is `DMAMUX2`'s alone, so the model takes the register pages.
//!
//! **Where this model departs from the vendor's file.** Section 17.6.3 types every bit of `DMAMUX1_CSR`
//! and of `DMAMUX1_RGSR` read-only, and ST's file types both read-write, so the model puts the flags back
//! after a write. Sections 17.6.5 and 17.6.11 type the clear registers write-only and ST's file types
//! `DMAMUX1_CFR`, `DMAMUX2_CFR` and `DMAMUX1_RGCFR` read-write, so the model clears each after a write. It
//! types `DMAMUX2_RGCFR` read-only, which the register file drops, and the model reads the write it is
//! shown. The file leaves `DMAMUX1_RGSR` with the eight flags the generators have, where the page prints
//! sixteen.
//!
//! **Determinism.** The model reads no clock and draws nothing. `two_runs_of_one_script_are_the_same_run`
//! drives two multiplexers through one script and compares every register, the interrupts, the requests
//! they drove, and the whole state of each.
use std::marker::PhantomData;
use crate::device::{Device, DeviceContext, Level, RegisterFile, RequestChange};
use crate::snapshot::{Error, Reader, Store, Writer};
use crate::soc::stm32h743::generated::{dmamux1, dmamux2};
use crate::soc::{FieldSpec, Layout};
const OVERRUN: usize = 0;
/// The multiplexer of the request lines of `DMA1` and `DMA2`.
pub type Dmamux1 = Multiplexer<dmamux1::Register>;
/// The multiplexer of the request lines of the BDMA.
pub type Dmamux2 = Multiplexer<dmamux2::Register>;
mod local {
pub(super) const RECEIVE: usize = 0;
pub(super) const TRANSMIT: usize = 1;
pub(super) const CONVERSION: usize = 0;
pub(super) const DAC_CHANNEL_1: usize = 0;
pub(super) const DAC_CHANNEL_2: usize = 1;
pub(super) const BLOCK_A: usize = 0;
pub(super) const BLOCK_B: usize = 1;
}
/// One input of a multiplexer, and the output of a peripheral it is wired to.
///
/// Tables 124 and 128 are this the other way round: a row names a resource and its id, and the part
/// writes down which instance's output the resource is. The same shape serves the synchronization and
/// the trigger tables, where the id is an input of that table.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Wire {
/// The instance driving it, as the vendor names it.
pub(crate) instance: &'static str,
/// Which of that instance's request outputs, in the instance's own numbering.
pub(crate) request: usize,
/// The id the multiplexer's register selects it by: `DMAREQ_ID`, `SYNC_ID` or `SIG_ID`.
pub(crate) id: usize,
}
const fn wire(instance: &'static str, request: usize, id: usize) -> Wire {
Wire {
instance,
request,
id,
}
}
/// A controller the multiplexer's channels are connected to, section 17.3.2.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct Controller {
/// The instance, as the vendor names it.
pub(crate) instance: &'static str,
/// The first channel of the multiplexer it listens to. Its stream or channel `n` hears channel
/// `first_channel + n`.
pub(crate) first_channel: usize,
}
/// The first of the events a multiplexer drives, after its request outputs: `DMAMUX1` has sixteen channels,
/// so channel `n`'s event is output `16 + n`.
pub const DMAMUX1_FIRST_EVENT: usize = 16;
/// The first of the events `DMAMUX2` drives: eight channels, so channel `n`'s event is output `8 + n`.
pub const DMAMUX2_FIRST_EVENT: usize = 8;
const DMAMUX1_FIRST_ACKNOWLEDGE: usize = 2 * 16;
/// The request of `instance` that `change` acknowledges, if it is the first multiplexer's acknowledge
/// of an input wired to that instance.
pub(crate) fn acknowledged_request(instance: &str, change: &RequestChange) -> Option<usize> {
if change.instance != "DMAMUX1" || change.level != Level::High {
return None;
}
let id = change.request.checked_sub(DMAMUX1_FIRST_ACKNOWLEDGE)?;
DMAMUX1_REQUESTS
.iter()
.find(|wire| wire.instance == instance && wire.id == id)
.map(|wire| wire.request)
}
use super::dma::{DMA1_FIRST_CHANNEL, DMA2_FIRST_CHANNEL};
use super::tim::{CHANNEL_REQUESTS, COMMUTATION_REQUEST, TRIGGER_REQUEST, UPDATE_REQUEST};
use super::{i2c, spi, usart};
use local::{BLOCK_A, BLOCK_B, CONVERSION, DAC_CHANNEL_1, DAC_CHANNEL_2, RECEIVE, TRANSMIT};
/// RM0433 rev 9 Table 124, the inputs of `DMAMUX1` that a peripheral drives, as the part wires them.
///
/// Ids 1 to 8 are the generators and are not here. Id 54 is reserved, ids 76 to 78 are the cryptographic
/// and hash processors, which the part's file has no instance for, ids 95 to 100 are `HR_REQ(1)` to
/// `HR_REQ(6)`, which the manual leaves unmapped, and ids 116 to 127 are reserved.
pub(crate) static DMAMUX1_REQUESTS: [Wire; 97] = [
wire("ADC1", super::adc::DMA_REQUEST, 9),
wire("ADC2", super::adc::DMA_REQUEST, 10),
wire("TIM1", CHANNEL_REQUESTS[0], 11),
wire("TIM1", CHANNEL_REQUESTS[1], 12),
wire("TIM1", CHANNEL_REQUESTS[2], 13),
wire("TIM1", CHANNEL_REQUESTS[3], 14),
wire("TIM1", UPDATE_REQUEST, 15),
wire("TIM1", TRIGGER_REQUEST, 16),
wire("TIM1", COMMUTATION_REQUEST, 17),
wire("TIM2", CHANNEL_REQUESTS[0], 18),
wire("TIM2", CHANNEL_REQUESTS[1], 19),
wire("TIM2", CHANNEL_REQUESTS[2], 20),
wire("TIM2", CHANNEL_REQUESTS[3], 21),
wire("TIM2", UPDATE_REQUEST, 22),
wire("TIM3", CHANNEL_REQUESTS[0], 23),
wire("TIM3", CHANNEL_REQUESTS[1], 24),
wire("TIM3", CHANNEL_REQUESTS[2], 25),
wire("TIM3", CHANNEL_REQUESTS[3], 26),
wire("TIM3", UPDATE_REQUEST, 27),
wire("TIM3", TRIGGER_REQUEST, 28),
wire("TIM4", CHANNEL_REQUESTS[0], 29),
wire("TIM4", CHANNEL_REQUESTS[1], 30),
wire("TIM4", CHANNEL_REQUESTS[2], 31),
wire("TIM4", UPDATE_REQUEST, 32),
wire("I2C1", i2c::RECEIVE_REQUEST, 33),
wire("I2C1", i2c::TRANSMIT_REQUEST, 34),
wire("I2C2", i2c::RECEIVE_REQUEST, 35),
wire("I2C2", i2c::TRANSMIT_REQUEST, 36),
wire("SPI1", spi::RECEIVE_REQUEST, 37),
wire("SPI1", spi::TRANSMIT_REQUEST, 38),
wire("SPI2", spi::RECEIVE_REQUEST, 39),
wire("SPI2", spi::TRANSMIT_REQUEST, 40),
wire("USART1", usart::RECEIVE_REQUEST, 41),
wire("USART1", usart::TRANSMIT_REQUEST, 42),
wire("USART2", usart::RECEIVE_REQUEST, 43),
wire("USART2", usart::TRANSMIT_REQUEST, 44),
wire("USART3", usart::RECEIVE_REQUEST, 45),
wire("USART3", usart::TRANSMIT_REQUEST, 46),
wire("TIM8", CHANNEL_REQUESTS[0], 47),
wire("TIM8", CHANNEL_REQUESTS[1], 48),
wire("TIM8", CHANNEL_REQUESTS[2], 49),
wire("TIM8", CHANNEL_REQUESTS[3], 50),
wire("TIM8", UPDATE_REQUEST, 51),
wire("TIM8", TRIGGER_REQUEST, 52),
wire("TIM8", COMMUTATION_REQUEST, 53),
wire("TIM5", CHANNEL_REQUESTS[0], 55),
wire("TIM5", CHANNEL_REQUESTS[1], 56),
wire("TIM5", CHANNEL_REQUESTS[2], 57),
wire("TIM5", CHANNEL_REQUESTS[3], 58),
wire("TIM5", UPDATE_REQUEST, 59),
wire("TIM5", TRIGGER_REQUEST, 60),
wire("SPI3", spi::RECEIVE_REQUEST, 61),
wire("SPI3", spi::TRANSMIT_REQUEST, 62),
wire("UART4", usart::RECEIVE_REQUEST, 63),
wire("UART4", usart::TRANSMIT_REQUEST, 64),
wire("UART5", usart::RECEIVE_REQUEST, 65),
wire("UART5", usart::TRANSMIT_REQUEST, 66),
wire("DAC", DAC_CHANNEL_1, 67),
wire("DAC", DAC_CHANNEL_2, 68),
wire("TIM6", UPDATE_REQUEST, 69),
wire("TIM7", UPDATE_REQUEST, 70),
wire("USART6", usart::RECEIVE_REQUEST, 71),
wire("USART6", usart::TRANSMIT_REQUEST, 72),
wire("I2C3", i2c::RECEIVE_REQUEST, 73),
wire("I2C3", i2c::TRANSMIT_REQUEST, 74),
wire("DCMI", CONVERSION, 75),
wire("UART7", usart::RECEIVE_REQUEST, 79),
wire("UART7", usart::TRANSMIT_REQUEST, 80),
wire("UART8", usart::RECEIVE_REQUEST, 81),
wire("UART8", usart::TRANSMIT_REQUEST, 82),
wire("SPI4", spi::RECEIVE_REQUEST, 83),
wire("SPI4", spi::TRANSMIT_REQUEST, 84),
wire("SPI5", spi::RECEIVE_REQUEST, 85),
wire("SPI5", spi::TRANSMIT_REQUEST, 86),
wire("SAI1", BLOCK_A, 87),
wire("SAI1", BLOCK_B, 88),
wire("SAI2", BLOCK_A, 89),
wire("SAI2", BLOCK_B, 90),
wire("SWPMI", RECEIVE, 91),
wire("SWPMI", TRANSMIT, 92),
wire("SPDIFRX", BLOCK_A, 93),
wire("SPDIFRX", BLOCK_B, 94),
wire("DFSDM", 0, 101),
wire("DFSDM", 1, 102),
wire("DFSDM", 2, 103),
wire("DFSDM", 3, 104),
wire("TIM15", CHANNEL_REQUESTS[0], 105),
wire("TIM15", UPDATE_REQUEST, 106),
wire("TIM15", TRIGGER_REQUEST, 107),
wire("TIM15", COMMUTATION_REQUEST, 108),
wire("TIM16", CHANNEL_REQUESTS[0], 109),
wire("TIM16", UPDATE_REQUEST, 110),
wire("TIM17", CHANNEL_REQUESTS[0], 111),
wire("TIM17", UPDATE_REQUEST, 112),
wire("SAI3", BLOCK_A, 113),
wire("SAI3", BLOCK_B, 114),
wire("ADC3", super::adc::DMA_REQUEST, 115),
];
/// RM0433 rev 9 Table 128, the inputs of `DMAMUX2` that a peripheral drives, as the part wires them.
/// Ids 1 to 8 are the generators and ids 18 to 31 are reserved.
pub(crate) static DMAMUX2_REQUESTS: [Wire; 9] = [
wire("LPUART1", usart::RECEIVE_REQUEST, 9),
wire("LPUART1", usart::TRANSMIT_REQUEST, 10),
wire("SPI6", spi::RECEIVE_REQUEST, 11),
wire("SPI6", spi::TRANSMIT_REQUEST, 12),
wire("I2C4", i2c::RECEIVE_REQUEST, 13),
wire("I2C4", i2c::TRANSMIT_REQUEST, 14),
wire("SAI4", BLOCK_A, 15),
wire("SAI4", BLOCK_B, 16),
wire("ADC3", super::adc::DMA_REQUEST, 17),
];
const DMAMUX1_SIGNALS: [Wire; 3] = [
wire("DMAMUX1", DMAMUX1_FIRST_EVENT, 0),
wire("DMAMUX1", DMAMUX1_FIRST_EVENT + 1, 1),
wire("DMAMUX1", DMAMUX1_FIRST_EVENT + 2, 2),
];
/// The trigger inputs of `DMAMUX1`, Table 126.
pub(crate) static DMAMUX1_TRIGGERS: [Wire; 3] = DMAMUX1_SIGNALS;
/// The synchronization inputs of `DMAMUX1`, Table 127.
pub(crate) static DMAMUX1_SYNCS: [Wire; 3] = DMAMUX1_SIGNALS;
/// The trigger inputs of `DMAMUX2` that something drives, Table 129: its events 0 to 6.
pub(crate) static DMAMUX2_TRIGGERS: [Wire; 7] = [
wire("DMAMUX2", DMAMUX2_FIRST_EVENT, 0),
wire("DMAMUX2", DMAMUX2_FIRST_EVENT + 1, 1),
wire("DMAMUX2", DMAMUX2_FIRST_EVENT + 2, 2),
wire("DMAMUX2", DMAMUX2_FIRST_EVENT + 3, 3),
wire("DMAMUX2", DMAMUX2_FIRST_EVENT + 4, 4),
wire("DMAMUX2", DMAMUX2_FIRST_EVENT + 5, 5),
wire("DMAMUX2", DMAMUX2_FIRST_EVENT + 6, 6),
];
/// The synchronization inputs of `DMAMUX2` that something drives, Table 130: its events 0 to 5.
pub(crate) static DMAMUX2_SYNCS: [Wire; 6] = [
wire("DMAMUX2", DMAMUX2_FIRST_EVENT, 0),
wire("DMAMUX2", DMAMUX2_FIRST_EVENT + 1, 1),
wire("DMAMUX2", DMAMUX2_FIRST_EVENT + 2, 2),
wire("DMAMUX2", DMAMUX2_FIRST_EVENT + 3, 3),
wire("DMAMUX2", DMAMUX2_FIRST_EVENT + 4, 4),
wire("DMAMUX2", DMAMUX2_FIRST_EVENT + 5, 5),
];
/// The controllers `DMAMUX1` serves, section 17.3.2.
pub(crate) static DMAMUX1_CONTROLLERS: [Controller; 2] = [
Controller {
instance: "DMA1",
first_channel: DMA1_FIRST_CHANNEL,
},
Controller {
instance: "DMA2",
first_channel: DMA2_FIRST_CHANNEL,
},
];
/// The controller `DMAMUX2` serves, section 17.3.3.
pub(crate) static DMAMUX2_CONTROLLERS: [Controller; 1] = [Controller {
instance: "BDMA",
first_channel: 0,
}];
const FIRST_GENERATOR_ID: usize = 1;
#[derive(Debug)]
struct Fields {
request: &'static FieldSpec,
overrun_interrupt: &'static FieldSpec,
events: &'static FieldSpec,
synchronize: &'static FieldSpec,
polarity: &'static FieldSpec,
forward: &'static FieldSpec,
sync: &'static FieldSpec,
signal: &'static FieldSpec,
generator_overrun_interrupt: &'static FieldSpec,
generate: &'static FieldSpec,
generator_polarity: &'static FieldSpec,
generated: &'static FieldSpec,
}
#[derive(Debug)]
struct Shape {
channels: usize,
request_ids: usize,
sync_ids: usize,
trigger_ids: usize,
fields: Fields,
requests: &'static [Wire],
syncs: &'static [Wire],
triggers: &'static [Wire],
controllers: &'static [Controller],
}
const GENERATORS: usize = 8;
static DMAMUX1_SHAPE: Shape = Shape {
channels: 16,
request_ids: 128,
sync_ids: 8,
trigger_ids: 8,
fields: Fields {
request: dmamux1::dmamux_c0cr::DMAREQ_ID,
overrun_interrupt: dmamux1::dmamux_c0cr::SOIE,
events: dmamux1::dmamux_c0cr::EGE,
synchronize: dmamux1::dmamux_c0cr::SE,
polarity: dmamux1::dmamux_c0cr::SPOL,
forward: dmamux1::dmamux_c0cr::NBREQ,
sync: dmamux1::dmamux_c0cr::SYNC_ID,
signal: dmamux1::dmamux_rg0cr::SIG_ID,
generator_overrun_interrupt: dmamux1::dmamux_rg0cr::OIE,
generate: dmamux1::dmamux_rg0cr::GE,
generator_polarity: dmamux1::dmamux_rg0cr::GPOL,
generated: dmamux1::dmamux_rg0cr::GNBREQ,
},
requests: &DMAMUX1_REQUESTS,
syncs: &DMAMUX1_SYNCS,
triggers: &DMAMUX1_TRIGGERS,
controllers: &DMAMUX1_CONTROLLERS,
};
static DMAMUX2_SHAPE: Shape = Shape {
channels: 8,
request_ids: 32,
sync_ids: 32,
trigger_ids: 32,
fields: Fields {
request: dmamux2::c0cr::DMAREQ_ID,
overrun_interrupt: dmamux2::c0cr::SOIE,
events: dmamux2::c0cr::EGE,
synchronize: dmamux2::c0cr::SE,
polarity: dmamux2::c0cr::SPOL,
forward: dmamux2::c0cr::NBREQ,
sync: dmamux2::c0cr::SYNC_ID,
signal: dmamux2::rg0cr::SIG_ID,
generator_overrun_interrupt: dmamux2::rg0cr::OIE,
generate: dmamux2::rg0cr::GE,
generator_polarity: dmamux2::rg0cr::GPOL,
generated: dmamux2::rg0cr::GNBREQ,
},
requests: &DMAMUX2_REQUESTS,
syncs: &DMAMUX2_SYNCS,
triggers: &DMAMUX2_TRIGGERS,
controllers: &DMAMUX2_CONTROLLERS,
};
#[derive(Debug, Clone, Copy)]
struct Channel {
request: usize,
synchronize: bool,
polarity: u64,
forward: u64,
sync: usize,
events: bool,
overrun_interrupt: bool,
}
#[derive(Debug, Clone, Copy)]
struct Generator {
signal: usize,
overrun_interrupt: bool,
enabled: bool,
polarity: u64,
generated: u64,
}
fn small(value: u64) -> usize {
usize::try_from(value).expect("a field of at most seven bits")
}
impl Fields {
fn channel(&self, value: u64) -> Channel {
Channel {
request: small(self.request.get(value)),
synchronize: self.synchronize.is_set(value),
polarity: self.polarity.get(value),
forward: self.forward.get(value),
sync: small(self.sync.get(value)),
events: self.events.is_set(value),
overrun_interrupt: self.overrun_interrupt.is_set(value),
}
}
fn generator(&self, value: u64) -> Generator {
Generator {
signal: small(self.signal.get(value)),
overrun_interrupt: self.generator_overrun_interrupt.is_set(value),
enabled: self.generate.is_set(value),
polarity: self.generator_polarity.get(value),
generated: self.generated.get(value),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Edge {
Rising,
Falling,
}
impl Edge {
fn between(before: Level, after: Level) -> Option<Self> {
match (before, after) {
(Level::Low, Level::High) => Some(Self::Rising),
(Level::High, Level::Low) => Some(Self::Falling),
_ => None,
}
}
fn is_named_by(self, polarity: u64) -> bool {
match self {
Self::Rising => matches!(polarity, 0b01 | 0b11),
Self::Falling => matches!(polarity, 0b10 | 0b11),
}
}
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
struct ChannelState {
connected: bool,
served: u64,
output: bool,
overrun: bool,
seen: u64,
}
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
struct GeneratorState {
generating: bool,
remaining: u64,
overrun: bool,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct State {
inputs: Vec<bool>,
syncs: Vec<Level>,
triggers: Vec<Level>,
channels: Vec<ChannelState>,
generators: Vec<GeneratorState>,
interrupt: Level,
}
impl State {
fn new(shape: &Shape) -> Self {
Self {
inputs: vec![false; shape.request_ids],
syncs: vec![Level::Low; shape.sync_ids],
triggers: vec![Level::Low; shape.trigger_ids],
channels: vec![ChannelState::default(); shape.channels],
generators: vec![GeneratorState::default(); GENERATORS],
interrupt: Level::Low,
}
}
}
/// One DMA request multiplexer of an STM32H743, RM0433 rev 9 chapter 17, on the layout `L`.
#[derive(Debug)]
pub struct Multiplexer<L> {
shape: &'static Shape,
state: State,
layout: PhantomData<L>,
}
impl<L> Store for Multiplexer<L> {
fn store(&self, to: &mut Writer) -> Result<(), Error> {
to.put(&self.state)
}
fn load(&mut self, from: &mut Reader) -> Result<(), Error> {
self.state = from.take()?;
Ok(())
}
}
impl<L> Multiplexer<L> {
fn with(shape: &'static Shape) -> Self {
Self {
state: State::new(shape),
shape,
layout: PhantomData,
}
}
}
impl Multiplexer<dmamux1::Register> {
/// `DMAMUX1`, wired as RM0433 rev 9 section 17.3.2 and Tables 124, 126 and 127 give it:
/// `DMAMUX1_REQUESTS`, `DMAMUX1_TRIGGERS`, `DMAMUX1_SYNCS` and `DMAMUX1_CONTROLLERS`.
///
/// The part names it a reader of requests, so it hears the peripherals, the two controllers'
/// acknowledges and its own events.
#[must_use]
pub fn new() -> Self {
Self::with(&DMAMUX1_SHAPE)
}
}
impl Default for Multiplexer<dmamux1::Register> {
fn default() -> Self {
Self::new()
}
}
impl Multiplexer<dmamux2::Register> {
/// `DMAMUX2`, wired as RM0433 rev 9 section 17.3.3 and Tables 128, 129 and 130 give it:
/// `DMAMUX2_REQUESTS`, `DMAMUX2_TRIGGERS`, `DMAMUX2_SYNCS` and `DMAMUX2_CONTROLLERS`.
///
/// The part names it a reader of requests, so it hears the peripherals, the BDMA's acknowledges and
/// its own events.
#[must_use]
pub fn new() -> Self {
Self::with(&DMAMUX2_SHAPE)
}
}
impl Default for Multiplexer<dmamux2::Register> {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Target {
Channel(usize),
Status,
Clear,
Generator(usize),
GeneratorStatus,
GeneratorClear,
}
impl<L: Layout> Multiplexer<L> {
fn classify(&self, index: usize) -> Option<Target> {
let channels = self.shape.channels;
let first_generator = channels + 2;
let generator_status = first_generator + GENERATORS;
match index {
index if index < channels => Some(Target::Channel(index)),
index if index == channels => Some(Target::Status),
index if index == channels + 1 => Some(Target::Clear),
index if index < generator_status => Some(Target::Generator(index - first_generator)),
index if index == generator_status => Some(Target::GeneratorStatus),
index if index == generator_status + 1 => Some(Target::GeneratorClear),
_ => None,
}
}
fn row(index: usize) -> L {
L::from_index(index).expect("the layout has every row of its multiplexer")
}
fn channel_register(channel: usize) -> L {
Self::row(channel)
}
fn status_register(&self) -> L {
Self::row(self.shape.channels)
}
fn clear_register(&self) -> L {
Self::row(self.shape.channels + 1)
}
fn generator_register(&self, generator: usize) -> L {
Self::row(self.shape.channels + 2 + generator)
}
fn generator_status_register(&self) -> L {
Self::row(self.shape.channels + 2 + GENERATORS)
}
fn flag(of: L, index: usize) -> &'static FieldSpec {
&of.spec().fields[index]
}
fn channel(&self, registers: &RegisterFile, channel: usize) -> Channel {
self.shape
.fields
.channel(registers.get(Self::channel_register(channel)))
}
fn generator(&self, registers: &RegisterFile, generator: usize) -> Generator {
self.shape
.fields
.generator(registers.get(self.generator_register(generator)))
}
fn mirror_flags(&self, registers: &mut RegisterFile) {
let status = self.status_register();
let mut value = 0;
for (index, channel) in self.state.channels.iter().enumerate() {
value = Self::flag(status, index).set(value, u64::from(channel.overrun));
}
registers.set(status, value);
let generator_status = self.generator_status_register();
let mut value = 0;
for (index, generator) in self.state.generators.iter().enumerate() {
value = Self::flag(generator_status, index).set(value, u64::from(generator.overrun));
}
registers.set(generator_status, value);
}
fn refresh(
&mut self,
channel: usize,
registers: &RegisterFile,
context: &mut DeviceContext<'_>,
) {
let config = self.channel(registers, channel);
let state = &mut self.state.channels[channel];
let standing = self
.state
.inputs
.get(config.request)
.copied()
.unwrap_or(false);
let wanted = config.request != 0 && standing && (!config.synchronize || state.connected);
if wanted != state.output {
state.output = wanted;
context.request(channel, Level::from(wanted));
}
}
fn refresh_all(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
for channel in 0..self.shape.channels {
self.refresh(channel, registers, context);
}
}
fn refresh_interrupt(&mut self, registers: &RegisterFile, context: &mut DeviceContext<'_>) {
let channels = self
.state
.channels
.iter()
.enumerate()
.any(|(index, state)| {
state.overrun && self.channel(registers, index).overrun_interrupt
});
let generators = self
.state
.generators
.iter()
.enumerate()
.any(|(index, state)| {
state.overrun && self.generator(registers, index).overrun_interrupt
});
let wanted = Level::from(channels || generators);
if wanted != self.state.interrupt {
self.state.interrupt = wanted;
context.interrupt(OVERRUN, wanted);
}
}
fn event(&self, channel: usize, context: &mut DeviceContext<'_>) {
let output = self.shape.channels + channel;
context.request(output, Level::High);
context.request(output, Level::Low);
}
fn served(
&mut self,
channel: usize,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let config = self.channel(registers, channel);
let generated = config
.request
.checked_sub(FIRST_GENERATOR_ID)
.filter(|generator| *generator < GENERATORS);
if let Some(generator) = generated {
let state = &mut self.state.generators[generator];
if state.generating {
state.remaining = state.remaining.saturating_sub(1);
if state.remaining == 0 {
state.generating = false;
self.state.inputs[config.request] = false;
}
}
}
if config.synchronize || config.events {
let state = &mut self.state.channels[channel];
state.served += 1;
if state.served > config.forward {
state.served = 0;
state.connected = false;
if config.events {
self.event(channel, context);
}
}
}
if config.request >= FIRST_GENERATOR_ID + GENERATORS {
let output = 2 * self.shape.channels + config.request;
context.request(output, Level::High);
context.request(output, Level::Low);
}
self.refresh_all(registers, context);
}
fn synchronize(
&mut self,
id: usize,
edge: Edge,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
for channel in 0..self.shape.channels {
let config = self.channel(registers, channel);
if !config.synchronize || config.sync != id || !edge.is_named_by(config.polarity) {
continue;
}
let standing = self
.state
.inputs
.get(config.request)
.copied()
.unwrap_or(false);
let state = &mut self.state.channels[channel];
if state.connected {
state.overrun = true;
} else if standing {
state.connected = true;
state.served = 0;
}
self.refresh(channel, registers, context);
}
self.mirror_flags(registers);
self.refresh_interrupt(registers, context);
}
fn trigger(
&mut self,
id: usize,
edge: Edge,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
for index in 0..GENERATORS {
let config = self.generator(registers, index);
if !config.enabled || config.signal != id || !edge.is_named_by(config.polarity) {
continue;
}
let state = &mut self.state.generators[index];
if state.generating {
state.overrun = true;
} else {
state.generating = true;
state.remaining = config.generated + 1;
self.state.inputs[FIRST_GENERATOR_ID + index] = true;
}
}
self.mirror_flags(registers);
self.refresh_all(registers, context);
self.refresh_interrupt(registers, context);
}
fn write_channel(
&mut self,
channel: usize,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let value = registers.get(Self::channel_register(channel));
let before = self.shape.fields.channel(self.state.channels[channel].seen);
let after = self.shape.fields.channel(value);
// ES0392 rev 15 2.5.4: the pattern the workaround avoids.
if !before.synchronize
&& before.polarity != 0
&& after.synchronize
&& after.polarity == before.polarity
&& after.request != before.request
&& after.sync != before.sync
{
context.tell(Self::channel_register(channel).spec().name, ERRATUM_2_5_4);
}
let state = &mut self.state.channels[channel];
state.seen = value;
state.connected = false;
state.served = 0;
self.refresh(channel, registers, context);
self.refresh_interrupt(registers, context);
}
fn write_generator(
&mut self,
generator: usize,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
if !self.generator(registers, generator).enabled {
let state = &mut self.state.generators[generator];
state.generating = false;
state.remaining = 0;
self.state.inputs[FIRST_GENERATOR_ID + generator] = false;
self.refresh_all(registers, context);
}
self.refresh_interrupt(registers, context);
}
fn clear_flags(
&mut self,
clear: L,
written: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let channels = clear.index() == self.clear_register().index();
let flags = if channels {
self.state.channels.len()
} else {
self.state.generators.len()
};
for index in 0..flags {
if Self::flag(clear, index).is_set(written) {
if channels {
self.state.channels[index].overrun = false;
} else {
self.state.generators[index].overrun = false;
}
}
}
registers.set(clear, 0);
self.mirror_flags(registers);
self.refresh_interrupt(registers, context);
}
}
const ERRATUM_2_5_4: &str = "ES0392 rev 15 2.5.4: this write sets SE with SPOL already nonzero and unchanged and changes both DMAREQ_ID and SYNC_ID, so a synchronization event two AHB clock cycles earlier can route the request selected before it. Write SPOL together with SE";
impl<L: Layout + 'static> Device for Multiplexer<L> {
type Register = L;
fn write(
&mut self,
register: L,
value: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let Some(target) = self.classify(register.index()) else {
return;
};
match target {
Target::Channel(channel) => self.write_channel(channel, registers, context),
Target::Generator(generator) => self.write_generator(generator, registers, context),
Target::Status | Target::GeneratorStatus => self.mirror_flags(registers),
Target::Clear | Target::GeneratorClear => {
self.clear_flags(register, value, registers, context);
}
}
}
/// A request any instance drives, which is one of four things to this multiplexer: a peripheral's
/// request, an acknowledge from a controller, a synchronization or a trigger. The tables the module
/// documents say which, and a change no table names is somebody else's.
fn on_request(
&mut self,
change: RequestChange,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let shape = self.shape;
let high = change.level == Level::High;
if high
&& let Some(controller) = shape
.controllers
.iter()
.find(|controller| controller.instance == change.instance)
&& let Some(channel) = change
.request
.checked_add(controller.first_channel)
.filter(|channel| *channel < shape.channels)
{
self.served(channel, registers, context);
}
let mut moved = false;
for wire in shape
.requests
.iter()
.filter(|wire| wire.instance == change.instance && wire.request == change.request)
{
self.state.inputs[wire.id] = high;
moved = true;
}
if moved {
self.refresh_all(registers, context);
}
for wire in shape
.syncs
.iter()
.filter(|wire| wire.instance == change.instance && wire.request == change.request)
{
let before = std::mem::replace(&mut self.state.syncs[wire.id], change.level);
if let Some(edge) = Edge::between(before, change.level) {
self.synchronize(wire.id, edge, registers, context);
}
}
for wire in shape
.triggers
.iter()
.filter(|wire| wire.instance == change.instance && wire.request == change.request)
{
let before = std::mem::replace(&mut self.state.triggers[wire.id], change.level);
if let Some(edge) = Edge::between(before, change.level) {
self.trigger(wire.id, edge, registers, context);
}
}
}
/// A system reset drops every level, connection and counter.
fn reset(&mut self) {
self.state = State::new(self.shape);
}
/// The multiplexer's reset line, section 8.4.3: the multiplexer goes back as a reset puts it, and
/// each channel the controller heard rise falls at once.
fn reset_line(&mut self, _registers: &mut RegisterFile, context: &mut DeviceContext<'_>) {
let outputs: Vec<Level> = self
.state
.channels
.iter()
.map(|channel| Level::from(channel.output))
.collect();
context.lower_requests(&outputs);
self.reset();
}
}
#[cfg(test)]
mod tests {
use std::collections::{BTreeSet, VecDeque};
use super::*;
use crate::device::{ResetCause, Told};
use crate::mcu::{Mcu, Operate, Single};
use crate::snapshot;
use crate::soc::stm32h743;
use crate::soc::stm32h743::generated::{DMAMUX1, DMAMUX2, PERIPHERALS};
use crate::soc::{RegisterSpec, Width};
use crate::time::VirtualNanos;
const AT: VirtualNanos = VirtualNanos(0);
#[derive(Clone, Copy, Default)]
struct Control {
request: u64,
synchronize: bool,
polarity: u64,
sync: u64,
forward: u64,
events: bool,
overrun_interrupt: bool,
}
impl Control {
fn value(self, fields: &Fields) -> u64 {
let mut value = fields.request.set(0, self.request);
value = fields.synchronize.set(value, u64::from(self.synchronize));
value = fields.polarity.set(value, self.polarity);
value = fields.sync.set(value, self.sync);
value = fields.forward.set(value, self.forward);
value = fields.events.set(value, u64::from(self.events));
fields
.overrun_interrupt
.set(value, u64::from(self.overrun_interrupt))
}
}
#[derive(Clone, Copy, Default)]
struct Trigger {
signal: u64,
polarity: u64,
generated: u64,
enabled: bool,
overrun_interrupt: bool,
}
impl Trigger {
fn value(self, fields: &Fields) -> u64 {
let mut value = fields.signal.set(0, self.signal);
value = fields.generator_polarity.set(value, self.polarity);
value = fields.generated.set(value, self.generated);
value = fields.generate.set(value, u64::from(self.enabled));
fields
.generator_overrun_interrupt
.set(value, u64::from(self.overrun_interrupt))
}
}
type Driven = Vec<(usize, Level)>;
struct Rig<L: Layout + 'static> {
soc: Single<Multiplexer<L>>,
base: u64,
shape: &'static Shape,
name: &'static str,
acknowledged: Vec<(usize, Level)>,
}
impl Rig<dmamux1::Register> {
fn first() -> Self {
Self {
soc: Single::new(stm32h743::builder(), "DMAMUX1", Dmamux1::new())
.expect("the bus builds"),
base: DMAMUX1.base,
shape: &DMAMUX1_SHAPE,
name: "DMAMUX1",
acknowledged: Vec::new(),
}
}
}
impl Rig<dmamux2::Register> {
fn second() -> Self {
Self {
soc: Single::new(stm32h743::builder(), "DMAMUX2", Dmamux2::new())
.expect("the bus builds"),
base: DMAMUX2.base,
shape: &DMAMUX2_SHAPE,
name: "DMAMUX2",
acknowledged: Vec::new(),
}
}
}
impl<L: Layout + 'static> Rig<L> {
fn row(index: usize) -> L {
L::from_index(index).expect("the layout has the row")
}
fn address(&self, register: L) -> u64 {
self.base + u64::from(register.spec().offset)
}
fn run(&mut self, first: Option<RequestChange>) -> Driven {
let mut queue: VecDeque<RequestChange> = first.into_iter().collect();
let mut driven = Vec::new();
loop {
for change in self.soc.drain_requests() {
assert_eq!(
change.instance, self.name,
"only the multiplexer drives here"
);
if change.request >= 2 * self.shape.channels {
self.acknowledged.push((change.request, change.level));
continue;
}
driven.push((change.request, change.level));
if change.request >= self.shape.channels {
queue.push_back(change);
}
}
let Some(change) = queue.pop_front() else {
return driven;
};
self.soc.drive(AT, |model, registers, context| {
model.on_request(change, registers, context);
});
}
}
fn write(&mut self, register: L, value: u64) -> Driven {
let address = self.address(register);
self.soc
.write(AT, address, Width::W32, value)
.expect("the instance answers");
self.run(None)
}
fn read(&mut self, register: L) -> u64 {
let address = self.address(register);
self.soc
.read(AT, address, Width::W32)
.expect("the instance answers")
}
fn configure(&mut self, channel: usize, control: Control) -> Driven {
let value = control.value(&self.shape.fields);
self.write(Self::row(channel), value)
}
fn generate(&mut self, generator: usize, trigger: Trigger) -> Driven {
let value = trigger.value(&self.shape.fields);
self.write(Self::row(self.shape.channels + 2 + generator), value)
}
fn send(&mut self, instance: &'static str, request: usize, level: Level) -> Driven {
self.run(Some(RequestChange {
instance,
request,
level,
}))
}
fn raise(&mut self, instance: &'static str, request: usize) -> Driven {
self.send(instance, request, Level::High)
}
fn lower(&mut self, instance: &'static str, request: usize) -> Driven {
self.send(instance, request, Level::Low)
}
fn acknowledge(&mut self, controller: &'static str, number: usize) -> Driven {
let mut driven = self.raise(controller, number);
driven.extend(self.lower(controller, number));
driven
}
fn interrupts(&mut self) -> Vec<(u16, Level)> {
self.soc
.drain_interrupts()
.into_iter()
.map(|change| (change.interrupt, change.level))
.collect()
}
fn told(&mut self) -> Vec<Told> {
self.soc.drain_told()
}
fn status(&mut self) -> u64 {
self.read(Self::row(self.shape.channels))
}
fn generator_status(&mut self) -> u64 {
self.read(Self::row(self.shape.channels + 2 + GENERATORS))
}
fn clear(&mut self, flags: u64) {
self.write(Self::row(self.shape.channels + 1), flags);
}
fn clear_generators(&mut self, flags: u64) {
self.write(Self::row(self.shape.channels + 3 + GENERATORS), flags);
}
}
const RISING: u64 = 0b01;
const FALLING: u64 = 0b10;
const EITHER: u64 = 0b11;
const USART1_RX: u64 = 41;
const USART2_RX: u64 = 43;
const LPTIM1_OUT: u64 = 2;
const FIRST_GENERATED: u64 = 1;
/// The layouts hold the registers in the order sections 17.6.1 to 17.6.12 put them, whatever the id
/// widths and the channel count, and every channel's register has the shape of channel 0's. The
/// model finds a row by arithmetic on that, so the generated table is held to it here.
#[test]
fn both_layouts_run_channels_then_status_then_generators_then_status() {
fn check(table: &'static [RegisterSpec], shape: &Shape) {
let channels = shape.channels;
assert_eq!(table.len(), channels + 4 + GENERATORS);
for channel in 0..channels {
let spec = &table[channel];
assert!(
spec.name.ends_with(&format!("C{channel}CR")),
"{}",
spec.name
);
assert_eq!(u64::from(spec.offset), 4 * channel as u64);
assert_eq!(spec.fields.len(), table[0].fields.len());
for (field, first) in spec.fields.iter().zip(table[0].fields) {
assert_eq!(
(field.name, field.bit, field.width),
(first.name, first.bit, first.width)
);
}
}
assert!(table[channels].name.ends_with("CSR"));
assert_eq!(table[channels].offset, 0x80);
assert!(table[channels + 1].name.ends_with("CFR"));
assert_eq!(table[channels + 1].offset, 0x84);
for generator in 0..GENERATORS {
let spec = &table[channels + 2 + generator];
assert!(
spec.name.ends_with(&format!("RG{generator}CR")),
"{}",
spec.name
);
assert_eq!(u64::from(spec.offset), 0x100 + 4 * generator as u64);
assert_eq!(spec.fields.len(), table[channels + 2].fields.len());
}
assert!(table[channels + 2 + GENERATORS].name.ends_with("RGSR"));
assert_eq!(table[channels + 2 + GENERATORS].offset, 0x140);
assert!(table[channels + 3 + GENERATORS].name.ends_with("RGCFR"));
assert_eq!(table[channels + 3 + GENERATORS].offset, 0x144);
}
check(&dmamux1::REGISTERS, &DMAMUX1_SHAPE);
check(&dmamux2::REGISTERS, &DMAMUX2_SHAPE);
}
/// `SOFx` and `CSOFx` sit at bit `x` of their registers and `OFx` and `COFx` likewise, sections
/// 17.6.3 to 17.6.6 and 17.6.9 to 17.6.12, which is how the model finds a flag by its channel.
#[test]
fn every_flag_sits_at_its_channels_bit() {
fn check<L: Layout + 'static>(rig: &Rig<L>) {
let channels = rig.shape.channels;
for (row, prefix, count) in [
(channels, "SOF", channels),
(channels + 1, "CSOF", channels),
(channels + 2 + GENERATORS, "OF", GENERATORS),
(channels + 3 + GENERATORS, "COF", GENERATORS),
] {
let spec = Rig::<L>::row(row).spec();
assert_eq!(spec.fields.len(), count, "{}", spec.name);
for (index, field) in spec.fields.iter().enumerate() {
assert_eq!(field.name, format!("{prefix}{index}"));
assert_eq!((u64::from(field.bit), field.width), (index as u64, 1));
}
}
}
check(&Rig::first());
check(&Rig::second());
}
/// Every register of both layouts comes out of reset zero, sections 17.6.1 to 17.6.12.
#[test]
fn every_register_resets_to_zero() {
for spec in dmamux1::REGISTERS.iter().chain(&dmamux2::REGISTERS) {
assert_eq!(spec.reset, 0, "{}", spec.name);
}
}
/// A channel's output follows the level of the input it selects, RM0433 rev 9 section 17.4.4:
/// `USART1`'s receive request is input 41 of Table 124, and it reaches the channel that selects it
/// and no other.
#[test]
fn a_selected_request_reaches_its_channel_and_follows_the_level() {
let mut rig = Rig::first();
rig.configure(
3,
Control {
request: USART1_RX,
..Control::default()
},
);
assert_eq!(rig.raise("USART1", RECEIVE), [(3, Level::High)]);
assert_eq!(rig.lower("USART1", RECEIVE), [(3, Level::Low)]);
assert!(
rig.raise("USART2", RECEIVE).is_empty(),
"another instance's request is not input 41"
);
assert!(
rig.raise("USART1", TRANSMIT).is_empty(),
"nor is its transmit request"
);
}
/// A zero `DMAREQ_ID` selects no request, section 17.4.4, and an id nothing is wired to never goes
/// high: id 54 is reserved in Table 124 and no instance's request is wired to it.
#[test]
fn a_request_nothing_selects_reaches_no_channel() {
let mut rig = Rig::first();
rig.configure(0, Control::default());
rig.configure(
1,
Control {
request: 54,
..Control::default()
},
);
rig.configure(
2,
Control {
request: 127,
..Control::default()
},
);
assert!(rig.raise("USART1", RECEIVE).is_empty());
assert!(
rig.raise("SPI6", RECEIVE).is_empty(),
"an instance the table never names"
);
}
/// Writing a channel's `DMAREQ_ID` moves its output to whatever the new input holds, and it follows
/// the input it selects now and not the one it selected.
#[test]
fn changing_the_selected_request_moves_the_output() {
let mut rig = Rig::first();
rig.configure(
0,
Control {
request: USART1_RX,
..Control::default()
},
);
rig.raise("USART1", RECEIVE);
assert_eq!(
rig.configure(
0,
Control {
request: USART2_RX,
..Control::default()
}
),
[(0, Level::Low)],
"input 43 is not standing"
);
assert_eq!(rig.raise("USART2", RECEIVE), [(0, Level::High)]);
assert!(
rig.configure(
0,
Control {
request: USART1_RX,
..Control::default()
}
)
.is_empty(),
"input 41 has stood all along, so the output stays up"
);
assert!(rig.lower("USART2", RECEIVE).is_empty());
assert_eq!(rig.lower("USART1", RECEIVE), [(0, Level::Low)]);
}
/// Two channels may select one request and both follow it, section 17.4.4: the caution is for a
/// firmware to keep the two streams from running at once.
#[test]
fn two_channels_selecting_one_request_both_follow_it() {
let mut rig = Rig::first();
rig.configure(
1,
Control {
request: USART1_RX,
..Control::default()
},
);
rig.configure(
9,
Control {
request: USART1_RX,
..Control::default()
},
);
assert_eq!(
rig.raise("USART1", RECEIVE),
[(1, Level::High), (9, Level::High)]
);
}
fn synchronized(request: u64, polarity: u64, sync: u64, forward: u64) -> Control {
Control {
request,
synchronize: true,
polarity,
sync,
forward,
..Control::default()
}
}
/// With `SE` set a pending request is connected to the output on the edge of the synchronization
/// input, and `NBREQ` zero forwards one request: section 17.4.4.
#[test]
fn synchronization_connects_a_pending_request_on_the_selected_edge() {
let mut rig = Rig::first();
rig.configure(0, synchronized(USART1_RX, RISING, LPTIM1_OUT, 0));
assert!(
rig.raise("USART1", RECEIVE).is_empty(),
"pending, and not connected"
);
assert_eq!(
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2),
[(0, Level::High)],
"the rising edge connects it"
);
assert_eq!(
rig.acknowledge("DMA1", 0),
[(0, Level::Low)],
"one request forwarded, so the input is disconnected"
);
assert!(rig.lower("USART1", RECEIVE).is_empty());
assert!(
rig.raise("USART1", RECEIVE).is_empty(),
"the next request waits for the next event"
);
rig.lower("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
assert_eq!(
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2),
[(0, Level::High)]
);
}
/// An event with no request standing is discarded, section 17.4.4: "The following asserted input
/// request lines is not connected to the DMAMUX multiplexer channel output until a synchronization
/// event occurs again."
#[test]
fn a_synchronization_event_with_no_request_pending_is_discarded() {
let mut rig = Rig::first();
rig.configure(0, synchronized(USART1_RX, RISING, LPTIM1_OUT, 0));
assert!(rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2).is_empty());
assert!(
rig.raise("USART1", RECEIVE).is_empty(),
"the event came and went with nothing to connect"
);
assert_eq!(rig.status(), 0, "and it was no overrun");
}
/// `NBREQ` plus one requests pass after an event, each counted as it is served, and the input is
/// then disconnected, section 17.4.4. Three are forwarded with `NBREQ` two.
#[test]
fn nbreq_plus_one_requests_pass_after_a_synchronization_event() {
let mut rig = Rig::first();
rig.configure(0, synchronized(USART1_RX, RISING, LPTIM1_OUT, 2));
rig.raise("USART1", RECEIVE);
assert_eq!(
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2),
[(0, Level::High)]
);
for served in 1..=2 {
assert!(
rig.acknowledge("DMA1", 0).is_empty(),
"served {served} of three"
);
assert_eq!(rig.lower("USART1", RECEIVE), [(0, Level::Low)]);
assert_eq!(
rig.raise("USART1", RECEIVE),
[(0, Level::High)],
"still connected"
);
}
assert_eq!(
rig.acknowledge("DMA1", 0),
[(0, Level::Low)],
"the third, and the counter runs out with the request still standing"
);
assert!(rig.lower("USART1", RECEIVE).is_empty());
assert!(rig.raise("USART1", RECEIVE).is_empty());
}
/// `SPOL` names the edge: 01 rising, 10 falling, 11 either and 00 none, sections 17.6.1 and 17.6.2.
#[test]
fn spol_selects_the_edge() {
for polarity in [0b00, RISING, FALLING, EITHER] {
let mut rig = Rig::first();
rig.configure(0, synchronized(USART1_RX, polarity, LPTIM1_OUT, 0));
rig.raise("USART1", RECEIVE);
let connected = !rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2).is_empty();
assert_eq!(
connected,
matches!(polarity, RISING | EITHER),
"rising, SPOL {polarity:02b}"
);
let mut rig = Rig::first();
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
rig.configure(0, synchronized(USART1_RX, polarity, LPTIM1_OUT, 0));
rig.raise("USART1", RECEIVE);
let connected = !rig.lower("DMAMUX1", DMAMUX1_FIRST_EVENT + 2).is_empty();
assert_eq!(
connected,
matches!(polarity, FALLING | EITHER),
"falling, SPOL {polarity:02b}"
);
}
}
/// A new event before the counter has run out sets `SOFx`, and with `SOIE` set the interrupt stands
/// while the flag does: sections 17.4.4 and 17.5.
#[test]
fn a_second_synchronization_event_before_the_underrun_sets_sof_and_the_interrupt() {
let sof0 = dmamux1::dmamux_csr::SOF0.mask();
let mut rig = Rig::first();
rig.configure(0, synchronized(USART1_RX, RISING, LPTIM1_OUT, 1));
rig.raise("USART1", RECEIVE);
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
assert_eq!(rig.status(), 0);
rig.lower("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
assert_eq!(
rig.status(),
0,
"the falling edge is none of this channel's"
);
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
assert_eq!(
rig.status(),
sof0,
"the second rising edge found the counter running"
);
assert!(rig.interrupts().is_empty(), "SOIE is clear");
let mut enabled = synchronized(USART1_RX, RISING, LPTIM1_OUT, 1);
enabled.overrun_interrupt = true;
rig.configure(0, enabled);
assert_eq!(
rig.interrupts(),
[(102, Level::High)],
"a level: setting the enable with the flag standing raises it, on the line Table 147 gives"
);
}
/// `CSOFx` clears the flag and the interrupt follows, and the clear register reads zero,
/// sections 17.6.3 and 17.6.5.
#[test]
fn csof_clears_the_flag_and_lowers_the_interrupt() {
let sof0 = dmamux1::dmamux_csr::SOF0.mask();
let mut rig = Rig::first();
let mut control = synchronized(USART1_RX, RISING, LPTIM1_OUT, 1);
control.overrun_interrupt = true;
rig.configure(0, control);
rig.raise("USART1", RECEIVE);
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
rig.lower("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
assert_eq!(rig.status(), sof0);
assert_eq!(rig.interrupts(), [(102, Level::High)]);
rig.clear(dmamux1::dmamux_cfr::CSOF1.mask());
assert_eq!(rig.status(), sof0, "another channel's clear bit leaves it");
rig.clear(dmamux1::dmamux_cfr::CSOF0.mask());
assert_eq!(rig.status(), 0);
assert_eq!(rig.interrupts(), [(102, Level::Low)]);
assert_eq!(rig.read(dmamux1::Register::DmamuxCfr), 0);
}
/// With `EGE` set the channel pulses an event when its counter reloads, which is after `NBREQ` plus
/// one served requests, section 17.4.4 and Figure 86. Requests pass untouched with `SE` clear.
#[test]
fn event_generation_pulses_when_the_counter_reloads() {
let mut rig = Rig::first();
rig.configure(
0,
Control {
request: USART1_RX,
events: true,
forward: 3,
..Control::default()
},
);
assert_eq!(rig.raise("USART1", RECEIVE), [(0, Level::High)]);
for served in 1..=3 {
assert!(
rig.acknowledge("DMA1", 0).is_empty(),
"{served} of four served"
);
}
assert_eq!(
rig.acknowledge("DMA1", 0),
[
(DMAMUX1_FIRST_EVENT, Level::High),
(DMAMUX1_FIRST_EVENT, Level::Low)
],
"the counter reloads and channel 0's event pulses"
);
assert!(
rig.acknowledge("DMA1", 0).is_empty(),
"and counts again from the top"
);
}
/// With `EGE` set and `NBREQ` zero an event follows each served request, the note under section
/// 17.4.4's Figure 86.
#[test]
fn an_event_follows_each_served_request_when_nbreq_is_zero() {
let mut rig = Rig::first();
rig.configure(
2,
Control {
request: USART1_RX,
events: true,
..Control::default()
},
);
let pulse = [
(DMAMUX1_FIRST_EVENT + 2, Level::High),
(DMAMUX1_FIRST_EVENT + 2, Level::Low),
];
assert_eq!(rig.acknowledge("DMA1", 2), pulse);
assert_eq!(rig.acknowledge("DMA1", 2), pulse);
}
/// The multiplexer acknowledges the selected peripheral request, sections 17.1 and 17.4.4: a request
/// served on a channel selecting `TIM1`'s update, input 15, is a pulse on output `32 + 15`, and
/// `acknowledged_request` finds the timer's own request number from it. A generated request has no
/// peripheral to acknowledge.
#[test]
fn the_multiplexer_acknowledges_the_selected_peripheral_request() {
let mut rig = Rig::first();
rig.configure(
0,
Control {
request: 15,
..Control::default()
},
);
rig.configure(
1,
Control {
request: FIRST_GENERATED,
..Control::default()
},
);
rig.acknowledge("DMA1", 0);
assert_eq!(rig.acknowledged, [(47, Level::High), (47, Level::Low)]);
rig.acknowledged.clear();
rig.acknowledge("DMA1", 1);
assert!(
rig.acknowledged.is_empty(),
"no peripheral stands behind a generator"
);
let high = RequestChange {
instance: "DMAMUX1",
request: 47,
level: Level::High,
};
assert_eq!(acknowledged_request("TIM1", &high), Some(UPDATE_REQUEST));
assert_eq!(
acknowledged_request("TIM2", &high),
None,
"TIM2's update is input 22"
);
assert_eq!(
acknowledged_request(
"TIM1",
&RequestChange {
level: Level::Low,
..high
}
),
None
);
}
/// A controller's acknowledge reaches the channel it listens to: the second controller's stream 3 is
/// channel 11, section 17.3.2, and the first controller's stream 3 is channel 3.
#[test]
fn a_controllers_acknowledge_reaches_the_channel_it_hears() {
let mut rig = Rig::first();
rig.configure(
11,
Control {
request: USART1_RX,
events: true,
..Control::default()
},
);
assert!(
rig.acknowledge("DMA1", 3).is_empty(),
"channel 3 has no event enabled"
);
assert_eq!(
rig.acknowledge("DMA2", 3),
[
(DMAMUX1_FIRST_EVENT + 11, Level::High),
(DMAMUX1_FIRST_EVENT + 11, Level::Low)
]
);
assert!(
rig.acknowledge("USART1", 3).is_empty(),
"an instance that is no controller"
);
}
/// A channel's event is the synchronization input of its number, Tables 126 and 127: channel 0's
/// event connects channel 1's pending request, which selects `dmamux1_evt0` to synchronize on.
#[test]
fn a_channels_event_is_the_synchronization_input_of_its_number() {
let mut rig = Rig::first();
rig.configure(
0,
Control {
request: USART1_RX,
events: true,
..Control::default()
},
);
rig.configure(1, synchronized(USART2_RX, RISING, 0, 0));
rig.raise("USART2", RECEIVE);
assert_eq!(
rig.acknowledge("DMA1", 0),
[
(DMAMUX1_FIRST_EVENT, Level::High),
(DMAMUX1_FIRST_EVENT, Level::Low),
(1, Level::High)
],
"the event is a rising edge of input 0, and channel 1 connects on it"
);
}
fn generator(signal: u64, polarity: u64, generated: u64) -> Trigger {
Trigger {
signal,
polarity,
generated,
enabled: true,
overrun_interrupt: false,
}
}
/// A trigger edge starts `GNBREQ` plus one requests on an enabled generator, each served when the
/// controller acknowledges it, and the generator then stops until the next trigger: section 17.4.5.
#[test]
fn a_trigger_edge_starts_gnbreq_plus_one_generated_requests() {
let mut rig = Rig::first();
rig.generate(0, generator(LPTIM1_OUT, RISING, 2));
rig.configure(
5,
Control {
request: FIRST_GENERATED,
..Control::default()
},
);
assert_eq!(
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2),
[(5, Level::High)],
"the generated request is input 1, and it stands"
);
assert!(
rig.acknowledge("DMA1", 5).is_empty(),
"one of three served, the next stands"
);
assert!(rig.acknowledge("DMA1", 5).is_empty(), "two of three");
assert_eq!(
rig.acknowledge("DMA1", 5),
[(5, Level::Low)],
"three served and the generator stops"
);
assert!(
rig.acknowledge("DMA1", 5).is_empty(),
"nothing is left to serve"
);
rig.lower("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
assert_eq!(
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2),
[(5, Level::High)],
"and the next trigger starts it again"
);
}
/// `GPOL` names the trigger edge as `SPOL` names the synchronization edge, section 17.6.7.
#[test]
fn gpol_selects_the_trigger_edge() {
for polarity in [0b00, RISING, FALLING, EITHER] {
let mut rig = Rig::first();
rig.generate(0, generator(LPTIM1_OUT, polarity, 0));
rig.configure(
0,
Control {
request: FIRST_GENERATED,
..Control::default()
},
);
let started = !rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2).is_empty();
assert_eq!(
started,
matches!(polarity, RISING | EITHER),
"rising, GPOL {polarity:02b}"
);
let mut rig = Rig::first();
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
rig.generate(0, generator(LPTIM1_OUT, polarity, 0));
rig.configure(
0,
Control {
request: FIRST_GENERATED,
..Control::default()
},
);
let started = !rig.lower("DMAMUX1", DMAMUX1_FIRST_EVENT + 2).is_empty();
assert_eq!(
started,
matches!(polarity, FALLING | EITHER),
"falling, GPOL {polarity:02b}"
);
}
}
/// A trigger on an enabled generator that hasn't run out sets `OFx`, section 17.4.5, and the
/// interrupt follows with `OIE`. A trigger on a disabled generator is no overrun and starts nothing.
#[test]
fn a_trigger_before_the_generator_has_finished_is_an_overrun() {
let of0 = dmamux1::dmamux_rgsr::OF0.mask();
let mut rig = Rig::first();
rig.configure(
0,
Control {
request: FIRST_GENERATED,
..Control::default()
},
);
rig.generate(
0,
Trigger {
enabled: false,
overrun_interrupt: true,
..generator(LPTIM1_OUT, RISING, 3)
},
);
assert!(rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2).is_empty());
assert_eq!(
rig.generator_status(),
0,
"disabled: nothing started and nothing overran"
);
rig.generate(
0,
Trigger {
overrun_interrupt: true,
..generator(LPTIM1_OUT, RISING, 3)
},
);
rig.lower("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
assert_eq!(
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2),
[(0, Level::High)]
);
assert_eq!(rig.generator_status(), 0);
rig.lower("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
assert_eq!(
rig.generator_status(),
of0,
"four requests were not served when the trigger came"
);
assert_eq!(rig.interrupts(), [(102, Level::High)]);
}
/// `COFx` clears `OFx`, section 17.6.11, and the interrupt follows `OIE` as a level: set the enable
/// with the flag standing and it rises, clear it and it falls.
#[test]
fn cof_clears_the_flag_and_the_interrupt_follows_oie() {
let of0 = dmamux1::dmamux_rgsr::OF0.mask();
let mut rig = Rig::first();
rig.generate(0, generator(LPTIM1_OUT, RISING, 3));
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
rig.lower("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
assert_eq!(rig.generator_status(), of0);
assert!(rig.interrupts().is_empty(), "OIE is clear");
rig.generate(
0,
Trigger {
overrun_interrupt: true,
..generator(LPTIM1_OUT, RISING, 3)
},
);
assert_eq!(rig.interrupts(), [(102, Level::High)]);
rig.generate(0, generator(LPTIM1_OUT, RISING, 3));
assert_eq!(rig.interrupts(), [(102, Level::Low)]);
rig.generate(
0,
Trigger {
overrun_interrupt: true,
..generator(LPTIM1_OUT, RISING, 3)
},
);
assert_eq!(rig.interrupts(), [(102, Level::High)]);
rig.clear_generators(dmamux1::dmamux_rgcfr::COF0.mask());
assert_eq!(rig.generator_status(), 0);
assert_eq!(rig.interrupts(), [(102, Level::Low)]);
assert_eq!(rig.read(dmamux1::Register::DmamuxRgcfr), 0);
}
/// Clearing `GE` stops a generator mid-run and withdraws its request, section 17.6.7.
#[test]
fn clearing_ge_stops_the_generator() {
let mut rig = Rig::first();
rig.generate(0, generator(LPTIM1_OUT, RISING, 3));
rig.configure(
0,
Control {
request: FIRST_GENERATED,
..Control::default()
},
);
assert_eq!(
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2),
[(0, Level::High)]
);
assert_eq!(
rig.generate(
0,
Trigger {
enabled: false,
..generator(LPTIM1_OUT, RISING, 3)
}
),
[(0, Level::Low)]
);
assert!(
rig.acknowledge("DMA1", 0).is_empty(),
"nothing left to serve"
);
rig.generate(0, generator(LPTIM1_OUT, RISING, 3));
rig.lower("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
assert_eq!(
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2),
[(0, Level::High)],
"enabled again, the next trigger starts it"
);
}
/// The one interrupt is position zero and stands while any flag stands with its enable set,
/// section 17.5 and Table 132, on the line Table 147 gives each multiplexer.
#[test]
fn the_overrun_interrupt_stands_while_a_flag_and_its_enable_do() {
let mut rig = Rig::first();
let mut control = synchronized(USART1_RX, RISING, LPTIM1_OUT, 1);
control.overrun_interrupt = true;
rig.configure(0, control);
rig.generate(
0,
Trigger {
overrun_interrupt: true,
..generator(LPTIM1_OUT, RISING, 3)
},
);
rig.raise("USART1", RECEIVE);
for _ in 0..2 {
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
rig.lower("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
}
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
assert_ne!(rig.status(), 0);
assert_ne!(rig.generator_status(), 0);
assert_eq!(
rig.interrupts(),
[(102, Level::High)],
"raised once, by whichever came first"
);
rig.clear(dmamux1::dmamux_cfr::CSOF0.mask());
assert!(
rig.interrupts().is_empty(),
"the generator's flag still holds it up"
);
rig.clear_generators(dmamux1::dmamux_rgcfr::COF0.mask());
assert_eq!(rig.interrupts(), [(102, Level::Low)]);
let mut second = Rig::second();
let mut control = synchronized(11, RISING, 3, 1);
control.overrun_interrupt = true;
second.configure(0, control);
second.raise("SPI6", RECEIVE);
second.raise("DMAMUX2", DMAMUX2_FIRST_EVENT + 3);
second.lower("DMAMUX2", DMAMUX2_FIRST_EVENT + 3);
second.raise("DMAMUX2", DMAMUX2_FIRST_EVENT + 3);
assert_eq!(second.interrupts(), [(128, Level::High)]);
}
/// `CSR` and `RGSR` are read-only, sections 17.6.3 and 17.6.9, and the clear registers read zero,
/// sections 17.6.5 and 17.6.11. ST's file types the first pair read-write and `DMAMUX2_RGCFR`
/// read-only, which a write must not change either way.
#[test]
fn the_status_registers_take_no_write_and_the_clear_registers_read_zero() {
let mut rig = Rig::first();
rig.configure(0, synchronized(USART1_RX, RISING, LPTIM1_OUT, 1));
rig.generate(0, generator(LPTIM1_OUT, RISING, 3));
rig.raise("USART1", RECEIVE);
for _ in 0..2 {
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
rig.lower("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
}
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
let (status, generators) = (rig.status(), rig.generator_status());
assert_ne!((status, generators), (0, 0));
rig.write(dmamux1::Register::DmamuxCsr, 0);
rig.write(dmamux1::Register::DmamuxCsr, u64::from(u32::MAX));
rig.write(dmamux1::Register::DmamuxRgsr, 0);
rig.write(dmamux1::Register::DmamuxRgsr, u64::from(u32::MAX));
assert_eq!((rig.status(), rig.generator_status()), (status, generators));
rig.write(dmamux1::Register::DmamuxCfr, 0);
assert_eq!(rig.read(dmamux1::Register::DmamuxCfr), 0);
assert_eq!(
rig.status(),
status,
"a clear register written zero clears nothing"
);
let mut second = Rig::second();
second.generate(0, generator(3, RISING, 3));
second.raise("DMAMUX2", DMAMUX2_FIRST_EVENT + 3);
second.lower("DMAMUX2", DMAMUX2_FIRST_EVENT + 3);
second.raise("DMAMUX2", DMAMUX2_FIRST_EVENT + 3);
assert_eq!(second.generator_status(), dmamux2::rgsr::OF0.mask());
second.clear_generators(dmamux2::rgcfr::COF0.mask());
assert_eq!(
second.generator_status(),
0,
"the write takes though the file drops it"
);
assert_eq!(second.read(dmamux2::Register::Rgcfr), 0);
second.write(dmamux2::Register::Csr, u64::from(u32::MAX));
assert_eq!(second.status(), 0);
}
/// The second multiplexer has eight channels and five-bit ids, section 17.3.3 and Table 123, and
/// serves the BDMA: `LPUART1`'s receive request is input 9 of Table 128 and `SPI6` waits for `EXTI`
/// line 0, input 14 of Table 130.
#[test]
fn the_second_multiplexer_routes_five_bit_ids_through_eight_channels() {
let mut rig = Rig::second();
rig.configure(
7,
Control {
request: 9,
..Control::default()
},
);
assert_eq!(rig.raise("LPUART1", RECEIVE), [(7, Level::High)]);
assert_eq!(rig.lower("LPUART1", RECEIVE), [(7, Level::Low)]);
rig.configure(6, synchronized(11, RISING, 3, 0));
rig.raise("SPI6", RECEIVE);
assert_eq!(
rig.raise("DMAMUX2", DMAMUX2_FIRST_EVENT + 3),
[(6, Level::High)]
);
assert_eq!(rig.acknowledge("BDMA", 6), [(6, Level::Low)]);
assert!(
rig.acknowledge("DMA1", 6).is_empty(),
"the first multiplexer's controllers are not this one's"
);
assert_eq!(
dmamux2::REGISTERS[dmamux2::Register::Csr.index()]
.fields
.len(),
8
);
assert_eq!(DMAMUX2_SHAPE.fields.request.width, 5);
assert_eq!(DMAMUX1_SHAPE.fields.request.width, 7);
}
/// ES0392 rev 15 2.5.4: a write that sets `SE` with `SPOL` nonzero and unchanged and changes both
/// ids is the one the erratum's workaround avoids, so the session is told. A write that touches
/// `SPOL`, changes one id, or finds `SE` already set is not.
#[test]
fn a_write_that_sets_se_and_changes_both_ids_without_writing_spol_is_told() {
let before = Control {
request: USART1_RX,
polarity: RISING,
sync: 1,
..Control::default()
};
let mut rig = Rig::first();
rig.configure(0, before);
rig.configure(0, synchronized(USART2_RX, RISING, 2, 0));
let told = rig.told();
assert_eq!(told.len(), 1);
assert_eq!(
(told[0].instance, told[0].register),
("DMAMUX1", "DMAMUX_C0CR")
);
assert!(told[0].reason.contains("2.5.4"));
for (name, after) in [
(
"SPOL written with SE",
synchronized(USART2_RX, FALLING, 2, 0),
),
(
"only the request changed",
synchronized(USART2_RX, RISING, 1, 0),
),
(
"only the synchronization changed",
synchronized(USART1_RX, RISING, 2, 0),
),
] {
let mut rig = Rig::first();
rig.configure(0, before);
rig.configure(0, after);
assert!(rig.told().is_empty(), "{name}");
}
let mut rig = Rig::first();
rig.configure(0, synchronized(USART1_RX, RISING, 1, 0));
rig.configure(0, synchronized(USART2_RX, RISING, 2, 0));
assert!(rig.told().is_empty(), "SE was already set");
}
/// A system reset drops every level, connection and counter: a request that stood before it is gone,
/// so a channel that selects the input again sees nothing until the source drives it again.
#[test]
fn a_reset_forgets_levels_connections_and_counters() {
let mut rig = Rig::first();
rig.configure(
0,
Control {
request: USART1_RX,
..Control::default()
},
);
rig.raise("USART1", RECEIVE);
rig.soc.reset(AT, ResetCause::Core);
assert!(
rig.configure(
0,
Control {
request: USART1_RX,
..Control::default()
}
)
.is_empty(),
"the level that stood is gone"
);
assert_eq!(rig.raise("USART1", RECEIVE), [(0, Level::High)]);
}
/// Sections 8.4.3 and 8.7.31: `BDMARST` resets `DMAMUX2` alone, and each channel the BDMA heard
/// rise falls at the reset itself. A channel set up again passes its request on again.
#[test]
fn a_reset_line_lowers_each_channel_at_once() {
const LPUART1_RX: u64 = 9;
let mut rig = Rig::second();
let lpuart = Control {
request: LPUART1_RX,
..Control::default()
};
rig.configure(6, lpuart);
rig.configure(7, lpuart);
assert_eq!(
rig.raise("LPUART1", RECEIVE),
[(6, Level::High), (7, Level::High)]
);
let slot = rig.soc.bus().slot_of("DMAMUX2").expect("an instance");
rig.soc.bus_mut().reset_slot(slot);
rig.soc.drive(AT, |model, registers, context| {
model.reset_line(registers, context);
});
assert_eq!(
rig.run(None),
[(6, Level::Low), (7, Level::Low)],
"both channels fall at the reset"
);
rig.configure(7, lpuart);
assert_eq!(
rig.raise("LPUART1", RECEIVE),
[(7, Level::High)],
"a channel set up again passes the request on again"
);
}
fn resource_of(wire: &Wire) -> String {
let instance = wire.instance;
let lower = instance.to_lowercase();
let number = |prefix: &str| instance.trim_start_matches(prefix).to_owned();
match instance {
_ if instance.starts_with("TIM") => {
let suffix = match wire.request {
UPDATE_REQUEST => "UP".to_owned(),
TRIGGER_REQUEST => "TRIG".to_owned(),
COMMUTATION_REQUEST => "COM".to_owned(),
capture => format!("CH{capture}"),
};
format!("{instance}_{suffix}")
}
_ if instance.starts_with("ADC") => format!("adc{}_dma", number("ADC")),
"DAC" => format!("dac_ch{}_dma", wire.request + 1),
"DCMI" => "dcmi_dma".to_owned(),
"SAI1" | "SAI2" => format!("sai{}{}_dma", number("SAI"), ["a", "b"][wire.request]),
"SAI3" | "SAI4" => format!("sai{}_{}_dma", number("SAI"), ["a", "b"][wire.request]),
"SPDIFRX" => format!("spdifrx_{}_dma", ["dat", "ctrl"][wire.request]),
"DFSDM" => format!("dfsdm1_dma{}", wire.request),
"DMAMUX1" => format!("dmamux1_evt{}", wire.request - DMAMUX1_FIRST_EVENT),
"DMAMUX2" => format!("dmamux2_evt{}", wire.request - DMAMUX2_FIRST_EVENT),
_ => format!("{lower}_{}_dma", ["rx", "tx"][wire.request]),
}
}
fn same_resource(printed: &str, derived: &str) -> bool {
printed.eq_ignore_ascii_case(derived)
}
/// Every row of the two request tables names the resource the manual prints at its id, and the ids
/// no row names are the ones the module says it leaves: the generators, the reserved ids, the
/// cryptographic and hash processors and the high-resolution timer.
#[test]
fn every_request_wire_names_the_resource_the_manual_prints() {
for (wires, printed) in [
(&DMAMUX1_REQUESTS[..], &TABLE_124[..]),
(&DMAMUX2_REQUESTS[..], &TABLE_128[..]),
] {
for wire in wires {
let resource = printed[wire.id - 1];
assert!(
same_resource(resource, &resource_of(wire)),
"{wire:?} is printed as {resource}"
);
}
let wired: BTreeSet<usize> = wires.iter().map(|wire| wire.id).collect();
for (index, resource) in printed.iter().enumerate() {
let kept_out = ["req_gen", "Reserved", "HR_REQ", "cryp_", "hash_"]
.iter()
.any(|prefix| resource.contains(prefix));
assert_eq!(
wired.contains(&(index + 1)),
!kept_out,
"id {} is printed as {resource}",
index + 1
);
}
}
}
/// Every synchronization and trigger wire names the resource the manual prints at its input, and
/// the inputs no wire names are the ones the module lists as having no source to drive them.
#[test]
fn every_synchronization_and_trigger_wire_names_the_resource_the_manual_prints() {
for (wires, printed) in [
(&DMAMUX1_TRIGGERS[..], &TABLE_126[..]),
(&DMAMUX1_SYNCS[..], &TABLE_126[..]),
(&DMAMUX2_TRIGGERS[..], &TABLE_129[..]),
(&DMAMUX2_SYNCS[..], &TABLE_130[..]),
] {
for wire in wires {
let resource = printed[wire.id];
assert!(
same_resource(resource, &resource_of(wire)),
"{wire:?} is printed as {resource}"
);
}
}
let unwired = |wires: &[Wire], printed: &[&'static str]| -> Vec<&'static str> {
let named: BTreeSet<usize> = wires.iter().map(|wire| wire.id).collect();
printed
.iter()
.enumerate()
.filter(|(id, _)| !named.contains(id))
.map(|(_, resource)| *resource)
.collect()
};
for (wires, printed) in [
(&DMAMUX1_TRIGGERS[..], &TABLE_126[..]),
(&DMAMUX2_TRIGGERS[..], &TABLE_129[..]),
(&DMAMUX2_SYNCS[..], &TABLE_130[..]),
] {
let left_out = unwired(wires, printed);
assert!(
left_out.iter().all(|resource| !resource.contains("evt")),
"every event is wired, and only sources nothing drives are left out: {left_out:?}"
);
}
}
/// Each table holds an id once and an output of an instance once, names only instances the part
/// has, and puts nothing on a generator's id.
#[test]
fn the_published_tables_hold_each_id_once_and_name_instances_the_part_has() {
let instances: BTreeSet<&str> = PERIPHERALS
.iter()
.map(|peripheral| peripheral.name)
.collect();
for (wires, space, is_request) in [
(&DMAMUX1_REQUESTS[..], DMAMUX1_SHAPE.request_ids, true),
(&DMAMUX2_REQUESTS[..], DMAMUX2_SHAPE.request_ids, true),
(&DMAMUX1_SYNCS[..], DMAMUX1_SHAPE.sync_ids, false),
(&DMAMUX2_SYNCS[..], DMAMUX2_SHAPE.sync_ids, false),
(&DMAMUX1_TRIGGERS[..], DMAMUX1_SHAPE.trigger_ids, false),
(&DMAMUX2_TRIGGERS[..], DMAMUX2_SHAPE.trigger_ids, false),
] {
let mut ids = BTreeSet::new();
let mut outputs = BTreeSet::new();
for wire in wires {
assert!(instances.contains(wire.instance), "{wire:?}");
assert!(wire.id < space, "{wire:?}");
assert!(ids.insert(wire.id), "{wire:?} repeats an id");
assert!(
outputs.insert((wire.instance, wire.request)),
"{wire:?} repeats an output"
);
if is_request {
assert!(
!(FIRST_GENERATOR_ID..FIRST_GENERATOR_ID + GENERATORS).contains(&wire.id),
"{wire:?} is on a generator's id"
);
}
}
}
for controller in DMAMUX1_CONTROLLERS.iter().chain(&DMAMUX2_CONTROLLERS) {
assert!(instances.contains(controller.instance));
}
assert_eq!(DMAMUX1_FIRST_EVENT, DMAMUX1_SHAPE.channels);
assert_eq!(DMAMUX2_FIRST_EVENT, DMAMUX2_SHAPE.channels);
assert_eq!(
[
DMAMUX1_CONTROLLERS[0].first_channel,
DMAMUX1_CONTROLLERS[1].first_channel
],
[0, 8],
"section 17.3.2: DMA1 channels 0 to 7 and DMA2 channels 8 to 15"
);
}
/// Two runs of one script are one run: the script synchronizes, generates, overruns both and clears
/// one, and every register, interrupt, output and the model's whole state agree.
#[test]
fn two_runs_of_one_script_are_the_same_run() {
fn scripted() -> (Rig<dmamux1::Register>, Driven, Vec<(u16, Level)>) {
let mut rig = Rig::first();
let mut driven = Vec::new();
let mut control = synchronized(USART1_RX, EITHER, LPTIM1_OUT, 2);
control.overrun_interrupt = true;
control.events = true;
driven.extend(rig.configure(0, control));
driven.extend(rig.generate(
1,
Trigger {
overrun_interrupt: true,
..generator(1, RISING, 1)
},
));
driven.extend(rig.configure(
4,
Control {
request: 2,
..Control::default()
},
));
driven.extend(rig.raise("USART1", RECEIVE));
for _ in 0..3 {
driven.extend(rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2));
driven.extend(rig.lower("DMAMUX1", DMAMUX1_FIRST_EVENT + 2));
driven.extend(rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 1));
driven.extend(rig.lower("DMAMUX1", DMAMUX1_FIRST_EVENT + 1));
driven.extend(rig.acknowledge("DMA1", 0));
driven.extend(rig.acknowledge("DMA1", 4));
}
rig.clear(dmamux1::dmamux_cfr::CSOF0.mask());
let interrupts = rig.interrupts();
(rig, driven, interrupts)
}
let (mut first, first_driven, first_interrupts) = scripted();
let (mut second, second_driven, second_interrupts) = scripted();
assert_eq!(first_driven, second_driven);
assert_eq!(first_interrupts, second_interrupts);
assert!(!first_driven.is_empty() && !first_interrupts.is_empty());
for register in dmamux1::Register::ALL {
assert_eq!(
first.read(register),
second.read(register),
"{register:?} disagrees between two runs of the same script"
);
}
assert_eq!(
format!("{:?}", first.soc.model),
format!("{:?}", second.soc.model)
);
}
/// The model's state serializes mid-synchronization, with a channel connected and a generator
/// running.
#[test]
fn the_model_state_round_trips_through_a_snapshot() {
let mut rig = Rig::first();
rig.configure(0, synchronized(USART1_RX, RISING, LPTIM1_OUT, 4));
rig.raise("USART1", RECEIVE);
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 2);
rig.generate(0, generator(1, RISING, 3));
rig.raise("DMAMUX1", DMAMUX1_FIRST_EVENT + 1);
rig.acknowledge("DMA1", 0);
let saved = snapshot::stored(&rig.soc.model).expect("the model serializes");
let mut restored = Dmamux1::new();
snapshot::load(&mut restored, snapshot::keep(saved)).expect("and deserializes");
assert_eq!(format!("{restored:?}"), format!("{:?}", rig.soc.model));
}
const TABLE_124: [&str; 127] = [
"dmamux1_req_gen0",
"dmamux1_req_gen1",
"dmamux1_req_gen2",
"dmamux1_req_gen3",
"dmamux1_req_gen4",
"dmamux1_req_gen5",
"dmamux1_req_gen6",
"dmamux1_req_gen7",
"adc1_dma",
"adc2_dma",
"TIM1_CH1",
"TIM1_CH2",
"TIM1_CH3",
"TIM1_CH4",
"TIM1_UP",
"TIM1_TRIG",
"TIM1_COM",
"TIM2_CH1",
"TIM2_CH2",
"TIM2_CH3",
"TIM2_CH4",
"TIM2_UP",
"TIM3_CH1",
"TIM3_CH2",
"TIM3_CH3",
"TIM3_CH4",
"TIM3_UP",
"TIM3_TRIG",
"TIM4_CH1",
"TIM4_CH2",
"TIM4_CH3",
"TIM4_UP",
"i2c1_rx_dma",
"i2c1_tx_dma",
"i2c2_rx_dma",
"i2c2_tx_dma",
"spi1_rx_dma",
"spi1_tx_dma",
"spi2_rx_dma",
"spi2_tx_dma",
"usart1_rx_dma",
"usart1_tx_dma",
"usart2_rx_dma",
"usart2_tx_dma",
"usart3_rx_dma",
"usart3_tx_dma",
"TIM8_CH1",
"TIM8_CH2",
"TIM8_CH3",
"TIM8_CH4",
"TIM8_UP",
"TIM8_TRIG",
"TIM8_COM",
"Reserved",
"TIM5_CH1",
"TIM5_CH2",
"TIM5_CH3",
"TIM5_CH4",
"TIM5_UP",
"TIM5_TRIG",
"spi3_rx_dma",
"spi3_tx_dma",
"uart4_rx_dma",
"uart4_tx_dma",
"uart5_rx_dma",
"uart5_tx_dma",
"dac_ch1_dma",
"dac_ch2_dma",
"TIM6_UP",
"TIM7_UP",
"usart6_rx_dma",
"usart6_tx_dma",
"i2c3_rx_dma",
"i2c3_tx_dma",
"dcmi_dma",
"cryp_in_dma",
"cryp_out_dma",
"hash_in_dma",
"uart7_rx_dma",
"uart7_tx_dma",
"uart8_rx_dma",
"uart8_tx_dma",
"spi4_rx_dma",
"spi4_tx_dma",
"spi5_rx_dma",
"spi5_tx_dma",
"sai1a_dma",
"sai1b_dma",
"sai2a_dma",
"sai2b_dma",
"swpmi_rx_dma",
"swpmi_tx_dma",
"spdifrx_dat_dma",
"spdifrx_ctrl_dma",
"HR_REQ(1)",
"HR_REQ(2)",
"HR_REQ(3)",
"HR_REQ(4)",
"HR_REQ(5)",
"HR_REQ(6)",
"dfsdm1_dma0",
"dfsdm1_dma1",
"dfsdm1_dma2",
"dfsdm1_dma3",
"TIM15_CH1",
"TIM15_UP",
"TIM15_TRIG",
"TIM15_COM",
"TIM16_CH1",
"TIM16_UP",
"TIM17_CH1",
"TIM17_UP",
"sai3_a_dma",
"sai3_b_dma",
"adc3_dma",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
];
const TABLE_126: [&str; 8] = [
"dmamux1_evt0",
"dmamux1_evt1",
"dmamux1_evt2",
"lptim1_out",
"lptim2_out",
"lptim3_out",
"extit0",
"TIM12_TRGO",
];
const TABLE_128: [&str; 32] = [
"dmamux2_req_gen0",
"dmamux2_req_gen1",
"dmamux2_req_gen2",
"dmamux2_req_gen3",
"dmamux2_req_gen4",
"dmamux2_req_gen5",
"dmamux2_req_gen6",
"dmamux2_req_gen7",
"lpuart1_rx_dma",
"lpuart1_tx_dma",
"spi6_rx_dma",
"spi6_tx_dma",
"i2c4_rx_dma",
"i2c4_tx_dma",
"sai4_a_dma",
"sai4_b_dma",
"adc3_dma",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
];
const TABLE_129: [&str; 32] = [
"dmamux2_evt0",
"dmamux2_evt1",
"dmamux2_evt2",
"dmamux2_evt3",
"dmamux2_evt4",
"dmamux2_evt5",
"dmamux2_evt6",
"lpuart_rx_wkup",
"lpuart_tx_wkup",
"lptim2_wkup",
"lptim2_out",
"lptim3_wkup",
"lptim3_out",
"Lptim4_ait",
"Lptim5_ait",
"I2c4_wkup",
"spi6_wkup",
"Comp1_out",
"Comp2_out",
"RTC_wkup",
"Syscfg_exti0_mux",
"Syscfg_exti2_mux",
"I2c4_event_it",
"spi6_it",
"Lpuart1_it_T",
"Lpuart1_it_R",
"adc3_it",
"adc3_awd1",
"bdma_ch0_it",
"bdma_ch1_it",
"Reserved",
"Reserved",
];
const TABLE_130: [&str; 32] = [
"dmamux2_evt0",
"dmamux2_evt1",
"dmamux2_evt2",
"dmamux2_evt3",
"dmamux2_evt4",
"dmamux2_evt5",
"lpuart1_rx_wkup",
"lpuart1_tx_wkup",
"Lptim2_out",
"Lptim3_out",
"I2c4_wkup",
"spi6_wkup",
"Comp1_out",
"RTC_wkup",
"Syscfg_exti0_mux",
"Syscfg_exti2_mux",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
"Reserved",
];
}