add main_memory_and_io::simple_uart::{transmitter, uart_clock_gen} and tests

This commit is contained in:
Jacob Lifshay 2026-02-25 00:08:42 -08:00
parent 3fcd1c7338
commit d61475faf4
Signed by: programmerjake
SSH key fingerprint: SHA256:HnFTLGpSm4Q4Fj502oCFisjZSoakwEuTsJJMSke63RQ
4 changed files with 20293 additions and 0 deletions

View file

@ -10,6 +10,8 @@ use crate::{
};
use fayalite::{prelude::*, util::ready_valid::ReadyValid};
pub mod simple_uart;
#[hdl]
pub enum MemoryOperationKind {
Read,

View file

@ -0,0 +1,167 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::config::CpuConfig;
use fayalite::{int::UIntInRange, prelude::*, util::ready_valid::ReadyValid};
const TICKS_PER_BAUD: usize = 16;
const PRECISION_BITS: usize = 16;
#[hdl_module]
pub fn uart_clock_gen(
clock_input_properties: PhantomConst<peripherals::ClockInputProperties>,
baud_rate: f64,
) {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let tick: Bool = m.output();
let divisor =
clock_input_properties.get().frequency.into_inner() / (baud_rate * TICKS_PER_BAUD as f64);
type NumeratorType = u128;
let numerator: NumeratorType = 1 << PRECISION_BITS;
let denominator: NumeratorType = (divisor * numerator as f64).round() as _;
let remainder_ty = UInt::range(0..denominator);
#[hdl]
let remainder_reg = reg_builder().clock_domain(cd).reset(remainder_ty.zero());
#[hdl]
let sum = wire((remainder_reg + numerator).ty());
connect_any(sum, remainder_reg + numerator);
#[hdl]
let tick_reg = reg_builder().clock_domain(cd).reset(false);
connect(tick_reg, false);
#[hdl]
let next_remainder = wire(remainder_ty);
connect(remainder_reg, next_remainder);
#[hdl]
if sum.cmp_ge(denominator) {
connect_any(next_remainder, sum - denominator);
connect(tick_reg, true);
} else {
connect(next_remainder, sum);
}
connect(tick, tick_reg);
}
#[hdl_module]
pub fn transmitter() {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let tick: Bool = m.input();
#[hdl]
let tx: Bool = m.output();
#[hdl]
let input_byte: ReadyValid<UInt<8>> = m.input();
#[hdl]
let not_tx_reg: Bool = reg_builder().clock_domain(cd).reset(false);
connect(tx, !not_tx_reg);
#[hdl]
let input_buf_reg: HdlOption<UInt<8>> = reg_builder().clock_domain(cd).reset(HdlNone());
const BYTE_ON_WIRE_WIDTH: usize = 10;
#[hdl]
let shift_reg: Array<HdlOption<Bool>, _> = reg_builder()
.clock_domain(cd)
.reset([HdlNone(); BYTE_ON_WIRE_WIDTH]);
#[hdl]
if let HdlSome(v) = shift_reg[0] {
connect(not_tx_reg, !v);
} else {
connect(not_tx_reg, false);
}
#[hdl]
let tick_count_reg = reg_builder()
.clock_domain(cd)
.reset(0u8.cast_to_static::<UIntInRange<0, { TICKS_PER_BAUD }>>());
#[hdl]
let next_tick_count = wire(tick_count_reg.ty());
connect(tick_count_reg, next_tick_count);
#[hdl]
if !tick {
connect(next_tick_count, tick_count_reg);
} else if tick_count_reg.cmp_ge(TICKS_PER_BAUD - 1) {
connect(next_tick_count, 0u8.cast_to(next_tick_count.ty()));
} else {
connect(
next_tick_count,
(tick_count_reg.cast_to(UInt[tick_count_reg.ty().bit_width()]) + 1u8)
.cast_to(next_tick_count.ty()),
);
}
#[hdl]
if tick & tick_count_reg.cmp_eq(0u8) {
#[hdl]
if let HdlSome(_) = shift_reg[0] {
for v in shift_reg.windows(2) {
connect(v[0], v[1]);
}
connect(shift_reg.last().expect("known to be non-empty"), HdlNone());
}
#[hdl]
if let HdlNone = shift_reg[1] {
// when shift_reg[1] is HdlNone that means shift_reg would be completely empty in the next clock
// cycle, so instead fill it with the next byte if there is one.
#[hdl]
if let HdlSome(byte) = input_buf_reg {
connect(input_buf_reg, HdlNone());
connect(shift_reg, [HdlSome(true); _]);
connect(shift_reg[0], HdlSome(false));
for (i, v) in (*shift_reg)[1..][..8].iter().enumerate() {
connect(v, HdlSome(byte[i]));
}
}
}
}
#[hdl]
if let HdlSome(_) = input_buf_reg {
connect(input_byte.ready, false);
} else {
connect(input_byte.ready, true);
connect(input_buf_reg, input_byte.data);
}
}
#[hdl_module]
pub fn simple_uart(
config: PhantomConst<CpuConfig>,
clock_input_properties: PhantomConst<peripherals::ClockInputProperties>,
baud_rate: f64,
) {
#[hdl]
let cd: ClockDomain = m.input();
#[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);
#[hdl]
let rx_sync_final_reg: Bool = reg_builder().clock_domain(cd).reset(true);
annotate(rx_sync_final_reg, DontTouchAnnotation);
connect(rx_sync_intermediate_reg, uart.rx);
connect(rx_sync_final_reg, rx_sync_intermediate_reg);
#[hdl]
let rx_syncronized: Bool = wire();
annotate(rx_syncronized, DontTouchAnnotation);
connect(rx_syncronized, rx_sync_final_reg);
todo!("add transmitter and receiver");
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,175 @@
// 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 fayalite::{
prelude::*,
sim::vcd::VcdWriterDecls,
util::{RcWriter, ready_valid::ReadyValid},
};
const fn half_period(frequency: f64) -> SimDuration {
SimDuration::from_attos((SimDuration::from_secs(1).as_attos() as f64 / frequency / 2.0) as u128)
}
const CLOCK_FREQUENCY: f64 = 200_000.0;
const CLOCK_HALF_PERIOD: SimDuration = half_period(CLOCK_FREQUENCY);
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);
fn clock_input_properties() -> PhantomConst<peripherals::ClockInputProperties> {
peripherals::ClockInput::new(CLOCK_FREQUENCY).properties
}
#[hdl_module(extern)]
fn reference_baud_rate_clk_gen() {
#[hdl]
let reference_baud_rate_clk: Clock = m.output();
m.extern_module_simulation_fn(
reference_baud_rate_clk,
async |reference_baud_rate_clk, mut sim| {
loop {
sim.write_clock(reference_baud_rate_clk, false).await;
sim.advance_time(BAUD_HALF_PERIOD).await;
sim.write_clock(reference_baud_rate_clk, true).await;
sim.advance_time(BAUD_HALF_PERIOD).await;
}
},
);
}
#[hdl_module(extern)]
fn sim_receiver() {
#[hdl]
let tx: Bool = m.input();
#[hdl]
let text_out: SimOnly<String> = m.output();
m.extern_module_simulation_fn((tx, text_out), async |(tx, text_out), mut sim| {
let mut text = SimOnlyValue::new(String::new());
sim.write(text_out, &text).await;
loop {
// wait for the starting edge of the start bit
while sim.read_bool(tx).await {
sim.wait_for_changes([tx], None).await;
}
// wait till the middle of the start bit
sim.advance_time(BAUD_HALF_PERIOD).await;
let mut byte = 0u8;
for i in 0..u8::BITS {
// wait till the middle of the next data bit
sim.advance_time(BAUD_PERIOD).await;
if sim.read_bool(tx).await {
byte |= 1 << i;
}
}
assert!(byte.is_ascii());
text.push(byte as char);
sim.write(text_out, &text).await;
// wait till the middle of the stop bit
sim.advance_time(BAUD_PERIOD).await;
// we're in the middle of the stop bit, the stop bit is a one so we can just loop and wait for
// the next transition to zero which is the next start bit.
}
});
}
#[hdl_module]
fn transmitter_and_uart_clock_gen() {
#[hdl]
let cd: ClockDomain = m.input();
#[hdl]
let tx: Bool = m.output();
#[hdl]
let input_byte: ReadyValid<UInt<8>> = m.input();
#[hdl]
let reference_baud_rate_clk: Clock = m.output();
#[hdl]
let text_out: SimOnly<String> = m.output();
#[hdl]
let sim_receiver = instance(sim_receiver());
connect(sim_receiver.tx, tx);
connect(text_out, sim_receiver.text_out);
#[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,
);
#[hdl]
let transmitter = instance(transmitter());
connect(transmitter.cd, cd);
connect(transmitter.input_byte, input_byte);
connect(tx, transmitter.tx);
#[hdl]
let clock_gen = instance(uart_clock_gen(clock_input_properties(), BAUD_RATE));
connect(clock_gen.cd, cd);
connect(transmitter.tick, clock_gen.tick);
}
#[test]
#[hdl]
fn test_transmitter_and_uart_clock_gen() {
let _n = SourceLocation::normalize_files_for_tests();
let m = transmitter_and_uart_clock_gen();
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 text = "Hi!\n";
let mut bytes = text.as_bytes().iter();
for cycle in 0..1000 {
let input_byte = match bytes.clone().next() {
Some(b) if cycle > 10 =>
{
#[hdl(sim)]
HdlSome(b)
}
_ =>
{
#[hdl(sim)]
HdlNone()
}
};
sim.write(sim.io().input_byte.data, &input_byte);
sim.advance_time(CLOCK_HALF_PERIOD);
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(CLOCK_HALF_PERIOD);
if sim.read_bool(sim.io().input_byte.ready) {
#[hdl(sim)]
if let HdlSome(_) = input_byte {
bytes.next();
}
}
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, false);
}
assert_eq!(sim.read(sim.io().text_out).as_str(), text);
assert!(bytes.as_slice().is_empty());
let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("expected/transmitter_and_uart_clock_gen.vcd") {
panic!();
}
}
// TODO: test receiver()
// TODO: test simple_uart()