forked from libre-chip/cpu
implement simple_uart::simple_uart
This commit is contained in:
parent
61e8ed96b1
commit
7f7e316b7b
3 changed files with 185443 additions and 6 deletions
|
|
@ -3,7 +3,14 @@
|
|||
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use crate::config::CpuConfig;
|
||||
use crate::{
|
||||
config::{CpuConfig, PhantomConstCpuConfig},
|
||||
main_memory_and_io::{
|
||||
MemoryInterface, MemoryOperationErrorKind, MemoryOperationFinish,
|
||||
MemoryOperationFinishKind, MemoryOperationKind, MemoryOperationStart,
|
||||
},
|
||||
util::array_vec::ArrayVec,
|
||||
};
|
||||
use fayalite::{
|
||||
int::UIntInRange,
|
||||
prelude::*,
|
||||
|
|
@ -258,6 +265,37 @@ pub enum ReceiverQueueStatus {
|
|||
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) {
|
||||
|
|
@ -317,16 +355,32 @@ pub fn receiver(queue_capacity: NonZeroUsize) {
|
|||
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_SIZE: u64 = 2;
|
||||
|
||||
#[hdl(no_static)]
|
||||
struct Operation<C: PhantomConstGet<CpuConfig> + PhantomConstCpuConfig> {
|
||||
start: MemoryOperationStart<C>,
|
||||
finish: HdlOption<MemoryOperationFinish<C>>,
|
||||
}
|
||||
|
||||
#[hdl_module]
|
||||
pub fn simple_uart(
|
||||
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 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);
|
||||
|
|
@ -340,5 +394,194 @@ pub fn simple_uart(
|
|||
annotate(rx_synchronized, DontTouchAnnotation);
|
||||
connect(rx_synchronized, rx_sync_final_reg);
|
||||
|
||||
todo!("add transmitter and receiver");
|
||||
#[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_fetch_block_ids_ty = memory_interface.ty().next_fetch_block_ids.HdlSome;
|
||||
#[hdl]
|
||||
if let HdlSome(operation) = operation_reg {
|
||||
#[hdl]
|
||||
let MemoryInterface::<_> {
|
||||
start,
|
||||
finish,
|
||||
next_fetch_block_ids,
|
||||
config: _,
|
||||
} = memory_interface;
|
||||
connect(start.ready, false);
|
||||
connect(finish.data, operation.finish);
|
||||
connect(
|
||||
next_fetch_block_ids,
|
||||
HdlSome(ArrayVec::from_parts_unchecked(
|
||||
repeat(
|
||||
operation.start.fetch_block_id,
|
||||
next_fetch_block_ids_ty.capacity(),
|
||||
),
|
||||
1u8.cast_to(next_fetch_block_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,
|
||||
fetch_block_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(SIMPLE_UART_RECEIVE_OFFSET) {
|
||||
connect(receiver.output_byte.ready, valid_addr);
|
||||
#[hdl]
|
||||
if let HdlSome(byte) = receiver.output_byte.data {
|
||||
connect(read_data, byte);
|
||||
}
|
||||
// if there is no byte ready yet, we read a zero to avoid blocking the CPU on external inputs.
|
||||
} else if byte_addr.cmp_eq(SIMPLE_UART_STATUS_OFFSET) {
|
||||
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(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_fetch_block_ids,
|
||||
config: _,
|
||||
} = memory_interface;
|
||||
connect(start.ready, true);
|
||||
connect(finish.data, finish.ty().data.HdlNone());
|
||||
connect(
|
||||
next_fetch_block_ids,
|
||||
HdlSome(next_fetch_block_ids_ty.new_sim(next_fetch_block_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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
184803
crates/cpu/tests/expected/simple_uart.vcd
generated
Normal file
184803
crates/cpu/tests/expected/simple_uart.vcd
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,16 +1,29 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// See Notices.txt for copyright information
|
||||
|
||||
use cpu::main_memory_and_io::simple_uart::{
|
||||
ReceiverQueueStatus, receiver, receiver_no_queue, transmitter, uart_clock_gen,
|
||||
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::num::NonZeroUsize;
|
||||
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)
|
||||
|
|
@ -564,4 +577,382 @@ fn test_receiver_and_uart_clock_gen() {
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: test simple_uart()
|
||||
#[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!();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue