add main_memory_and_io::simple_uart::receiver* and test for receiver_no_queue

This commit is contained in:
Jacob Lifshay 2026-02-25 18:05:50 -08:00
parent e6caa320ef
commit b07ef2b363
Signed by: programmerjake
SSH key fingerprint: SHA256:HnFTLGpSm4Q4Fj502oCFisjZSoakwEuTsJJMSke63RQ
3 changed files with 189001 additions and 2 deletions

View file

@ -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>,

File diff suppressed because it is too large Load diff

View file

@ -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()