use std::collections::VecDeque;
use std::marker::PhantomData;
use crate::device::{Device, DeviceContext, Level, RegisterFile, RequestChange};
use crate::snapshot::{self, Reader, Store, Writer};
use crate::soc::{FieldSpec, Layout, RegisterSpec, Width, field_of, row_of};
pub const STREAMS: usize = 8;
pub const CHANNELS: usize = 8;
const FIFO_BYTES: usize = 16;
const FIRST_STREAM_ROW: usize = 4;
const ROWS_PER_STREAM: usize = 6;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Row {
Control = 0,
Count = 1,
Peripheral = 2,
Memory0 = 3,
Memory1 = 4,
Fifo = 5,
}
const ROWS: [Row; ROWS_PER_STREAM] = [
Row::Control,
Row::Count,
Row::Peripheral,
Row::Memory0,
Row::Memory1,
Row::Fifo,
];
const FLAG_NAMES: [[&str; 5]; STREAMS] = [
["FEIF0", "DMEIF0", "TEIF0", "HTIF0", "TCIF0"],
["FEIF1", "DMEIF1", "TEIF1", "HTIF1", "TCIF1"],
["FEIF2", "DMEIF2", "TEIF2", "HTIF2", "TCIF2"],
["FEIF3", "DMEIF3", "TEIF3", "HTIF3", "TCIF3"],
["FEIF4", "DMEIF4", "TEIF4", "HTIF4", "TCIF4"],
["FEIF5", "DMEIF5", "TEIF5", "HTIF5", "TCIF5"],
["FEIF6", "DMEIF6", "TEIF6", "HTIF6", "TCIF6"],
["FEIF7", "DMEIF7", "TEIF7", "HTIF7", "TCIF7"],
];
const fn flags(table: &'static [RegisterSpec]) -> [[&'static FieldSpec; 5]; STREAMS] {
let mut flags = [[field_of(table, "LISR", "FEIF0"); 5]; STREAMS];
let mut stream = 0;
while stream < STREAMS {
let status = if stream < STREAMS / 2 { "LISR" } else { "HISR" };
let mut flag = 0;
while flag < 5 {
flags[stream][flag] = field_of(table, status, FLAG_NAMES[stream][flag]);
flag += 1;
}
stream += 1;
}
flags
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Flag {
Fifo = 0,
Direct = 1,
Transfer = 2,
Half = 3,
Complete = 4,
}
#[derive(Debug, Clone, Copy)]
pub struct Wire {
pub instance: &'static str,
pub request: usize,
pub channel: usize,
pub streams: &'static [usize],
}
#[derive(Debug, Clone, Copy)]
pub enum Requests {
Channels {
wiring: &'static [Wire],
last_data: &'static [Wire],
select: &'static FieldSpec,
},
Multiplexer {
instance: &'static str,
first: usize,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Span {
pub base: u64,
pub bytes: u64,
}
impl Span {
#[must_use]
pub const fn overlaps(self, address: u64, bytes: u64) -> bool {
address < self.base + self.bytes && address + bytes > self.base
}
#[must_use]
pub const fn holds(self, address: u64, bytes: u64) -> bool {
address >= self.base && address + bytes <= self.base + self.bytes
}
}
#[derive(Debug, Clone, Copy)]
pub struct Reach {
pub memory_to_memory: bool,
pub unreachable: &'static [Span],
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum Direction {
PeripheralToMemory,
MemoryToPeripheral,
MemoryToMemory,
}
impl Direction {
fn of(encoding: u64) -> Option<Self> {
match encoding {
0 => Some(Self::PeripheralToMemory),
1 => Some(Self::MemoryToPeripheral),
2 => Some(Self::MemoryToMemory),
_ => None,
}
}
fn peripheral_is_the_source(self) -> bool {
self != Self::MemoryToPeripheral
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
enum Port {
Source = 0,
Destination = 1,
}
#[derive(Clone, Copy, Debug)]
struct Shape {
direction: Direction,
peripheral_width: Width,
memory_width: Width,
direct: bool,
threshold: u64,
circular: bool,
double_buffer: bool,
peripheral_step: Option<u64>,
memory_step: Option<u64>,
memory_beats: u64,
channel: usize,
}
impl Shape {
fn source_width(self) -> Width {
if self.direction.peripheral_is_the_source() {
self.peripheral_width
} else {
self.memory_width
}
}
fn destination_width(self) -> Width {
if self.direction.peripheral_is_the_source() {
self.memory_width
} else {
self.peripheral_width
}
}
fn request_gates_the_destination(self) -> bool {
self.direction == Direction::MemoryToPeripheral
}
fn serves_a_request_on(self, port: Port) -> bool {
matches!(
(self.direction, port),
(Direction::PeripheralToMemory, Port::Source)
| (Direction::MemoryToPeripheral, Port::Destination)
)
}
}
fn data_width(encoding: u64) -> Width {
match encoding {
0 => Width::W8,
1 => Width::W16,
_ => Width::W32,
}
}
fn burst_beats(encoding: u64) -> u64 {
match encoding {
0 => 1,
other => 4 << (other - 1),
}
}
fn threshold_bytes(encoding: u64) -> u64 {
(encoding + 1) * 4
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
enum Finish {
Running,
Signaled,
Flushing,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
enum Phase {
Neither,
Refilling,
Draining,
}
#[derive(Debug, serde::Serialize, serde::Deserialize)]
struct Stream {
peripheral_at: u64,
memory_at: u64,
fifo: VecDeque<u8>,
to_load: u64,
to_store: u64,
stored: u64,
total: u64,
programmed: u64,
outstanding: Option<Port>,
inputs: [bool; CHANNELS],
halved: bool,
finish: Finish,
phase: Phase,
guarded: [u64; ROWS_PER_STREAM],
interrupt: Level,
}
impl Default for Stream {
fn default() -> Self {
Self {
peripheral_at: 0,
memory_at: 0,
fifo: VecDeque::with_capacity(FIFO_BYTES),
to_load: 0,
to_store: 0,
stored: 0,
total: 0,
programmed: 0,
outstanding: None,
inputs: [false; CHANNELS],
halved: false,
finish: Finish::Running,
phase: Phase::Neither,
guarded: [0; ROWS_PER_STREAM],
interrupt: Level::Low,
}
}
}
#[derive(Debug)]
pub struct Dma<L> {
streams: [Stream; STREAMS],
requests: Requests,
reach: Reach,
layout: PhantomData<L>,
}
impl<L: Layout> Store for Dma<L> {
fn store(&self, to: &mut Writer) -> Result<(), snapshot::Error> {
to.put(&self.streams)
}
fn load(&mut self, from: &mut Reader) -> Result<(), snapshot::Error> {
self.streams = from.take()?;
Ok(())
}
}
impl<L: Layout> Dma<L> {
const LISR: usize = row_of(L::REGISTERS, "LISR");
const HISR: usize = row_of(L::REGISTERS, "HISR");
const LIFCR: usize = row_of(L::REGISTERS, "LIFCR");
const HIFCR: usize = row_of(L::REGISTERS, "HIFCR");
const FLAGS: [[&'static FieldSpec; 5]; STREAMS] = flags(L::REGISTERS);
const EN: &'static FieldSpec = field_of(L::REGISTERS, "S0CR", "EN");
const DMEIE: &'static FieldSpec = field_of(L::REGISTERS, "S0CR", "DMEIE");
const TEIE: &'static FieldSpec = field_of(L::REGISTERS, "S0CR", "TEIE");
const HTIE: &'static FieldSpec = field_of(L::REGISTERS, "S0CR", "HTIE");
const TCIE: &'static FieldSpec = field_of(L::REGISTERS, "S0CR", "TCIE");
const PFCTRL: &'static FieldSpec = field_of(L::REGISTERS, "S0CR", "PFCTRL");
const DIR: &'static FieldSpec = field_of(L::REGISTERS, "S0CR", "DIR");
const CIRC: &'static FieldSpec = field_of(L::REGISTERS, "S0CR", "CIRC");
const PINC: &'static FieldSpec = field_of(L::REGISTERS, "S0CR", "PINC");
const MINC: &'static FieldSpec = field_of(L::REGISTERS, "S0CR", "MINC");
const PSIZE: &'static FieldSpec = field_of(L::REGISTERS, "S0CR", "PSIZE");
const MSIZE: &'static FieldSpec = field_of(L::REGISTERS, "S0CR", "MSIZE");
const PINCOS: &'static FieldSpec = field_of(L::REGISTERS, "S0CR", "PINCOS");
const DBM: &'static FieldSpec = field_of(L::REGISTERS, "S0CR", "DBM");
const CT: &'static FieldSpec = field_of(L::REGISTERS, "S0CR", "CT");
const PBURST: &'static FieldSpec = field_of(L::REGISTERS, "S0CR", "PBURST");
const MBURST: &'static FieldSpec = field_of(L::REGISTERS, "S0CR", "MBURST");
const NDT: &'static FieldSpec = field_of(L::REGISTERS, "S0NDTR", "NDT");
const FTH: &'static FieldSpec = field_of(L::REGISTERS, "S0FCR", "FTH");
const DMDIS: &'static FieldSpec = field_of(L::REGISTERS, "S0FCR", "DMDIS");
const FS: &'static FieldSpec = field_of(L::REGISTERS, "S0FCR", "FS");
const FEIE: &'static FieldSpec = field_of(L::REGISTERS, "S0FCR", "FEIE");
#[must_use]
pub fn wired(requests: Requests, reach: Reach) -> Self {
Self {
streams: std::array::from_fn(|_| Stream::default()),
requests,
reach,
layout: PhantomData,
}
}
#[must_use]
pub fn remaining(&self, stream: usize) -> (u64, u64) {
(self.streams[stream].to_load, self.streams[stream].to_store)
}
fn register(stream: usize, row: Row) -> L {
let index = FIRST_STREAM_ROW + stream * ROWS_PER_STREAM + row as usize;
L::from_index(index).expect("a controller carries eight streams of six registers")
}
fn row(index: usize) -> L {
L::from_index(index).expect("a status register of the controller")
}
fn held(registers: &RegisterFile, stream: usize, row: Row) -> u64 {
registers.get(Self::register(stream, row))
}
fn modify(
registers: &mut RegisterFile,
stream: usize,
row: Row,
change: impl FnOnce(u64) -> u64,
) {
let target = Self::register(stream, row);
let value = registers.get(target);
registers.set(target, change(value));
}
fn shape(&self, registers: &RegisterFile, stream: usize) -> Option<Shape> {
let control = Self::held(registers, stream, Row::Control);
let fifo = Self::held(registers, stream, Row::Fifo);
let direction = Direction::of(Self::DIR.get(control))?;
let direct = !Self::DMDIS.is_set(fifo);
let peripheral_width = data_width(Self::PSIZE.get(control));
let memory_width = data_width(Self::MSIZE.get(control));
let peripheral_step = Self::PINC.is_set(control).then(|| {
if Self::PINCOS.is_set(control) {
4
} else {
u64::from(peripheral_width.bytes())
}
});
let channel = match self.requests {
Requests::Channels { select, .. } => {
usize::try_from(select.get(control)).expect("three bits")
}
Requests::Multiplexer { .. } => 0,
};
Some(Shape {
direction,
peripheral_width,
memory_width,
direct,
threshold: threshold_bytes(Self::FTH.get(fifo)),
circular: Self::CIRC.is_set(control),
double_buffer: Self::DBM.is_set(control),
peripheral_step,
memory_step: Self::MINC
.is_set(control)
.then(|| u64::from(memory_width.bytes())),
memory_beats: burst_beats(Self::MBURST.get(control)),
channel,
})
}
fn enabled(registers: &RegisterFile, stream: usize) -> bool {
Self::EN.is_set(Self::held(registers, stream, Row::Control))
}
fn status_of(stream: usize) -> L {
Self::row(if stream < STREAMS / 2 {
Self::LISR
} else {
Self::HISR
})
}
fn set_flag(registers: &mut RegisterFile, stream: usize, flag: Flag, raised: bool) {
let status = Self::status_of(stream);
let value = registers.get(status);
let field = Self::FLAGS[stream][flag as usize];
registers.set(status, field.set(value, u64::from(raised)));
}
fn flag_stands(registers: &RegisterFile, stream: usize, flag: Flag) -> bool {
Self::FLAGS[stream][flag as usize].is_set(registers.get(Self::status_of(stream)))
}
fn disable(registers: &mut RegisterFile, stream: usize) {
Self::modify(registers, stream, Row::Control, |control| {
Self::EN.set(control, 0)
});
}
fn reaches(&self, address: u64, width: Width) -> bool {
let bytes = u64::from(width.bytes());
!self
.reach
.unreachable
.iter()
.any(|span| span.overlaps(address, bytes))
}
fn start(
&mut self,
stream: usize,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let control = Self::held(registers, stream, Row::Control);
let mut forced = control;
if Self::DBM.is_set(control) {
forced = Self::CIRC.set(forced, 1);
}
if Self::PFCTRL.is_set(control) {
forced = Self::CIRC.set(forced, 0);
}
let memory_to_memory =
Direction::of(Self::DIR.get(control)) == Some(Direction::MemoryToMemory);
if memory_to_memory {
forced = Self::PFCTRL.set(forced, 0);
Self::modify(registers, stream, Row::Fifo, |fifo| {
Self::DMDIS.set(fifo, 1)
});
}
let direct = !Self::DMDIS.is_set(Self::held(registers, stream, Row::Fifo));
if direct {
forced = Self::MSIZE.set(forced, Self::PSIZE.get(forced));
forced = Self::MBURST.set(forced, 0);
forced = Self::PBURST.set(forced, 0);
}
if direct || Self::PBURST.get(forced) != 0 {
forced = Self::PINCOS.set(forced, 0);
}
registers.set(Self::register(stream, Row::Control), forced);
let Some(shape) = self.shape(registers, stream) else {
return;
};
if memory_to_memory && !self.reach.memory_to_memory {
self.fail(stream, Flag::Transfer, registers, context);
return;
}
if !shape.direct
&& shape.threshold % (shape.memory_beats * u64::from(shape.memory_width.bytes())) != 0
{
self.fail(stream, Flag::Fifo, registers, context);
return;
}
let programmed = self.streams[stream].programmed;
Self::modify(registers, stream, Row::Count, |count| {
Self::NDT.set(count, programmed)
});
if Self::PFCTRL.is_set(forced) {
Self::modify(registers, stream, Row::Count, |count| {
Self::NDT.set(count, Self::NDT.mask() >> Self::NDT.bit)
});
}
let items = Self::NDT.get(Self::held(registers, stream, Row::Count));
let total = items * u64::from(shape.peripheral_width.bytes());
let state = &mut self.streams[stream];
state.fifo.clear();
state.total = total;
state.to_load = total;
state.to_store = total;
state.stored = 0;
state.halved = false;
state.finish = Finish::Running;
state.phase = Phase::Refilling;
state.outstanding = None;
self.take_pointers(stream, registers, shape);
self.report_fifo(stream, registers);
self.pump(stream, registers, context);
}
fn take_pointers(&mut self, stream: usize, registers: &RegisterFile, shape: Shape) {
let control = Self::held(registers, stream, Row::Control);
let second = shape.double_buffer && Self::CT.is_set(control);
let memory = if second { Row::Memory1 } else { Row::Memory0 };
let state = &mut self.streams[stream];
state.peripheral_at = Self::held(registers, stream, Row::Peripheral);
state.memory_at = Self::held(registers, stream, memory);
}
fn fail(
&mut self,
stream: usize,
flag: Flag,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
Self::set_flag(registers, stream, flag, true);
Self::disable(registers, stream);
let state = &mut self.streams[stream];
state.outstanding = None;
state.to_load = 0;
state.to_store = 0;
state.fifo.clear();
state.finish = Finish::Running;
self.report_fifo(stream, registers);
self.refresh_interrupt(stream, registers, context);
}
fn suspend(
&mut self,
stream: usize,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let shape = self.shape(registers, stream);
let flushes = shape.is_some_and(|shape| {
shape.direction != Direction::MemoryToPeripheral
&& !self.streams[stream].fifo.is_empty()
});
self.streams[stream].to_load = 0;
if flushes {
self.streams[stream].finish = Finish::Flushing;
self.pump(stream, registers, context);
return;
}
self.streams[stream].to_store = 0;
self.stop(stream, registers);
self.refresh_interrupt(stream, registers, context);
}
fn stop(&mut self, stream: usize, registers: &mut RegisterFile) {
Self::set_flag(registers, stream, Flag::Complete, true);
self.streams[stream].finish = Finish::Running;
self.streams[stream].fifo.clear();
self.report_fifo(stream, registers);
Self::disable(registers, stream);
}
}
fn token(stream: usize, port: Port) -> u64 {
let stream = u64::try_from(stream).expect("eight streams");
stream * 2 + port as u64
}
fn from_token(token: u64) -> (usize, Port) {
let stream = usize::try_from(token / 2).expect("eight streams");
let port = if token.is_multiple_of(2) {
Port::Source
} else {
Port::Destination
};
(stream, port)
}
impl<L: Layout> Dma<L> {
fn pump(
&mut self,
stream: usize,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let Some(shape) = self.shape(registers, stream) else {
return;
};
if self.streams[stream].outstanding.is_some() {
return;
}
if self.streams[stream].finish != Finish::Flushing && !Self::enabled(registers, stream) {
return;
}
let Some(port) = self.next_port(stream, shape) else {
if self.streams[stream].finish == Finish::Flushing {
self.stop(stream, registers);
self.refresh_interrupt(stream, registers, context);
}
return;
};
let state = &self.streams[stream];
let (width, address) = match port {
Port::Source if shape.direction.peripheral_is_the_source() => {
(shape.source_width(), state.peripheral_at)
}
Port::Source => (shape.source_width(), state.memory_at),
Port::Destination if shape.direction.peripheral_is_the_source() => {
(shape.destination_width(), state.memory_at)
}
Port::Destination => (shape.destination_width(), state.peripheral_at),
};
if !self.reaches(address, width) {
self.fail(stream, Flag::Transfer, registers, context);
return;
}
if matches!(self.requests, Requests::Multiplexer { .. }) && shape.serves_a_request_on(port)
{
context.request(stream, Level::High);
context.request(stream, Level::Low);
}
match port {
Port::Source => context.read_from(address, width, token(stream, port)),
Port::Destination => {
let value = self.take(stream, width);
self.report_fifo(stream, registers);
context.write_to(address, width, value, token(stream, port));
}
}
self.streams[stream].outstanding = Some(port);
}
fn next_port(&self, stream: usize, shape: Shape) -> Option<Port> {
let state = &self.streams[stream];
let source_bytes = u64::from(shape.source_width().bytes());
let destination_bytes = u64::from(shape.destination_width().bytes());
let level = u64::try_from(state.fifo.len()).expect("sixteen bytes");
let room = u64::try_from(FIFO_BYTES).expect("sixteen bytes") - level;
let requested = state.inputs[shape.channel];
let drained = shape.direct
|| state.finish == Finish::Flushing
|| state.phase == Phase::Draining
|| level >= shape.threshold
|| state.to_load == 0;
let store_allowed = if shape.request_gates_the_destination() {
requested
} else {
drained
};
let partial_tail = state.finish == Finish::Flushing && level > 0;
if state.to_store > 0 && (level >= destination_bytes || partial_tail) && store_allowed {
return Some(Port::Destination);
}
let load_allowed = match shape.direction {
Direction::PeripheralToMemory => requested,
Direction::MemoryToPeripheral => shape.direct || state.phase == Phase::Refilling,
Direction::MemoryToMemory => true,
} && (!shape.direct || level < destination_bytes);
if state.finish != Finish::Flushing
&& state.to_load >= source_bytes
&& room >= source_bytes
&& load_allowed
{
return Some(Port::Source);
}
None
}
fn give(&mut self, stream: usize, width: Width, value: u64) {
for offset in 0..width.bytes() {
let byte = u8::try_from((value >> (offset * 8)) & 0xFF).expect("one byte");
self.streams[stream].fifo.push_back(byte);
}
}
fn take(&mut self, stream: usize, width: Width) -> u64 {
(0..width.bytes())
.map(|offset| {
let byte = self.streams[stream].fifo.pop_front().unwrap_or(0);
u64::from(byte) << (offset * 8)
})
.sum()
}
fn report_fifo(&mut self, stream: usize, registers: &mut RegisterFile) {
let level = self.streams[stream].fifo.len();
let encoding = match level {
0 => 4,
FIFO_BYTES => 5,
partial => u64::try_from(partial / (FIFO_BYTES / 4)).expect("three quarters"),
};
Self::modify(registers, stream, Row::Fifo, |fifo| {
Self::FS.set(fifo, encoding)
});
}
fn set_count(&mut self, stream: usize, registers: &mut RegisterFile, shape: Shape) {
if self.streams[stream].finish == Finish::Flushing {
return;
}
let state = &self.streams[stream];
let remaining = if shape.direction.peripheral_is_the_source() {
state.to_load
} else {
state.to_store
};
let items = remaining / u64::from(shape.peripheral_width.bytes());
Self::modify(registers, stream, Row::Count, |count| {
Self::NDT.set(count, items)
});
}
fn answered(
&mut self,
token: u64,
value: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let (stream, port) = from_token(token);
let Some(shape) = self.shape(registers, stream) else {
return;
};
if self.streams[stream].outstanding != Some(port) {
return;
}
self.streams[stream].outstanding = None;
let peripheral_is_the_source = shape.direction.peripheral_is_the_source();
match port {
Port::Source => {
let width = shape.source_width();
self.give(stream, width, value);
let taken = u64::from(width.bytes()).min(self.streams[stream].to_load);
self.streams[stream].to_load -= taken;
self.step(stream, peripheral_is_the_source, shape);
let state = &mut self.streams[stream];
let level = u64::try_from(state.fifo.len()).expect("sixteen bytes");
if shape.direction == Direction::MemoryToPeripheral {
if state.fifo.len() == FIFO_BYTES || state.to_load == 0 {
state.phase = Phase::Neither;
}
} else if level >= shape.threshold {
state.phase = Phase::Draining;
}
}
Port::Destination => {
let width = shape.destination_width();
let moved = u64::from(width.bytes());
self.streams[stream].to_store -= moved.min(self.streams[stream].to_store);
self.streams[stream].stored += moved;
self.step(stream, !peripheral_is_the_source, shape);
let state = &mut self.streams[stream];
let level = u64::try_from(state.fifo.len()).expect("sixteen bytes");
if shape.direction == Direction::MemoryToPeripheral {
if level <= shape.threshold {
state.phase = Phase::Refilling;
}
} else if level < moved {
state.phase = Phase::Neither;
}
if !state.halved && state.stored * 2 >= state.total {
self.streams[stream].halved = true;
Self::set_flag(registers, stream, Flag::Half, true);
}
}
}
self.report_fifo(stream, registers);
self.set_count(stream, registers, shape);
if self.streams[stream].to_store == 0 {
self.complete(stream, shape, registers);
}
self.refresh_interrupt(stream, registers, context);
self.pump(stream, registers, context);
}
fn step(&mut self, stream: usize, peripheral: bool, shape: Shape) {
let state = &mut self.streams[stream];
if peripheral {
if let Some(step) = shape.peripheral_step {
state.peripheral_at = state.peripheral_at.wrapping_add(step);
}
} else if let Some(step) = shape.memory_step {
state.memory_at = state.memory_at.wrapping_add(step);
}
}
fn end_if_signaled(
&mut self,
stream: usize,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let Some(shape) = self.shape(registers, stream) else {
return;
};
let state = &self.streams[stream];
let control = Self::held(registers, stream, Row::Control);
if state.finish != Finish::Signaled
|| state.inputs[shape.channel]
|| state.outstanding.is_some()
|| !Self::enabled(registers, stream)
|| !Self::PFCTRL.is_set(control)
{
return;
}
self.streams[stream].finish = Finish::Flushing;
self.pump(stream, registers, context);
}
fn complete(&mut self, stream: usize, shape: Shape, registers: &mut RegisterFile) {
let again = shape.circular
&& shape.direction != Direction::MemoryToMemory
&& self.streams[stream].finish != Finish::Flushing;
if !again {
self.stop(stream, registers);
return;
}
Self::set_flag(registers, stream, Flag::Complete, true);
if shape.double_buffer {
Self::modify(registers, stream, Row::Control, |control| {
Self::CT.set(control, u64::from(!Self::CT.is_set(control)))
});
}
let state = &mut self.streams[stream];
state.to_load = state.total;
state.to_store = state.total;
state.stored = 0;
state.halved = false;
self.set_count(stream, registers, shape);
self.take_pointers(stream, registers, shape);
}
fn refresh_interrupt(
&mut self,
stream: usize,
registers: &RegisterFile,
context: &mut DeviceContext<'_>,
) {
let control = Self::held(registers, stream, Row::Control);
let fifo = Self::held(registers, stream, Row::Fifo);
let enabled = [
(Flag::Fifo, Self::FEIE.is_set(fifo)),
(Flag::Direct, Self::DMEIE.is_set(control)),
(Flag::Transfer, Self::TEIE.is_set(control)),
(Flag::Half, Self::HTIE.is_set(control)),
(Flag::Complete, Self::TCIE.is_set(control)),
];
let wanted = Level::from(
enabled
.into_iter()
.any(|(flag, on)| on && Self::flag_stands(registers, stream, flag)),
);
if wanted == self.streams[stream].interrupt {
return;
}
self.streams[stream].interrupt = wanted;
context.interrupt(stream, wanted);
}
fn stream_row(register: L) -> Option<(usize, Row)> {
let index = register.index().checked_sub(FIRST_STREAM_ROW)?;
Some((index / ROWS_PER_STREAM, ROWS[index % ROWS_PER_STREAM]))
}
fn remember(&mut self, stream: usize, registers: &RegisterFile) {
for row in ROWS {
self.streams[stream].guarded[row as usize] = Self::held(registers, stream, row);
}
}
fn clear_flags(
&mut self,
clear: L,
written: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let (status, streams) = if clear.index() == Self::LIFCR {
(Self::row(Self::LISR), 0..STREAMS / 2)
} else {
(Self::row(Self::HISR), STREAMS / 2..STREAMS)
};
let standing = registers.get(status);
registers.set(status, standing & !written);
registers.set(clear, 0);
for stream in streams {
self.refresh_interrupt(stream, registers, context);
}
}
fn write_stream(
&mut self,
stream: usize,
row: Row,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let kept = self.streams[stream].guarded[row as usize];
let running = Self::EN.is_set(self.streams[stream].guarded[Row::Control as usize]);
let target = Self::register(stream, row);
match row {
Row::Control if running => {
let open = Self::EN.mask()
| Self::DMEIE.mask()
| Self::TEIE.mask()
| Self::HTIE.mask()
| Self::TCIE.mask();
registers.restore(target, !open, kept);
}
Row::Fifo if running => {
registers.restore(target, !Self::FEIE.mask(), kept);
}
Row::Memory0 | Row::Memory1 if running => {
let control = Self::held(registers, stream, Row::Control);
let second_is_current = Self::CT.is_set(control);
let current = (row == Row::Memory1) == second_is_current;
if !Self::DBM.is_set(control) || current {
registers.set(target, kept);
}
if Self::DBM.is_set(control) && current {
self.fail(stream, Flag::Transfer, registers, context);
}
return;
}
_ if running => {
registers.set(target, kept);
return;
}
Row::Count => {
self.streams[stream].programmed =
Self::NDT.get(Self::held(registers, stream, Row::Count));
}
_ => {}
}
if row != Row::Control {
return;
}
let now = Self::enabled(registers, stream);
match (running, now) {
(false, true) => self.start(stream, registers, context),
(true, false) => self.suspend(stream, registers, context),
_ => self.refresh_interrupt(stream, registers, context),
}
}
fn heard(
&mut self,
stream: usize,
high: bool,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
if high {
self.pump(stream, registers, context);
} else {
self.end_if_signaled(stream, registers, context);
}
self.remember(stream, registers);
}
}
impl<L: Layout> Device for Dma<L> {
type Register = L;
fn write(
&mut self,
register: L,
value: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
if register.index() == Self::LIFCR || register.index() == Self::HIFCR {
self.clear_flags(register, value, registers, context);
} else if let Some((stream, row)) = Self::stream_row(register) {
self.write_stream(stream, row, registers, context);
self.remember(stream, registers);
self.refresh_interrupt(stream, registers, context);
}
}
fn on_request(
&mut self,
change: RequestChange,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let high = change.level == Level::High;
match self.requests {
Requests::Multiplexer { instance, first } => {
if change.instance != instance {
return;
}
if let Some(stream) = change
.request
.checked_sub(first)
.filter(|stream| *stream < STREAMS)
{
self.streams[stream].inputs[0] = high;
self.heard(stream, high, registers, context);
}
}
Requests::Channels {
wiring, last_data, ..
} => {
let arriving = |wire: &&Wire| {
wire.instance == change.instance && wire.request == change.request
};
for wire in wiring.iter().filter(arriving) {
for &stream in wire.streams {
self.streams[stream].inputs[wire.channel] = high;
self.heard(stream, high, registers, context);
}
}
if !high {
return;
}
for wire in last_data.iter().filter(arriving) {
for &stream in wire.streams {
if self
.shape(registers, stream)
.is_some_and(|shape| shape.channel == wire.channel)
&& self.streams[stream].finish == Finish::Running
{
self.streams[stream].finish = Finish::Signaled;
}
self.end_if_signaled(stream, registers, context);
self.remember(stream, registers);
}
}
}
}
}
fn on_access(
&mut self,
token: u64,
value: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
self.answered(token, value, registers, context);
let stream = from_token(token).0;
self.end_if_signaled(stream, registers, context);
self.remember(stream, registers);
}
fn on_refused(
&mut self,
token: u64,
registers: &mut RegisterFile,
context: &mut DeviceContext<'_>,
) {
let (stream, port) = from_token(token);
if self.streams[stream].outstanding == Some(port) {
self.fail(stream, Flag::Transfer, registers, context);
}
self.remember(stream, registers);
}
fn reset(&mut self) {
for stream in &mut self.streams {
*stream = Stream {
inputs: stream.inputs,
..Stream::default()
};
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn the_streams_registers_run_in_blocks_of_six<L: Layout>() {
const SUFFIXES: [&str; ROWS_PER_STREAM] = ["CR", "NDTR", "PAR", "M0AR", "M1AR", "FCR"];
assert_eq!(
L::REGISTERS.len(),
FIRST_STREAM_ROW + STREAMS * ROWS_PER_STREAM,
"four status registers and six to a stream"
);
for number in 0..STREAMS {
for (index, row) in ROWS.into_iter().enumerate() {
let spec = Dma::<L>::register(number, row).spec();
assert_eq!(spec.name, format!("S{number}{}", SUFFIXES[index]));
assert_eq!(
u64::from(spec.offset),
0x10 + 0x18 * number as u64 + 4 * index as u64
);
}
}
}
fn every_streams_flags_sit_where_the_manual_groups_them<L: Layout>() {
const GROUPS: [u8; 4] = [0, 6, 16, 22];
const WITHIN: [u8; 5] = [0, 2, 3, 4, 5];
for number in 0..STREAMS {
let status = if number < STREAMS / 2 { "LISR" } else { "HISR" };
assert_eq!(Dma::<L>::status_of(number).spec().name, status);
for (index, within) in WITHIN.into_iter().enumerate() {
let field = Dma::<L>::FLAGS[number][index];
assert_eq!(field.bit, GROUPS[number % 4] + within, "{}", field.name);
assert_eq!(field.width, 1);
}
}
}
fn every_flag_has_its_clearing_bit_at_the_same_position<L: Layout>() {
for (number, flags) in Dma::<L>::FLAGS.into_iter().enumerate() {
let clearing = if number < STREAMS / 2 {
"LIFCR"
} else {
"HIFCR"
};
for field in flags {
let clear = field_of(L::REGISTERS, clearing, &format!("C{}", field.name));
assert_eq!(clear.bit, field.bit, "{}", field.name);
}
}
}
#[cfg(feature = "stm32f427")]
mod stm32f427 {
use crate::soc::stm32f427::generated::dma2::Register;
#[test]
fn the_streams_registers_run_in_blocks_of_six() {
super::the_streams_registers_run_in_blocks_of_six::<Register>();
}
#[test]
fn every_streams_flags_sit_where_the_manual_groups_them() {
super::every_streams_flags_sit_where_the_manual_groups_them::<Register>();
}
#[test]
fn every_flag_has_its_clearing_bit_at_the_same_position() {
super::every_flag_has_its_clearing_bit_at_the_same_position::<Register>();
}
}
#[cfg(feature = "stm32h743")]
mod stm32h743 {
use crate::device::{Device, Level, RequestChange};
use crate::mcu::{Operate, Single};
use crate::snapshot;
use crate::soc::stm32h743::generated::dma1::{Register, s0cr, s0fcr};
use crate::soc::stm32h743::peripherals::dma::{DMA1_FIRST_CHANNEL, Dma};
use crate::soc::{Layout, Width};
use crate::time::VirtualNanos;
#[test]
fn the_streams_registers_run_in_blocks_of_six() {
super::the_streams_registers_run_in_blocks_of_six::<Register>();
}
#[test]
fn every_streams_flags_sit_where_the_manual_groups_them() {
super::every_streams_flags_sit_where_the_manual_groups_them::<Register>();
}
#[test]
fn every_flag_has_its_clearing_bit_at_the_same_position() {
super::every_flag_has_its_clearing_bit_at_the_same_position::<Register>();
}
#[derive(Debug, Default)]
struct Ram(std::collections::BTreeMap<u64, u8>);
impl crate::emulator::Memory for Ram {
fn read(&mut self, address: u64, width: Width) -> Result<u64, crate::emulator::Error> {
Ok((0..u64::from(width.bytes()))
.map(|offset| {
u64::from(self.0.get(&(address + offset)).copied().unwrap_or(0))
<< (offset * 8)
})
.sum())
}
fn write(
&mut self,
address: u64,
width: Width,
value: u64,
) -> Result<(), crate::emulator::Error> {
for offset in 0..u64::from(width.bytes()) {
let byte = u8::try_from((value >> (offset * 8)) & 0xFF).expect("one byte");
self.0.insert(address + offset, byte);
}
Ok(())
}
}
#[test]
fn the_model_state_round_trips_through_a_snapshot() {
const AT: VirtualNanos = VirtualNanos(0);
let mut soc = Single::new(
crate::soc::stm32h743::builder(),
"DMA1",
Dma::new(DMA1_FIRST_CHANNEL),
)
.expect("the bus builds");
let mut memory = Ram::default();
let base = crate::soc::stm32h743::generated::DMA1.base;
let address = |register: Register| base + u64::from(register.spec().offset);
let write = |soc: &mut Single<Dma>, register: Register, value: u64| {
soc.write(AT, address(register), Width::W32, value)
.expect("the controller answers");
};
write(
&mut soc,
Register::S2fcr,
s0fcr::DMDIS.set(s0fcr::FTH.set(0, 3), 1),
);
write(&mut soc, Register::S2par, 0x3000_0000);
write(&mut soc, Register::S2m0ar, 0x3000_1000);
write(&mut soc, Register::S2ndtr, 16);
write(
&mut soc,
Register::S2cr,
s0cr::MINC.set(s0cr::EN.set(0, 1), 1),
);
for level in [Level::High, Level::Low] {
soc.drive(AT, |model, registers, context| {
let change = RequestChange {
instance: "DMAMUX1",
request: 2,
level,
};
model.on_request(change, registers, context);
});
}
soc.settle(&mut memory).expect("the memory answers");
assert_eq!(
soc.model.streams[2].fifo.len(),
1,
"one byte is in the FIFO"
);
let saved = snapshot::stored(&soc.model).expect("the model serializes");
let mut restored = Dma::new(DMA1_FIRST_CHANNEL);
snapshot::load(&mut restored, snapshot::keep(saved)).expect("and deserializes");
assert_eq!(format!("{restored:?}"), format!("{:?}", soc.model));
}
}
#[test]
fn the_burst_and_threshold_encodings_are_the_ones_the_manual_lists() {
assert_eq!([0, 1, 2, 3].map(burst_beats), [1, 4, 8, 16]);
assert_eq!([0, 1, 2, 3].map(threshold_bytes), [4, 8, 12, 16]);
assert_eq!(
[0, 1, 2].map(data_width),
[Width::W8, Width::W16, Width::W32]
);
}
#[test]
fn the_threshold_rule_is_tables_forty_nine_and_one_hundred_fourteen() {
const TABLE: [(u64, u64, u64, bool); 12] = [
(1, 4, 4, true),
(1, 4, 8, false),
(1, 8, 8, true),
(1, 12, 8, false),
(1, 16, 16, true),
(2, 4, 4, false),
(2, 8, 4, true),
(2, 12, 4, false),
(2, 16, 8, true),
(4, 4, 4, false),
(4, 12, 4, false),
(4, 16, 4, true),
];
for (width, threshold, beats, allowed) in TABLE {
assert_eq!(
threshold % (beats * width) == 0,
allowed,
"{beats} beats of {width} bytes against a threshold of {threshold}"
);
}
}
}