From 3080ea4ce29392a1c707cdf5f1954c114e95ab9f Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Mon, 2 Mar 2026 23:28:01 -0800 Subject: [PATCH] add address_range to MemoryInterfaceConfig and add support to simple_uart --- crates/cpu/src/main_memory_and_io.rs | 99 ++++++++++++++++++- .../cpu/src/main_memory_and_io/simple_uart.rs | 38 +++++-- crates/cpu/tests/expected/fetch.vcd | 48 ++++----- crates/cpu/tests/expected/simple_uart.vcd | 22 ++--- crates/cpu/tests/simple_uart.rs | 12 ++- 5 files changed, 169 insertions(+), 50 deletions(-) diff --git a/crates/cpu/src/main_memory_and_io.rs b/crates/cpu/src/main_memory_and_io.rs index d4c1462..af35662 100644 --- a/crates/cpu/src/main_memory_and_io.rs +++ b/crates/cpu/src/main_memory_and_io.rs @@ -3,16 +3,110 @@ use crate::{config::CpuConfig, next_pc::FETCH_BLOCK_ID_WIDTH, util::array_vec::ArrayVec}; use fayalite::{prelude::*, util::ready_valid::ReadyValid}; -use std::num::NonZeroUsize; +use std::num::{NonZeroU64, NonZeroUsize, Wrapping}; pub mod simple_uart; -#[derive(Clone, Eq, PartialEq, Hash, Debug, serde::Serialize, serde::Deserialize)] +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, serde::Serialize, serde::Deserialize)] +pub enum AddressRange { + Full, + Limited { + start: Wrapping, + size: NonZeroU64, + }, +} + +impl AddressRange { + pub const fn from_wrapping_range_inclusive(range: std::ops::RangeInclusive) -> Self { + let start = Wrapping(*range.start()); + let Some(size) = NonZeroU64::new(range.end().wrapping_sub(start.0).wrapping_add(1)) else { + return Self::Full; + }; + Self::Limited { start, size } + } + pub const fn try_from_range(range: std::ops::Range) -> Option { + let start = Wrapping(range.start); + let Some(size) = NonZeroU64::new(range.end.saturating_sub(range.start)) else { + return None; + }; + Some(Self::Limited { start, size }) + } + #[track_caller] + pub const fn from_range(range: std::ops::Range) -> Self { + Self::try_from_range(range).expect("range must not be empty") + } + pub const fn try_from_range_inclusive(range: std::ops::RangeInclusive) -> Option { + let start = Wrapping(*range.start()); + let Some(end_minus_start) = range.end().checked_sub(start.0) else { + return None; + }; + let Some(size) = end_minus_start.checked_add(1) else { + return Some(Self::Full); + }; + let Some(size) = NonZeroU64::new(size) else { + unreachable!(); + }; + Some(Self::Limited { start, size }) + } + #[track_caller] + pub const fn from_range_inclusive(range: std::ops::RangeInclusive) -> Self { + Self::try_from_range_inclusive(range).expect("range must not be empty") + } + pub const fn start(self) -> Wrapping { + match self { + Self::Full => Wrapping(0), + Self::Limited { start, .. } => start, + } + } + pub const fn size(self) -> Option { + match self { + Self::Full => None, + Self::Limited { size, .. } => Some(size), + } + } + pub const fn size_minus_one(self) -> u64 { + match self { + AddressRange::Full => u64::MAX, + AddressRange::Limited { size, .. } => size.get() - 1, + } + } + /// last address contained in `self` + pub const fn last(self) -> u64 { + self.start().0.wrapping_add(self.size_minus_one()) + } + pub const fn contains(self, address: u64) -> bool { + match self { + Self::Full => true, + Self::Limited { start, size } => address.wrapping_sub(start.0) < size.get(), + } + } + pub const fn wrapping_add(self, offset: u64) -> Self { + match self { + Self::Full => Self::Full, + Self::Limited { start, size } => Self::Limited { + start: Wrapping(start.0.wrapping_add(offset)), + size, + }, + } + } + pub const fn wrapping_sub(self, offset: u64) -> Self { + match self { + Self::Full => Self::Full, + Self::Limited { start, size } => Self::Limited { + start: Wrapping(start.0.wrapping_sub(offset)), + size, + }, + } + } +} + +#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug, serde::Serialize, serde::Deserialize)] #[non_exhaustive] pub struct MemoryInterfaceConfig { pub log2_bus_width_in_bytes: u8, pub queue_capacity: NonZeroUsize, pub op_id_width: usize, + pub address_range: AddressRange, } impl MemoryInterfaceConfig { @@ -21,6 +115,7 @@ impl MemoryInterfaceConfig { log2_bus_width_in_bytes: config.log2_fetch_width_in_bytes, queue_capacity: config.max_fetches_in_flight, op_id_width: FETCH_BLOCK_ID_WIDTH, + address_range: AddressRange::Full, } } pub const fn bus_width_in_bytes(&self) -> usize { diff --git a/crates/cpu/src/main_memory_and_io/simple_uart.rs b/crates/cpu/src/main_memory_and_io/simple_uart.rs index 82754b4..7ed24a0 100644 --- a/crates/cpu/src/main_memory_and_io/simple_uart.rs +++ b/crates/cpu/src/main_memory_and_io/simple_uart.rs @@ -1,12 +1,13 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use std::num::NonZeroUsize; +use std::num::{NonZeroU64, NonZeroUsize, Wrapping}; use crate::{ main_memory_and_io::{ - MemoryInterface, MemoryInterfaceConfig, MemoryOperationErrorKind, MemoryOperationFinish, - MemoryOperationFinishKind, MemoryOperationKind, MemoryOperationStart, + AddressRange, MemoryInterface, MemoryInterfaceConfig, MemoryOperationErrorKind, + MemoryOperationFinish, MemoryOperationFinishKind, MemoryOperationKind, + MemoryOperationStart, }, util::array_vec::ArrayVec, }; @@ -357,7 +358,8 @@ pub fn receiver(queue_capacity: NonZeroUsize) { pub const SIMPLE_UART_RECEIVE_OFFSET: u64 = 0; pub const SIMPLE_UART_TRANSMIT_OFFSET: u64 = 0; pub const SIMPLE_UART_STATUS_OFFSET: u64 = 1; -pub const SIMPLE_UART_SIZE: u64 = 1 << SIMPLE_UART_LOG2_SIZE; +pub const SIMPLE_UART_SIZE: NonZeroU64 = + NonZeroU64::new(1 << SIMPLE_UART_LOG2_SIZE).expect("known to be non-zero"); pub const SIMPLE_UART_LOG2_SIZE: u8 = 1; #[hdl(no_static)] @@ -366,11 +368,22 @@ struct Operation> { finish: HdlOption>, } -pub const fn simple_uart_memory_interface_config(op_id_width: usize) -> MemoryInterfaceConfig { +pub const fn simple_uart_memory_interface_config( + op_id_width: usize, + start_address: Wrapping, +) -> MemoryInterfaceConfig { + assert!( + start_address.0 % SIMPLE_UART_SIZE.get() == 0, + "start_address must be properly aligned" + ); MemoryInterfaceConfig { log2_bus_width_in_bytes: SIMPLE_UART_LOG2_SIZE, queue_capacity: const { NonZeroUsize::new(1).unwrap() }, op_id_width, + address_range: AddressRange::Limited { + start: start_address, + size: SIMPLE_UART_SIZE, + }, } } @@ -381,9 +394,10 @@ pub fn simple_uart( baud_rate: f64, receiver_queue_size: NonZeroUsize, ) { + let start_address = config.get().address_range.start(); assert_eq!( *config.get(), - simple_uart_memory_interface_config(config.get().op_id_width), + simple_uart_memory_interface_config(config.get().op_id_width, start_address), ); #[hdl] let cd: ClockDomain = m.input(); @@ -486,14 +500,18 @@ pub fn simple_uart( match kind { MemoryOperationKind::Read => { #[hdl] - if byte_addr.cmp_eq(SIMPLE_UART_RECEIVE_OFFSET) { + if byte_addr + .cmp_eq(start_address.0.wrapping_add(SIMPLE_UART_RECEIVE_OFFSET)) + { connect(receiver.output_byte.ready, valid_addr); #[hdl] if let HdlSome(byte) = receiver.output_byte.data { connect(read_data, byte); } // if there is no byte ready yet, we read a zero to avoid blocking the CPU on external inputs. - } else if byte_addr.cmp_eq(SIMPLE_UART_STATUS_OFFSET) { + } else if byte_addr + .cmp_eq(start_address.0.wrapping_add(SIMPLE_UART_STATUS_OFFSET)) + { connect( read_data, receiver @@ -507,7 +525,9 @@ pub fn simple_uart( } MemoryOperationKind::Write => { #[hdl] - if byte_addr.cmp_eq(SIMPLE_UART_TRANSMIT_OFFSET) { + if byte_addr + .cmp_eq(start_address.0.wrapping_add(SIMPLE_UART_TRANSMIT_OFFSET)) + { #[hdl] if !transmitter.input_byte.ready { connect(all_ready, false); diff --git a/crates/cpu/tests/expected/fetch.vcd b/crates/cpu/tests/expected/fetch.vcd index dcbd137..4016b31 100644 --- a/crates/cpu/tests/expected/fetch.vcd +++ b/crates/cpu/tests/expected/fetch.vcd @@ -3633,7 +3633,7 @@ b0 UA{3," 0mS#3<" 0^qjb[" b0 tN>yu" -sPhantomConst({\"log2_bus_width_in_bytes\":3,\"queue_capacity\":16,\"op_id_width\":8}) '@T[?" +sPhantomConst({\"log2_bus_width_in_bytes\":3,\"queue_capacity\":16,\"op_id_width\":8,\"address_range\":\"Full\"}) '@T[?" 0BXk&[" sHdlNone\x20(0) &wG|&" sSuccess\x20(0) ^F2z4" @@ -3647,7 +3647,7 @@ b0 VXdV?" b0 =`@6L" b0 e2JF6" b0 ]ocL{" -sPhantomConst({\"log2_bus_width_in_bytes\":3,\"queue_capacity\":16,\"op_id_width\":8}) ^yu -sPhantomConst({\"log2_bus_width_in_bytes\":3,\"queue_capacity\":16,\"op_id_width\":8}) '@T[? +sPhantomConst({\"log2_bus_width_in_bytes\":3,\"queue_capacity\":16,\"op_id_width\":8,\"address_range\":\"Full\"}) '@T[? 0BXk&[ sHdlNone\x20(0) &wG|& sSuccess\x20(0) ^F2z4 @@ -5536,7 +5536,7 @@ b0 VXdV? b0 =`@6L b0 e2JF6 b0 ]ocL{ -sPhantomConst({\"log2_bus_width_in_bytes\":3,\"queue_capacity\":16,\"op_id_width\":8}) ^Z\" -sPhantomConst({\"log2_bus_width_in_bytes\":3,\"queue_capacity\":16,\"op_id_width\":8}) %zH|b" +sPhantomConst({\"log2_bus_width_in_bytes\":3,\"queue_capacity\":16,\"op_id_width\":8,\"address_range\":\"Full\"}) %zH|b" 0'(8m+" sHdlNone\x20(0) MMwRE" b0 96-j|" @@ -5680,7 +5680,7 @@ b0 n:O1E" b0 \TnT)" b0 %KEY#" sPhantomConst(\"0..=16\") 2SZ\ -sPhantomConst({\"log2_bus_width_in_bytes\":3,\"queue_capacity\":16,\"op_id_width\":8}) %zH|b +sPhantomConst({\"log2_bus_width_in_bytes\":3,\"queue_capacity\":16,\"op_id_width\":8,\"address_range\":\"Full\"}) %zH|b 0'(8m+ sHdlNone\x20(0) MMwRE b0 96-j| @@ -5788,7 +5788,7 @@ b0 n:O1E b0 \TnT) b0 %KEY# sPhantomConst(\"0..=16\") 2Si/ 0f><7V b0 #+y/d -sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8}) k$)j$ +sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":2}}}) k$)j$ 1z' @@ -557,13 +557,13 @@ sRead\x20(0) i8%Hy sGeneric\x20(0) }u!d sSuccess\x20(0) $TH!~ @@ -583,13 +583,13 @@ sRead\x20(0) @nru sGeneric\x20(0) =rNet b0 _aF6z b0 fBY~q -sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8}) 3Cr%: +sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":2}}}) 3Cr%: 0jP_rY sHdlSome\x20(1) fl1G| b0 >-gg} 0d2Gl| sPhantomConst(\"0..=1\") |@PWl -sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8}) 7A<7@ +sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":2}}}) 7A<7@ 1?:DT^ 1*V%!4 01-L[% @@ -730,14 +730,14 @@ b0 &]$B; 0N-YM; 0R,uS' b0 GEq-0 -sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8}) ,;U@A +sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":2}}}) ,;U@A sHdlNone\x20(0) 0?MV, sSuccess\x20(0) D>VoK sRead\x20(0) d8ku7 sGeneric\x20(0) Y[<1d b0 .^N`/ b0 'ThQe -sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8}) 8pZSz +sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":2}}}) 8pZSz 1'LW/7 1(+=/^ b0 Whx|K @@ -752,7 +752,7 @@ b0 qwy9i 044.En 0Pq(.9 b0 4zG]Q -sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8}) MTVBb +sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":2}}}) MTVBb 1IifsN sHdlNone\x20(0) EA1Ra sSuccess\x20(0) [ -sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8}) {[zRy +sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":2}}}) {[zRy 0q2OoF sHdlSome\x20(1) d#Of8 b0 J4y0s 0vC#Sj sPhantomConst(\"0..=1\") ye/Gl -sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8}) bH=hU +sPhantomConst({\"log2_bus_width_in_bytes\":1,\"queue_capacity\":1,\"op_id_width\":8,\"address_range\":{\"Limited\":{\"start\":0,\"size\":2}}}) bH=hU 1"QAdC 1K%|4s $end diff --git a/crates/cpu/tests/simple_uart.rs b/crates/cpu/tests/simple_uart.rs index 3351fe1..d02230a 100644 --- a/crates/cpu/tests/simple_uart.rs +++ b/crates/cpu/tests/simple_uart.rs @@ -21,7 +21,11 @@ use fayalite::{ sim::vcd::VcdWriterDecls, util::{RcWriter, ready_valid::ReadyValid}, }; -use std::{borrow::BorrowMut, marker::PhantomData, num::NonZeroUsize}; +use std::{ + borrow::BorrowMut, + marker::PhantomData, + num::{NonZeroUsize, Wrapping}, +}; const fn half_period(frequency: f64) -> SimDuration { SimDuration::from_attos((SimDuration::from_secs(1).as_attos() as f64 / frequency / 2.0) as u128) @@ -603,7 +607,7 @@ fn simple_uart_loopback( } const SIMPLE_UART_MEMORY_INTERFACE_CONFIG: MemoryInterfaceConfig = - simple_uart_memory_interface_config(FETCH_BLOCK_ID_WIDTH); + simple_uart_memory_interface_config(FETCH_BLOCK_ID_WIDTH, Wrapping(0)); struct MemoryOperationRunner>, T: BundleType> { sim: Sim, @@ -916,10 +920,10 @@ fn test_simple_uart() { for i in 0..2 * BUS_WIDTH_IN_BYTES as u64 { mem_op_runner - .read_bytes::<1>(SIMPLE_UART_SIZE + i, 1) + .read_bytes::<1>(SIMPLE_UART_SIZE.get() + i, 1) .unwrap_err(); mem_op_runner - .write_bytes(SIMPLE_UART_SIZE + i, [0], 1) + .write_bytes(SIMPLE_UART_SIZE.get() + i, [0], 1) .unwrap_err(); }