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
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
// See Notices.txt for copyright information
|
// See Notices.txt for copyright information
|
||||||
|
|
||||||
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use crate::config::CpuConfig;
|
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 TICKS_PER_BAUD: usize = 16;
|
||||||
const PRECISION_BITS: 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]
|
#[hdl_module]
|
||||||
pub fn simple_uart(
|
pub fn simple_uart(
|
||||||
config: PhantomConst<CpuConfig>,
|
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
|
// 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::{transmitter, uart_clock_gen};
|
use cpu::main_memory_and_io::simple_uart::{receiver_no_queue, transmitter, uart_clock_gen};
|
||||||
use fayalite::{
|
use fayalite::{
|
||||||
|
intern::{Intern, Interned},
|
||||||
prelude::*,
|
prelude::*,
|
||||||
sim::vcd::VcdWriterDecls,
|
sim::vcd::VcdWriterDecls,
|
||||||
util::{RcWriter, ready_valid::ReadyValid},
|
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_HALF_PERIOD: SimDuration = half_period(BAUD_RATE);
|
||||||
const BAUD_PERIOD: SimDuration = BAUD_HALF_PERIOD.saturating_add(BAUD_HALF_PERIOD);
|
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> {
|
fn clock_input_properties() -> PhantomConst<peripherals::ClockInputProperties> {
|
||||||
peripherals::ClockInput::new(CLOCK_FREQUENCY).properties
|
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 receiver()
|
||||||
// TODO: test simple_uart()
|
// TODO: test simple_uart()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue