// SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information use clap::builder::TypedValueParser; use fayalite::{ build::{ToArgs, WriteArgs}, platform::PeripheralRef, prelude::*, }; use ordered_float::NotNan; fn pick_clock<'a>( platform_io_builder: &PlatformIOBuilder<'a>, ) -> PeripheralRef<'a, peripherals::ClockInput> { let mut clks = platform_io_builder.peripherals_with_type::(); clks.sort_by_key(|clk| { // sort clocks by preference, smaller return values means higher preference let mut frequency = clk.ty().frequency(); let priority; if frequency < 10e6 { frequency = -frequency; // prefer bigger frequencies priority = 1; } else if frequency > 50e6 { // prefer smaller frequencies priority = 2; // least preferred } else { priority = 0; // most preferred frequency = (frequency - 25e6).abs(); // prefer closer to 25MHz } (priority, NotNan::new(frequency).expect("should be valid")) }); clks[0] } #[hdl_module] fn tx_only_uart( platform_io_builder: PlatformIOBuilder<'_>, divisor: f64, message: impl AsRef<[u8]>, ) { let message = message.as_ref(); let clk_input = pick_clock(&platform_io_builder).use_peripheral(); let rst = platform_io_builder.peripherals_with_type::()[0].use_peripheral(); let cd = #[hdl] ClockDomain { clk: clk_input.clk, rst, }; let numerator = 1u128 << 16; let denominator = (divisor * numerator as f64).round() as u128; #[hdl] let remainder_reg: UInt<128> = reg_builder().clock_domain(cd).reset(0u128); #[hdl] let sum: UInt<128> = wire(); 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: UInt<128> = wire(); 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); } #[hdl] let uart_state_reg = reg_builder().clock_domain(cd).reset(0_hdl_u4); #[hdl] let next_uart_state: UInt<4> = wire(); connect_any(next_uart_state, uart_state_reg + 1u8); #[hdl] let message_mem: Array> = wire(Array[UInt::new_static()][message.len()]); for (message, message_mem) in message.iter().zip(message_mem) { connect(message_mem, *message); } #[hdl] let addr_reg: UInt<32> = reg_builder().clock_domain(cd).reset(0u32); #[hdl] let next_addr: UInt<32> = wire(); connect(next_addr, addr_reg); #[hdl] let tx = reg_builder().clock_domain(cd).reset(true); #[hdl] let tx_bits: Array = wire(); connect(tx_bits[0], false); // start bit connect(tx_bits[9], true); // stop bit for i in 0..8 { connect(tx_bits[i + 1], message_mem[addr_reg][i]); // data bits } connect(tx, tx_bits[uart_state_reg]); #[hdl] if uart_state_reg.cmp_eq(Expr::ty(tx_bits).len() - 1) { connect(next_uart_state, 0_hdl_u4); let next_addr_val = addr_reg + 1u8; #[hdl] if next_addr_val.cmp_lt(message.len()) { connect_any(next_addr, next_addr_val); } else { connect(next_addr, 0u32); } } #[hdl] if tick_reg { connect(uart_state_reg, next_uart_state); connect(addr_reg, next_addr); } for uart in platform_io_builder.peripherals_with_type::() { connect(uart.use_peripheral().tx, tx); } #[hdl] let io = m.add_platform_io(platform_io_builder); } fn parse_baud_rate( v: impl AsRef, ) -> Result, Box> { let retval: NotNan = v .as_ref() .parse() .map_err(|_| "invalid baud rate, must be a finite positive floating-point value")?; if *retval > 0.0 && retval.is_finite() { Ok(retval) } else { Err("baud rate must be finite and positive".into()) } } #[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] pub struct ExtraArgs { #[arg(long, value_parser = clap::builder::StringValueParser::new().try_map(parse_baud_rate), default_value = "115200")] pub baud_rate: NotNan, #[arg(long, default_value = "Hello World from Fayalite!!!\r\n", value_parser = clap::builder::NonEmptyStringValueParser::new())] pub message: String, } impl ToArgs for ExtraArgs { fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { let Self { baud_rate, message } = self; args.write_display_arg(format_args!("--baud-rate={baud_rate}")); args.write_long_option_eq("message", message); } } fn main() { type Cli = BuildCli; Cli::main( "tx_only_uart", |_, platform, ExtraArgs { baud_rate, message }| { Ok(JobParams::new(platform.try_wrap_main_module(|io| { let clk = pick_clock(&io).ty(); let divisor = clk.frequency() / *baud_rate; let baud_rate_error = |msg| { ::command() .error(clap::error::ErrorKind::ValueValidation, msg) }; const HUGE_DIVISOR: f64 = u64::MAX as f64; match divisor { divisor if !divisor.is_finite() => { return Err(baud_rate_error("bad baud rate")); } HUGE_DIVISOR.. => return Err(baud_rate_error("baud rate is too small")), 4.0.. => {} _ => return Err(baud_rate_error("baud rate is too large")), } Ok(tx_only_uart(io, divisor, message)) })?)) }, ); }