From d61475faf47732ebf341f8b0f7e39696a03e13c7 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Wed, 25 Feb 2026 00:08:42 -0800 Subject: [PATCH] add main_memory_and_io::simple_uart::{transmitter, uart_clock_gen} and tests --- crates/cpu/src/main_memory_and_io.rs | 2 + .../cpu/src/main_memory_and_io/simple_uart.rs | 167 + .../transmitter_and_uart_clock_gen.vcd | 19949 ++++++++++++++++ crates/cpu/tests/simple_uart.rs | 175 + 4 files changed, 20293 insertions(+) create mode 100644 crates/cpu/src/main_memory_and_io/simple_uart.rs create mode 100644 crates/cpu/tests/expected/transmitter_and_uart_clock_gen.vcd create mode 100644 crates/cpu/tests/simple_uart.rs diff --git a/crates/cpu/src/main_memory_and_io.rs b/crates/cpu/src/main_memory_and_io.rs index ae0fa84..2caa61a 100644 --- a/crates/cpu/src/main_memory_and_io.rs +++ b/crates/cpu/src/main_memory_and_io.rs @@ -10,6 +10,8 @@ use crate::{ }; use fayalite::{prelude::*, util::ready_valid::ReadyValid}; +pub mod simple_uart; + #[hdl] pub enum MemoryOperationKind { Read, diff --git a/crates/cpu/src/main_memory_and_io/simple_uart.rs b/crates/cpu/src/main_memory_and_io/simple_uart.rs new file mode 100644 index 0000000..ffd3468 --- /dev/null +++ b/crates/cpu/src/main_memory_and_io/simple_uart.rs @@ -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, + 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> = 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> = reg_builder().clock_domain(cd).reset(HdlNone()); + + const BYTE_ON_WIRE_WIDTH: usize = 10; + + #[hdl] + let shift_reg: Array, _> = 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::>()); + + #[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, + clock_input_properties: PhantomConst, + 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"); +} diff --git a/crates/cpu/tests/expected/transmitter_and_uart_clock_gen.vcd b/crates/cpu/tests/expected/transmitter_and_uart_clock_gen.vcd new file mode 100644 index 0000000..d1622db --- /dev/null +++ b/crates/cpu/tests/expected/transmitter_and_uart_clock_gen.vcd @@ -0,0 +1,19949 @@ +$timescale 1 ps $end +$scope module transmitter_and_uart_clock_gen $end +$scope struct cd $end +$var wire 1 x86]l clk $end +$var wire 1 MHfv6 rst $end +$upscope $end +$var wire 1 dN \$tag $end +$var wire 8 %xyQs HdlSome $end +$upscope $end +$var wire 1 `bM"j ready $end +$upscope $end +$var wire 1 \TD\A reference_baud_rate_clk $end +$var string 1 of&[2 text_out $end +$scope struct sim_receiver $end +$var wire 1 gzHS7 tx $end +$var string 1 r)4h< text_out $end +$upscope $end +$scope module sim_receiver_2 $end +$var wire 1 gzHS7" tx $end +$var string 1 r)4h<" text_out $end +$upscope $end +$scope struct reference_baud_rate_clk_gen $end +$var wire 1 io_3J reference_baud_rate_clk $end +$upscope $end +$scope module reference_baud_rate_clk_gen_2 $end +$var wire 1 io_3J" reference_baud_rate_clk $end +$upscope $end +$scope struct transmitter $end +$scope struct cd $end +$var wire 1 m_jls clk $end +$var wire 1 :djMK rst $end +$upscope $end +$var wire 1 N)r~D tick $end +$var wire 1 XM2H+ tx $end +$scope struct input_byte $end +$scope struct data $end +$var string 1 @n/ea \$tag $end +$var wire 8 #B;HH HdlSome $end +$upscope $end +$var wire 1 Ls[`I ready $end +$upscope $end +$upscope $end +$scope module transmitter_2 $end +$scope struct cd $end +$var wire 1 m_jls" clk $end +$var wire 1 :djMK" rst $end +$upscope $end +$var wire 1 N)r~D" tick $end +$var wire 1 XM2H+" tx $end +$scope struct input_byte $end +$scope struct data $end +$var string 1 @n/ea" \$tag $end +$var wire 8 #B;HH" HdlSome $end +$upscope $end +$var wire 1 Ls[`I" ready $end +$upscope $end +$var reg 1 b;JB{ not_tx_reg $end +$scope struct input_buf_reg $end +$var string 1 K+7:6 \$tag $end +$var reg 8 _6f~v HdlSome $end +$upscope $end +$scope struct shift_reg $end +$scope struct \[0] $end +$var string 1 %%}]E \$tag $end +$var reg 1 [w`?2 range $end +$upscope $end +$upscope $end +$scope struct clock_gen $end +$scope struct cd $end +$var wire 1 Po}Jw clk $end +$var wire 1 awAl9 rst $end +$upscope $end +$var wire 1 U{RYW tick $end +$upscope $end +$scope module uart_clock_gen $end +$scope struct cd $end +$var wire 1 hq`hZ clk $end +$var wire 1 `+6P, rst $end +$upscope $end +$var wire 1 b%?WW tick $end +$var reg 17 ayGy* remainder_reg $end +$var wire 129 IE[.2 sum $end +$var reg 1 .n:ww tick_reg $end +$var wire 17 7vb_V next_remainder $end +$upscope $end +$upscope $end +$enddefinitions $end +$dumpvars +0x86]l +1MHfv6 +1dN +b0 %xyQs +1`bM"j +0\TD\A +s\"\" of&[2 +1gzHS7" +s\"\" r)4h<" +1gzHS7 +s\"\" r)4h< +0io_3J" +0io_3J +0m_jls" +1:djMK" +0N)r~D" +1XM2H+" +sHdlNone\x20(0) @n/ea" +b0 #B;HH" +1Ls[`I" +0b;JB{ +sHdlNone\x20(0) K+7:6 +b0 _6f~v +sHdlNone\x20(0) %%}]E +0[w`?2 +0m_jls +1:djMK +0N)r~D +1XM2H+ +sHdlNone\x20(0) @n/ea +b0 #B;HH +1Ls[`I +0hq`hZ +1`+6P, +0b%?WW +b0 ayGy* +b10000000000000000 IE[.2 +0.n:ww +b10000000000000000 7vb_V +0Po}Jw +1awAl9 +0U{RYW +$end +#2500000 +1x86]l +1m_jls" +1m_jls +1hq`hZ +1Po}Jw +#5000000 +0x86]l +0MHfv6 +0m_jls" +0:djMK" +0m_jls +0:djMK +0hq`hZ +0`+6P, +0Po}Jw +0awAl9 +#7500000 +1x86]l +1m_jls" +1m_jls +1hq`hZ +b10000000000000000 ayGy* +b100000000000000000 IE[.2 +b1011001010101011 7vb_V +1Po}Jw +#10000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#12500000 +1x86]l +1m_jls" +1N)r~D" +b1 %Y2[v +1m_jls +1N)r~D +1hq`hZ +1b%?WW +b1011001010101011 ayGy* +b11011001010101011 IE[.2 +1.n:ww +b110010101010110 7vb_V +1Po}Jw +1U{RYW +#15000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#17500000 +1x86]l +1m_jls" +b1 {|t"\ +b10 %Y2[v +1m_jls +1hq`hZ +b110010101010110 ayGy* +b10110010101010110 IE[.2 +b1100000000001 7vb_V +1Po}Jw +#20000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#22500000 +1x86]l +1m_jls" +b10 {|t"\ +b11 %Y2[v +1m_jls +1hq`hZ +b1100000000001 ayGy* +b10001100000000001 IE[.2 +b10001100000000001 7vb_V +1Po}Jw +#25000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#27500000 +1x86]l +1m_jls" +0N)r~D" +b11 {|t"\ +1m_jls +0N)r~D +1hq`hZ +0b%?WW +b10001100000000001 ayGy* +b100001100000000001 IE[.2 +0.n:ww +b1100101010101100 7vb_V +1Po}Jw +0U{RYW +#30000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#32500000 +1x86]l +1m_jls" +1N)r~D" +b100 %Y2[v +1m_jls +1N)r~D +1hq`hZ +1b%?WW +b1100101010101100 ayGy* +b11100101010101100 IE[.2 +1.n:ww +b111110101010111 7vb_V +1Po}Jw +1U{RYW +#35000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#37500000 +1x86]l +1m_jls" +b100 {|t"\ +b101 %Y2[v +1m_jls +1hq`hZ +b111110101010111 ayGy* +b10111110101010111 IE[.2 +b11000000000010 7vb_V +1Po}Jw +#40000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#42500000 +1x86]l +1m_jls" +b101 {|t"\ +b110 %Y2[v +1m_jls +1hq`hZ +b11000000000010 ayGy* +b10011000000000010 IE[.2 +b10011000000000010 7vb_V +1Po}Jw +#45000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#47500000 +1x86]l +1m_jls" +0N)r~D" +b110 {|t"\ +1m_jls +0N)r~D +1hq`hZ +0b%?WW +b10011000000000010 ayGy* +b100011000000000010 IE[.2 +0.n:ww +b1110001010101101 7vb_V +1Po}Jw +0U{RYW +#50000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#52083333 +1\TD\A +1io_3J" +1io_3J +#52500000 +1x86]l +1m_jls" +1N)r~D" +b111 %Y2[v +1m_jls +1N)r~D +1hq`hZ +1b%?WW +b1110001010101101 ayGy* +b11110001010101101 IE[.2 +1.n:ww +b1001010101011000 7vb_V +1Po}Jw +1U{RYW +#55000000 +0x86]l +sHdlSome\x20(1) #Zg>N +b1001000 %xyQs +0m_jls" +sHdlSome\x20(1) @n/ea" +b1001000 #B;HH" +0m_jls +sHdlSome\x20(1) @n/ea +b1001000 #B;HH +0hq`hZ +0Po}Jw +#57500000 +1x86]l +0`bM"j +1m_jls" +0Ls[`I" +sHdlSome\x20(1) K+7:6 +b1001000 _6f~v +b111 {|t"\ +b1000 %Y2[v +1m_jls +0Ls[`I +1hq`hZ +b1001010101011000 ayGy* +b11001010101011000 IE[.2 +b100100000000011 7vb_V +1Po}Jw +#60000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#62500000 +1x86]l +1m_jls" +b1000 {|t"\ +b1001 %Y2[v +1m_jls +1hq`hZ +b100100000000011 ayGy* +b10100100000000011 IE[.2 +b10100100000000011 7vb_V +1Po}Jw +#65000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#67500000 +1x86]l +1m_jls" +0N)r~D" +b1001 {|t"\ +1m_jls +0N)r~D +1hq`hZ +0b%?WW +b10100100000000011 ayGy* +b100100100000000011 IE[.2 +0.n:ww +b1111101010101110 7vb_V +1Po}Jw +0U{RYW +#70000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#72500000 +1x86]l +1m_jls" +1N)r~D" +b1010 %Y2[v +1m_jls +1N)r~D +1hq`hZ +1b%?WW +b1111101010101110 ayGy* +b11111101010101110 IE[.2 +1.n:ww +b1010110101011001 7vb_V +1Po}Jw +1U{RYW +#75000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#77500000 +1x86]l +1m_jls" +b1010 {|t"\ +b1011 %Y2[v +1m_jls +1hq`hZ +b1010110101011001 ayGy* +b11010110101011001 IE[.2 +b110000000000100 7vb_V +1Po}Jw +#80000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#82500000 +1x86]l +1m_jls" +b1011 {|t"\ +b1100 %Y2[v +1m_jls +1hq`hZ +b110000000000100 ayGy* +b10110000000000100 IE[.2 +b1001010101111 7vb_V +1Po}Jw +#85000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#87500000 +1x86]l +1m_jls" +b1100 {|t"\ +b1101 %Y2[v +1m_jls +1hq`hZ +b1001010101111 ayGy* +b10001001010101111 IE[.2 +b10001001010101111 7vb_V +1Po}Jw +#90000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#92500000 +1x86]l +1m_jls" +0N)r~D" +b1101 {|t"\ +1m_jls +0N)r~D +1hq`hZ +0b%?WW +b10001001010101111 ayGy* +b100001001010101111 IE[.2 +0.n:ww +b1100010101011010 7vb_V +1Po}Jw +0U{RYW +#95000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#97500000 +1x86]l +1m_jls" +1N)r~D" +b1110 %Y2[v +1m_jls +1N)r~D +1hq`hZ +1b%?WW +b1100010101011010 ayGy* +b11100010101011010 IE[.2 +1.n:ww +b111100000000101 7vb_V +1Po}Jw +1U{RYW +#100000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#102500000 +1x86]l +1m_jls" +b1110 {|t"\ +b1111 %Y2[v +1m_jls +1hq`hZ +b111100000000101 ayGy* +b10111100000000101 IE[.2 +b10101010110000 7vb_V +1Po}Jw +#104166667 +0\TD\A +0io_3J" +0io_3J +#105000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#107500000 +1x86]l +1m_jls" +b1111 {|t"\ +b0 %Y2[v +1m_jls +1hq`hZ +b10101010110000 ayGy* +b10010101010110000 IE[.2 +b10010101010110000 7vb_V +1Po}Jw +#110000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#112500000 +1x86]l +1m_jls" +0N)r~D" +b0 {|t"\ +1m_jls +0N)r~D +1hq`hZ +0b%?WW +b10010101010110000 ayGy* +b100010101010110000 IE[.2 +0.n:ww +b1101110101011011 7vb_V +1Po}Jw +0U{RYW +#115000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#117500000 +1x86]l +1m_jls" +1N)r~D" +b1 %Y2[v +1m_jls +1N)r~D +1hq`hZ +1b%?WW +b1101110101011011 ayGy* +b11101110101011011 IE[.2 +1.n:ww +b1001000000000110 7vb_V +1Po}Jw +1U{RYW +#120000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#122500000 +1x86]l +1`bM"j +1m_jls" +1Ls[`I" +sHdlNone\x20(0) K+7:6 +b0 _6f~v +sHdlSome\x20(1) %%}]E +sHdlSome\x20(1) zj*Rc +sHdlSome\x20(1) }3WHg +sHdlSome\x20(1) bu5`y +sHdlSome\x20(1) smZKb +109$=$ +sHdlSome\x20(1) Of2\e +sHdlSome\x20(1) hBaJt +sHdlSome\x20(1) NnyNN +1$B.xu +sHdlSome\x20(1) wfp6c +sHdlSome\x20(1) [{)KI +1eKp+- +b1 {|t"\ +b10 %Y2[v +1m_jls +1Ls[`I +1hq`hZ +b1001000000000110 ayGy* +b11001000000000110 IE[.2 +b100001010110001 7vb_V +1Po}Jw +#125000000 +0x86]l +b1101001 %xyQs +0m_jls" +b1101001 #B;HH" +0m_jls +b1101001 #B;HH +0hq`hZ +0Po}Jw +#127500000 +1x86]l +0dN +b0 %xyQs +0m_jls" +sHdlNone\x20(0) @n/ea" +b0 #B;HH" +0m_jls +sHdlNone\x20(0) @n/ea +b0 #B;HH +0hq`hZ +0Po}Jw +#3252500000 +1x86]l +0d 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::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 = 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> = m.input(); + #[hdl] + let reference_baud_rate_clk: Clock = m.output(); + #[hdl] + let text_out: SimOnly = 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, + } + 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()