forked from libre-chip/fayalite
188 lines
5.9 KiB
Rust
188 lines
5.9 KiB
Rust
// 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::<peripherals::ClockInput>();
|
|
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::<Reset>()[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<UInt<8>> = 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<Bool, 10> = 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::<peripherals::Uart>() {
|
|
connect(uart.use_peripheral().tx, tx);
|
|
}
|
|
|
|
#[hdl]
|
|
let io = m.add_platform_io(platform_io_builder);
|
|
}
|
|
|
|
fn parse_baud_rate(
|
|
v: impl AsRef<str>,
|
|
) -> Result<NotNan<f64>, Box<dyn std::error::Error + Send + Sync>> {
|
|
let retval: NotNan<f64> = 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<f64>,
|
|
#[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<ExtraArgs>;
|
|
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| {
|
|
<Cli as clap::CommandFactory>::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))
|
|
})?))
|
|
},
|
|
);
|
|
}
|