forked from libre-chip/cpu
958 lines
33 KiB
Rust
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!();
|
|
}
|
|
}
|