forked from libre-chip/cpu
add main_memory_and_io::simple_uart::receiver* and test for receiver_no_queue
This commit is contained in:
parent
e6caa320ef
commit
b07ef2b363
3 changed files with 189001 additions and 2 deletions
|
|
@ -1,8 +1,14 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// See Notices.txt for copyright information
|
||||
|
||||
use std::num::NonZeroUsize;
|
||||
|
||||
use crate::config::CpuConfig;
|
||||
use fayalite::{int::UIntInRange, prelude::*, util::ready_valid::ReadyValid};
|
||||
use fayalite::{
|
||||
int::UIntInRange,
|
||||
prelude::*,
|
||||
util::ready_valid::{ReadyValid, queue},
|
||||
};
|
||||
|
||||
const TICKS_PER_BAUD: usize = 16;
|
||||
const PRECISION_BITS: usize = 16;
|
||||
|
|
@ -140,6 +146,177 @@ pub fn transmitter() {
|
|||
}
|
||||
}
|
||||
|
||||
/// `rx_synchronized` is [`peripherals::Uart::rx`], but after being synchronized to `cd.clk`.
|
||||
/// `output_byte` is `HdlSome` for 1 clock cycle for every received byte.
|
||||
#[hdl_module]
|
||||
pub fn receiver_no_queue() {
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let tick: Bool = m.input();
|
||||
#[hdl]
|
||||
let rx_synchronized: Bool = m.input();
|
||||
#[hdl]
|
||||
let output_byte: HdlOption<UInt<8>> = m.output();
|
||||
|
||||
const BITS_PER_BYTE: usize = 8;
|
||||
|
||||
#[hdl]
|
||||
let output_byte_reg: HdlOption<UInt<{ BITS_PER_BYTE }>> =
|
||||
reg_builder().clock_domain(cd).reset(HdlNone());
|
||||
|
||||
connect(output_byte, output_byte_reg);
|
||||
connect(output_byte_reg, HdlNone()); // by default always reset to HdlNone()
|
||||
|
||||
#[hdl]
|
||||
let tick_count_reg = reg_builder()
|
||||
.clock_domain(cd)
|
||||
.reset(0u8.cast_to(UInt::range(0..TICKS_PER_BAUD)));
|
||||
|
||||
#[hdl]
|
||||
let shift_reg: UInt<{ BITS_PER_BYTE }> = reg_builder().clock_domain(cd).reset(0u8);
|
||||
|
||||
#[hdl]
|
||||
enum State {
|
||||
WaitingForStartBit,
|
||||
StartBit,
|
||||
DataBits(UIntInRange<0, { BITS_PER_BYTE }>),
|
||||
StopBit,
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
let state_reg = reg_builder()
|
||||
.clock_domain(cd)
|
||||
.reset(State.WaitingForStartBit());
|
||||
|
||||
#[hdl]
|
||||
if tick {
|
||||
#[hdl]
|
||||
match state_reg {
|
||||
State::WaitingForStartBit => {
|
||||
connect(tick_count_reg, tick_count_reg.ty().zero());
|
||||
#[hdl]
|
||||
if !rx_synchronized {
|
||||
connect(state_reg, State.StartBit());
|
||||
}
|
||||
}
|
||||
State::StartBit =>
|
||||
{
|
||||
#[hdl]
|
||||
if tick_count_reg.cmp_eq(TICKS_PER_BAUD - 1) {
|
||||
connect(tick_count_reg, tick_count_reg.ty().zero());
|
||||
connect(state_reg, State.DataBits(0u8.cast_to(State.DataBits)));
|
||||
} else {
|
||||
connect_any(tick_count_reg, tick_count_reg + 1u8);
|
||||
}
|
||||
}
|
||||
State::DataBits(count) => {
|
||||
#[hdl]
|
||||
if tick_count_reg.cmp_eq(TICKS_PER_BAUD - 1) {
|
||||
connect(tick_count_reg, tick_count_reg.ty().zero());
|
||||
#[hdl]
|
||||
if count.cmp_eq(BITS_PER_BYTE - 1) {
|
||||
connect(state_reg, State.StopBit());
|
||||
connect(output_byte_reg, HdlSome(shift_reg));
|
||||
} else {
|
||||
connect(
|
||||
state_reg,
|
||||
State.DataBits((count.cast_to(UInt[8]) + 1u8).cast_to(State.DataBits)),
|
||||
);
|
||||
}
|
||||
} else {
|
||||
connect_any(tick_count_reg, tick_count_reg + 1u8);
|
||||
}
|
||||
#[hdl]
|
||||
if tick_count_reg.cmp_eq(TICKS_PER_BAUD / 2) {
|
||||
connect_any(
|
||||
shift_reg,
|
||||
(shift_reg >> 1) | (rx_synchronized.cast_to_static::<UInt<1>>() << 7),
|
||||
);
|
||||
}
|
||||
}
|
||||
State::StopBit => {
|
||||
#[hdl]
|
||||
if tick_count_reg.cmp_eq(TICKS_PER_BAUD / 2) {
|
||||
// go to WaitingForStartBit in the middle of the stop bit so we can adjust if the sender is faster than us.
|
||||
connect(tick_count_reg, tick_count_reg.ty().zero());
|
||||
connect(state_reg, State.WaitingForStartBit());
|
||||
} else {
|
||||
connect_any(tick_count_reg, tick_count_reg + 1u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
pub enum ReceiverQueueStatus {
|
||||
QueueEmpty,
|
||||
QueueNotEmpty,
|
||||
QueueAlmostFull,
|
||||
QueueFull,
|
||||
QueueOverflowed,
|
||||
}
|
||||
|
||||
/// `rx_synchronized` is [`peripherals::Uart::rx`], but after being synchronized to `cd.clk`.
|
||||
#[hdl_module]
|
||||
pub fn receiver(queue_capacity: NonZeroUsize) {
|
||||
#[hdl]
|
||||
let cd: ClockDomain = m.input();
|
||||
#[hdl]
|
||||
let tick: Bool = m.input();
|
||||
#[hdl]
|
||||
let rx_synchronized: Bool = m.input();
|
||||
#[hdl]
|
||||
let output_byte: ReadyValid<UInt<8>> = m.output();
|
||||
#[hdl]
|
||||
let queue_status: ReceiverQueueStatus = m.output();
|
||||
|
||||
#[hdl]
|
||||
let queue = instance(queue(UInt::<8>::new_static(), queue_capacity, false, false));
|
||||
|
||||
connect(queue.cd, cd);
|
||||
connect(output_byte, queue.out);
|
||||
|
||||
#[hdl]
|
||||
let queue_overflowed_reg = reg_builder().clock_domain(cd).reset(false);
|
||||
|
||||
#[hdl]
|
||||
if queue_overflowed_reg {
|
||||
connect(queue_status, ReceiverQueueStatus.QueueOverflowed());
|
||||
} else if queue.count.cmp_eq(0u8) {
|
||||
connect(queue_status, ReceiverQueueStatus.QueueEmpty());
|
||||
} else if queue.count.cmp_eq(queue_capacity) {
|
||||
connect(queue_status, ReceiverQueueStatus.QueueFull());
|
||||
} else if queue.count.cmp_eq(queue_capacity.get() - 1) {
|
||||
connect(queue_status, ReceiverQueueStatus.QueueAlmostFull());
|
||||
} else {
|
||||
connect(queue_status, ReceiverQueueStatus.QueueNotEmpty());
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
if ReadyValid::firing(output_byte) {
|
||||
// clear overflow when a byte is read from the queue
|
||||
connect(queue_overflowed_reg, false);
|
||||
}
|
||||
|
||||
// we ignore queue.inp.ready other than noting an overflow when there was data but the queue wasn't ready
|
||||
#[hdl]
|
||||
if !queue.inp.ready {
|
||||
#[hdl]
|
||||
if let HdlSome(_) = queue.inp.data {
|
||||
connect(queue_overflowed_reg, true);
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
let inner = instance(receiver_no_queue());
|
||||
connect(inner.cd, cd);
|
||||
connect(inner.tick, tick);
|
||||
connect(inner.rx_synchronized, rx_synchronized);
|
||||
connect(queue.inp.data, inner.output_byte);
|
||||
}
|
||||
|
||||
#[hdl_module]
|
||||
pub fn simple_uart(
|
||||
config: PhantomConst<CpuConfig>,
|
||||
|
|
|
|||
188659
crates/cpu/tests/expected/receiver_no_queue_and_uart_clock_gen.vcd
generated
Normal file
188659
crates/cpu/tests/expected/receiver_no_queue_and_uart_clock_gen.vcd
generated
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,8 +1,9 @@
|
|||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
// See Notices.txt for copyright information
|
||||
|
||||
use cpu::main_memory_and_io::simple_uart::{transmitter, uart_clock_gen};
|
||||
use cpu::main_memory_and_io::simple_uart::{receiver_no_queue, transmitter, uart_clock_gen};
|
||||
use fayalite::{
|
||||
intern::{Intern, Interned},
|
||||
prelude::*,
|
||||
sim::vcd::VcdWriterDecls,
|
||||
util::{RcWriter, ready_valid::ReadyValid},
|
||||
|
|
@ -18,6 +19,23 @@ 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
|
||||
}
|
||||
|
|
@ -171,5 +189,150 @@ fn test_transmitter_and_uart_clock_gen() {
|
|||
}
|
||||
}
|
||||
|
||||
#[hdl_module(extern)]
|
||||
fn receiver_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_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!();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: test receiver()
|
||||
// TODO: test simple_uart()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue