From 8ca0cd56c4a8144e780b925e055c8d71bb2f1b5b Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 21 Jul 2024 20:44:29 -0700 Subject: [PATCH 1/5] fix warning --- .../src/module/transform_body/expand_aggregate_literals.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs index f6ae7cd..ccc7252 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body/expand_aggregate_literals.rs @@ -437,6 +437,7 @@ impl Visitor { #(#lets)* #make_expr_fn::<#ty>(|#infallible_var| { let #retval_var = #check_literal; + #[allow(unreachable_code)] match #retval_var { #variant_or_type { .. } => #retval_var, #[allow(unreachable_patterns)] From bdfa18e11d896af8b47dd2d9acba6fbd291c74c7 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 21 Jul 2024 20:45:36 -0700 Subject: [PATCH 2/5] add IntTypeTrait::literal utility function --- crates/fayalite/src/int.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/fayalite/src/int.rs b/crates/fayalite/src/int.rs index 4f31480..d6f4cfa 100644 --- a/crates/fayalite/src/int.rs +++ b/crates/fayalite/src/int.rs @@ -887,6 +887,15 @@ pub trait IntTypeTrait: CanonicalType = DynSIntType, CanonicalValue = DynSInt, >; + fn literal(self, value: impl Into) -> Expr> + where + Self: IntTypeTrait< + CanonicalType = DynIntType<::Signed>, + CanonicalValue = DynInt<::Signed>, + >, + { + IntValue::with_type(self, value).to_expr() + } fn from_width_unchecked(width: usize) -> Self; fn width(self) -> usize; fn as_same_width_uint(self) -> Self::SameWidthUInt; From 8080cc7b5c8e061513baee7c656cc8fc55f5269a Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 21 Jul 2024 20:46:23 -0700 Subject: [PATCH 3/5] add utility functions for getting smallest integer type that fits a range/value --- crates/fayalite/src/int.rs | 65 +++++++++++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-) diff --git a/crates/fayalite/src/int.rs b/crates/fayalite/src/int.rs index d6f4cfa..4f41e53 100644 --- a/crates/fayalite/src/int.rs +++ b/crates/fayalite/src/int.rs @@ -18,7 +18,10 @@ use std::{ fmt, hash::Hash, marker::PhantomData, - ops::{Add, BitAnd, BitOr, BitXor, Bound, Mul, Neg, Not, Range, RangeBounds, Shl, Shr, Sub}, + ops::{ + Add, BitAnd, BitOr, BitXor, Bound, Mul, Neg, Not, Range, RangeBounds, RangeInclusive, Shl, + Shr, Sub, + }, }; #[derive(Clone, Eq, PartialEq, Hash, Default)] @@ -1003,6 +1006,66 @@ impl DynUIntType { pub fn mask(self) -> BigUint { self.modulo() - 1u8 } + /// gets the smallest `UInt` that fits `v` losslessly + pub fn for_value(v: impl Into) -> Self { + let v: BigUint = v.into(); + Self::new(v.bits().try_into().expect("too big")) + } + /// gets the smallest `UInt` that fits `r` losslessly, panics if `r` is empty + #[track_caller] + pub fn range(r: Range>) -> Self { + let start: BigUint = r.start.into(); + let end: BigUint = r.end.into(); + assert!(!end.is_zero(), "empty range"); + Self::range_inclusive(start..=(end - 1u8)) + } + /// gets the smallest `UInt` that fits `r` losslessly, panics if `r` is empty + #[track_caller] + pub fn range_inclusive(r: RangeInclusive>) -> Self { + let (start, end) = r.into_inner(); + let start: BigUint = start.into(); + let end: BigUint = end.into(); + assert!(start <= end, "empty range"); + // no need to check `start`` since it's no larger than `end` + // so must not take more bits than `end` + Self::for_value(end) + } +} + +impl DynSIntType { + /// gets the smallest `SInt` that fits `v` losslessly + pub fn for_value(v: impl Into) -> Self { + let v: BigInt = v.into(); + Self::new( + match v.sign() { + Sign::Minus => { + // account for sign bit and for the minimum value of an `SInt` + // being the negative of the maximum value minus one. + v.not().bits().checked_add(1).expect("too big") + } + Sign::NoSign => 0, + Sign::Plus => v.bits(), + } + .try_into() + .expect("too big"), + ) + } + /// gets the smallest `SInt` that fits `r` losslessly, panics if `r` is empty + #[track_caller] + pub fn range(r: Range>) -> Self { + let start: BigInt = r.start.into(); + let end: BigInt = r.end.into(); + Self::range_inclusive(start..=(end - 1)) + } + /// gets the smallest `SInt` that fits `r` losslessly, panics if `r` is empty + #[track_caller] + pub fn range_inclusive(r: RangeInclusive>) -> Self { + let (start, end) = r.into_inner(); + let start: BigInt = start.into(); + let end: BigInt = end.into(); + assert!(start <= end, "empty range"); + Self::new(Self::for_value(start).width.max(Self::for_value(end).width)) + } } impl sealed::Sealed for DynIntType {} From 23a77368b318067c0406ec8e826295f2e0f34747 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 21 Jul 2024 20:47:52 -0700 Subject: [PATCH 4/5] add beginnings of simple CLI --- Cargo.lock | 137 +++++++++++++++++++++++++++++++++++++ crates/fayalite/Cargo.toml | 4 +- crates/fayalite/src/cli.rs | 116 +++++++++++++++++++++++++++++++ crates/fayalite/src/lib.rs | 1 + 4 files changed, 257 insertions(+), 1 deletion(-) create mode 100644 crates/fayalite/src/cli.rs diff --git a/Cargo.lock b/Cargo.lock index 052a0d3..058310b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -20,6 +20,55 @@ version = "0.2.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0942ffc6dcaadf03badf6e6a2d0228460359d5e34b57ccdc720b7382dfbd5ec5" +[[package]] +name = "anstream" +version = "0.6.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "418c75fa768af9c03be99d17643f93f79bbba589895012a80e3452a19ddda15b" +dependencies = [ + "anstyle", + "anstyle-parse", + "anstyle-query", + "anstyle-wincon", + "colorchoice", + "is_terminal_polyfill", + "utf8parse", +] + +[[package]] +name = "anstyle" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "038dfcf04a5feb68e9c60b21c9625a54c2c0616e79b72b0fd87075a056ae1d1b" + +[[package]] +name = "anstyle-parse" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c03a11a9034d92058ceb6ee011ce58af4a9bf61491aa7e1e59ecd24bd40d22d4" +dependencies = [ + "utf8parse", +] + +[[package]] +name = "anstyle-query" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ad186efb764318d35165f1758e7dcef3b10628e26d41a44bc5550652e6804391" +dependencies = [ + "windows-sys", +] + +[[package]] +name = "anstyle-wincon" +version = "3.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61a38449feb7068f52bb06c12759005cf459ee52bb4adc1d5a7c4322d716fb19" +dependencies = [ + "anstyle", + "windows-sys", +] + [[package]] name = "autocfg" version = "1.1.0" @@ -75,6 +124,52 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "clap" +version = "4.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "64acc1846d54c1fe936a78dc189c34e28d3f5afc348403f28ecf53660b9b8462" +dependencies = [ + "clap_builder", + "clap_derive", +] + +[[package]] +name = "clap_builder" +version = "4.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fb8393d67ba2e7bfaf28a23458e4e2b543cc73a99595511eb207fdb8aede942" +dependencies = [ + "anstream", + "anstyle", + "clap_lex", + "strsim", +] + +[[package]] +name = "clap_derive" +version = "4.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2bac35c6dafb060fd4d275d9a4ffae97917c13a6327903a8be2153cd964f7085" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" + +[[package]] +name = "colorchoice" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" + [[package]] name = "cpufeatures" version = "0.2.12" @@ -120,6 +215,16 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "eyre" +version = "0.6.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" +dependencies = [ + "indenter", + "once_cell", +] + [[package]] name = "fastrand" version = "2.0.1" @@ -131,6 +236,8 @@ name = "fayalite" version = "0.1.0" dependencies = [ "bitvec", + "clap", + "eyre", "fayalite-proc-macros", "fayalite-visit-gen", "hashbrown", @@ -208,6 +315,18 @@ dependencies = [ "allocator-api2", ] +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + +[[package]] +name = "indenter" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" + [[package]] name = "indexmap" version = "2.2.6" @@ -219,6 +338,12 @@ dependencies = [ "serde", ] +[[package]] +name = "is_terminal_polyfill" +version = "1.70.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8478577c03552c21db0e2724ffb8986a5ce7af88107e6be5d2ee6e158c12800" + [[package]] name = "itoa" version = "1.0.10" @@ -368,6 +493,12 @@ dependencies = [ "digest", ] +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.66" @@ -453,6 +584,12 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" +[[package]] +name = "utf8parse" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" + [[package]] name = "version_check" version = "0.9.4" diff --git a/crates/fayalite/Cargo.toml b/crates/fayalite/Cargo.toml index e412fd2..23d8bbf 100644 --- a/crates/fayalite/Cargo.toml +++ b/crates/fayalite/Cargo.toml @@ -21,6 +21,8 @@ num-traits = { workspace = true } fayalite-proc-macros = { workspace = true } serde = { workspace = true } serde_json = { workspace = true } +clap = { version = "4.5.9", features = ["derive"] } +eyre = "0.6.12" [dev-dependencies] trybuild = { workspace = true } @@ -32,4 +34,4 @@ fayalite-visit-gen = { workspace = true } unstable-doc = [] [package.metadata.docs.rs] -features = ["unstable-doc"] \ No newline at end of file +features = ["unstable-doc"] diff --git a/crates/fayalite/src/cli.rs b/crates/fayalite/src/cli.rs new file mode 100644 index 0000000..cb0ddf7 --- /dev/null +++ b/crates/fayalite/src/cli.rs @@ -0,0 +1,116 @@ +use crate::{ + bundle::{BundleType, BundleValue, DynBundle}, + firrtl, + intern::Interned, + module::Module, +}; +use clap::{Parser, Subcommand, ValueHint}; +use std::{path::PathBuf, process::exit}; + +#[derive(Subcommand)] +enum CliCommand { + /// Generate FIRRTL + Firrtl { + /// the directory to put the generated FIRRTL and associated files in + #[arg(short, long, value_hint = ValueHint::DirPath)] + output: PathBuf, + /// the stem of the generated .fir file, e.g. to get foo.fir, pass --file-stem=foo + #[arg(long)] + file_stem: Option, + }, +} + +/// a simple CLI +/// +/// Use like: +/// +/// ```no_run +/// # use fayalite::{hdl_module, cli::Cli}; +/// # #[hdl_module] +/// # fn my_module() {} +/// fn main() { +/// Cli::parse().run(my_module()); +/// } +/// ``` +/// +/// You can also use it with a larger [`clap`]-based CLI like so: +/// +/// ```no_run +/// # use fayalite::{hdl_module}; +/// # #[hdl_module] +/// # fn my_module() {} +/// +/// #[derive(clap::Subcommand)] +/// pub enum Subcommand { +/// #[command(flatten)] +/// Fayalite(fayalite::cli::Cli), +/// MySpecialCommand { +/// #[arg] +/// foo: bool, +/// }, +/// } +/// +/// #[derive(clap::Parser)] +/// pub struct Cli { +/// #[command(subcommand)] +/// subcommand: Subcommand, // or just use fayalite::cli::Cli directly +/// } +/// fn main() { +/// match Cli::parse().subcommand { +/// Subcommand::Fayalite(v) => v.run(my_module()), +/// Subcommand::MySpecialCommand { foo } => println!("special: foo={foo}"), +/// } +/// } +/// ``` +#[derive(Parser)] +// clear things that would be crate-specific +#[command(name = "Fayalite Simple CLI", about = None, long_about = None)] +pub struct Cli { + #[command(subcommand)] + subcommand: CliCommand, +} + +impl clap::Subcommand for Cli { + fn augment_subcommands(cmd: clap::Command) -> clap::Command { + CliCommand::augment_subcommands(cmd) + } + + fn augment_subcommands_for_update(cmd: clap::Command) -> clap::Command { + CliCommand::augment_subcommands_for_update(cmd) + } + + fn has_subcommand(name: &str) -> bool { + CliCommand::has_subcommand(name) + } +} + +impl Cli { + /// forwards to [`clap::Parser::parse()`] so you don't have to import [`clap::Parser`] + pub fn parse() -> Self { + clap::Parser::parse() + } + fn run_impl(self, top_module: Module) -> ! { + match self.subcommand { + CliCommand::Firrtl { output, file_stem } => { + let result = firrtl::export( + firrtl::FileBackend { + dir_path: output, + top_fir_file_stem: file_stem, + }, + &top_module, + ); + if let Err(e) = result { + eprintln!("Error: {:?}", eyre::Report::new(e)); + exit(1); + } + } + } + exit(0) + } + pub fn run(self, top_module: Interned>) -> ! + where + T::Type: BundleType, + { + self.run_impl(top_module.canonical()) + } +} diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index 15828ab..c0a3775 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -27,6 +27,7 @@ pub mod _docs; pub mod annotations; pub mod array; pub mod bundle; +pub mod cli; pub mod clock; pub mod enum_; pub mod expr; From a191ece9a5c4a034c98c916336fe1fa71ea73f48 Mon Sep 17 00:00:00 2001 From: Jacob Lifshay Date: Sun, 21 Jul 2024 20:48:43 -0700 Subject: [PATCH 5/5] add blinky example --- crates/fayalite/examples/blinky.rs | 50 ++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) create mode 100644 crates/fayalite/examples/blinky.rs diff --git a/crates/fayalite/examples/blinky.rs b/crates/fayalite/examples/blinky.rs new file mode 100644 index 0000000..694c736 --- /dev/null +++ b/crates/fayalite/examples/blinky.rs @@ -0,0 +1,50 @@ +use clap::Parser; +use fayalite::{ + clock::{Clock, ClockDomain}, + hdl_module, + int::{DynUInt, DynUIntType, IntTypeTrait, UInt}, + reset::{SyncReset, ToReset}, +}; + +#[hdl_module] +fn blinky(clock_frequency: u64) { + #[hdl] + let clk: Clock = m.input(); + #[hdl] + let rst: SyncReset = m.input(); + let cd = #[hdl] + ClockDomain { + clk, + rst: rst.to_reset(), + }; + let max_value = clock_frequency / 2 - 1; + let int_ty = DynUIntType::range_inclusive(0..=max_value); + #[hdl] + let counter: DynUInt = m.reg_builder().clock_domain(cd).reset(int_ty.literal(0)); + #[hdl] + let output_reg: UInt<1> = m.reg_builder().clock_domain(cd).reset_default(); + #[hdl] + if counter == int_ty.literal(max_value) { + m.connect(counter, int_ty.literal(0)); + m.connect(output_reg, !output_reg); + } else { + m.connect_any(counter, counter + 1_hdl_u1); + } + #[hdl] + let led: UInt<1> = m.output(); + m.connect(led, output_reg); +} + +#[derive(Parser)] +struct Cli { + /// clock frequency in hertz + #[arg(long, default_value = "1000000", value_parser = clap::value_parser!(u64).range(2..))] + clock_frequency: u64, + #[command(subcommand)] + cli: fayalite::cli::Cli, +} + +fn main() { + let cli = Cli::parse(); + cli.cli.run(blinky(cli.clock_frequency)) +}