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..9219b10 --- /dev/null +++ b/crates/cpu/src/main_memory_and_io/simple_uart.rs @@ -0,0 +1,157 @@ +// 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 tx_buf_reg: Bool = reg_builder().clock_domain(cd).reset(true); + + connect(tx, tx_buf_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(tx_buf_reg, v); + } else { + connect(tx_buf_reg, true); + } + + #[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()); + + #[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]); + } + } else 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..5c07fcd --- /dev/null +++ b/crates/cpu/tests/expected/transmitter_and_uart_clock_gen.vcd @@ -0,0 +1,17195 @@ +$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 +$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 3p9KX tx_buf_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 19 ayGy* remainder_reg $end +$var wire 129 IE[.2 sum $end +$var reg 1 .n:ww tick_reg $end +$var wire 19 7vb_V next_remainder $end +$upscope $end +$upscope $end +$enddefinitions $end +$dumpvars +0x86]l +1MHfv6 +0dN +b0 %xyQs +1`bM"j +0\TD\A +0io_3J" +0io_3J +0m_jls" +1:djMK" +0N)r~D" +0XM2H+" +sHdlNone\x20(0) @n/ea" +b0 #B;HH" +1Ls[`I" +03p9KX +sHdlNone\x20(0) K+7:6 +b0 _6f~v +sHdlNone\x20(0) %%}]E +0[w`?2 +0m_jls +1:djMK +0N)r~D +0XM2H+ +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 +#500000 +1x86]l +1dN +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 +#11500000 +1x86]l +0`bM"j +1m_jls" +0Ls[`I" +sHdlSome\x20(1) K+7:6 +b1001000 _6f~v +1m_jls +0Ls[`I +1hq`hZ +b1000111110101010101 ayGy* +b1010111110101010101 IE[.2 +b1010111110101010101 7vb_V +1Po}Jw +#12000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#12500000 +1x86]l +1m_jls" +1m_jls +1hq`hZ +b1010111110101010101 ayGy* +b1100111110101010101 IE[.2 +b1100111110101010101 7vb_V +1Po}Jw +#13000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#13500000 +1x86]l +1m_jls" +1m_jls +1hq`hZ +b1100111110101010101 ayGy* +b1110111110101010101 IE[.2 +b1111101010101010 7vb_V +1Po}Jw +#14000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#14500000 +1x86]l +1m_jls" +1N)r~D" +b1 %Y2[v +1m_jls +1N)r~D +1hq`hZ +1b%?WW +b1111101010101010 ayGy* +b11111101010101010 IE[.2 +1.n:ww +b11111101010101010 7vb_V +1Po}Jw +1U{RYW +#15000000 +0x86]l +0m_jls" +0m_jls +0hq`hZ +0Po}Jw +#15500000 +1x86]l +1`bM"j +1m_jls" +0N)r~D" +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+- +b0 %Y2[v +1m_jls +0N)r~D +1Ls[`I +1hq`hZ +0b%?WW +b11111101010101010 ayGy* +b101111101010101010 IE[.2 +0.n:ww +b101111101010101010 7vb_V +1Po}Jw +0U{RYW +#16000000 +0x86]l +b1101001 %xyQs +0m_jls" +b1101001 #B;HH" +0m_jls +b1101001 #B;HH +0hq`hZ +0Po}Jw +#16500000 +1x86]l +0d SimDuration { + SimDuration::from_attos((SimDuration::from_secs(1).as_attos() as f64 / frequency / 2.0) as u128) +} + +const CLOCK_FREQUENCY: f64 = 1e6; +const CLOCK_HALF_PERIOD: SimDuration = half_period(CLOCK_FREQUENCY); +const BAUD_RATE: f64 = 9600.0; +const BAUD_HALF_PERIOD: SimDuration = half_period(BAUD_RATE); + +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] +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 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!(bytes.as_slice().is_empty()); + let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap(); + println!("####### VCD:\n{vcd}\n#######"); + // VCD isn't known to be correct, it's just whatever we happen to generate now + if vcd != include_str!("expected/transmitter_and_uart_clock_gen.vcd") { + panic!(); + } +} + +// TODO: test receiver() +// TODO: test simple_uart()