Compare commits

..

10 commits

17 changed files with 696248 additions and 199571 deletions

8
Cargo.lock generated
View file

@ -388,7 +388,7 @@ checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6"
[[package]]
name = "fayalite"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#2aa41137d4eec592bd591fddb558bc78a52c635c"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#80b92c7dd3adcad8487a3f8224846e381219bfa6"
dependencies = [
"base64",
"bitvec",
@ -417,7 +417,7 @@ dependencies = [
[[package]]
name = "fayalite-proc-macros"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#2aa41137d4eec592bd591fddb558bc78a52c635c"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#80b92c7dd3adcad8487a3f8224846e381219bfa6"
dependencies = [
"fayalite-proc-macros-impl",
]
@ -425,7 +425,7 @@ dependencies = [
[[package]]
name = "fayalite-proc-macros-impl"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#2aa41137d4eec592bd591fddb558bc78a52c635c"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#80b92c7dd3adcad8487a3f8224846e381219bfa6"
dependencies = [
"base16ct 0.2.0",
"num-bigint",
@ -440,7 +440,7 @@ dependencies = [
[[package]]
name = "fayalite-visit-gen"
version = "0.3.0"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#2aa41137d4eec592bd591fddb558bc78a52c635c"
source = "git+https://git.libre-chip.org/libre-chip/fayalite.git?branch=master#80b92c7dd3adcad8487a3f8224846e381219bfa6"
dependencies = [
"indexmap",
"prettyplease",

View file

@ -9,8 +9,9 @@ use crate::{
CpuConfigMaxFetchesInFlight, PhantomConstCpuConfig,
},
main_memory_and_io::{
MemoryInterface, MemoryOperationErrorKind, MemoryOperationFinish,
MemoryOperationFinishKind, MemoryOperationKind, MemoryOperationStart,
CpuConfigFetchMemoryInterfaceConfig, MemoryInterface, MemoryInterfaceConfig,
MemoryOperationErrorKind, MemoryOperationFinish, MemoryOperationFinishKind,
MemoryOperationKind, MemoryOperationStart,
},
next_pc::{
FETCH_BLOCK_ID_WIDTH, NextPcToFetchInterface, NextPcToFetchInterfaceInner, ResetStatus,
@ -415,7 +416,9 @@ impl<C: PhantomConstCpuConfig> L1ICacheStateSim<C> {
self.state_expr.ty().config
}
#[hdl]
fn try_start_memory_operation(&mut self) -> Option<SimValue<MemoryOperationStart<C>>> {
fn try_start_memory_operation(
&mut self,
) -> Option<SimValue<MemoryOperationStart<CpuConfigFetchMemoryInterfaceConfig<C>>>> {
let config = self.config();
for cache_miss in &mut self.cache_misses {
let Some(next_start_fetch_block) = CacheMiss::next_start_fetch_block(cache_miss) else {
@ -430,7 +433,7 @@ impl<C: PhantomConstCpuConfig> L1ICacheStateSim<C> {
error: _, // handled by CacheMiss::next_start_fetch_block()
config: _,
} = cache_miss;
let mem_op_ty = MemoryOperationStart[config];
let mem_op_ty = MemoryOperationStart[CpuConfigFetchMemoryInterfaceConfig[config]];
let mut addr = SplitAddr[config].split_addr_sim(addr);
#[hdl(sim)]
let SplitAddr::<_> {
@ -452,8 +455,9 @@ impl<C: PhantomConstCpuConfig> L1ICacheStateSim<C> {
mem_op_ty.write_data.element().zero(),
mem_op_ty.write_data.len(),
),
fetch_block_id,
config,
rw_mask: repeat(true, mem_op_ty.write_data.len()),
op_id: SimValue::into_dyn_int(fetch_block_id.clone()),
config: CpuConfigFetchMemoryInterfaceConfig[config],
},
);
}
@ -496,7 +500,9 @@ impl<C: PhantomConstCpuConfig> L1ICacheStateSim<C> {
fn do_memory_operation_finish<'a>(
&mut self,
ready_for_memory_operation_finish: ReadyForMemoryOperationFinish,
memory_operation_finish: impl ToSimValue<Type = MemoryOperationFinish<C>>,
memory_operation_finish: impl ToSimValue<
Type = MemoryOperationFinish<CpuConfigFetchMemoryInterfaceConfig<C>>,
>,
) -> Option<SimValue<WriteBackStep<C>>> {
let config = self.config();
let cache_miss = &mut self.cache_misses[ready_for_memory_operation_finish.cache_miss_index];
@ -620,9 +626,7 @@ impl<C: PhantomConstCpuConfig> L1ICacheStateSim<C> {
#[hdl]
fn check_memory_next_fetch_block_ids(
&self,
memory_next_fetch_block_ids: SimValue<
ArrayVec<UInt<{ FETCH_BLOCK_ID_WIDTH }>, CpuConfigMaxFetchesInFlight<C>>,
>,
memory_next_fetch_block_ids: SimValue<ArrayVec<UInt, DynSize>>,
) {
let memory_next_fetch_block_ids = ArrayVec::elements_sim_ref(&memory_next_fetch_block_ids);
let mut expected_memory_next_fetch_block_ids = Vec::new();
@ -1070,8 +1074,8 @@ fn l1_i_cache_impl(config: PhantomConst<CpuConfig>) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let memory_interface: MemoryInterface<PhantomConst<CpuConfig>> =
m.output(MemoryInterface[config]);
let memory_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.output(MemoryInterface[CpuConfigFetchMemoryInterfaceConfig[config]]);
#[hdl]
let from_next_pc: NextPcToFetchInterface<PhantomConst<CpuConfig>> =
m.input(NextPcToFetchInterface[config]);
@ -1102,7 +1106,7 @@ fn l1_i_cache_impl(config: PhantomConst<CpuConfig>) {
async fn run(
mut sim: ExternModuleSimulationState,
cd: Expr<ClockDomain>,
memory_interface: Expr<MemoryInterface<PhantomConst<CpuConfig>>>,
memory_interface: Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
from_next_pc: Expr<NextPcToFetchInterface<PhantomConst<CpuConfig>>>,
to_decode_fetched: Expr<ReadyValid<FetchToDecodeInterfaceInner<PhantomConst<CpuConfig>>>>,
to_decode_next_fetch_block_ids: Expr<
@ -1195,9 +1199,8 @@ fn l1_i_cache_impl(config: PhantomConst<CpuConfig>) {
)
.await;
#[hdl(sim)]
if let HdlSome(next_fetch_block_ids) = sim
.read_past(memory_interface.next_fetch_block_ids, cd.clk)
.await
if let HdlSome(next_fetch_block_ids) =
sim.read_past(memory_interface.next_op_ids, cd.clk).await
{
state.check_memory_next_fetch_block_ids(next_fetch_block_ids);
}
@ -1358,7 +1361,7 @@ fn l1_i_cache_impl(config: PhantomConst<CpuConfig>) {
state_for_debug,
),
mut sim| async move {
let config = memory_interface.ty().config;
let config = from_next_pc.ty().config;
let cache_line_ty = CacheLine[config];
sim.write(i_cache_port.clk, false).await; // externally overridden with cd.clk, so just write a constant here
sim.resettable(
@ -1425,8 +1428,8 @@ pub fn l1_i_cache(config: PhantomConst<CpuConfig>) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let memory_interface: MemoryInterface<PhantomConst<CpuConfig>> =
m.output(MemoryInterface[config]);
let memory_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.output(MemoryInterface[CpuConfigFetchMemoryInterfaceConfig[config]]);
#[hdl]
let from_next_pc: NextPcToFetchInterface<PhantomConst<CpuConfig>> =
m.input(NextPcToFetchInterface[config]);
@ -1523,8 +1526,8 @@ pub fn fetch(config: PhantomConst<CpuConfig>) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let memory_interface: MemoryInterface<PhantomConst<CpuConfig>> =
m.output(MemoryInterface[config]);
let memory_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.output(MemoryInterface[CpuConfigFetchMemoryInterfaceConfig[config]]);
#[hdl]
let from_next_pc: NextPcToFetchInterface<PhantomConst<CpuConfig>> =
m.input(NextPcToFetchInterface[config]);

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,618 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use std::num::{NonZeroU64, NonZeroUsize, Wrapping};
use crate::{
main_memory_and_io::{
AddressRange, MemoryInterface, MemoryInterfaceConfig, MemoryOperationErrorKind,
MemoryOperationFinish, MemoryOperationFinishKind, MemoryOperationKind,
MemoryOperationStart,
},
util::array_vec::ArrayVec,
};
use fayalite::{
int::UIntInRange,
prelude::*,
util::ready_valid::{ReadyValid, queue},
};
const TICKS_PER_BAUD: usize = 16;
const PRECISION_BITS: usize = 16;
#[hdl_module]
pub fn uart_clock_gen(
clock_input_properties: PhantomConst<peripherals::ClockInputProperties>,
baud_rate: f64,
) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let tick: Bool = m.output();
let divisor =
clock_input_properties.get().frequency.into_inner() / (baud_rate * TICKS_PER_BAUD as f64);
type NumeratorType = u128;
let numerator: NumeratorType = 1 << PRECISION_BITS;
let denominator: NumeratorType = (divisor * numerator as f64).round() as _;
let remainder_ty = UInt::range(0..denominator);
#[hdl]
let remainder_reg = reg_builder().clock_domain(cd).reset(remainder_ty.zero());
#[hdl]
let sum = wire((remainder_reg + numerator).ty());
connect_any(sum, remainder_reg + numerator);
#[hdl]
let tick_reg = reg_builder().clock_domain(cd).reset(false);
connect(tick_reg, false);
#[hdl]
let next_remainder = wire(remainder_ty);
connect(remainder_reg, next_remainder);
#[hdl]
if sum.cmp_ge(denominator) {
connect_any(next_remainder, sum - denominator);
connect(tick_reg, true);
} else {
connect(next_remainder, sum);
}
connect(tick, tick_reg);
}
#[hdl_module]
pub fn transmitter() {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let tick: Bool = m.input();
#[hdl]
let tx: Bool = m.output();
#[hdl]
let input_byte: ReadyValid<UInt<8>> = m.input();
#[hdl]
let not_tx_reg: Bool = reg_builder().clock_domain(cd).reset(false);
connect(tx, !not_tx_reg);
#[hdl]
let input_buf_reg: HdlOption<UInt<8>> = reg_builder().clock_domain(cd).reset(HdlNone());
const BYTE_ON_WIRE_WIDTH: usize = 10;
#[hdl]
let shift_reg: Array<HdlOption<Bool>, _> = reg_builder()
.clock_domain(cd)
.reset([HdlNone(); BYTE_ON_WIRE_WIDTH]);
#[hdl]
if let HdlSome(v) = shift_reg[0] {
connect(not_tx_reg, !v);
} else {
connect(not_tx_reg, false);
}
#[hdl]
let tick_count_reg = reg_builder()
.clock_domain(cd)
.reset(0u8.cast_to_static::<UIntInRange<0, { TICKS_PER_BAUD }>>());
#[hdl]
let next_tick_count = wire(tick_count_reg.ty());
connect(tick_count_reg, next_tick_count);
#[hdl]
if !tick {
connect(next_tick_count, tick_count_reg);
} else if tick_count_reg.cmp_ge(TICKS_PER_BAUD - 1) {
connect(next_tick_count, 0u8.cast_to(next_tick_count.ty()));
} else {
connect(
next_tick_count,
(tick_count_reg.cast_to(UInt[tick_count_reg.ty().bit_width()]) + 1u8)
.cast_to(next_tick_count.ty()),
);
}
#[hdl]
if tick & tick_count_reg.cmp_eq(0u8) {
#[hdl]
if let HdlSome(_) = shift_reg[0] {
for v in shift_reg.windows(2) {
connect(v[0], v[1]);
}
connect(shift_reg.last().expect("known to be non-empty"), HdlNone());
}
#[hdl]
if let HdlNone = shift_reg[1] {
// when shift_reg[1] is HdlNone that means shift_reg would be completely empty in the next clock
// cycle, so instead fill it with the next byte if there is one.
#[hdl]
if let HdlSome(byte) = input_buf_reg {
connect(input_buf_reg, HdlNone());
connect(shift_reg, [HdlSome(true); _]);
connect(shift_reg[0], HdlSome(false));
for (i, v) in (*shift_reg)[1..][..8].iter().enumerate() {
connect(v, HdlSome(byte[i]));
}
}
}
}
#[hdl]
if let HdlSome(_) = input_buf_reg {
connect(input_byte.ready, false);
} else {
connect(input_byte.ready, true);
connect(input_buf_reg, input_byte.data);
}
}
/// `rx_synchronized` is [`peripherals::Uart::rx`], but after being synchronized to `cd.clk`.
/// `output_byte` is `HdlSome` for 1 clock cycle for every received byte.
#[hdl_module]
pub fn receiver_no_queue() {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let tick: Bool = m.input();
#[hdl]
let rx_synchronized: Bool = m.input();
#[hdl]
let output_byte: HdlOption<UInt<8>> = m.output();
const BITS_PER_BYTE: usize = 8;
#[hdl]
let output_byte_reg: HdlOption<UInt<{ BITS_PER_BYTE }>> =
reg_builder().clock_domain(cd).reset(HdlNone());
connect(output_byte, output_byte_reg);
connect(output_byte_reg, HdlNone()); // by default always reset to HdlNone()
#[hdl]
let tick_count_reg = reg_builder()
.clock_domain(cd)
.reset(0u8.cast_to(UInt::range(0..TICKS_PER_BAUD)));
#[hdl]
let shift_reg: UInt<{ BITS_PER_BYTE }> = reg_builder().clock_domain(cd).reset(0u8);
#[hdl]
enum State {
WaitingForStartBit,
StartBit,
DataBits(UIntInRange<0, { BITS_PER_BYTE }>),
StopBit,
}
#[hdl]
let state_reg = reg_builder()
.clock_domain(cd)
.reset(State.WaitingForStartBit());
#[hdl]
if tick {
#[hdl]
match state_reg {
State::WaitingForStartBit => {
connect(tick_count_reg, tick_count_reg.ty().zero());
#[hdl]
if !rx_synchronized {
connect(state_reg, State.StartBit());
}
}
State::StartBit =>
{
#[hdl]
if tick_count_reg.cmp_eq(TICKS_PER_BAUD - 1) {
connect(tick_count_reg, tick_count_reg.ty().zero());
connect(state_reg, State.DataBits(0u8.cast_to(State.DataBits)));
} else {
connect_any(tick_count_reg, tick_count_reg + 1u8);
}
}
State::DataBits(count) => {
#[hdl]
if tick_count_reg.cmp_eq(TICKS_PER_BAUD - 1) {
connect(tick_count_reg, tick_count_reg.ty().zero());
#[hdl]
if count.cmp_eq(BITS_PER_BYTE - 1) {
connect(state_reg, State.StopBit());
connect(output_byte_reg, HdlSome(shift_reg));
} else {
connect(
state_reg,
State.DataBits((count.cast_to(UInt[8]) + 1u8).cast_to(State.DataBits)),
);
}
} else {
connect_any(tick_count_reg, tick_count_reg + 1u8);
}
#[hdl]
if tick_count_reg.cmp_eq(TICKS_PER_BAUD / 2) {
connect_any(
shift_reg,
(shift_reg >> 1) | (rx_synchronized.cast_to_static::<UInt<1>>() << 7),
);
}
}
State::StopBit => {
#[hdl]
if tick_count_reg.cmp_eq(TICKS_PER_BAUD / 2) {
// go to WaitingForStartBit in the middle of the stop bit so we can adjust if the sender is faster than us.
connect(tick_count_reg, tick_count_reg.ty().zero());
connect(state_reg, State.WaitingForStartBit());
} else {
connect_any(tick_count_reg, tick_count_reg + 1u8);
}
}
}
}
}
#[hdl]
pub enum ReceiverQueueStatus {
QueueEmpty,
QueueNotEmpty,
QueueAlmostFull,
QueueFull,
QueueOverflowed,
}
impl ReceiverQueueStatus {
pub fn sim_as_u8(this: impl ToSimValue<Type = Self>) -> u8 {
SimValue::bits(&this.into_sim_value())
.cast_to_static::<UInt<8>>()
.as_int()
}
#[hdl]
pub fn for_queue_len_sim(current_count: usize, queue_size: NonZeroUsize) -> SimValue<Self> {
if current_count == 0 {
#[hdl(sim)]
ReceiverQueueStatus.QueueEmpty()
} else if current_count == queue_size.get() {
#[hdl(sim)]
ReceiverQueueStatus.QueueFull()
} else if current_count == queue_size.get() - 1 {
#[hdl(sim)]
ReceiverQueueStatus.QueueAlmostFull()
} else if current_count > queue_size.get() {
#[hdl(sim)]
ReceiverQueueStatus.QueueOverflowed()
} else {
#[hdl(sim)]
ReceiverQueueStatus.QueueNotEmpty()
}
}
#[hdl]
pub fn for_queue_len_as_u8(current_count: usize, queue_size: NonZeroUsize) -> u8 {
Self::sim_as_u8(Self::for_queue_len_sim(current_count, queue_size))
}
}
/// `rx_synchronized` is [`peripherals::Uart::rx`], but after being synchronized to `cd.clk`.
#[hdl_module]
pub fn receiver(queue_capacity: NonZeroUsize) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let tick: Bool = m.input();
#[hdl]
let rx_synchronized: Bool = m.input();
#[hdl]
let output_byte: ReadyValid<UInt<8>> = m.output();
#[hdl]
let queue_status: ReceiverQueueStatus = m.output();
#[hdl]
let queue = instance(queue(UInt::<8>::new_static(), queue_capacity, false, false));
connect(queue.cd, cd);
connect(output_byte, queue.out);
#[hdl]
let queue_overflowed_reg = reg_builder().clock_domain(cd).reset(false);
#[hdl]
if queue_overflowed_reg {
connect(queue_status, ReceiverQueueStatus.QueueOverflowed());
} else if queue.count.cmp_eq(0u8) {
connect(queue_status, ReceiverQueueStatus.QueueEmpty());
} else if queue.count.cmp_eq(queue_capacity) {
connect(queue_status, ReceiverQueueStatus.QueueFull());
} else if queue.count.cmp_eq(queue_capacity.get() - 1) {
connect(queue_status, ReceiverQueueStatus.QueueAlmostFull());
} else {
connect(queue_status, ReceiverQueueStatus.QueueNotEmpty());
}
#[hdl]
if ReadyValid::firing(output_byte) {
// clear overflow when a byte is read from the queue
connect(queue_overflowed_reg, false);
}
// we ignore queue.inp.ready other than noting an overflow when there was data but the queue wasn't ready
#[hdl]
if !queue.inp.ready {
#[hdl]
if let HdlSome(_) = queue.inp.data {
connect(queue_overflowed_reg, true);
}
}
#[hdl]
let inner = instance(receiver_no_queue());
connect(inner.cd, cd);
connect(inner.tick, tick);
connect(inner.rx_synchronized, rx_synchronized);
connect(queue.inp.data, inner.output_byte);
}
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_LOG2_BUS_WIDTH: u8 = 1;
pub const SIMPLE_UART_USED_SIZE: NonZeroU64 = NonZeroU64::new(2).expect("known non-zero");
pub const SIMPLE_UART_ADDRESS_SIZE: NonZeroU64 = NonZeroU64::new(1 << 6).expect("known non-zero");
#[hdl(no_static)]
struct Operation<C: PhantomConstGet<MemoryInterfaceConfig>> {
start: MemoryOperationStart<C>,
finish: HdlOption<MemoryOperationFinish<C>>,
}
pub const fn simple_uart_memory_interface_config(
op_id_width: usize,
start_address: Wrapping<u64>,
) -> MemoryInterfaceConfig {
assert!(
start_address
.0
.is_multiple_of(SIMPLE_UART_ADDRESS_SIZE.get()),
"start_address must be properly aligned"
);
MemoryInterfaceConfig {
log2_bus_width_in_bytes: SIMPLE_UART_LOG2_BUS_WIDTH,
queue_capacity: const { NonZeroUsize::new(1).unwrap() },
op_id_width,
address_range: AddressRange::Limited {
start: start_address,
size: SIMPLE_UART_ADDRESS_SIZE,
},
}
}
#[hdl_module]
pub fn simple_uart(
config: PhantomConst<MemoryInterfaceConfig>,
clock_input_properties: PhantomConst<peripherals::ClockInputProperties>,
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, start_address),
);
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let memory_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.input(MemoryInterface[config]);
#[hdl]
let uart: peripherals::Uart = m.output();
#[hdl]
let rx_sync_intermediate_reg: Bool = reg_builder().clock_domain(cd).reset(true);
annotate(rx_sync_intermediate_reg, DontTouchAnnotation);
#[hdl]
let rx_sync_final_reg: Bool = reg_builder().clock_domain(cd).reset(true);
annotate(rx_sync_final_reg, DontTouchAnnotation);
connect(rx_sync_intermediate_reg, uart.rx);
connect(rx_sync_final_reg, rx_sync_intermediate_reg);
#[hdl]
let rx_synchronized: Bool = wire();
annotate(rx_synchronized, DontTouchAnnotation);
connect(rx_synchronized, rx_sync_final_reg);
#[hdl]
let clk_gen = instance(uart_clock_gen(clock_input_properties, baud_rate));
connect(clk_gen.cd, cd);
#[hdl]
let transmitter = instance(transmitter());
connect(transmitter.cd, cd);
connect(transmitter.tick, clk_gen.tick);
connect(uart.tx, transmitter.tx);
#[hdl]
let receiver = instance(receiver(receiver_queue_size));
connect(receiver.cd, cd);
connect(receiver.tick, clk_gen.tick);
connect(receiver.rx_synchronized, rx_synchronized);
#[hdl]
let operation_reg = reg_builder()
.clock_domain(cd)
.reset(HdlOption[Operation[config]].HdlNone());
let next_op_ids_ty = memory_interface.ty().next_op_ids.HdlSome;
#[hdl]
if let HdlSome(operation) = operation_reg {
#[hdl]
let MemoryInterface::<_> {
start,
finish,
next_op_ids,
config: _,
} = memory_interface;
connect(start.ready, false);
connect(finish.data, operation.finish);
connect(
next_op_ids,
HdlSome(ArrayVec::from_parts_unchecked(
repeat(operation.start.op_id, next_op_ids_ty.capacity()),
1u8.cast_to(next_op_ids_ty.len_ty()),
)),
);
connect(transmitter.input_byte.data, HdlNone());
connect(receiver.output_byte.ready, false);
#[hdl]
if let HdlSome(_) = operation.finish {
#[hdl]
if finish.ready {
connect(operation_reg, operation_reg.ty().HdlNone());
}
} else {
#[hdl]
let MemoryOperationStart::<_> {
kind,
addr,
write_data,
rw_mask,
op_id: _,
config: _,
} = operation.start;
#[hdl]
let valid_addr = wire();
connect(valid_addr, true);
#[hdl]
let all_ready = wire();
connect(all_ready, true);
#[hdl]
let read_data = wire(write_data.ty());
for (byte_index, ((rw_mask, write_data), read_data)) in rw_mask
.into_iter()
.zip(write_data)
.zip(read_data)
.enumerate()
{
let byte_addr = (addr | byte_index).cast_to_static::<UInt<64>>();
connect(read_data, 0u8);
#[hdl]
if rw_mask {
#[hdl]
match kind {
MemoryOperationKind::Read => {
#[hdl]
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(start_address.0.wrapping_add(SIMPLE_UART_STATUS_OFFSET))
{
connect(
read_data,
receiver
.queue_status
.cast_to_bits()
.cast_to_static::<UInt<8>>(),
);
} else {
connect(valid_addr, false);
}
}
MemoryOperationKind::Write => {
#[hdl]
if byte_addr
.cmp_eq(start_address.0.wrapping_add(SIMPLE_UART_TRANSMIT_OFFSET))
{
#[hdl]
if !transmitter.input_byte.ready {
connect(all_ready, false);
}
#[hdl]
if valid_addr {
connect(transmitter.input_byte.data, HdlSome(write_data));
}
} else {
connect(valid_addr, false);
}
}
}
}
}
#[hdl]
if !valid_addr {
connect(
operation_reg,
HdlSome(
#[hdl]
Operation::<_> {
start: operation.start,
finish: HdlSome(
#[hdl]
MemoryOperationFinish::<_> {
kind: MemoryOperationFinishKind
.Error(MemoryOperationErrorKind.Generic()),
read_data,
config,
},
),
},
),
);
} else if all_ready {
connect(
operation_reg,
HdlSome(
#[hdl]
Operation::<_> {
start: operation.start,
finish: HdlSome(
#[hdl]
MemoryOperationFinish::<_> {
kind: MemoryOperationFinishKind.Success(kind),
read_data,
config,
},
),
},
),
);
}
}
} else {
#[hdl]
let MemoryInterface::<_> {
start,
finish,
next_op_ids,
config: _,
} = memory_interface;
connect(start.ready, true);
connect(finish.data, finish.ty().data.HdlNone());
connect(
next_op_ids,
HdlSome(next_op_ids_ty.new_sim(next_op_ids_ty.element().zero())),
);
#[hdl]
if let HdlSome(start) = start.data {
connect(
operation_reg,
HdlSome(
#[hdl]
Operation::<_> {
start,
finish: operation_reg.ty().HdlSome.finish.HdlNone(),
},
),
);
}
connect(transmitter.input_byte.data, HdlNone());
connect(receiver.output_byte.ready, false);
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

114674
crates/cpu/tests/expected/simple_uart.vcd generated Normal file

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -5,8 +5,9 @@ use cpu::{
config::{CpuConfig, UnitConfig},
fetch::{FetchToDecodeInterface, FetchToDecodeInterfaceInner, fetch},
main_memory_and_io::{
MemoryInterface, MemoryOperationErrorKind, MemoryOperationFinish,
MemoryOperationFinishKind, MemoryOperationKind, MemoryOperationStart,
CpuConfigFetchMemoryInterfaceConfig, MemoryInterface, MemoryInterfaceConfig,
MemoryOperationErrorKind, MemoryOperationFinish, MemoryOperationFinishKind,
MemoryOperationKind, MemoryOperationStart,
},
next_pc::{FETCH_BLOCK_ID_WIDTH, NextPcToFetchInterface, NextPcToFetchInterfaceInner},
unit::UnitKind,
@ -92,8 +93,8 @@ fn mock_memory(config: PhantomConst<CpuConfig>) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let memory_interface: MemoryInterface<PhantomConst<CpuConfig>> =
m.input(MemoryInterface[config]);
let memory_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.input(MemoryInterface[CpuConfigFetchMemoryInterfaceConfig[config]]);
#[hdl]
let queue_debug: ArrayVec<MemoryQueueEntry, ConstUsize<{ MEMORY_QUEUE_SIZE }>> = m.output();
m.register_clock_for_past(cd.clk);
@ -109,13 +110,12 @@ fn mock_memory(config: PhantomConst<CpuConfig>) {
let MemoryInterface::<_> {
start,
finish,
next_fetch_block_ids,
next_op_ids,
config: _,
} = memory_interface;
sim.write(start.ready, false).await;
sim.write(finish.data, finish.ty().data.HdlNone()).await;
sim.write(next_fetch_block_ids, next_fetch_block_ids.ty().HdlNone())
.await;
sim.write(next_op_ids, next_op_ids.ty().HdlNone()).await;
sim.write(
queue_debug,
queue_debug.ty().new_sim(MemoryQueueEntry.default_sim()),
@ -130,7 +130,7 @@ fn mock_memory(config: PhantomConst<CpuConfig>) {
#[hdl]
async fn run_fn(
cd: Expr<ClockDomain>,
memory_interface: Expr<MemoryInterface<PhantomConst<CpuConfig>>>,
memory_interface: Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
queue_debug: Expr<ArrayVec<MemoryQueueEntry, ConstUsize<{ MEMORY_QUEUE_SIZE }>>>,
random: &RefCell<Random>,
mut sim: ExternModuleSimulationState,
@ -138,7 +138,7 @@ fn mock_memory(config: PhantomConst<CpuConfig>) {
let mut random = random.borrow_mut();
let config = memory_interface.config.ty();
let finish_data_ty = memory_interface.finish.data.ty();
let next_fetch_block_ids_ty = memory_interface.next_fetch_block_ids.ty();
let next_op_ids_ty = memory_interface.next_op_ids.ty();
let mut queue: VecDeque<SimValue<MemoryQueueEntry>> = VecDeque::new();
loop {
for entry in &mut queue {
@ -156,12 +156,17 @@ fn mock_memory(config: PhantomConst<CpuConfig>) {
)
.await;
sim.write(
memory_interface.next_fetch_block_ids,
memory_interface.next_op_ids,
#[hdl(sim)]
next_fetch_block_ids_ty.HdlSome(
next_fetch_block_ids_ty
next_op_ids_ty.HdlSome(
next_op_ids_ty
.HdlSome
.from_iter_sim(0u8, queue.iter().map(|entry| &entry.fetch_block_id))
.from_iter_sim(
0u8,
queue
.iter()
.map(|entry| SimValue::into_dyn_int(entry.fetch_block_id.clone())),
)
.ok()
.expect("queue is known to be small enough"),
),
@ -234,7 +239,8 @@ fn mock_memory(config: PhantomConst<CpuConfig>) {
kind,
addr,
write_data: _,
fetch_block_id,
rw_mask,
op_id,
config: _,
} = memory_operation_start;
#[hdl(sim)]
@ -242,10 +248,14 @@ fn mock_memory(config: PhantomConst<CpuConfig>) {
MemoryOperationKind::Read => {}
MemoryOperationKind::Write => unreachable!(),
}
assert!(
rw_mask.iter().all(|v| **v),
"rw_mask should be all true: {rw_mask:?}"
);
let entry = #[hdl(sim)]
MemoryQueueEntry {
addr,
fetch_block_id,
fetch_block_id: SimValue::from_dyn_int(op_id),
cycles_left: MemoryQueueEntry::get_next_delay(&mut random),
};
println!("mock memory start: {entry:#?}");

View file

@ -0,0 +1,305 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use cpu::{
main_memory_and_io::{
AddressRange, MemoryInterface, MemoryInterfaceConfig, MemoryOperationErrorKind,
MemoryOperationFinish, MemoryOperationFinishKind, MemoryOperationKind,
MemoryOperationStart, main_memory_and_io, simple_uart::SIMPLE_UART_TRANSMIT_OFFSET,
},
next_pc::FETCH_BLOCK_ID_WIDTH,
};
use fayalite::{prelude::*, sim::vcd::VcdWriterDecls, util::RcWriter};
use std::num::{NonZeroU64, NonZeroUsize, Wrapping};
const CLOCK_FREQUENCY: f64 = 2_000_000.0;
const fn half_period(frequency: f64) -> SimDuration {
SimDuration::from_attos((SimDuration::from_secs(1).as_attos() as f64 / frequency / 2.0) as u128)
}
const CLOCK_HALF_PERIOD: SimDuration = half_period(CLOCK_FREQUENCY);
fn clock_input_properties() -> PhantomConst<peripherals::ClockInputProperties> {
peripherals::ClockInput::new(CLOCK_FREQUENCY).properties
}
const SRAM_START: u64 = 0x1000;
const SRAM_SIZE: NonZeroU64 = NonZeroU64::new(0x30).unwrap();
const UART_START: u64 = 0x100000;
const UART_BAUD_RATE: f64 = 115200.0;
const UART_RECEIVER_QUEUE_SIZE: NonZeroUsize = NonZeroUsize::new(16).unwrap();
const SRAM_INITIAL_CONTENTS: &[u8; SRAM_SIZE.get() as usize] =
b"This is a main memory and I/O test. ";
const SRAM_WRITING_CONTENTS: &[u8; SRAM_SIZE.get() as usize] =
b"Testing. Hello World. abcdefghijklmnopqrstuvwxyz";
const CONFIG: MemoryInterfaceConfig =
MemoryInterfaceConfig::new(3, 8, FETCH_BLOCK_ID_WIDTH, AddressRange::Full);
const BUS_WIDTH_IN_BYTES: usize = CONFIG.bus_width_in_bytes();
fn config() -> PhantomConst<MemoryInterfaceConfig> {
PhantomConst::new_sized(CONFIG)
}
#[hdl_module]
fn test_harness() {
let config = config();
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let memory_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.input(MemoryInterface[config]);
#[hdl]
let dut = instance(main_memory_and_io(
config,
clock_input_properties(),
AddressRange::Limited {
start: Wrapping(SRAM_START),
size: SRAM_SIZE,
},
SRAM_INITIAL_CONTENTS,
UART_START,
UART_BAUD_RATE,
UART_RECEIVER_QUEUE_SIZE,
));
connect(dut.cd, cd);
connect(dut.memory_interface, memory_interface);
connect(dut.uart.rx, dut.uart.tx); // loop back for testing
}
struct MainMemoryAndIoTester {
sim: Simulation<test_harness>,
next_op_id: u64,
}
impl MainMemoryAndIoTester {
fn reset(&mut self) {
let sim = &mut self.sim;
sim.write(
sim.io().memory_interface.start.data,
sim.io().ty().memory_interface.start.data.HdlNone(),
);
sim.write(sim.io().memory_interface.finish.ready, false);
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, false);
}
fn wait(&mut self, cycles: u64) {
let sim = &mut self.sim;
sim.write(
sim.io().memory_interface.start.data,
sim.io().ty().memory_interface.start.data.HdlNone(),
);
sim.write(sim.io().memory_interface.finish.ready, false);
for _ in 0..cycles {
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
}
}
#[track_caller]
#[hdl]
fn try_rw_op(
&mut self,
addr: u64,
write: Option<[u8; BUS_WIDTH_IN_BYTES]>,
rw_mask: [bool; BUS_WIDTH_IN_BYTES],
max_wait: u64,
) -> Result<[u8; BUS_WIDTH_IN_BYTES], SimValue<MemoryOperationErrorKind>> {
let config = config();
let sim = &mut self.sim;
let op_id = self.next_op_id.cast_to(UInt[FETCH_BLOCK_ID_WIDTH]);
self.next_op_id += 1;
sim.write(
sim.io().memory_interface.start.data,
#[hdl(sim)]
(sim.io().ty().memory_interface.start.data).HdlSome(match write {
Some(write) =>
{
#[hdl(sim)]
MemoryOperationStart::<_> {
kind: #[hdl(sim)]
MemoryOperationKind.Write(),
addr,
write_data: &write[..],
rw_mask: &rw_mask[..],
op_id,
config,
}
}
None =>
{
#[hdl(sim)]
MemoryOperationStart::<_> {
kind: #[hdl(sim)]
MemoryOperationKind.Read(),
addr,
write_data: &[0u8; BUS_WIDTH_IN_BYTES][..],
rw_mask: &rw_mask[..],
op_id,
config,
}
}
}),
);
sim.write(sim.io().memory_interface.finish.ready, true);
let mut cleared_start = false;
for _ in 0..max_wait {
sim.advance_time(CLOCK_HALF_PERIOD);
let start_ready = sim.read_bool(sim.io().memory_interface.start.ready);
let finish_data = sim.read(sim.io().memory_interface.finish.data);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
if start_ready && !cleared_start {
cleared_start = true;
sim.write(
sim.io().memory_interface.start.data,
sim.io().ty().memory_interface.start.data.HdlNone(),
);
}
#[hdl(sim)]
if let HdlSome(finish) = finish_data {
#[hdl(sim)]
let MemoryOperationFinish::<_> {
kind,
read_data,
config: _,
} = finish;
#[hdl(sim)]
match kind {
MemoryOperationFinishKind::Success(_) => {
return Ok(std::array::from_fn(|i| read_data[i].as_int()));
}
MemoryOperationFinishKind::Error(e) => {
return Err(e);
}
};
}
}
panic!("waited too many cycles");
}
#[track_caller]
fn try_read<const N: usize>(
&mut self,
addr: u64,
max_wait: u64,
) -> Result<[u8; N], SimValue<MemoryOperationErrorKind>> {
const { assert!(N <= BUS_WIDTH_IN_BYTES) }
let bus_addr = addr - addr % BUS_WIDTH_IN_BYTES as u64;
let mut rw_mask = [false; BUS_WIDTH_IN_BYTES];
let start = (addr % BUS_WIDTH_IN_BYTES as u64) as usize;
rw_mask[start..start + N].fill(true);
self.try_rw_op(bus_addr, None, rw_mask, max_wait)
.map(|v| std::array::from_fn(|i| v[i + start]))
}
#[track_caller]
fn read<const N: usize>(&mut self, addr: u64, max_wait: u64) -> [u8; N] {
self.try_read(addr, max_wait).expect("read returned error")
}
#[track_caller]
fn try_write<const N: usize>(
&mut self,
addr: u64,
data: [u8; N],
max_wait: u64,
) -> Result<(), SimValue<MemoryOperationErrorKind>> {
const { assert!(N <= BUS_WIDTH_IN_BYTES) }
let bus_addr = addr - addr % BUS_WIDTH_IN_BYTES as u64;
let mut rw_mask = [false; BUS_WIDTH_IN_BYTES];
let mut write_data = [0u8; BUS_WIDTH_IN_BYTES];
let start = (addr % BUS_WIDTH_IN_BYTES as u64) as usize;
rw_mask[start..start + N].fill(true);
write_data[start..start + N].copy_from_slice(&data);
self.try_rw_op(bus_addr, Some(write_data), rw_mask, max_wait)?;
Ok(())
}
#[track_caller]
fn write<const N: usize>(&mut self, addr: u64, data: [u8; N], max_wait: u64) {
self.try_write(addr, data, max_wait)
.expect("read returned error")
}
}
#[test]
#[hdl]
fn test_main_memory_and_io() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(test_harness());
let writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
struct DumpVcdOnDrop {
writer: Option<RcWriter>,
}
impl Drop for DumpVcdOnDrop {
fn drop(&mut self) {
if let Some(mut writer) = self.writer.take() {
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
}
}
}
let mut writer = DumpVcdOnDrop {
writer: Some(writer),
};
let mut tester = MainMemoryAndIoTester { sim, next_op_id: 0 };
tester.reset();
const UART_MESSAGE: &str = "Test.";
for b in UART_MESSAGE.bytes() {
tester.write(UART_START + SIMPLE_UART_TRANSMIT_OFFSET, [b], 200);
}
for (i, expected_chunk) in SRAM_INITIAL_CONTENTS
.as_chunks::<BUS_WIDTH_IN_BYTES>()
.0
.iter()
.enumerate()
{
let addr = (i as u64 * BUS_WIDTH_IN_BYTES as u64) + SRAM_START;
let chunk = tester.read::<BUS_WIDTH_IN_BYTES>(addr, 10);
assert_eq!(chunk, *expected_chunk);
}
for (i, chunk) in SRAM_WRITING_CONTENTS
.as_chunks::<BUS_WIDTH_IN_BYTES>()
.0
.iter()
.enumerate()
{
let addr = (i as u64 * BUS_WIDTH_IN_BYTES as u64) + SRAM_START;
tester.write(addr, *chunk, 10);
}
for (i, expected_chunk) in SRAM_WRITING_CONTENTS
.as_chunks::<BUS_WIDTH_IN_BYTES>()
.0
.iter()
.enumerate()
{
let addr = (i as u64 * BUS_WIDTH_IN_BYTES as u64) + SRAM_START;
let chunk = tester.read::<BUS_WIDTH_IN_BYTES>(addr, 10);
assert_eq!(chunk, *expected_chunk);
}
tester.wait(400);
for expected_byte in UART_MESSAGE.bytes() {
let [byte] = tester.read(UART_START + SIMPLE_UART_TRANSMIT_OFFSET, 10);
assert_eq!(byte, expected_byte);
}
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("expected/main_memory_and_io.vcd") {
panic!();
}
}

View file

@ -0,0 +1,750 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use cpu::{
main_memory_and_io::{
AddressRange, MemoryInterface, MemoryInterfaceConfig, MemoryOperationErrorKind,
MemoryOperationFinish, MemoryOperationFinishKind, MemoryOperationKind,
MemoryOperationStart, memory_interface_adapter_no_split,
},
next_pc::FETCH_BLOCK_ID_WIDTH,
util::array_vec::ArrayVec,
};
use fayalite::{
bundle::{BundleField, BundleType},
intern::{Intern, Interned},
module::instance_with_loc,
prelude::*,
sim::vcd::VcdWriterDecls,
util::RcWriter,
};
use std::{cell::Cell, collections::VecDeque, fmt};
fn get_next_delay(delay_sequence_index: &Cell<u64>) -> u8 {
let index = delay_sequence_index.get();
delay_sequence_index.set(delay_sequence_index.get().wrapping_add(1));
// make a pseudo-random number deterministically based on index
let random = index
.wrapping_add(1)
.wrapping_mul(0x8c16a62518f86883) // random prime
.rotate_left(32)
.wrapping_mul(0xf807b7df2082353d) // random prime
.rotate_right(60);
const DELAYS: &[u8; 0x20] = &[
0, 0, 0, 0, 0, 0, 0, 0, //
1, 1, 1, 1, 1, 1, 1, 1, //
2, 2, 2, 2, 2, 2, 2, 2, //
3, 3, 3, 3, 4, 5, 6, 20, //
];
DELAYS[(random & 0x1F) as usize]
}
#[hdl_module(extern)]
fn mock_memory(memory: Memory) {
let Memory { config, contents } = memory;
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let input_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.input(MemoryInterface[config]);
m.register_clock_for_past(cd.clk);
#[hdl]
async fn run(
cd: Expr<ClockDomain>,
input_interface: Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
config: PhantomConst<MemoryInterfaceConfig>,
contents: Interned<str>,
delay_sequence_index: &Cell<u64>,
mut sim: ExternModuleSimulationState,
) {
#[derive(Debug)]
struct Op {
cycles_left: u8,
op_id: SimValue<UInt>,
finish: SimValue<MemoryOperationFinish<PhantomConst<MemoryInterfaceConfig>>>,
}
let mut ops = VecDeque::<Op>::with_capacity(config.get().queue_capacity.get());
let finish_ty = input_interface.ty().finish.data.HdlSome;
loop {
for op in &mut ops {
op.cycles_left = op.cycles_left.saturating_sub(1);
}
let next_op_ids_ty = input_interface.ty().next_op_ids.HdlSome;
sim.write(
input_interface.next_op_ids,
#[hdl(sim)]
(input_interface.ty().next_op_ids).HdlSome(
next_op_ids_ty
.from_iter_sim(
next_op_ids_ty.element().zero(),
ops.iter().map(|op| &op.op_id),
)
.expect("known to fit"),
),
)
.await;
if let Some(Op {
cycles_left: 0,
op_id: _,
finish,
}) = ops.front()
{
sim.write(
input_interface.finish.data,
#[hdl(sim)]
(input_interface.ty().finish.data).HdlSome(finish),
)
.await;
} else {
sim.write(
input_interface.finish.data,
#[hdl(sim)]
(input_interface.ty().finish.data).HdlNone(),
)
.await;
}
sim.write_bool(
input_interface.start.ready,
ops.len() < config.get().queue_capacity.get(),
)
.await;
sim.wait_for_clock_edge(cd.clk).await;
if sim
.read_past_bool(input_interface.finish.ready, cd.clk)
.await
{
dbg!(ops.pop_front_if(|op| op.cycles_left == 0));
}
if sim
.read_past_bool(input_interface.start.ready, cd.clk)
.await
{
#[hdl(sim)]
if let HdlSome(start) = sim.read_past(input_interface.start.data, cd.clk).await {
#[hdl(sim)]
let MemoryOperationStart::<_> {
kind,
addr,
write_data,
rw_mask,
op_id,
config: _,
} = start;
let mut error = false;
let mut read_data = vec![0u8; finish_ty.read_data.len()];
#[hdl(sim)]
match &kind {
MemoryOperationKind::Read => {
for (i, v) in read_data.iter_mut().enumerate() {
if *rw_mask[i] {
let addr = addr.as_int().wrapping_add(i as u64);
let offset =
addr.wrapping_sub(config.get().address_range.start().0);
if !config.get().address_range.contains(addr)
|| offset >= contents.len() as u64
{
error = true;
break;
}
*v = contents.as_bytes()[offset as usize];
println!(
"reading byte at {addr:#x} (offset={offset:#x}) -> {v:#x} (contents={contents:?})",
);
}
}
if !error {
println!(
"read chunk at {addr:#x}: {:#x?}",
std::fmt::from_fn(|f| f
.debug_map()
.entries(
read_data
.iter()
.enumerate()
.filter_map(|(i, &v)| rw_mask[i].then(|| (i, v)))
)
.finish()),
addr = addr.as_int(),
);
}
}
MemoryOperationKind::Write => {
todo!("write {write_data:?}");
}
}
let finish_kind = if error {
println!("error at: {addr} config: {config:?}");
read_data.fill(0);
#[hdl(sim)]
MemoryOperationFinishKind.Error(
#[hdl(sim)]
MemoryOperationErrorKind.Generic(),
)
} else {
#[hdl(sim)]
MemoryOperationFinishKind.Success(kind)
};
ops.push_back(Op {
cycles_left: get_next_delay(delay_sequence_index),
op_id,
finish: #[hdl(sim)]
MemoryOperationFinish::<_> {
kind: finish_kind,
read_data,
config,
},
});
}
}
}
}
m.extern_module_simulation_fn(
(cd, input_interface, config, contents),
async |(cd, input_interface, config, contents), mut sim| {
// intentionally have a different sequence each time we're reset
let delay_sequence_index = Cell::new(0);
sim.resettable(
cd,
async |mut sim| {
sim.write(
input_interface.next_op_ids,
#[hdl(sim)]
(input_interface.ty().next_op_ids).HdlNone(),
)
.await;
sim.write_bool(input_interface.start.ready, false).await;
sim.write(
input_interface.finish.data,
#[hdl(sim)]
(input_interface.ty().finish.data).HdlNone(),
)
.await;
},
|sim, ()| {
run(
cd,
input_interface,
config,
contents,
&delay_sequence_index,
sim,
)
},
)
.await
},
);
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
struct Memory {
contents: Interned<str>,
config: PhantomConst<MemoryInterfaceConfig>,
}
impl Memory {
fn new(
contents: impl AsRef<str>,
log2_bus_width_in_bytes: u8,
address_range: AddressRange,
) -> Self {
Self {
contents: contents.as_ref().intern(),
config: PhantomConst::new_sized(MemoryInterfaceConfig::new(
log2_bus_width_in_bytes,
8,
FETCH_BLOCK_ID_WIDTH,
address_range,
)),
}
}
}
#[hdl_module(extern)]
fn mock_cpu(memories: Interned<[Memory]>) {
const LOG2_BUS_WIDTH: u8 = 3;
const BUS_WIDTH: usize = 1 << LOG2_BUS_WIDTH;
let config = PhantomConst::new_sized(MemoryInterfaceConfig::new(
LOG2_BUS_WIDTH,
8,
FETCH_BLOCK_ID_WIDTH,
AddressRange::Full,
));
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let output_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.output(MemoryInterface[config]);
#[hdl]
let finished: Bool = m.output();
m.register_clock_for_past(cd.clk);
#[derive(PartialEq, Clone, Copy)]
struct Op {
addr: u64,
read_mask: [bool; BUS_WIDTH],
}
impl fmt::Debug for Op {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self { addr, read_mask } = self;
f.debug_struct("Op")
.field("addr", &fmt::from_fn(|f| write!(f, "{addr:#x}")))
.field("read_mask", read_mask)
.finish()
}
}
#[hdl]
async fn generator(
cd: Expr<ClockDomain>,
output_interface: Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
config: PhantomConst<MemoryInterfaceConfig>,
sequence: &[Op],
delay_sequence_index: &Cell<u64>,
mut sim: ExternModuleSimulationState,
) {
println!("generator: start");
let start_ty = MemoryOperationStart[config];
for (op_index, op) in sequence.iter().enumerate() {
sim.write(
output_interface.start.data,
#[hdl(sim)]
(output_interface.ty().start.data).HdlNone(),
)
.await;
let delay = get_next_delay(delay_sequence_index);
println!("generator: delay by {delay}");
for i in 0..delay {
println!("generator: delay cycle {i}");
sim.wait_for_clock_edge(cd.clk).await;
}
println!("generator: generated {op:?}");
sim.write(
output_interface.start.data,
#[hdl(sim)]
(output_interface.ty().start.data).HdlSome(
#[hdl(sim)]
MemoryOperationStart::<_> {
kind: #[hdl(sim)]
MemoryOperationKind.Read(),
addr: op.addr,
write_data: &[0u8; BUS_WIDTH][..],
rw_mask: &op.read_mask[..],
op_id: op_index.cast_to(start_ty.op_id),
config,
},
),
)
.await;
sim.wait_for_clock_edge(cd.clk).await;
while !sim
.read_past_bool(output_interface.start.ready, cd.clk)
.await
{
println!("generator: start.ready was false");
sim.wait_for_clock_edge(cd.clk).await;
}
}
sim.write(
output_interface.start.data,
#[hdl(sim)]
(output_interface.ty().start.data).HdlNone(),
)
.await;
}
#[hdl]
async fn checker(
cd: Expr<ClockDomain>,
output_interface: Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
config: PhantomConst<MemoryInterfaceConfig>,
sequence: &[Op],
memories: Interned<[Memory]>,
delay_sequence_index: &Cell<u64>,
sim: &mut ExternModuleSimulationState,
) {
println!("checker: start");
for op in sequence {
sim.write_bool(output_interface.finish.ready, false).await;
let delay = get_next_delay(delay_sequence_index);
println!("checker: delay by {delay}");
for i in 0..delay {
println!("checker: delay cycle {i}");
sim.wait_for_clock_edge(cd.clk).await;
}
sim.write_bool(output_interface.finish.ready, true).await;
sim.wait_for_clock_edge(cd.clk).await;
let mut finish = loop {
#[hdl(sim)]
if let HdlSome(finish) = sim.read_past(output_interface.finish.data, cd.clk).await {
break finish;
}
println!("checker: finish.data was HdlNone");
sim.wait_for_clock_edge(cd.clk).await;
};
println!("checker: checking for {op:?}");
let finish_unmasked = finish.clone();
for (v, &mask) in finish.read_data.iter_mut().zip(&op.read_mask) {
if !mask {
*v = 0u8.to_sim_value(); // ignore outputs for ignored bytes
}
}
let mut expected_finish = memories
.iter()
.find(|m| dbg!(dbg!(m.config.get().address_range).contains(op.addr)))
.and_then(
|&Memory {
config: memory_config,
contents,
}|
-> Option<_> {
let mut read_data = [0u8; BUS_WIDTH];
let mut first_enabled = None;
let mut last_enabled = None;
for (i, &mask) in op.read_mask.iter().enumerate() {
if mask {
first_enabled.get_or_insert(i);
last_enabled = Some(i);
read_data[i] = *dbg!(
dbg!(contents).as_bytes().get(
dbg!(usize::try_from(
op.addr.wrapping_add(i as u64).wrapping_sub(
memory_config.get().address_range.start().0,
),
))
.ok()?,
)
)?;
}
}
if let (Some(first_enabled), Some(last_enabled)) =
(first_enabled, last_enabled)
{
let log2_bus_width_in_bytes =
memory_config.get().log2_bus_width_in_bytes;
if first_enabled >> log2_bus_width_in_bytes
!= last_enabled >> log2_bus_width_in_bytes
{
// this operation requires more than one operation at the final memory,
// so it gets turned into an error since we're using
// memory_interface_adapter_no_split
return None;
}
}
Some(
#[hdl(sim)]
MemoryOperationFinish::<_> {
kind: #[hdl(sim)]
MemoryOperationFinishKind.Success(
#[hdl(sim)]
MemoryOperationKind.Read(),
),
read_data: &read_data[..],
config,
},
)
},
)
.unwrap_or_else(|| {
#[hdl(sim)]
MemoryOperationFinish::<_> {
kind: #[hdl(sim)]
MemoryOperationFinishKind.Error(
#[hdl(sim)]
MemoryOperationErrorKind.Generic(),
),
read_data: &[0u8; BUS_WIDTH][..],
config,
}
});
// make SimValue fill in the enum padding so they format the same
SimValue::bits_mut(&mut finish);
SimValue::bits_mut(&mut expected_finish);
assert!(
format!("{finish:#?}") == format!("{expected_finish:#?}"),
"op={op:#?}\nexpected_finish={expected_finish:#?}\n\
finish={finish:#?}\nfinish_unmasked={finish_unmasked:#?}"
);
}
}
m.extern_module_simulation_fn(
(cd, output_interface, finished, config, memories),
async |(cd, output_interface, finished, config, memories), mut sim| {
sim.write_bool(finished, false).await;
sim.write_bool(output_interface.finish.ready, false).await;
sim.write(
output_interface.start.data,
#[hdl(sim)]
(output_interface.ty().start.data).HdlNone(),
)
.await;
// intentionally have a different sequence each time we're reset
let generator_delay_sequence_index = Cell::new(1 << 63);
let checker_delay_sequence_index = Cell::new(1 << 62);
let mut sequence = Vec::new();
sequence.push(Op {
addr: !0 << 16,
read_mask: [true; _],
});
for (i, memory) in memories.iter().enumerate() {
assert!(
memory
.config
.get()
.address_range
.start()
.0
.is_multiple_of(BUS_WIDTH as u64)
);
assert!(
memory
.config
.get()
.address_range
.last()
.wrapping_add(1)
.is_multiple_of(BUS_WIDTH as u64)
);
if i == 0 {
for log2_read_size in 0..=LOG2_BUS_WIDTH {
let read_size = 1 << log2_read_size;
for offset in (0..BUS_WIDTH).step_by(read_size) {
sequence.push(Op {
addr: memory.config.get().address_range.start().0,
read_mask: std::array::from_fn(|byte_index| {
byte_index
.checked_sub(offset)
.is_some_and(|v| v < read_size)
}),
});
}
}
}
for (addr, chunk) in (memory.config.get().address_range.start().0..)
.step_by(BUS_WIDTH)
.zip(memory.contents.as_bytes().chunks(BUS_WIDTH))
{
let size = memory.config.get().bus_width_in_bytes();
for offset in (0..BUS_WIDTH).step_by(size) {
let mut op = Op {
addr,
read_mask: std::array::from_fn(|i| i >= offset && i < offset + size),
};
op.read_mask[chunk.len()..].fill(false);
if op.read_mask.contains(&true) && sequence.last() != Some(&op) {
sequence.push(op);
}
}
}
}
sequence.extend_from_within(..);
sequence.extend_from_within(..);
sim.fork_join_scope(async |scope, mut sim| {
scope.spawn_detached(async |_scope, mut sim| {
sim.resettable(
cd,
async |mut sim| {
sim.write(
output_interface.start.data,
#[hdl(sim)]
(output_interface.ty().start.data).HdlNone(),
)
.await;
},
|sim, ()| {
generator(
cd,
output_interface,
config,
&sequence,
&generator_delay_sequence_index,
sim,
)
},
)
.await
});
scope.spawn_detached(async |_scope, mut sim| {
sim.resettable(
cd,
async |_| {},
async |mut sim, ()| {
let mut expected_range = 0u32..0u32;
loop {
sim.wait_for_clock_edge(cd.clk).await;
let next_op_ids =
sim.read_past(output_interface.next_op_ids, cd.clk).await;
#[hdl(sim)]
if let HdlSome(next_op_ids) = next_op_ids {
let op_id_ty = next_op_ids.ty().element();
let capacity = next_op_ids.ty().capacity();
let next_op_ids = ArrayVec::elements_sim_ref(&next_op_ids);
let expected_next_op_ids = Vec::from_iter(
expected_range
.clone()
.map(|i| i.cast_to(op_id_ty).into_sim_value()),
);
assert_eq!(
next_op_ids,
expected_next_op_ids,
"{:#x}..{:#x} ({expected_range:?}){}",
expected_range.start,
expected_range.end,
if expected_range.len() > capacity {
"\nexpected_range is bigger than capacity"
} else {
""
}
);
}
#[hdl(sim)]
if let HdlSome(_) =
sim.read_past(output_interface.start.data, cd.clk).await
{
if sim
.read_past_bool(output_interface.start.ready, cd.clk)
.await
{
expected_range.end += 1;
}
}
#[hdl(sim)]
if let HdlSome(_) =
sim.read_past(output_interface.finish.data, cd.clk).await
{
if sim
.read_past_bool(output_interface.finish.ready, cd.clk)
.await
{
expected_range.start += 1;
}
}
}
},
)
.await;
});
sim.resettable(
cd,
async |mut sim| {
sim.write_bool(finished, false).await;
sim.write_bool(output_interface.finish.ready, false).await;
},
async |mut sim, ()| {
checker(
cd,
output_interface,
config,
&sequence,
memories,
&checker_delay_sequence_index,
&mut sim,
)
.await;
sim.write_bool(finished, true).await;
loop {
sim.write_bool(output_interface.finish.ready, true).await;
sim.wait_for_clock_edge(cd.clk).await;
#[hdl(sim)]
if let HdlSome(finish) =
sim.read_past(output_interface.finish.data, cd.clk).await
{
panic!("spurious finished transaction: {finish:#?}");
}
}
},
)
.await
})
.await;
},
);
}
#[hdl_module]
fn memory_interface_adapter_no_split_dut(memories: Interned<[Memory]>) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let finished: Bool = m.output();
#[hdl]
let mock_cpu = instance(mock_cpu(memories));
connect(mock_cpu.cd, cd);
connect(finished, mock_cpu.finished);
let (fields, inputs): (Vec<_>, Vec<_>) = memories
.iter()
.enumerate()
.map(|(index, &memory)| {
let mock_mem = instance_with_loc(
&format!("mock_mem_{index}"),
mock_memory(memory),
SourceLocation::caller(),
);
connect(mock_mem.cd, cd);
(
BundleField {
name: format!("{index}").intern_deref(),
flipped: false,
ty: MemoryInterface[memory.config].canonical(),
},
mock_mem.input_interface,
)
})
.unzip();
let bundle_ty = Bundle::new(fields.intern_deref());
#[hdl]
let adapter = instance(memory_interface_adapter_no_split(
mock_cpu.ty().output_interface.config,
bundle_ty,
));
connect(adapter.cd, cd);
connect(adapter.input_interface, mock_cpu.output_interface);
for (field, input) in bundle_ty.fields().into_iter().zip(inputs) {
connect(input, Expr::field(adapter.output_interfaces, &field.name));
}
}
#[test]
#[hdl]
fn test_memory_interface_adapter_no_split() {
let _n = SourceLocation::normalize_files_for_tests();
let memories = vec![
Memory::new("Testing", 3, AddressRange::from_range(0x1000..0x2000)),
Memory::new("Memory2.", 2, AddressRange::from_range(0x2000..0x2010)),
Memory::new("Contents Test", 0, AddressRange::from_range(0x3000..0x3100)),
]
.intern_deref();
let m = memory_interface_adapter_no_split_dut(memories);
let mut sim = Simulation::new(m);
let writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
struct DumpVcdOnDrop {
writer: Option<RcWriter>,
}
impl Drop for DumpVcdOnDrop {
fn drop(&mut self) {
if let Some(mut writer) = self.writer.take() {
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
}
}
}
let mut writer = DumpVcdOnDrop {
writer: Some(writer),
};
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
for cycle in 0..500 {
println!("cycle: {cycle}");
sim.advance_time(SimDuration::from_nanos(500));
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(SimDuration::from_nanos(500));
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, false);
}
sim.advance_time(SimDuration::from_nanos(500));
assert!(sim.read_bool(sim.io().finished));
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("expected/memory_interface_adapter_no_split.vcd") {
panic!();
}
}

View file

@ -0,0 +1,980 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use cpu::{
main_memory_and_io::{
MemoryInterface, MemoryInterfaceConfig, MemoryOperationErrorKind, MemoryOperationFinish,
MemoryOperationFinishKind, MemoryOperationKind, MemoryOperationStart,
simple_uart::{
ReceiverQueueStatus, SIMPLE_UART_RECEIVE_OFFSET, SIMPLE_UART_STATUS_OFFSET,
SIMPLE_UART_TRANSMIT_OFFSET, SIMPLE_UART_USED_SIZE, receiver, receiver_no_queue,
simple_uart, simple_uart_memory_interface_config, transmitter, uart_clock_gen,
},
},
next_pc::FETCH_BLOCK_ID_WIDTH,
util::array_vec::ArrayVec,
};
use fayalite::{
bundle::BundleType,
intern::{Intern, Interned},
prelude::*,
sim::vcd::VcdWriterDecls,
util::{RcWriter, ready_valid::ReadyValid},
};
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)
}
const CLOCK_FREQUENCY: f64 = 200_000.0;
const CLOCK_HALF_PERIOD: SimDuration = half_period(CLOCK_FREQUENCY);
const BAUD_RATE: f64 = 9600.0;
const BAUD_HALF_PERIOD: SimDuration = half_period(BAUD_RATE);
const BAUD_PERIOD: SimDuration = BAUD_HALF_PERIOD.saturating_add(BAUD_HALF_PERIOD);
const fn baud_period_slow_percentage(percent: i128) -> SimDuration {
let v = BAUD_PERIOD.as_attos();
let v = v.strict_add_signed(v as i128 * percent / 100);
SimDuration::from_attos(v)
}
/// 3% slow BAUD_PERIOD
const BAUD_PERIOD_SLOW: SimDuration = baud_period_slow_percentage(3);
/// 3% fast BAUD_PERIOD
const BAUD_PERIOD_FAST: SimDuration = baud_period_slow_percentage(-3);
const _: () = {
assert!(BAUD_HALF_PERIOD.as_attos() < BAUD_PERIOD_FAST.as_attos());
assert!(BAUD_PERIOD_FAST.as_attos() < BAUD_PERIOD.as_attos());
assert!(BAUD_PERIOD.as_attos() < BAUD_PERIOD_SLOW.as_attos());
};
fn clock_input_properties() -> PhantomConst<peripherals::ClockInputProperties> {
peripherals::ClockInput::new(CLOCK_FREQUENCY).properties
}
#[hdl_module(extern)]
fn reference_baud_rate_clk_gen() {
#[hdl]
let reference_baud_rate_clk: Clock = m.output();
m.extern_module_simulation_fn(
reference_baud_rate_clk,
async |reference_baud_rate_clk, mut sim| {
loop {
sim.write_clock(reference_baud_rate_clk, false).await;
sim.advance_time(BAUD_HALF_PERIOD).await;
sim.write_clock(reference_baud_rate_clk, true).await;
sim.advance_time(BAUD_HALF_PERIOD).await;
}
},
);
}
#[hdl_module(extern)]
fn sim_receiver() {
#[hdl]
let tx: Bool = m.input();
#[hdl]
let text_out: SimOnly<String> = m.output();
m.extern_module_simulation_fn((tx, text_out), async |(tx, text_out), mut sim| {
let mut text = SimOnlyValue::new(String::new());
sim.write(text_out, &text).await;
loop {
// wait for the starting edge of the start bit
while sim.read_bool(tx).await {
sim.wait_for_changes([tx], None).await;
}
// wait till the middle of the start bit
sim.advance_time(BAUD_HALF_PERIOD).await;
let mut byte = 0u8;
for i in 0..u8::BITS {
// wait till the middle of the next data bit
sim.advance_time(BAUD_PERIOD).await;
if sim.read_bool(tx).await {
byte |= 1 << i;
}
}
assert!(byte.is_ascii());
text.push(byte as char);
sim.write(text_out, &text).await;
// wait till the middle of the stop bit
sim.advance_time(BAUD_PERIOD).await;
// we're in the middle of the stop bit, the stop bit is a one so we can just loop and wait for
// the next transition to zero which is the next start bit.
}
});
}
#[hdl_module]
fn transmitter_and_uart_clock_gen() {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let tx: Bool = m.output();
#[hdl]
let input_byte: ReadyValid<UInt<8>> = m.input();
#[hdl]
let reference_baud_rate_clk: Clock = m.output();
#[hdl]
let text_out: SimOnly<String> = m.output();
#[hdl]
let sim_receiver = instance(sim_receiver());
connect(sim_receiver.tx, tx);
connect(text_out, sim_receiver.text_out);
#[hdl]
let reference_baud_rate_clk_gen = instance(reference_baud_rate_clk_gen());
connect(
reference_baud_rate_clk,
reference_baud_rate_clk_gen.reference_baud_rate_clk,
);
#[hdl]
let transmitter = instance(transmitter());
connect(transmitter.cd, cd);
connect(transmitter.input_byte, input_byte);
connect(tx, transmitter.tx);
#[hdl]
let clock_gen = instance(uart_clock_gen(clock_input_properties(), BAUD_RATE));
connect(clock_gen.cd, cd);
connect(transmitter.tick, clock_gen.tick);
}
#[test]
#[hdl]
fn test_transmitter_and_uart_clock_gen() {
let _n = SourceLocation::normalize_files_for_tests();
let m = transmitter_and_uart_clock_gen();
let mut sim = Simulation::new(m);
let writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
struct DumpVcdOnDrop {
writer: Option<RcWriter>,
}
impl Drop for DumpVcdOnDrop {
fn drop(&mut self) {
if let Some(mut writer) = self.writer.take() {
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
}
}
}
let mut writer = DumpVcdOnDrop {
writer: Some(writer),
};
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
let text = "Hi!\n";
let mut bytes = text.as_bytes().iter();
for cycle in 0..1000 {
let input_byte = match bytes.clone().next() {
Some(b) if cycle > 10 =>
{
#[hdl(sim)]
HdlSome(b)
}
_ =>
{
#[hdl(sim)]
HdlNone()
}
};
sim.write(sim.io().input_byte.data, &input_byte);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
if sim.read_bool(sim.io().input_byte.ready) {
#[hdl(sim)]
if let HdlSome(_) = input_byte {
bytes.next();
}
}
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, false);
}
assert_eq!(sim.read(sim.io().text_out).as_str(), text);
assert!(bytes.as_slice().is_empty());
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("expected/transmitter_and_uart_clock_gen.vcd") {
panic!();
}
}
#[hdl_module(extern)]
fn receiver_no_queue_test_cases(text: Interned<str>) {
#[hdl]
let rx: Bool = m.output();
#[hdl]
let cur_byte: UInt<8> = m.output();
m.extern_module_simulation_fn(
(rx, cur_byte, text),
async |(rx, cur_byte, text), mut sim| {
// ensure the receiver can properly handle slightly fast or slow baud rates
let baud_periods = [
BAUD_PERIOD_FAST,
BAUD_PERIOD_FAST,
BAUD_PERIOD_FAST,
BAUD_PERIOD_FAST,
BAUD_PERIOD,
BAUD_PERIOD,
BAUD_PERIOD,
BAUD_PERIOD,
BAUD_PERIOD_SLOW,
BAUD_PERIOD_SLOW,
BAUD_PERIOD_SLOW,
BAUD_PERIOD_SLOW,
]
.iter()
.copied()
.cycle();
sim.write(rx, true).await;
sim.write(cur_byte, 0u8).await;
// allow time for reset and stuff
sim.advance_time(SimDuration::from_micros(100)).await;
for (byte, baud_period) in text.bytes().cycle().zip(baud_periods) {
sim.write(cur_byte, byte).await;
sim.write(rx, false).await; // start bit
sim.advance_time(baud_period).await;
for i in 0..u8::BITS {
sim.write(rx, byte & (1 << i) != 0).await; // data bit
sim.advance_time(baud_period).await;
}
sim.write(rx, true).await; // stop bit
sim.advance_time(baud_period).await;
}
},
);
}
#[hdl_module]
fn receiver_no_queue_and_uart_clock_gen(text: Interned<str>) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let rx: Bool = m.output();
#[hdl]
let output_byte: HdlOption<UInt<8>> = m.output();
#[hdl]
let cur_byte: UInt<8> = m.output();
#[hdl]
let reference_baud_rate_clk: Clock = m.output();
#[hdl]
let test_cases = instance(receiver_no_queue_test_cases(text));
connect(rx, test_cases.rx);
connect(cur_byte, test_cases.cur_byte);
#[hdl]
let reference_baud_rate_clk_gen = instance(reference_baud_rate_clk_gen());
connect(
reference_baud_rate_clk,
reference_baud_rate_clk_gen.reference_baud_rate_clk,
);
// only need one stage of synchronization in the simulator
#[hdl]
let rx_synchronized = reg_builder().clock_domain(cd).reset(true);
connect(rx_synchronized, rx);
#[hdl]
let receiver = instance(receiver_no_queue());
connect(receiver.cd, cd);
connect(receiver.rx_synchronized, rx_synchronized);
connect(output_byte, receiver.output_byte);
#[hdl]
let clock_gen = instance(uart_clock_gen(clock_input_properties(), BAUD_RATE));
connect(clock_gen.cd, cd);
connect(receiver.tick, clock_gen.tick);
}
#[test]
#[hdl]
fn test_receiver_no_queue_and_uart_clock_gen() {
let _n = SourceLocation::normalize_files_for_tests();
let text = "Testing 123, testing, testing.\n".intern();
let m = receiver_no_queue_and_uart_clock_gen(text);
let mut sim = Simulation::new(m);
let writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
struct DumpVcdOnDrop {
writer: Option<RcWriter>,
}
impl Drop for DumpVcdOnDrop {
fn drop(&mut self) {
if let Some(mut writer) = self.writer.take() {
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
}
}
}
let mut writer = DumpVcdOnDrop {
writer: Some(writer),
};
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
let mut received_text = String::new();
for _cycle in 0..10000 {
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, false);
#[hdl(sim)]
if let HdlSome(byte) = sim.read(sim.io().output_byte) {
let byte = byte.as_int();
let expected_byte = sim.read(sim.io().cur_byte).as_int();
assert_eq!(
byte, expected_byte,
"byte={:#x} {:?}, expected_byte={:#x} {:?}",
byte, byte as char, expected_byte, expected_byte as char,
);
assert!(byte.is_ascii());
received_text.push(byte as char);
}
}
println!("{received_text:?}");
assert!(received_text.len() >= text.len());
let mut expected_text = text.repeat(received_text.len().div_ceil(text.len()));
expected_text.truncate(received_text.len());
assert_eq!(received_text, expected_text);
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("expected/receiver_no_queue_and_uart_clock_gen.vcd") {
panic!();
}
}
#[hdl_module(extern)]
fn receiver_test_cases() {
#[hdl]
let clk: Clock = m.input();
#[hdl]
let rx: Bool = m.output();
#[hdl]
let cur_byte: UInt<8> = m.output();
#[hdl]
let ready: Bool = m.output();
#[hdl]
let text: SimOnly<String> = m.input();
m.register_clock_for_past(clk);
m.extern_module_simulation_fn(
(clk, rx, cur_byte, ready, text),
async |(clk, rx, cur_byte, ready, text), mut sim| {
sim.write(rx, true).await;
sim.write(ready, false).await;
loop {
sim.write(cur_byte, 0u8).await;
sim.wait_for_clock_edge(clk).await;
sim.write(ready, true).await;
let text = loop {
sim.wait_for_clock_edge(clk).await;
let text = sim.read_past(text, clk).await;
if text.is_empty() {
continue;
}
break text;
};
sim.write(ready, false).await;
sim.wait_for_clock_edge(clk).await;
for byte in text.bytes() {
sim.write(cur_byte, byte).await;
sim.write(rx, false).await; // start bit
sim.advance_time(BAUD_PERIOD).await;
for i in 0..u8::BITS {
sim.write(rx, byte & (1 << i) != 0).await; // data bit
sim.advance_time(BAUD_PERIOD).await;
}
sim.write(rx, true).await; // stop bit
sim.advance_time(BAUD_PERIOD).await;
}
}
},
);
}
#[hdl_module]
fn receiver_and_uart_clock_gen(queue_capacity: NonZeroUsize) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let rx: Bool = m.output();
#[hdl]
let reference_baud_rate_clk: Clock = m.output();
#[hdl]
let cur_byte: UInt<8> = m.output();
#[hdl]
let ready: Bool = m.output();
#[hdl]
let text: SimOnly<String> = m.input();
#[hdl]
let output_byte: ReadyValid<UInt<8>> = m.output();
#[hdl]
let queue_status: ReceiverQueueStatus = m.output();
#[hdl]
let test_cases = instance(receiver_test_cases());
connect(test_cases.clk, cd.clk);
connect(rx, test_cases.rx);
connect(cur_byte, test_cases.cur_byte);
connect(ready, test_cases.ready);
connect(test_cases.text, text);
#[hdl]
let reference_baud_rate_clk_gen = instance(reference_baud_rate_clk_gen());
connect(
reference_baud_rate_clk,
reference_baud_rate_clk_gen.reference_baud_rate_clk,
);
// only need one stage of synchronization in the simulator
#[hdl]
let rx_synchronized = reg_builder().clock_domain(cd).reset(true);
connect(rx_synchronized, rx);
#[hdl]
let receiver = instance(receiver(queue_capacity));
connect(receiver.cd, cd);
connect(receiver.rx_synchronized, rx_synchronized);
connect(output_byte, receiver.output_byte);
connect(queue_status, receiver.queue_status);
#[hdl]
let clock_gen = instance(uart_clock_gen(clock_input_properties(), BAUD_RATE));
connect(clock_gen.cd, cd);
connect(receiver.tick, clock_gen.tick);
}
#[test]
#[hdl]
fn test_receiver_and_uart_clock_gen() {
let _n = SourceLocation::normalize_files_for_tests();
const QUEUE_SIZE: NonZeroUsize = NonZeroUsize::new(4).expect("not zero");
let m = receiver_and_uart_clock_gen(QUEUE_SIZE);
let mut sim = Simulation::new(m);
let writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
struct DumpVcdOnDrop {
writer: Option<RcWriter>,
}
impl Drop for DumpVcdOnDrop {
fn drop(&mut self) {
if let Some(mut writer) = self.writer.take() {
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
}
}
}
let mut writer = DumpVcdOnDrop {
writer: Some(writer),
};
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
let empty_text = SimOnlyValue::new(String::new());
sim.write(sim.io().text, &empty_text);
sim.write_bool(sim.io().output_byte.ready, false);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, false);
const TEST_TEXT: &str = "Test\n";
const { assert!(TEST_TEXT.len() > QUEUE_SIZE.get()) }
for bytes_before_dequeue in 0..=TEST_TEXT.len() {
println!("\nbytes_before_dequeue={bytes_before_dequeue}");
for _cycle in 0..10 {
if sim.read_bool(sim.io().ready) {
break;
}
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
}
assert!(sim.read_bool(sim.io().ready));
let text = SimOnlyValue::new(TEST_TEXT[..bytes_before_dequeue].to_string());
dbg!(&text);
sim.write(sim.io().text, &text);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
sim.write(sim.io().text, &empty_text);
for _cycle in 0..2000 {
if sim.read_bool(sim.io().ready) {
break;
}
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
}
assert!(sim.read_bool(sim.io().ready));
for _cycle in 0..10 {
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
}
dbg!(bytes_before_dequeue);
#[hdl(sim)]
match dbg!(sim.read(sim.io().queue_status)) {
ReceiverQueueStatus::QueueEmpty => {
assert_eq!(bytes_before_dequeue, 0);
}
ReceiverQueueStatus::QueueNotEmpty => {
assert!(bytes_before_dequeue > 0 && bytes_before_dequeue < QUEUE_SIZE.get() - 1);
}
ReceiverQueueStatus::QueueAlmostFull => {
assert_eq!(bytes_before_dequeue, QUEUE_SIZE.get() - 1);
}
ReceiverQueueStatus::QueueFull => {
assert_eq!(bytes_before_dequeue, QUEUE_SIZE.get());
}
ReceiverQueueStatus::QueueOverflowed => {
assert!(bytes_before_dequeue > QUEUE_SIZE.get());
}
ReceiverQueueStatus::Unknown => unreachable!(),
}
let expected_text = &TEST_TEXT[..bytes_before_dequeue.min(QUEUE_SIZE.get())];
dbg!(expected_text);
let mut received_text = String::new();
for expected_byte in expected_text.bytes() {
sim.write_bool(sim.io().output_byte.ready, true);
#[hdl(sim)]
if let HdlSome(byte) = sim.read(sim.io().output_byte.data) {
let byte = byte.as_int();
assert_eq!(
byte, expected_byte,
"byte={:#x} {:?}, expected_byte={:#x} {:?}",
byte, byte as char, expected_byte, expected_byte as char,
);
assert!(byte.is_ascii());
received_text.push(byte as char);
} else {
break;
}
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
}
#[hdl(sim)]
if let HdlSome(_) = sim.read(sim.io().output_byte.data) {
panic!("queue should be empty now");
}
sim.write_bool(sim.io().output_byte.ready, false);
}
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("expected/receiver_and_uart_clock_gen.vcd") {
panic!();
}
}
#[hdl_module]
fn simple_uart_loopback(
config: PhantomConst<MemoryInterfaceConfig>,
clock_input_properties: PhantomConst<peripherals::ClockInputProperties>,
baud_rate: f64,
receiver_queue_size: NonZeroUsize,
) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let memory_interface: MemoryInterface<PhantomConst<MemoryInterfaceConfig>> =
m.input(MemoryInterface[config]);
#[hdl]
let tx: Bool = m.output();
#[hdl]
let inner = instance(simple_uart(
config,
clock_input_properties,
baud_rate,
receiver_queue_size,
));
connect(inner.cd, cd);
connect(inner.memory_interface, memory_interface);
connect(tx, inner.uart.tx);
connect(inner.uart.rx, inner.uart.tx);
}
const SIMPLE_UART_MEMORY_INTERFACE_CONFIG: MemoryInterfaceConfig =
simple_uart_memory_interface_config(FETCH_BLOCK_ID_WIDTH, Wrapping(0));
struct MemoryOperationRunner<Sim: BorrowMut<Simulation<T>>, T: BundleType> {
sim: Sim,
clk: Expr<Clock>,
memory_interface: Expr<MemoryInterface<PhantomConst<MemoryInterfaceConfig>>>,
next_op_id: u64,
_phantom: PhantomData<T>,
}
const BUS_WIDTH_IN_BYTES: usize = SIMPLE_UART_MEMORY_INTERFACE_CONFIG.bus_width_in_bytes();
impl<Sim: BorrowMut<Simulation<T>>, T: BundleType> MemoryOperationRunner<Sim, T> {
#[hdl]
fn run_memory_operation(
&mut self,
kind: impl ToSimValue<Type = MemoryOperationKind>,
addr: u64,
write_data: [u8; BUS_WIDTH_IN_BYTES],
rw_mask: [bool; BUS_WIDTH_IN_BYTES],
timeout_cycles: usize,
) -> Result<[u8; BUS_WIDTH_IN_BYTES], SimValue<MemoryOperationErrorKind>> {
let kind = kind.into_sim_value();
assert_eq!(addr % BUS_WIDTH_IN_BYTES as u64, 0);
let config = self.memory_interface.ty().config;
let op_id = self.next_op_id.cast_to(UInt[FETCH_BLOCK_ID_WIDTH]);
self.next_op_id += 1;
println!(
"started: MemoryOperationStart {{\n \
kind: {kind:?},\n \
addr: {addr:#x},\n \
write_data: {write_data:?},\n \
rw_mask: {rw_mask:?},\n \
op_id: {op_id},\n\
}}"
);
let start_value = #[hdl(sim)]
MemoryOperationStart::<_> {
kind,
addr,
write_data: &write_data[..],
rw_mask: &rw_mask[..],
op_id: op_id,
config,
};
#[hdl]
let MemoryInterface::<_> {
start,
finish,
next_op_ids,
config: _,
} = self.memory_interface;
let sim = self.sim.borrow_mut();
let check_io = |sim: &mut Simulation<T>,
expected_start_ready: bool,
expected_finish_data_is_some: bool,
expected_next_op_ids: &[_]| {
assert_eq!(sim.read_bool(start.ready), expected_start_ready);
let finish_data = sim.read(finish.data);
let finish_data_is_some = #[hdl(sim)]
if let HdlSome(_) = &finish_data {
true
} else {
false
};
assert_eq!(
finish_data_is_some, expected_finish_data_is_some,
"finish.data: {finish_data:?}"
);
#[hdl(sim)]
if let HdlSome(next_op_ids) = sim.read(next_op_ids) {
let next_op_ids: Vec<_> = ArrayVec::elements_sim_ref(&next_op_ids)
.iter()
.map(|v| {
v.cast_to_static::<UInt<{ FETCH_BLOCK_ID_WIDTH }>>()
.as_int()
})
.collect();
assert_eq!(next_op_ids, expected_next_op_ids);
} else {
panic!("next_op_ids should be HdlSome");
}
};
check_io(sim, true, false, &[]);
sim.write(
start.data,
#[hdl(sim)]
(start.ty().data).HdlSome(&start_value),
);
sim.write(finish.ready, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(self.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(self.clk, false);
sim.write(
start.data,
#[hdl(sim)]
(start.ty().data).HdlNone(),
);
check_io(
sim,
false,
false,
&[start_value
.op_id
.cast_to_static::<UInt<{ FETCH_BLOCK_ID_WIDTH }>>()
.as_int()],
);
for _ in 0..timeout_cycles {
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(self.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(self.clk, false);
#[hdl(sim)]
if let HdlSome(finish_data) = sim.read(finish.data) {
#[hdl(sim)]
let MemoryOperationFinish::<_> {
kind,
read_data,
config: _,
} = finish_data;
assert_eq!(read_data.len(), BUS_WIDTH_IN_BYTES);
let read_data = std::array::from_fn(|i| read_data[i].as_int());
println!(
"finished: MemoryOperationFinish {{\n \
kind: {kind:?},\n \
read_data: {read_data:?},\n\
}}"
);
check_io(
sim,
false,
true,
&[start_value
.op_id
.cast_to_static::<UInt<{ FETCH_BLOCK_ID_WIDTH }>>()
.as_int()],
);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(self.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(self.clk, false);
sim.write(finish.ready, false);
check_io(sim, true, false, &[]);
return #[hdl(sim)]
match kind {
MemoryOperationFinishKind::Success(kind) => {
assert_eq!(
SimValue::bits(&kind),
SimValue::bits(&start_value.kind),
"finish_data.kind={kind:?}",
);
Ok(read_data)
}
MemoryOperationFinishKind::Error(error) => Err(error),
};
}
check_io(
sim,
false,
false,
&[start_value
.op_id
.cast_to_static::<UInt<{ FETCH_BLOCK_ID_WIDTH }>>()
.as_int()],
);
}
panic!("memory operation took too long");
}
#[hdl]
fn rw_bytes(
&mut self,
start_addr: u64,
is_read: bool,
bytes: &mut [u8],
timeout_cycles: usize,
) -> Result<(), SimValue<MemoryOperationErrorKind>> {
let offset_in_chunk = start_addr as usize % BUS_WIDTH_IN_BYTES;
let masked_addr = start_addr - offset_in_chunk as u64;
assert!(
offset_in_chunk
.checked_add(bytes.len())
.is_some_and(|v| v <= BUS_WIDTH_IN_BYTES)
);
let bytes_range = offset_in_chunk..(offset_in_chunk + bytes.len());
let mut write_data = [0u8; BUS_WIDTH_IN_BYTES];
let mut rw_mask = [false; BUS_WIDTH_IN_BYTES];
rw_mask[bytes_range.clone()].fill(true);
if !is_read {
write_data[bytes_range.clone()].copy_from_slice(bytes);
}
let read_bytes = self.run_memory_operation(
if is_read {
#[hdl(sim)]
MemoryOperationKind.Read()
} else {
#[hdl(sim)]
MemoryOperationKind.Write()
},
masked_addr,
write_data,
rw_mask,
timeout_cycles,
)?;
if is_read {
bytes.copy_from_slice(&read_bytes[bytes_range]);
}
Ok(())
}
#[hdl]
fn read_bytes<const N: usize>(
&mut self,
start_addr: u64,
timeout_cycles: usize,
) -> Result<[u8; N], SimValue<MemoryOperationErrorKind>> {
let mut retval = [0; _];
self.rw_bytes(start_addr, true, &mut retval, timeout_cycles)?;
Ok(retval)
}
#[hdl]
fn write_bytes<const N: usize>(
&mut self,
start_addr: u64,
mut bytes: [u8; N],
timeout_cycles: usize,
) -> Result<(), SimValue<MemoryOperationErrorKind>> {
self.rw_bytes(start_addr, false, &mut bytes, timeout_cycles)
}
}
#[test]
#[hdl]
fn test_simple_uart() {
let _n = SourceLocation::normalize_files_for_tests();
const RECEIVER_QUEUE_SIZE: NonZeroUsize = NonZeroUsize::new(16).expect("not zero");
let m = simple_uart_loopback(
PhantomConst::new_sized(SIMPLE_UART_MEMORY_INTERFACE_CONFIG),
clock_input_properties(),
BAUD_RATE,
RECEIVER_QUEUE_SIZE,
);
let mut sim = Simulation::new(m);
let writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
struct DumpVcdOnDrop {
writer: Option<RcWriter>,
}
impl Drop for DumpVcdOnDrop {
fn drop(&mut self) {
if let Some(mut writer) = self.writer.take() {
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
}
}
}
let mut writer = DumpVcdOnDrop {
writer: Some(writer),
};
sim.write(
sim.io().memory_interface.start.data,
sim.io().ty().memory_interface.start.data.HdlNone(),
);
sim.write(sim.io().memory_interface.finish.ready, false);
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, false);
let mut mem_op_runner = MemoryOperationRunner {
clk: sim.io().cd.clk,
memory_interface: sim.io().memory_interface,
next_op_id: 0,
_phantom: PhantomData,
sim,
};
let empty_status = ReceiverQueueStatus::sim_as_u8(
#[hdl(sim)]
ReceiverQueueStatus.QueueEmpty(),
);
assert_eq!(
mem_op_runner
.read_bytes(SIMPLE_UART_STATUS_OFFSET, 1)
.unwrap(),
[empty_status],
);
mem_op_runner
.write_bytes(SIMPLE_UART_STATUS_OFFSET, [0], 1)
.unwrap_err();
assert_eq!(
mem_op_runner
.read_bytes(SIMPLE_UART_RECEIVE_OFFSET, 1)
.unwrap(),
[0],
);
const { assert!(SIMPLE_UART_RECEIVE_OFFSET + 1 == SIMPLE_UART_STATUS_OFFSET) };
assert_eq!(
mem_op_runner
.read_bytes(SIMPLE_UART_RECEIVE_OFFSET, 1)
.unwrap(),
[0, empty_status],
);
for i in 0..2 * BUS_WIDTH_IN_BYTES as u64 {
mem_op_runner
.read_bytes::<1>(SIMPLE_UART_USED_SIZE.get() + i, 1)
.unwrap_err();
mem_op_runner
.write_bytes(SIMPLE_UART_USED_SIZE.get() + i, [0], 1)
.unwrap_err();
}
const TEST_TEXT: &str = "Test test test test\n";
const DELAY: usize = 2;
const {
assert!(
TEST_TEXT.len() > RECEIVER_QUEUE_SIZE.get() + DELAY,
"TEST_TEXT must be long enough to make the queue overflow when accounting for DELAY"
)
};
for (i, b) in TEST_TEXT.bytes().enumerate() {
let current_count = i + 1;
mem_op_runner
.write_bytes(SIMPLE_UART_TRANSMIT_OFFSET, [b], 300)
.unwrap();
let expected_status = ReceiverQueueStatus::for_queue_len_as_u8(
current_count.saturating_sub(DELAY),
RECEIVER_QUEUE_SIZE,
);
assert_eq!(
mem_op_runner
.read_bytes(SIMPLE_UART_STATUS_OFFSET, 1)
.unwrap(),
[expected_status],
);
}
for (i, mut expected_byte) in TEST_TEXT.bytes().enumerate() {
let read = mem_op_runner
.read_bytes(SIMPLE_UART_RECEIVE_OFFSET, 1)
.unwrap();
let current_count = if i == 0 {
RECEIVER_QUEUE_SIZE.get() + 1 // initial read starts with overflow
} else {
RECEIVER_QUEUE_SIZE.get().saturating_sub(i)
};
if current_count == 0 {
expected_byte = 0;
}
let expected_status =
ReceiverQueueStatus::for_queue_len_as_u8(current_count, RECEIVER_QUEUE_SIZE);
assert_eq!(read, [expected_byte, expected_status]);
}
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("expected/simple_uart.vcd") {
panic!();
}
}