cpu/crates/cpu/tests/simple_uart.rs

958 lines
33 KiB
Rust

// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use cpu::{
config::{CpuConfig, UnitConfig},
main_memory_and_io::{
MemoryInterface, MemoryOperationErrorKind, MemoryOperationFinish,
MemoryOperationFinishKind, MemoryOperationKind, MemoryOperationStart,
simple_uart::{
ReceiverQueueStatus, SIMPLE_UART_RECEIVE_OFFSET, SIMPLE_UART_SIZE,
SIMPLE_UART_STATUS_OFFSET, SIMPLE_UART_TRANSMIT_OFFSET, receiver, receiver_no_queue,
simple_uart, transmitter, uart_clock_gen,
},
},
next_pc::FETCH_BLOCK_ID_WIDTH,
unit::UnitKind,
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};
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<CpuConfig>,
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<CpuConfig>> =
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);
}
struct MemoryOperationRunner<Sim: BorrowMut<Simulation<T>>, T: BundleType> {
sim: Sim,
clk: Expr<Clock>,
memory_interface: Expr<MemoryInterface<PhantomConst<CpuConfig>>>,
next_fetch_block_id: u64,
_phantom: PhantomData<T>,
}
const FETCH_WIDTH_IN_BYTES: usize = 1 << CpuConfig::DEFAULT_LOG2_FETCH_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; FETCH_WIDTH_IN_BYTES],
rw_mask: [bool; FETCH_WIDTH_IN_BYTES],
timeout_cycles: usize,
) -> Result<[u8; FETCH_WIDTH_IN_BYTES], SimValue<MemoryOperationErrorKind>> {
let kind = kind.into_sim_value();
assert_eq!(addr % FETCH_WIDTH_IN_BYTES as u64, 0);
let config = self.memory_interface.ty().config;
let fetch_block_id = self
.next_fetch_block_id
.cast_to_static::<UInt<{ FETCH_BLOCK_ID_WIDTH }>>();
self.next_fetch_block_id += 1;
println!(
"started: MemoryOperationStart {{\n \
kind: {kind:?},\n \
addr: {addr:#x},\n \
write_data: {write_data:?},\n \
rw_mask: {rw_mask:?},\n \
fetch_block_id: {fetch_block_id},\n\
}}"
);
let start_value = #[hdl(sim)]
MemoryOperationStart::<_> {
kind,
addr,
write_data: &write_data[..],
rw_mask: &rw_mask[..],
fetch_block_id,
config,
};
#[hdl]
let MemoryInterface::<_> {
start,
finish,
next_fetch_block_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_fetch_block_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_fetch_block_ids) = sim.read(next_fetch_block_ids) {
let next_fetch_block_ids: Vec<_> =
ArrayVec::elements_sim_ref(&next_fetch_block_ids)
.iter()
.map(|v| v.as_int())
.collect();
assert_eq!(next_fetch_block_ids, expected_next_fetch_block_ids);
} else {
panic!("next_fetch_block_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.fetch_block_id.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(), FETCH_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.fetch_block_id.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.fetch_block_id.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 % FETCH_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 <= FETCH_WIDTH_IN_BYTES)
);
let bytes_range = offset_in_chunk..(offset_in_chunk + bytes.len());
let mut write_data = [0u8; FETCH_WIDTH_IN_BYTES];
let mut rw_mask = [false; FETCH_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();
let config = CpuConfig::new(
vec![
UnitConfig::new(UnitKind::AluBranch),
UnitConfig::new(UnitKind::AluBranch),
],
NonZeroUsize::new(20).unwrap(),
);
const RECEIVER_QUEUE_SIZE: NonZeroUsize = NonZeroUsize::new(16).expect("not zero");
let m = simple_uart_loopback(
PhantomConst::new_sized(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_fetch_block_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 * FETCH_WIDTH_IN_BYTES as u64 {
mem_op_runner
.read_bytes::<1>(SIMPLE_UART_SIZE + i, 1)
.unwrap_err();
mem_op_runner
.write_bytes(SIMPLE_UART_SIZE + 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!();
}
}