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 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::{
|
use fayalite::{
|
||||||
int::UIntInRange,
|
int::UIntInRange,
|
||||||
prelude::*,
|
prelude::*,
|
||||||
|
|
@ -258,6 +265,37 @@ pub enum ReceiverQueueStatus {
|
||||||
QueueOverflowed,
|
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`.
|
/// `rx_synchronized` is [`peripherals::Uart::rx`], but after being synchronized to `cd.clk`.
|
||||||
#[hdl_module]
|
#[hdl_module]
|
||||||
pub fn receiver(queue_capacity: NonZeroUsize) {
|
pub fn receiver(queue_capacity: NonZeroUsize) {
|
||||||
|
|
@ -317,16 +355,32 @@ pub fn receiver(queue_capacity: NonZeroUsize) {
|
||||||
connect(queue.inp.data, inner.output_byte);
|
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]
|
#[hdl_module]
|
||||||
pub fn simple_uart(
|
pub fn simple_uart(
|
||||||
config: PhantomConst<CpuConfig>,
|
config: PhantomConst<CpuConfig>,
|
||||||
clock_input_properties: PhantomConst<peripherals::ClockInputProperties>,
|
clock_input_properties: PhantomConst<peripherals::ClockInputProperties>,
|
||||||
baud_rate: f64,
|
baud_rate: f64,
|
||||||
|
receiver_queue_size: NonZeroUsize,
|
||||||
) {
|
) {
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let cd: ClockDomain = m.input();
|
let cd: ClockDomain = m.input();
|
||||||
#[hdl]
|
#[hdl]
|
||||||
|
let memory_interface: MemoryInterface<PhantomConst<CpuConfig>> =
|
||||||
|
m.input(MemoryInterface[config]);
|
||||||
|
#[hdl]
|
||||||
let uart: peripherals::Uart = m.output();
|
let uart: peripherals::Uart = m.output();
|
||||||
|
|
||||||
#[hdl]
|
#[hdl]
|
||||||
let rx_sync_intermediate_reg: Bool = reg_builder().clock_domain(cd).reset(true);
|
let rx_sync_intermediate_reg: Bool = reg_builder().clock_domain(cd).reset(true);
|
||||||
annotate(rx_sync_intermediate_reg, DontTouchAnnotation);
|
annotate(rx_sync_intermediate_reg, DontTouchAnnotation);
|
||||||
|
|
@ -340,5 +394,194 @@ pub fn simple_uart(
|
||||||
annotate(rx_synchronized, DontTouchAnnotation);
|
annotate(rx_synchronized, DontTouchAnnotation);
|
||||||
connect(rx_synchronized, rx_sync_final_reg);
|
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
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
use cpu::main_memory_and_io::simple_uart::{
|
use cpu::{
|
||||||
ReceiverQueueStatus, receiver, receiver_no_queue, transmitter, uart_clock_gen,
|
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::{
|
use fayalite::{
|
||||||
|
bundle::BundleType,
|
||||||
intern::{Intern, Interned},
|
intern::{Intern, Interned},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
sim::vcd::VcdWriterDecls,
|
sim::vcd::VcdWriterDecls,
|
||||||
util::{RcWriter, ready_valid::ReadyValid},
|
util::{RcWriter, ready_valid::ReadyValid},
|
||||||
};
|
};
|
||||||
use std::num::NonZeroUsize;
|
use std::{borrow::BorrowMut, marker::PhantomData, num::NonZeroUsize};
|
||||||
|
|
||||||
const fn half_period(frequency: f64) -> SimDuration {
|
const fn half_period(frequency: f64) -> SimDuration {
|
||||||
SimDuration::from_attos((SimDuration::from_secs(1).as_attos() as f64 / frequency / 2.0) as u128)
|
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