diff --git a/.forgejo/workflows/test.yml b/.forgejo/workflows/test.yml index 13e98434..1b9910eb 100644 --- a/.forgejo/workflows/test.yml +++ b/.forgejo/workflows/test.yml @@ -21,4 +21,4 @@ jobs: - run: cargo test --doc --features=unstable-doc - run: cargo doc --features=unstable-doc - run: FAYALITE_TEST_HASHER=always_zero cargo test --test=module --features=unstable-doc,unstable-test-hasher - - run: cargo run --example blinky yosys-nextpnr-xray --platform=arty-a7-100t --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db -o target/blinky-out + - run: cargo run --example blinky yosys-nextpnr-xray --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db --device xc7a100ticsg324-1L -o target/blinky-out --clock-frequency=$((1000*1000*100)) diff --git a/Cargo.lock b/Cargo.lock index be5f3bcb..f4b564a2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -319,7 +319,6 @@ dependencies = [ "jobslot", "num-bigint", "num-traits", - "ordered-float", "petgraph", "serde", "serde_json", @@ -525,17 +524,6 @@ version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" -[[package]] -name = "ordered-float" -version = "5.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" -dependencies = [ - "num-traits", - "rand", - "serde", -] - [[package]] name = "petgraph" version = "0.8.1" @@ -588,25 +576,6 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "rand_core", - "serde", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "serde", -] - [[package]] name = "rustix" version = "0.38.31" diff --git a/Cargo.toml b/Cargo.toml index 2380ea7c..b905f73d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,7 +30,6 @@ indexmap = { version = "2.5.0", features = ["serde"] } jobslot = "0.2.23" num-bigint = "0.4.6" num-traits = "0.2.16" -ordered-float = { version = "5.1.0", features = ["serde"] } petgraph = "0.8.1" prettyplease = "0.2.20" proc-macro2 = "1.0.83" diff --git a/README.md b/README.md index 011922d8..0b91833e 100644 --- a/README.md +++ b/README.md @@ -8,43 +8,6 @@ Fayalite is a library for designing digital hardware -- a hardware description l [FIRRTL]: https://github.com/chipsalliance/firrtl-spec -# Building the [Blinky example] for the Arty A7 100T on Linux - -[Blinky example]: crates/fayalite/examples/blinky.rs - -This uses the container image containing all the external programs and files that Fayalite needs to build for FPGAs, the sources for the container image are in https://git.libre-chip.org/libre-chip/fayalite-deps - -Steps: - -Install podman (or docker). - -Run: -```bash -podman run --rm --security-opt label=disable --volume="$(pwd):$(pwd)" -w="$(pwd)" -it git.libre-chip.org/libre-chip/fayalite-deps:latest cargo run --example blinky yosys-nextpnr-xray --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db --platform arty-a7-100t -o target/blinky-out -``` - -To actually program the FPGA, you'll need to install [openFPGALoader] on your host OS: - -[openFPGALoader]: https://github.com/trabucayre/openFPGALoader - -On Debian 12: -```bash -sudo apt update && sudo apt install openfpgaloader -``` - -Then program the FPGA: -```bash -sudo openFPGALoader --board arty_a7_100t target/blinky-out/blinky.bit -``` - -This will program the FPGA but leave the Flash chip unmodified, so the FPGA will revert when the board is power-cycled. - -To program the Flash also, so it stays programmed when power-cycling the board: - -```bash -sudo openFPGALoader --board arty_a7_100t -f target/blinky-out/blinky.bit -``` - # Funding ## NLnet Grants diff --git a/crates/fayalite-proc-macros-impl/src/lib.rs b/crates/fayalite-proc-macros-impl/src/lib.rs index 13336fa2..def91eb4 100644 --- a/crates/fayalite-proc-macros-impl/src/lib.rs +++ b/crates/fayalite-proc-macros-impl/src/lib.rs @@ -66,7 +66,6 @@ mod kw { } custom_keyword!(__evaluated_cfgs); - custom_keyword!(add_platform_io); custom_keyword!(all); custom_keyword!(any); custom_keyword!(cfg); diff --git a/crates/fayalite-proc-macros-impl/src/module.rs b/crates/fayalite-proc-macros-impl/src/module.rs index 5628ff94..c7caa160 100644 --- a/crates/fayalite-proc-macros-impl/src/module.rs +++ b/crates/fayalite-proc-macros-impl/src/module.rs @@ -4,7 +4,7 @@ use crate::{ Errors, HdlAttr, PairsIterExt, hdl_type_common::{ParsedGenerics, SplitForImpl}, kw, - module::transform_body::{HdlLet, HdlLetKindIO, ModuleIOOrAddPlatformIO}, + module::transform_body::{HdlLet, HdlLetKindIO}, options, }; use proc_macro2::TokenStream; @@ -39,7 +39,7 @@ pub(crate) fn check_name_conflicts_with_module_builder(name: &Ident) -> syn::Res if name == "m" { Err(Error::new_spanned( name, - "name conflicts with implicit `m: &ModuleBuilder`", + "name conflicts with implicit `m: &mut ModuleBuilder<_>`", )) } else { Ok(()) @@ -67,7 +67,7 @@ struct ModuleFnModule { vis: Visibility, sig: Signature, block: Box, - struct_generics: Option, + struct_generics: ParsedGenerics, the_struct: TokenStream, } @@ -290,7 +290,7 @@ impl ModuleFn { paren_token, body, } => { - debug_assert!(matches!(io, ModuleIOOrAddPlatformIO::ModuleIO(v) if v.is_empty())); + debug_assert!(io.is_empty()); return Ok(Self(ModuleFnImpl::Fn { attrs, config_options: HdlAttr { @@ -322,21 +322,6 @@ impl ModuleFn { body, }, }; - let io = match io { - ModuleIOOrAddPlatformIO::ModuleIO(io) => io, - ModuleIOOrAddPlatformIO::AddPlatformIO => { - return Ok(Self(ModuleFnImpl::Module(ModuleFnModule { - attrs, - config_options, - module_kind: module_kind.unwrap(), - vis, - sig, - block, - struct_generics: None, - the_struct: TokenStream::new(), - }))); - } - }; let (_struct_impl_generics, _struct_type_generics, struct_where_clause) = struct_generics.split_for_impl(); let struct_where_clause: Option = parse_quote! { #struct_where_clause }; @@ -379,7 +364,7 @@ impl ModuleFn { vis, sig, block, - struct_generics: Some(struct_generics), + struct_generics, the_struct, }))) } @@ -448,14 +433,9 @@ impl ModuleFn { ModuleKind::Normal => quote! { ::fayalite::module::ModuleKind::Normal }, }; let fn_name = &outer_sig.ident; - let struct_ty = match struct_generics { - Some(struct_generics) => { - let (_struct_impl_generics, struct_type_generics, _struct_where_clause) = - struct_generics.split_for_impl(); - quote! {#fn_name #struct_type_generics} - } - None => quote! {::fayalite::bundle::Bundle}, - }; + let (_struct_impl_generics, struct_type_generics, _struct_where_clause) = + struct_generics.split_for_impl(); + let struct_ty = quote! {#fn_name #struct_type_generics}; body_sig.ident = parse_quote! {__body}; body_sig .inputs diff --git a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs index 7b41f5eb..6859f69f 100644 --- a/crates/fayalite-proc-macros-impl/src/module/transform_body.rs +++ b/crates/fayalite-proc-macros-impl/src/module/transform_body.rs @@ -39,7 +39,6 @@ options! { pub(crate) enum LetFnKind { Input(input), Output(output), - AddPlatformIO(add_platform_io), Instance(instance), RegBuilder(reg_builder), Wire(wire), @@ -217,49 +216,6 @@ impl HdlLetKindToTokens for HdlLetKindInstance { } } -#[derive(Clone, Debug)] -pub(crate) struct HdlLetKindAddPlatformIO { - pub(crate) m: kw::m, - pub(crate) dot_token: Token![.], - pub(crate) add_platform_io: kw::add_platform_io, - pub(crate) paren: Paren, - pub(crate) platform_io_builder: Box, -} - -impl ParseTypes for HdlLetKindAddPlatformIO { - fn parse_types(input: &mut Self, _parser: &mut TypesParser<'_>) -> Result { - Ok(input.clone()) - } -} - -impl_fold! { - struct HdlLetKindAddPlatformIO<> { - m: kw::m, - dot_token: Token![.], - add_platform_io: kw::add_platform_io, - paren: Paren, - platform_io_builder: Box, - } -} - -impl HdlLetKindToTokens for HdlLetKindAddPlatformIO { - fn ty_to_tokens(&self, _tokens: &mut TokenStream) {} - - fn expr_to_tokens(&self, tokens: &mut TokenStream) { - let Self { - m, - dot_token, - add_platform_io, - paren, - platform_io_builder, - } = self; - m.to_tokens(tokens); - dot_token.to_tokens(tokens); - add_platform_io.to_tokens(tokens); - paren.surround(tokens, |tokens| platform_io_builder.to_tokens(tokens)); - } -} - #[derive(Clone, Debug)] pub(crate) struct RegBuilderClockDomain { pub(crate) dot_token: Token![.], @@ -755,7 +711,6 @@ impl HdlLetKindMemory { #[derive(Clone, Debug)] pub(crate) enum HdlLetKind { IO(HdlLetKindIO), - AddPlatformIO(HdlLetKindAddPlatformIO), Incomplete(HdlLetKindIncomplete), Instance(HdlLetKindInstance), RegBuilder(HdlLetKindRegBuilder), @@ -766,7 +721,6 @@ pub(crate) enum HdlLetKind { impl_fold! { enum HdlLetKind { IO(HdlLetKindIO), - AddPlatformIO(HdlLetKindAddPlatformIO), Incomplete(HdlLetKindIncomplete), Instance(HdlLetKindInstance), RegBuilder(HdlLetKindRegBuilder), @@ -782,9 +736,6 @@ impl, I> ParseTypes> for HdlLetKind { ) -> Result { match input { HdlLetKind::IO(input) => ParseTypes::parse_types(input, parser).map(HdlLetKind::IO), - HdlLetKind::AddPlatformIO(input) => { - ParseTypes::parse_types(input, parser).map(HdlLetKind::AddPlatformIO) - } HdlLetKind::Incomplete(input) => { ParseTypes::parse_types(input, parser).map(HdlLetKind::Incomplete) } @@ -910,23 +861,6 @@ impl HdlLetKindParse for HdlLetKind { ModuleIOKind::Output(output), ) .map(Self::IO), - LetFnKind::AddPlatformIO((add_platform_io,)) => { - if let Some(parsed_ty) = parsed_ty { - return Err(Error::new_spanned( - parsed_ty.1, - "type annotation not allowed for instance", - )); - } - let (m, dot_token) = unwrap_m_dot(m_dot, kind)?; - let paren_contents; - Ok(Self::AddPlatformIO(HdlLetKindAddPlatformIO { - m, - dot_token, - add_platform_io, - paren: parenthesized!(paren_contents in input), - platform_io_builder: paren_contents.call(parse_single_fn_arg)?, - })) - } LetFnKind::Instance((instance,)) => { if let Some(parsed_ty) = parsed_ty { return Err(Error::new_spanned( @@ -1002,7 +936,6 @@ impl HdlLetKindToTokens for HdlLetKind { fn ty_to_tokens(&self, tokens: &mut TokenStream) { match self { HdlLetKind::IO(v) => v.ty_to_tokens(tokens), - HdlLetKind::AddPlatformIO(v) => v.ty_to_tokens(tokens), HdlLetKind::Incomplete(v) => v.ty_to_tokens(tokens), HdlLetKind::Instance(v) => v.ty_to_tokens(tokens), HdlLetKind::RegBuilder(v) => v.ty_to_tokens(tokens), @@ -1014,7 +947,6 @@ impl HdlLetKindToTokens for HdlLetKind { fn expr_to_tokens(&self, tokens: &mut TokenStream) { match self { HdlLetKind::IO(v) => v.expr_to_tokens(tokens), - HdlLetKind::AddPlatformIO(v) => v.expr_to_tokens(tokens), HdlLetKind::Incomplete(v) => v.expr_to_tokens(tokens), HdlLetKind::Instance(v) => v.expr_to_tokens(tokens), HdlLetKind::RegBuilder(v) => v.expr_to_tokens(tokens), @@ -1217,7 +1149,7 @@ impl ToTokens for ImplicitName { struct Visitor<'a> { module_kind: Option, errors: Errors, - io: ModuleIOOrAddPlatformIO, + io: Vec, block_depth: usize, parsed_generics: &'a ParsedGenerics, } @@ -1357,81 +1289,7 @@ impl Visitor<'_> { }), semi_token: hdl_let.semi_token, }; - match &mut self.io { - ModuleIOOrAddPlatformIO::ModuleIO(io) => io.push(hdl_let), - ModuleIOOrAddPlatformIO::AddPlatformIO => { - self.errors.error( - kind, - "can't have other inputs/outputs in a module using m.add_platform_io()", - ); - } - } - let_stmt - } - fn process_hdl_let_add_platform_io( - &mut self, - hdl_let: HdlLet, - ) -> Local { - let HdlLet { - mut attrs, - hdl_attr: _, - let_token, - mut_token, - ref name, - eq_token, - kind: - HdlLetKindAddPlatformIO { - m, - dot_token, - add_platform_io, - paren, - platform_io_builder, - }, - semi_token, - } = hdl_let; - let mut expr = quote! {#m #dot_token #add_platform_io}; - paren.surround(&mut expr, |expr| { - let name_str = ImplicitName { - name, - span: name.span(), - }; - quote_spanned! {name.span()=> - #name_str, #platform_io_builder - } - .to_tokens(expr); - }); - self.require_module(add_platform_io); - attrs.push(parse_quote_spanned! {let_token.span=> - #[allow(unused_variables)] - }); - let let_stmt = Local { - attrs, - let_token, - pat: parse_quote! { #mut_token #name }, - init: Some(LocalInit { - eq_token, - expr: parse_quote! { #expr }, - diverge: None, - }), - semi_token, - }; - match &mut self.io { - ModuleIOOrAddPlatformIO::ModuleIO(io) => { - for io in io { - self.errors.error( - io.kind.kind, - "can't have other inputs/outputs in a module using m.add_platform_io()", - ); - } - } - ModuleIOOrAddPlatformIO::AddPlatformIO => { - self.errors.error( - add_platform_io, - "can't use m.add_platform_io() more than once in a single module", - ); - } - } - self.io = ModuleIOOrAddPlatformIO::AddPlatformIO; + self.io.push(hdl_let); let_stmt } fn process_hdl_let_instance(&mut self, hdl_let: HdlLet) -> Local { @@ -1652,7 +1510,6 @@ impl Visitor<'_> { } the_match! { IO => process_hdl_let_io, - AddPlatformIO => process_hdl_let_add_platform_io, Incomplete => process_hdl_let_incomplete, Instance => process_hdl_let_instance, RegBuilder => process_hdl_let_reg_builder, @@ -1896,20 +1753,15 @@ impl Fold for Visitor<'_> { } } -pub(crate) enum ModuleIOOrAddPlatformIO { - ModuleIO(Vec), - AddPlatformIO, -} - pub(crate) fn transform_body( module_kind: Option, mut body: Box, parsed_generics: &ParsedGenerics, -) -> syn::Result<(Box, ModuleIOOrAddPlatformIO)> { +) -> syn::Result<(Box, Vec)> { let mut visitor = Visitor { module_kind, errors: Errors::new(), - io: ModuleIOOrAddPlatformIO::ModuleIO(vec![]), + io: vec![], block_depth: 0, parsed_generics, }; diff --git a/crates/fayalite/Cargo.toml b/crates/fayalite/Cargo.toml index fdf1c871..2403ff5f 100644 --- a/crates/fayalite/Cargo.toml +++ b/crates/fayalite/Cargo.toml @@ -26,7 +26,6 @@ hashbrown.workspace = true jobslot.workspace = true num-bigint.workspace = true num-traits.workspace = true -ordered-float.workspace = true petgraph.workspace = true serde_json.workspace = true serde.workspace = true diff --git a/crates/fayalite/examples/blinky.rs b/crates/fayalite/examples/blinky.rs index 75799fdc..40b22dc7 100644 --- a/crates/fayalite/examples/blinky.rs +++ b/crates/fayalite/examples/blinky.rs @@ -1,64 +1,55 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use fayalite::prelude::*; +use fayalite::{ + build::{ToArgs, WriteArgs}, + prelude::*, +}; #[hdl_module] -fn blinky(platform_io_builder: PlatformIOBuilder<'_>) { - let clk_input = - platform_io_builder.peripherals_with_type::()[0].use_peripheral(); - let rst = platform_io_builder.peripherals_with_type::()[0].use_peripheral(); +fn blinky(clock_frequency: u64) { + #[hdl] + let clk: Clock = m.input(); + #[hdl] + let rst: SyncReset = m.input(); let cd = #[hdl] ClockDomain { - clk: clk_input.clk, - rst, + clk, + rst: rst.to_reset(), }; - let max_value = (Expr::ty(clk_input).frequency() / 2.0).round_ties_even() as u64 - 1; + let max_value = clock_frequency / 2 - 1; let int_ty = UInt::range_inclusive(0..=max_value); #[hdl] let counter_reg: UInt = reg_builder().clock_domain(cd).reset(0u8.cast_to(int_ty)); #[hdl] let output_reg: Bool = reg_builder().clock_domain(cd).reset(false); #[hdl] - let rgb_output_reg = reg_builder().clock_domain(cd).reset( - #[hdl] - peripherals::RgbLed { - r: false, - g: false, - b: false, - }, - ); - #[hdl] if counter_reg.cmp_eq(max_value) { connect_any(counter_reg, 0u8); connect(output_reg, !output_reg); - connect(rgb_output_reg.r, !rgb_output_reg.r); - #[hdl] - if rgb_output_reg.r { - connect(rgb_output_reg.g, !rgb_output_reg.g); - #[hdl] - if rgb_output_reg.g { - connect(rgb_output_reg.b, !rgb_output_reg.b); - } - } } else { connect_any(counter_reg, counter_reg + 1_hdl_u1); } - for led in platform_io_builder.peripherals_with_type::() { - if let Ok(led) = led.try_use_peripheral() { - connect(led.on, output_reg); - } - } - for rgb_led in platform_io_builder.peripherals_with_type::() { - if let Ok(rgb_led) = rgb_led.try_use_peripheral() { - connect(rgb_led, rgb_output_reg); - } - } #[hdl] - let io = m.add_platform_io(platform_io_builder); + let led: Bool = m.output(); + connect(led, output_reg); +} + +#[derive(clap::Args, Clone, PartialEq, Eq, Hash, Debug)] +struct ExtraArgs { + /// clock frequency in hertz + #[arg(long, default_value = "1000000", value_parser = clap::value_parser!(u64).range(2..))] + clock_frequency: u64, +} + +impl ToArgs for ExtraArgs { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { + let Self { clock_frequency } = self; + args.write_arg(format_args!("--clock-frequency={clock_frequency}")); + } } fn main() { - ::main("blinky", |_, platform, _| { - Ok(JobParams::new(platform.wrap_main_module(blinky))) + BuildCli::main(|_cli, ExtraArgs { clock_frequency }| { + Ok(JobParams::new(blinky(clock_frequency), "blinky")) }); } diff --git a/crates/fayalite/examples/tx_only_uart.rs b/crates/fayalite/examples/tx_only_uart.rs deleted file mode 100644 index c50e987f..00000000 --- a/crates/fayalite/examples/tx_only_uart.rs +++ /dev/null @@ -1,169 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// See Notices.txt for copyright information -use clap::builder::TypedValueParser; -use fayalite::{ - build::{ToArgs, WriteArgs}, - prelude::*, -}; -use ordered_float::NotNan; - -#[hdl_module] -fn tx_only_uart( - platform_io_builder: PlatformIOBuilder<'_>, - divisor: f64, - message: impl AsRef<[u8]>, -) { - let message = message.as_ref(); - let clk_input = - platform_io_builder.peripherals_with_type::()[0].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 mut message_mem = memory_with_init(message); - message_mem.read_latency(4); - #[hdl] - let startup_reg = reg_builder().clock_domain(cd).reset(0u128); - connect_any(startup_reg, (startup_reg << 1) | 1u8); - let message_read = message_mem.new_read_port(); - connect(message_read.clk, cd.clk); - connect(message_read.en, true); - #[hdl] - let addr_reg: UInt<32> = reg_builder().clock_domain(cd).reset(0u32); - connect_any(message_read.addr, addr_reg); - #[hdl] - let next_addr: UInt<32> = wire(); - connect(next_addr, addr_reg); - - #[hdl] - let tx = reg_builder().clock_domain(cd).reset(true); - - #[hdl] - if !startup_reg[message_mem.get_read_latency()] { - connect(next_uart_state, 0_hdl_u4); - connect(tx, true); - } else if uart_state_reg.cmp_eq(0_hdl_u4) { - connect(tx, false); // start bit - } else if uart_state_reg.cmp_le(8_hdl_u4) { - connect( - tx, - (message_read.data >> (uart_state_reg - 1_hdl_u4))[0].cast_to_static(), - ); // data bit - } else { - connect(tx, true); // stop bit - 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 = io.peripherals_with_type::()[0].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)) - })?)) - }, - ); -} diff --git a/crates/fayalite/src/annotations.rs b/crates/fayalite/src/annotations.rs index 4ca84dd1..d5786262 100644 --- a/crates/fayalite/src/annotations.rs +++ b/crates/fayalite/src/annotations.rs @@ -145,73 +145,52 @@ pub struct DocStringAnnotation { macro_rules! make_annotation_enum { ( - #[$non_exhaustive:ident] $(#[$meta:meta])* - $vis:vis enum $AnnotationEnum:ident { + $vis:vis enum $Annotation:ident { $($Variant:ident($T:ty),)* } ) => { - crate::annotations::make_annotation_enum!(@require_non_exhaustive $non_exhaustive); - - #[$non_exhaustive] $(#[$meta])* - #[derive(Clone, PartialEq, Eq, Hash)] - $vis enum $AnnotationEnum { + $vis enum $Annotation { $($Variant($T),)* } - impl std::fmt::Debug for $AnnotationEnum { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - match self { - $(Self::$Variant(v) => v.fmt(f),)* - } - } - } - - $(impl From<$T> for crate::annotations::Annotation { - fn from(v: $T) -> Self { - $AnnotationEnum::$Variant(v).into() - } - } - - impl crate::annotations::IntoAnnotations for $T { - type IntoAnnotations = [crate::annotations::Annotation; 1]; + $(impl IntoAnnotations for $T { + type IntoAnnotations = [$Annotation; 1]; fn into_annotations(self) -> Self::IntoAnnotations { - [self.into()] + [$Annotation::$Variant(self)] } } - impl crate::annotations::IntoAnnotations for &'_ $T { - type IntoAnnotations = [crate::annotations::Annotation; 1]; + impl IntoAnnotations for &'_ $T { + type IntoAnnotations = [$Annotation; 1]; fn into_annotations(self) -> Self::IntoAnnotations { - [crate::annotations::Annotation::from(self.clone())] + [$Annotation::$Variant(*self)] } } - impl crate::annotations::IntoAnnotations for &'_ mut $T { - type IntoAnnotations = [crate::annotations::Annotation; 1]; + impl IntoAnnotations for &'_ mut $T { + type IntoAnnotations = [$Annotation; 1]; fn into_annotations(self) -> Self::IntoAnnotations { - [crate::annotations::Annotation::from(self.clone())] + [$Annotation::$Variant(*self)] } } - impl crate::annotations::IntoAnnotations for Box<$T> { - type IntoAnnotations = [crate::annotations::Annotation; 1]; + impl IntoAnnotations for Box<$T> { + type IntoAnnotations = [$Annotation; 1]; fn into_annotations(self) -> Self::IntoAnnotations { - [crate::annotations::Annotation::from(*self)] + [$Annotation::$Variant(*self)] } })* }; - (@require_non_exhaustive non_exhaustive) => {}; } -pub(crate) use make_annotation_enum; - make_annotation_enum! { + #[derive(Clone, PartialEq, Eq, Hash, Debug)] #[non_exhaustive] pub enum Annotation { DontTouch(DontTouchAnnotation), @@ -220,7 +199,8 @@ make_annotation_enum! { BlackBoxPath(BlackBoxPathAnnotation), DocString(DocStringAnnotation), CustomFirrtl(CustomFirrtlAnnotation), - Xilinx(crate::vendor::xilinx::XilinxAnnotation), + XdcLocation(crate::build::vendor::xilinx::XdcLocationAnnotation), + XdcIOStandard(crate::build::vendor::xilinx::XdcIOStandardAnnotation), } } diff --git a/crates/fayalite/src/build.rs b/crates/fayalite/src/build.rs index a9e9635c..96492cf9 100644 --- a/crates/fayalite/src/build.rs +++ b/crates/fayalite/src/build.rs @@ -4,11 +4,9 @@ use crate::{ build::graph::JobGraph, bundle::{Bundle, BundleType}, - intern::{Intern, InternSlice, Interned}, + intern::{Intern, Interned}, module::Module, - platform::{DynPlatform, Platform}, - util::{job_server::AcquiredJob, os_str_strip_prefix}, - vendor, + util::job_server::AcquiredJob, }; use clap::ArgAction; use serde::{ @@ -20,11 +18,9 @@ use std::{ any::{Any, TypeId}, borrow::Cow, cmp::Ordering, - ffi::{OsStr, OsString}, + ffi::OsStr, fmt, hash::{Hash, Hasher}, - io::Write, - marker::PhantomData, path::{Path, PathBuf}, sync::{Arc, OnceLock}, }; @@ -35,25 +31,61 @@ pub mod firrtl; pub mod formal; pub mod graph; pub mod registry; +pub mod vendor; pub mod verilog; pub(crate) fn built_in_job_kinds() -> impl IntoIterator { - [DynJobKind::new(BaseJobKind)] - .into_iter() - .chain(firrtl::built_in_job_kinds()) - .chain(formal::built_in_job_kinds()) - .chain(vendor::built_in_job_kinds()) - .chain(verilog::built_in_job_kinds()) + [ + DynJobKind::new(BaseJobKind), + DynJobKind::new(CreateOutputDirJobKind), + ] + .into_iter() + .chain(firrtl::built_in_job_kinds()) + .chain(formal::built_in_job_kinds()) + .chain(vendor::built_in_job_kinds()) + .chain(verilog::built_in_job_kinds()) +} + +#[track_caller] +fn intern_known_utf8_path_buf(v: PathBuf) -> Interned { + let Ok(v) = v.into_os_string().into_string().map(str::intern_owned) else { + unreachable!("known to be valid UTF-8"); + }; + v +} + +#[track_caller] +fn intern_known_utf8_str(v: impl AsRef) -> Interned { + let Some(v) = v.as_ref().to_str().map(str::intern) else { + unreachable!("known to be valid UTF-8"); + }; + v +} + +#[track_caller] +fn interned_known_utf8_method &OsStr>( + v: impl AsRef, + f: F, +) -> Interned { + intern_known_utf8_str(f(v.as_ref().as_ref())) +} + +#[track_caller] +fn interned_known_utf8_path_buf_method PathBuf>( + v: impl AsRef, + f: F, +) -> Interned { + intern_known_utf8_path_buf(f(v.as_ref().as_ref())) } #[derive(Clone, Hash, PartialEq, Eq, Debug)] #[non_exhaustive] pub enum JobItem { Path { - path: Interned, + path: Interned, }, DynamicPaths { - paths: Vec>, + paths: Vec>, source_job_name: Interned, }, } @@ -73,7 +105,7 @@ impl JobItem { #[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Serialize, Deserialize)] #[non_exhaustive] pub enum JobItemName { - Path { path: Interned }, + Path { path: Interned }, DynamicPaths { source_job_name: Interned }, } @@ -90,7 +122,7 @@ impl JobItemName { #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] enum JobItemNameRef<'a> { - Path { path: &'a Path }, + Path { path: &'a str }, DynamicPaths { source_job_name: &'a str }, } @@ -114,191 +146,119 @@ impl Ord for JobItemName { pub trait WriteArgs: for<'a> Extend<&'a str> - + for<'a> Extend<&'a OsStr> - + for<'a> Extend<&'a Path> - + for<'a> Extend> - + for<'a> Extend> - + for<'a> Extend> + Extend - + Extend - + Extend + Extend> - + Extend> - + Extend> + + for<'a> Extend> { - fn write_display_args(&mut self, args: impl IntoIterator) { - self.extend(args.into_iter().map(|v| v.to_string())); + fn write_args(&mut self, args: impl IntoIterator) { + self.extend(args.into_iter().map(|v| format!("{v}"))); } - fn write_owned_args(&mut self, args: impl IntoIterator>) { - self.extend(args.into_iter().map(Into::::into)) + fn write_string_args(&mut self, args: impl IntoIterator) { + self.extend(args); } - fn write_args<'a>(&mut self, args: impl IntoIterator>); - fn write_interned_args(&mut self, args: impl IntoIterator>>) { - self.extend(args.into_iter().map(Into::>::into)) + fn write_str_args<'a>(&mut self, args: impl IntoIterator) { + self.extend(args); } - fn write_display_arg(&mut self, arg: impl fmt::Display) { - self.write_display_args([arg]); + fn write_interned_args(&mut self, args: impl IntoIterator>) { + self.extend(args); } - fn write_owned_arg(&mut self, arg: impl Into) { - self.extend([arg.into()]); + fn write_arg(&mut self, arg: impl fmt::Display) { + self.extend([format_args!("{arg}")]); } - fn write_arg(&mut self, arg: impl AsRef) { - self.extend([arg.as_ref()]); + fn write_string_arg(&mut self, arg: String) { + self.extend([arg]); } - /// writes `--{name}={value}` - fn write_long_option_eq(&mut self, name: impl AsRef, value: impl AsRef) { - let name = name.as_ref(); - let value = value.as_ref(); - let mut option = - OsString::with_capacity(name.len().saturating_add(value.len()).saturating_add(3)); - option.push("--"); - option.push(name); - option.push("="); - option.push(value); - self.write_owned_arg(option); + fn write_str_arg(&mut self, arg: &str) { + self.extend([arg]); } - fn write_interned_arg(&mut self, arg: impl Into>) { - self.extend([arg.into()]); + fn write_interned_arg(&mut self, arg: Interned) { + self.extend([arg]); } /// finds the first option that is `--{option_name}={value}` and returns `value` - fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&OsStr>; + fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&str>; } -pub trait ArgsWriterArg: - AsRef - + From> - + for<'a> From> - + for<'a> From<&'a OsStr> - + From -{ -} +pub struct ArgsWriter(pub Vec); -impl ArgsWriterArg for Interned {} - -impl ArgsWriterArg for OsString {} - -pub struct ArgsWriter(pub Vec); - -impl Default for ArgsWriter { +impl Default for ArgsWriter { fn default() -> Self { Self(Default::default()) } } -impl ArgsWriter { - fn get_long_option_eq_helper(&self, option_name: &str) -> Option<&OsStr> { +impl> ArgsWriter { + fn get_long_option_eq_helper(&self, option_name: &str) -> Option<&str> { self.0.iter().find_map(|arg| { - os_str_strip_prefix(arg.as_ref(), "--") - .and_then(|arg| os_str_strip_prefix(arg, option_name)) - .and_then(|arg| os_str_strip_prefix(arg, "=")) + arg.as_ref() + .strip_prefix("--") + .and_then(|arg| arg.strip_prefix(option_name)) + .and_then(|arg| arg.strip_prefix("=")) }) } } -impl<'a, A: ArgsWriterArg> Extend<&'a str> for ArgsWriter { +impl<'a, W> Extend> for ArgsWriter +where + Self: Extend, +{ + fn extend>>(&mut self, iter: T) { + self.extend(iter.into_iter().map(|v| v.to_string())) + } +} + +impl<'a> Extend<&'a str> for ArgsWriter> { fn extend>(&mut self, iter: T) { - self.extend(iter.into_iter().map(AsRef::::as_ref)) + self.extend(iter.into_iter().map(str::intern)) } } -impl<'a, A: ArgsWriterArg> Extend<&'a OsStr> for ArgsWriter { - fn extend>(&mut self, iter: T) { - self.0.extend(iter.into_iter().map(Into::into)) - } -} - -impl<'a, A: ArgsWriterArg> Extend<&'a Path> for ArgsWriter { - fn extend>(&mut self, iter: T) { - self.extend(iter.into_iter().map(AsRef::::as_ref)) - } -} - -impl Extend for ArgsWriter { +impl Extend for ArgsWriter> { fn extend>(&mut self, iter: T) { - self.extend(iter.into_iter().map(OsString::from)) + self.extend(iter.into_iter().map(str::intern_owned)) } } -impl Extend for ArgsWriter { - fn extend>(&mut self, iter: T) { - self.0.extend(iter.into_iter().map(Into::into)) - } -} - -impl Extend for ArgsWriter { - fn extend>(&mut self, iter: T) { - self.extend(iter.into_iter().map(OsString::from)) - } -} - -impl Extend> for ArgsWriter { +impl Extend> for ArgsWriter { fn extend>>(&mut self, iter: T) { - self.extend(iter.into_iter().map(Interned::::from)) + self.extend(iter.into_iter().map(String::from)) } } -impl Extend> for ArgsWriter { - fn extend>>(&mut self, iter: T) { - self.0.extend(iter.into_iter().map(Into::into)) +impl<'a> Extend<&'a str> for ArgsWriter { + fn extend>(&mut self, iter: T) { + self.extend(iter.into_iter().map(String::from)) } } -impl Extend> for ArgsWriter { - fn extend>>(&mut self, iter: T) { - self.extend(iter.into_iter().map(Interned::::from)) +impl Extend for ArgsWriter { + fn extend>(&mut self, iter: T) { + self.0.extend(iter); } } -impl<'a, A: ArgsWriterArg> Extend> for ArgsWriter { - fn extend>>(&mut self, iter: T) { - self.0.extend(iter.into_iter().map(|v| { - match v { - Cow::Borrowed(v) => Cow::::Borrowed(v.as_ref()), - Cow::Owned(v) => Cow::Owned(v.into()), - } - .into() - })) +impl WriteArgs for ArgsWriter { + fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&str> { + self.get_long_option_eq_helper(option_name.as_ref()) } } -impl<'a, A: ArgsWriterArg> Extend> for ArgsWriter { - fn extend>>(&mut self, iter: T) { - self.0.extend(iter.into_iter().map(Into::into)) - } -} - -impl<'a, A: ArgsWriterArg> Extend> for ArgsWriter { - fn extend>>(&mut self, iter: T) { - self.0.extend(iter.into_iter().map(|v| { - match v { - Cow::Borrowed(v) => Cow::::Borrowed(v.as_ref()), - Cow::Owned(v) => Cow::Owned(v.into()), - } - .into() - })) - } -} - -impl WriteArgs for ArgsWriter { - fn write_args<'a>(&mut self, args: impl IntoIterator>) { - self.0.extend(args.into_iter().map(|v| v.as_ref().into())) - } - fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&OsStr> { +impl WriteArgs for ArgsWriter> { + fn get_long_option_eq(&self, option_name: impl AsRef) -> Option<&str> { self.get_long_option_eq_helper(option_name.as_ref()) } } pub trait ToArgs: clap::Args + 'static + Send + Sync + Hash + Eq + fmt::Debug + Clone { fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)); - fn to_interned_args(&self) -> Interned<[Interned]> { + fn to_interned_args(&self) -> Interned<[Interned]> { Intern::intern_owned(self.to_interned_args_vec()) } - fn to_interned_args_vec(&self) -> Vec> { + fn to_interned_args_vec(&self) -> Vec> { let mut retval = ArgsWriter::default(); self.to_args(&mut retval); retval.0 } - fn to_os_string_args(&self) -> Vec { + fn to_string_args(&self) -> Vec { let mut retval = ArgsWriter::default(); self.to_args(&mut retval); retval.0 @@ -316,7 +276,6 @@ impl JobKindAndArgs { self, dependencies: ::KindsAndArgs, params: &JobParams, - global_params: &GlobalParams, ) -> eyre::Result> { K::args_to_jobs( JobArgsAndDependencies { @@ -324,7 +283,6 @@ impl JobKindAndArgs { dependencies, }, params, - global_params, ) } } @@ -403,18 +361,6 @@ pub struct JobAndDependencies { pub dependencies: ::JobsAndKinds, } -impl JobAndDependencies { - pub fn get_job(&self) -> &J - where - Self: GetJob, - { - GetJob::get_job(self) - } - pub fn base_job(&self) -> &BaseJob { - self.job.kind.base_job(&self.job.job, &self.dependencies) - } -} - impl Clone for JobAndDependencies where K::Job: Clone, @@ -449,17 +395,8 @@ where } impl JobArgsAndDependencies { - pub fn args_to_jobs( - self, - params: &JobParams, - global_params: &GlobalParams, - ) -> eyre::Result> { - K::args_to_jobs(self, params, global_params) - } - pub fn base_job_args(&self) -> &BaseJobArgs { - self.args - .kind - .base_job_args(&self.args.args, &self.dependencies) + pub fn args_to_jobs(self, params: &JobParams) -> eyre::Result> { + K::args_to_jobs(self, params) } } @@ -467,7 +404,6 @@ impl>, D: JobKind> JobArgsAn pub fn args_to_jobs_simple( self, params: &JobParams, - global_params: &GlobalParams, f: F, ) -> eyre::Result> where @@ -477,7 +413,7 @@ impl>, D: JobKind> JobArgsAn args: JobKindAndArgs { kind, args }, dependencies, } = self; - let mut dependencies = dependencies.args_to_jobs(params, global_params)?; + let mut dependencies = dependencies.args_to_jobs(params)?; let job = f(kind, args, &mut dependencies)?; Ok(JobAndDependencies { job: JobAndKind { kind, job }, @@ -492,7 +428,6 @@ impl>, D: pub fn args_to_jobs_external_simple( self, params: &JobParams, - global_params: &GlobalParams, f: F, ) -> eyre::Result<( C::AdditionalJobData, @@ -508,7 +443,7 @@ impl>, D: args: JobKindAndArgs { kind: _, args }, dependencies, } = self; - let mut dependencies = dependencies.args_to_jobs(params, global_params)?; + let mut dependencies = dependencies.args_to_jobs(params)?; let additional_job_data = f(args, &mut dependencies)?; Ok((additional_job_data, dependencies)) } @@ -544,15 +479,6 @@ pub trait JobDependencies: 'static + Send + Sync + Hash + Eq + fmt::Debug + Copy } } -pub trait JobDependenciesHasBase: JobDependencies { - fn base_job_args(args: &Self::KindsAndArgs) -> &BaseJobArgs; - fn base_job(jobs: &Self::JobsAndKinds) -> &BaseJob; - #[track_caller] - fn base_job_args_dyn(dependencies_args: &[DynJobArgs]) -> &BaseJobArgs; - #[track_caller] - fn base_job_dyn(dependencies: &[DynJob]) -> &BaseJob; -} - impl JobDependencies for JobKindAndDependencies { type KindsAndArgs = JobArgsAndDependencies; type JobsAndKinds = JobAndDependencies; @@ -592,44 +518,6 @@ impl JobDependencies for JobKindAndDependencies { } } -impl JobDependenciesHasBase for JobKindAndDependencies { - fn base_job_args(args: &Self::KindsAndArgs) -> &BaseJobArgs { - args.base_job_args() - } - - fn base_job(jobs: &Self::JobsAndKinds) -> &BaseJob { - jobs.base_job() - } - - #[track_caller] - fn base_job_args_dyn(dependencies_args: &[DynJobArgs]) -> &BaseJobArgs { - let [dependencies_args @ .., args] = dependencies_args else { - panic!("wrong number of dependencies"); - }; - let Some((kind, args)) = args.downcast_ref::() else { - panic!( - "wrong type of dependency, expected {} got:\n{args:?}", - std::any::type_name::() - ) - }; - kind.base_job_args_dyn(args, dependencies_args) - } - - #[track_caller] - fn base_job_dyn(dependencies: &[DynJob]) -> &BaseJob { - let [dependencies @ .., job] = dependencies else { - panic!("wrong number of dependencies"); - }; - let Some((kind, job)) = job.downcast_ref::() else { - panic!( - "wrong type of dependency, expected {} got:\n{job:?}", - std::any::type_name::() - ) - }; - kind.base_job_dyn(job, dependencies) - } -} - macro_rules! impl_job_dependencies { (@impl $(($v:ident: $T:ident),)*) => { impl<$($T: JobDependencies),*> JobDependencies for ($($T,)*) { @@ -685,6 +573,7 @@ impl_job_dependencies! { #[derive(Clone, Debug, PartialEq, Eq, Hash)] pub struct JobParams { main_module: Module, + application_name: Interned, } impl AsRef for JobParams { @@ -694,65 +583,24 @@ impl AsRef for JobParams { } impl JobParams { - pub fn new_canonical(main_module: Module) -> Self { - Self { main_module } + pub fn new_canonical(main_module: Module, application_name: Interned) -> Self { + Self { + main_module, + application_name, + } } - pub fn new(main_module: impl AsRef>) -> Self { - Self::new_canonical(main_module.as_ref().canonical()) + pub fn new( + main_module: impl AsRef>, + application_name: impl AsRef, + ) -> Self { + Self::new_canonical( + main_module.as_ref().canonical(), + application_name.as_ref().intern(), + ) } pub fn main_module(&self) -> &Module { &self.main_module } -} - -#[derive(Clone, Debug)] -pub struct GlobalParams { - top_level_cmd: Option, - application_name: Interned, -} - -impl AsRef for GlobalParams { - fn as_ref(&self) -> &Self { - self - } -} - -impl GlobalParams { - pub fn new(top_level_cmd: Option, application_name: impl AsRef) -> Self { - Self { - top_level_cmd, - application_name: application_name.as_ref().intern(), - } - } - pub fn top_level_cmd(&self) -> Option<&clap::Command> { - self.top_level_cmd.as_ref() - } - pub fn into_top_level_cmd(self) -> Option { - self.top_level_cmd - } - pub fn extract_clap_error(&self, e: eyre::Report) -> eyre::Result { - let e = e.downcast::()?; - Ok(match &self.top_level_cmd { - Some(cmd) => e.with_cmd(cmd), - None => e, - }) - } - pub fn exit_if_clap_error(&self, e: eyre::Report) -> eyre::Report { - match self.extract_clap_error(e) { - Ok(e) => e.exit(), - Err(e) => e, - } - } - pub fn clap_error( - &self, - kind: clap::error::ErrorKind, - message: impl fmt::Display, - ) -> clap::Error { - match self.top_level_cmd.clone() { - Some(top_level_cmd) => top_level_cmd.clone().error(kind, message), - None => clap::Error::raw(kind, message), - } - } pub fn application_name(&self) -> Interned { self.application_name } @@ -760,116 +608,47 @@ impl GlobalParams { #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct CommandParams { - pub command_line: Interned<[Interned]>, - pub current_dir: Option>, + pub command_line: Interned<[Interned]>, + pub current_dir: Option>, } impl CommandParams { - fn to_unix_shell_line( + fn to_unix_shell_line( self, - output: &mut String, - mut escape_arg: impl FnMut(&OsStr, &mut String) -> Result<(), E>, - ) -> Result<(), E> { + output: &mut W, + mut escape_arg: impl FnMut(&str, &mut W) -> fmt::Result, + ) -> fmt::Result { let Self { command_line, current_dir, } = self; let mut end = None; let mut separator = if let Some(current_dir) = current_dir { - output.push_str("(cd "); + output.write_str("(cd ")?; end = Some(")"); - if !current_dir - .as_os_str() - .as_encoded_bytes() - .first() - .is_some_and(|ch| ch.is_ascii_alphanumeric() || matches!(ch, b'/' | b'\\' | b'.')) - { - output.push_str("-- "); + if !current_dir.starts_with(|ch: char| { + ch.is_ascii_alphanumeric() || matches!(ch, '/' | '\\' | '.') + }) { + output.write_str("-- ")?; } - escape_arg(current_dir.as_ref(), output)?; + escape_arg(¤t_dir, output)?; "; exec -- " } else { "" }; for arg in command_line { - output.push_str(separator); + output.write_str(separator)?; separator = " "; escape_arg(&arg, output)?; } if let Some(end) = end { - output.push_str(end); + output.write_str(end)?; } Ok(()) } } -pub trait JobKindHelper: 'static + Send + Sync + Hash + Eq + fmt::Debug + Copy { - fn base_job_args<'a>( - self, - args: &'a ::Args, - dependencies: &'a <::Dependencies as JobDependencies>::KindsAndArgs, - ) -> &'a BaseJobArgs - where - Self: JobKind; - fn base_job<'a>( - self, - job: &'a ::Job, - dependencies: &'a <::Dependencies as JobDependencies>::JobsAndKinds, - ) -> &'a BaseJob - where - Self: JobKind; - #[track_caller] - fn base_job_args_dyn<'a>( - self, - args: &'a ::Args, - dependencies_args: &'a [DynJobArgs], - ) -> &'a BaseJobArgs - where - Self: JobKind; - #[track_caller] - fn base_job_dyn<'a>( - self, - job: &'a ::Job, - dependencies: &'a [DynJob], - ) -> &'a BaseJob - where - Self: JobKind; -} - -impl> JobKindHelper for K { - fn base_job_args<'a>( - self, - _args: &'a ::Args, - dependencies: &'a <::Dependencies as JobDependencies>::KindsAndArgs, - ) -> &'a BaseJobArgs { - K::Dependencies::base_job_args(dependencies) - } - fn base_job<'a>( - self, - _job: &'a ::Job, - dependencies: &'a <::Dependencies as JobDependencies>::JobsAndKinds, - ) -> &'a BaseJob { - K::Dependencies::base_job(dependencies) - } - #[track_caller] - fn base_job_args_dyn<'a>( - self, - _args: &'a ::Args, - dependencies_args: &'a [DynJobArgs], - ) -> &'a BaseJobArgs { - K::Dependencies::base_job_args_dyn(dependencies_args) - } - #[track_caller] - fn base_job_dyn<'a>( - self, - _job: &'a ::Job, - dependencies: &'a [DynJob], - ) -> &'a BaseJob { - K::Dependencies::base_job_dyn(dependencies) - } -} - -pub trait JobKind: JobKindHelper { +pub trait JobKind: 'static + Send + Sync + Hash + Eq + fmt::Debug + Sized + Copy { type Args: ToArgs; type Job: 'static + Send + Sync + Hash + Eq + fmt::Debug + Serialize + DeserializeOwned; type Dependencies: JobDependencies; @@ -877,7 +656,6 @@ pub trait JobKind: JobKindHelper { fn args_to_jobs( args: JobArgsAndDependencies, params: &JobParams, - global_params: &GlobalParams, ) -> eyre::Result>; fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]>; fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]>; @@ -888,7 +666,6 @@ pub trait JobKind: JobKindHelper { job: &Self::Job, inputs: &[JobItem], params: &JobParams, - global_params: &GlobalParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result>; fn subcommand_hidden(self) -> bool { @@ -921,7 +698,7 @@ trait DynJobKindTrait: 'static + Send + Sync + fmt::Debug { ) -> serde_json::Result; } -impl DynJobKindTrait for K { +impl DynJobKindTrait for T { fn as_any(&self) -> &dyn Any { self } @@ -946,15 +723,15 @@ impl DynJobKindTrait for K { } fn args_group_id_dyn(&self) -> Option { - ::group_id() + ::group_id() } fn augment_args_dyn(&self, cmd: clap::Command) -> clap::Command { - ::augment_args(cmd) + ::augment_args(cmd) } fn augment_args_for_update_dyn(&self, cmd: clap::Command) -> clap::Command { - ::augment_args_for_update(cmd) + ::augment_args_for_update(cmd) } fn from_arg_matches_dyn( @@ -963,7 +740,7 @@ impl DynJobKindTrait for K { ) -> clap::error::Result { Ok(DynJobArgs::new( *self, - ::from_arg_matches_mut(matches)?, + ::from_arg_matches_mut(matches)?, )) } @@ -991,21 +768,21 @@ impl DynJobKindTrait for K { pub struct DynJobKind(Arc); impl DynJobKind { - pub fn from_arc(job_kind: Arc) -> Self { + pub fn from_arc(job_kind: Arc) -> Self { Self(job_kind) } - pub fn new(job_kind: K) -> Self { + pub fn new(job_kind: T) -> Self { Self(Arc::new(job_kind)) } pub fn type_id(&self) -> TypeId { DynJobKindTrait::as_any(&*self.0).type_id() } - pub fn downcast(&self) -> Option { + pub fn downcast(&self) -> Option { DynJobKindTrait::as_any(&*self.0).downcast_ref().copied() } - pub fn downcast_arc(self) -> Result, Self> { - if self.downcast::().is_some() { - Ok(Arc::downcast::(self.0.as_arc_any()) + pub fn downcast_arc(self) -> Result, Self> { + if self.downcast::().is_some() { + Ok(Arc::downcast::(self.0.as_arc_any()) .ok() .expect("already checked type")) } else { @@ -1222,7 +999,7 @@ trait DynJobArgsTrait: 'static + Send + Sync + fmt::Debug { fn eq_dyn(&self, other: &dyn DynJobArgsTrait) -> bool; fn hash_dyn(&self, state: &mut dyn Hasher); fn kind(&self) -> DynJobKind; - fn to_args_extend_vec(&self, args: Vec>) -> Vec>; + fn to_args_extend_vec(&self, args: Vec>) -> Vec>; fn clone_into_arc(&self) -> Arc; fn update_from_arg_matches( &mut self, @@ -1233,10 +1010,7 @@ trait DynJobArgsTrait: 'static + Send + Sync + fmt::Debug { self: Arc, dependencies_args: Vec, params: &JobParams, - global_params: &GlobalParams, ) -> eyre::Result<(DynJob, Vec)>; - #[track_caller] - fn base_job_args_dyn<'a>(&'a self, dependencies_args: &'a [DynJobArgs]) -> &'a BaseJobArgs; } impl DynJobArgsTrait for DynJobArgsInner { @@ -1267,7 +1041,7 @@ impl DynJobArgsTrait for DynJobArgsInner { DynJobKind::new(self.0.kind) } - fn to_args_extend_vec(&self, args: Vec>) -> Vec> { + fn to_args_extend_vec(&self, args: Vec>) -> Vec> { let mut writer = ArgsWriter(args); self.0.args.to_args(&mut writer); writer.0 @@ -1289,22 +1063,14 @@ impl DynJobArgsTrait for DynJobArgsInner { self: Arc, dependencies_args: Vec, params: &JobParams, - global_params: &GlobalParams, ) -> eyre::Result<(DynJob, Vec)> { let JobAndDependencies { job, dependencies } = JobArgsAndDependencies { args: Arc::unwrap_or_clone(self).0, dependencies: K::Dependencies::from_dyn_args(dependencies_args), } - .args_to_jobs(params, global_params)?; + .args_to_jobs(params)?; Ok((job.into(), K::Dependencies::into_dyn_jobs(dependencies))) } - - #[track_caller] - fn base_job_args_dyn<'a>(&'a self, dependencies_args: &'a [DynJobArgs]) -> &'a BaseJobArgs { - self.0 - .kind - .base_job_args_dyn(&self.0.args, dependencies_args) - } } #[derive(Clone)] @@ -1335,10 +1101,10 @@ impl DynJobArgs { pub fn kind(&self) -> DynJobKind { DynJobArgsTrait::kind(&*self.0) } - pub fn to_args_vec(&self) -> Vec> { + pub fn to_args_vec(&self) -> Vec> { self.to_args_extend_vec(Vec::new()) } - pub fn to_args_extend_vec(&self, args: Vec>) -> Vec> { + pub fn to_args_extend_vec(&self, args: Vec>) -> Vec> { DynJobArgsTrait::to_args_extend_vec(&*self.0, args) } fn make_mut(&mut self) -> &mut dyn DynJobArgsTrait { @@ -1359,13 +1125,8 @@ impl DynJobArgs { self, dependencies_args: Vec, params: &JobParams, - global_params: &GlobalParams, ) -> eyre::Result<(DynJob, Vec)> { - DynJobArgsTrait::args_to_jobs(self.0, dependencies_args, params, global_params) - } - #[track_caller] - pub fn base_job_args_dyn<'a>(&'a self, dependencies_args: &'a [DynJobArgs]) -> &'a BaseJobArgs { - DynJobArgsTrait::base_job_args_dyn(&*self.0, dependencies_args) + DynJobArgsTrait::args_to_jobs(self.0, dependencies_args, params) } } @@ -1391,15 +1152,15 @@ impl fmt::Debug for DynJobArgs { } #[derive(PartialEq, Eq, Hash)] -struct DynJobInner { - kind: Arc, - job: K::Job, +struct DynJobInner { + kind: Arc, + job: T::Job, inputs: Interned<[JobItemName]>, outputs: Interned<[JobItemName]>, external_command_params: Option, } -impl> Clone for DynJobInner { +impl> Clone for DynJobInner { fn clone(&self) -> Self { Self { kind: self.kind.clone(), @@ -1411,7 +1172,7 @@ impl> Clone for DynJobInner { } } -impl fmt::Debug for DynJobInner { +impl fmt::Debug for DynJobInner { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let Self { kind, @@ -1446,14 +1207,11 @@ trait DynJobTrait: 'static + Send + Sync + fmt::Debug { &self, inputs: &[JobItem], params: &JobParams, - global_params: &GlobalParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result>; - #[track_caller] - fn base_job_dyn<'a>(&'a self, dependencies: &'a [DynJob]) -> &'a BaseJob; } -impl DynJobTrait for DynJobInner { +impl DynJobTrait for DynJobInner { fn as_any(&self) -> &dyn Any { self } @@ -1474,7 +1232,7 @@ impl DynJobTrait for DynJobInner { } fn kind_type_id(&self) -> TypeId { - TypeId::of::() + TypeId::of::() } fn kind(&self) -> DynJobKind { @@ -1505,16 +1263,9 @@ impl DynJobTrait for DynJobInner { &self, inputs: &[JobItem], params: &JobParams, - global_params: &GlobalParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result> { - self.kind - .run(&self.job, inputs, params, global_params, acquired_job) - } - - #[track_caller] - fn base_job_dyn<'a>(&'a self, dependencies: &'a [DynJob]) -> &'a BaseJob { - self.kind.base_job_dyn(&self.job, dependencies) + self.kind.run(&self.job, inputs, params, acquired_job) } } @@ -1522,7 +1273,7 @@ impl DynJobTrait for DynJobInner { pub struct DynJob(Arc); impl DynJob { - pub fn from_arc(job_kind: Arc, job: K::Job) -> Self { + pub fn from_arc(job_kind: Arc, job: T::Job) -> Self { let inputs = job_kind.inputs(&job); let outputs = job_kind.outputs(&job); let external_command_params = job_kind.external_command_params(&job); @@ -1534,7 +1285,7 @@ impl DynJob { external_command_params, })) } - pub fn new(job_kind: K, job: K::Job) -> Self { + pub fn new(job_kind: T, job: T::Job) -> Self { Self::from_arc(Arc::new(job_kind), job) } pub fn kind_type_id(&self) -> TypeId { @@ -1578,13 +1329,11 @@ impl DynJob { #[track_caller] pub fn internal_command_params_with_program_prefix( &self, - internal_program_prefix: &[Interned], - platform: Option<&DynPlatform>, - extra_args: &[Interned], + internal_program_prefix: &[Interned], + extra_args: &[Interned], ) -> CommandParams { let mut command_line = internal_program_prefix.to_vec(); - let command_line = match RunSingleJob::try_add_subcommand(platform, self, &mut command_line) - { + let command_line = match RunSingleJob::try_add_subcommand(self, &mut command_line) { Ok(()) => { command_line.extend_from_slice(extra_args); Intern::intern_owned(command_line) @@ -1597,42 +1346,28 @@ impl DynJob { } } #[track_caller] - pub fn internal_command_params( - &self, - platform: Option<&DynPlatform>, - extra_args: &[Interned], - ) -> CommandParams { + pub fn internal_command_params(&self, extra_args: &[Interned]) -> CommandParams { self.internal_command_params_with_program_prefix( &[program_name_for_internal_jobs()], - platform, extra_args, ) } #[track_caller] pub fn command_params_with_internal_program_prefix( &self, - internal_program_prefix: &[Interned], - platform: Option<&DynPlatform>, - extra_args: &[Interned], + internal_program_prefix: &[Interned], + extra_args: &[Interned], ) -> CommandParams { match self.external_command_params() { Some(v) => v, - None => self.internal_command_params_with_program_prefix( - internal_program_prefix, - platform, - extra_args, - ), + None => self + .internal_command_params_with_program_prefix(internal_program_prefix, extra_args), } } #[track_caller] - pub fn command_params( - &self, - platform: Option<&DynPlatform>, - extra_args: &[Interned], - ) -> CommandParams { + pub fn command_params(&self, extra_args: &[Interned]) -> CommandParams { self.command_params_with_internal_program_prefix( &[program_name_for_internal_jobs()], - platform, extra_args, ) } @@ -1640,14 +1375,9 @@ impl DynJob { &self, inputs: &[JobItem], params: &JobParams, - global_params: &GlobalParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result> { - DynJobTrait::run(&*self.0, inputs, params, global_params, acquired_job) - } - #[track_caller] - pub fn base_job_dyn<'a>(&'a self, dependencies: &'a [DynJob]) -> &'a BaseJob { - DynJobTrait::base_job_dyn(&*self.0, dependencies) + DynJobTrait::run(&*self.0, inputs, params, acquired_job) } } @@ -1697,172 +1427,61 @@ impl<'de> Deserialize<'de> for DynJob { } pub trait RunBuild: Sized { - fn main_without_platform(application_name: impl AsRef, make_params: F) + fn main(make_params: F) where Self: clap::Parser + Clone, F: FnOnce(Self, Extra) -> eyre::Result, { - let application_name = application_name.as_ref(); - match Self::try_main_without_platform(application_name, make_params) { + match Self::try_main(make_params) { Ok(()) => {} Err(e) => { - let e = GlobalParams::new(Some(Self::command()), application_name) - .exit_if_clap_error(e); eprintln!("{e:#}"); std::process::exit(1); } } } - fn try_main_without_platform( - application_name: impl AsRef, - make_params: F, - ) -> eyre::Result<()> + fn try_main(make_params: F) -> eyre::Result<()> where Self: clap::Parser + Clone, F: FnOnce(Self, Extra) -> eyre::Result, { let args = Self::parse(); - let global_params = GlobalParams::new(Some(Self::command()), application_name); args.clone() - .run_without_platform(|extra| make_params(args, extra), &global_params) - .map_err(|e| global_params.exit_if_clap_error(e)) + .run(|extra| make_params(args, extra), Self::command()) } - fn run_without_platform( - self, - make_params: F, - global_params: &GlobalParams, - ) -> eyre::Result<()> + fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> where F: FnOnce(Extra) -> eyre::Result; - fn get_platform(&self) -> Option<&DynPlatform>; - fn main(application_name: impl AsRef, make_params: F) - where - Self: clap::Parser + Clone, - F: FnOnce(Self, DynPlatform, Extra) -> eyre::Result, - { - let application_name = application_name.as_ref(); - match Self::try_main(application_name, make_params) { - Ok(()) => {} - Err(e) => { - let e = GlobalParams::new(Some(Self::command()), application_name) - .exit_if_clap_error(e); - eprintln!("{e:#}"); - std::process::exit(1); - } - } - } - fn try_main(application_name: impl AsRef, make_params: F) -> eyre::Result<()> - where - Self: clap::Parser + Clone, - F: FnOnce(Self, DynPlatform, Extra) -> eyre::Result, - { - let args = Self::parse(); - let global_params = GlobalParams::new(Some(Self::command()), application_name); - let Some(platform) = args.get_platform().cloned() else { - return args.handle_missing_platform(&global_params); - }; - args.clone() - .run( - |platform, extra| make_params(args, platform, extra), - platform, - &global_params, - ) - .map_err(|e| global_params.exit_if_clap_error(e)) - } - fn handle_missing_platform(self, global_params: &GlobalParams) -> eyre::Result<()> { - global_params - .clap_error( - clap::error::ErrorKind::MissingRequiredArgument, - "--platform is required", - ) - .exit(); - } - fn run( - self, - make_params: F, - platform: DynPlatform, - global_params: &GlobalParams, - ) -> eyre::Result<()> - where - F: FnOnce(DynPlatform, Extra) -> eyre::Result, - { - self.run_without_platform(|extra| make_params(platform, extra), global_params) - } } impl RunBuild for JobArgsAndDependencies { - fn run_without_platform( - self, - make_params: F, - global_params: &GlobalParams, - ) -> eyre::Result<()> + fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> where F: FnOnce(NoArgs) -> eyre::Result, { let params = make_params(NoArgs)?; - self.args_to_jobs(¶ms, global_params)? - .run_without_platform(|_| Ok(params), global_params) - } - fn get_platform(&self) -> Option<&DynPlatform> { - self.base_job_args().platform.as_ref() - } - fn run( - self, - make_params: F, - platform: DynPlatform, - global_params: &GlobalParams, - ) -> eyre::Result<()> - where - F: FnOnce(DynPlatform, NoArgs) -> eyre::Result, - { - let params = make_params(platform.clone(), NoArgs)?; - self.args_to_jobs(¶ms, global_params)? - .run(|_, _| Ok(params), platform, global_params) + self.args_to_jobs(¶ms)?.run(|_| Ok(params), cmd) } } impl RunBuild for JobAndDependencies { - fn run_without_platform( - self, - make_params: F, - global_params: &GlobalParams, - ) -> eyre::Result<()> + fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> where F: FnOnce(NoArgs) -> eyre::Result, { + let _ = cmd; let params = make_params(NoArgs)?; let Self { job, dependencies } = self; let mut jobs = vec![DynJob::from(job)]; K::Dependencies::into_dyn_jobs_extend(dependencies, &mut jobs); let mut job_graph = JobGraph::new(); job_graph.add_jobs(jobs); // add all at once to avoid recomputing graph properties multiple times - job_graph.run(¶ms, global_params) - } - fn get_platform(&self) -> Option<&DynPlatform> { - self.base_job().platform() - } - fn run( - self, - make_params: F, - platform: DynPlatform, - global_params: &GlobalParams, - ) -> eyre::Result<()> - where - F: FnOnce(DynPlatform, NoArgs) -> eyre::Result, - { - let params = make_params(platform, NoArgs)?; - let Self { job, dependencies } = self; - let mut jobs = vec![DynJob::from(job)]; - K::Dependencies::into_dyn_jobs_extend(dependencies, &mut jobs); - let mut job_graph = JobGraph::new(); - job_graph.add_jobs(jobs); // add all at once to avoid recomputing graph properties multiple times - job_graph.run(¶ms, global_params) + job_graph.run(¶ms) } } #[derive(Clone, PartialEq, Eq, Hash, Debug)] pub struct RunSingleJob { - pub platform: Option, pub job: DynJob, pub extra: Extra, } @@ -1870,26 +1489,16 @@ pub struct RunSingleJob { impl RunSingleJob { pub const SUBCOMMAND_NAME: &'static str = "run-single-job"; fn try_add_subcommand( - platform: Option<&DynPlatform>, job: &DynJob, - subcommand_line: &mut Vec>, + subcommand_line: &mut Vec>, ) -> serde_json::Result<()> { let mut json = job.serialize_to_json_ascii()?; json.insert_str(0, "--json="); - subcommand_line.push(Self::SUBCOMMAND_NAME.intern().into()); - if let Some(platform) = platform { - subcommand_line.push( - format!("--platform={}", platform.name()) - .intern_deref() - .into(), - ); - } - subcommand_line.push( - format!("--name={}", job.kind().name()) - .intern_deref() - .into(), - ); - subcommand_line.push(json.intern_deref().into()); + subcommand_line.extend([ + Self::SUBCOMMAND_NAME.intern(), + Intern::intern_owned(format!("--name={}", job.kind().name())), + Intern::intern_owned(json), + ]); Ok(()) } } @@ -1899,7 +1508,6 @@ impl TryFrom> for RunSingleJob { fn try_from(value: RunSingleJobClap) -> Result { let RunSingleJobClap::RunSingleJob { - platform, name: job_kind, json, extra, @@ -1913,11 +1521,7 @@ impl TryFrom> for RunSingleJob { format_args!("failed to parse job {name} from JSON: {e}"), ) }) - .map(|job| Self { - platform, - job, - extra, - }) + .map(|job| Self { job, extra }) } } @@ -1925,8 +1529,6 @@ impl TryFrom> for RunSingleJob { enum RunSingleJobClap { #[command(name = RunSingleJob::SUBCOMMAND_NAME, hide = true)] RunSingleJob { - #[arg(long)] - platform: Option, #[arg(long)] name: DynJobKind, #[arg(long)] @@ -1971,21 +1573,15 @@ impl clap::FromArgMatches for RunSingleJob { } impl RunBuild for RunSingleJob { - fn run_without_platform( - self, - make_params: F, - global_params: &GlobalParams, - ) -> eyre::Result<()> + fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> where F: FnOnce(Extra) -> eyre::Result, { + let _ = cmd; let params = make_params(self.extra)?; let mut job_graph = JobGraph::new(); job_graph.add_jobs([self.job]); - job_graph.run(¶ms, global_params) - } - fn get_platform(&self) -> Option<&DynPlatform> { - self.platform.as_ref() + job_graph.run(¶ms) } } @@ -2022,37 +1618,23 @@ impl Completions { } impl RunBuild for Completions { - fn run_without_platform( - self, - _make_params: F, - global_params: &GlobalParams, - ) -> eyre::Result<()> + fn run(self, _make_params: F, mut cmd: clap::Command) -> eyre::Result<()> where F: FnOnce(NoArgs) -> eyre::Result, { let Self::Completions { shell } = self; - let Some(cmd) = global_params.top_level_cmd() else { - eyre::bail!("completions command requires GlobalParams::top_level_cmd() to be Some"); - }; - let bin_name = cmd.get_bin_name().map(str::intern).unwrap_or_else(|| { - program_name_for_internal_jobs() - .to_interned_str() - .expect("program name is invalid UTF-8") - }); + let bin_name = cmd + .get_bin_name() + .map(str::intern) + .unwrap_or_else(|| program_name_for_internal_jobs()); clap_complete::aot::generate( shell, - &mut cmd.clone(), + &mut cmd, &*bin_name, &mut std::io::BufWriter::new(std::io::stdout().lock()), ); Ok(()) } - fn handle_missing_platform(self, global_params: &GlobalParams) -> eyre::Result<()> { - self.run_without_platform(|_| unreachable!(), global_params) - } - fn get_platform(&self) -> Option<&DynPlatform> { - None - } } #[derive( @@ -2085,72 +1667,24 @@ pub enum BuildCli { RunSingleJob(RunSingleJob), #[clap(flatten)] Completions(Completions), - #[cfg(unix)] #[clap(flatten)] CreateUnixShellScript(CreateUnixShellScript), } impl RunBuild for BuildCli { - fn run_without_platform( - self, - make_params: F, - global_params: &GlobalParams, - ) -> eyre::Result<()> + fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> where F: FnOnce(Extra) -> eyre::Result, { match self { - BuildCli::Job(v) => v.run_without_platform(make_params, global_params), - BuildCli::RunSingleJob(v) => v.run_without_platform(make_params, global_params), - BuildCli::Completions(v) => { - v.run_without_platform(|NoArgs {}| unreachable!(), global_params) - } - #[cfg(unix)] - BuildCli::CreateUnixShellScript(v) => { - v.run_without_platform(make_params, global_params) - } - } - } - fn handle_missing_platform(self, global_params: &GlobalParams) -> eyre::Result<()> { - match self { - BuildCli::Job(v) => v.handle_missing_platform(global_params), - BuildCli::RunSingleJob(v) => v.handle_missing_platform(global_params), - BuildCli::Completions(v) => v.handle_missing_platform(global_params), - #[cfg(unix)] - BuildCli::CreateUnixShellScript(v) => v.handle_missing_platform(global_params), - } - } - fn get_platform(&self) -> Option<&DynPlatform> { - match self { - BuildCli::Job(v) => v.get_platform(), - BuildCli::RunSingleJob(v) => v.get_platform(), - BuildCli::Completions(v) => v.get_platform(), - #[cfg(unix)] - BuildCli::CreateUnixShellScript(v) => v.get_platform(), - } - } - fn run( - self, - make_params: F, - platform: DynPlatform, - global_params: &GlobalParams, - ) -> eyre::Result<()> - where - F: FnOnce(DynPlatform, Extra) -> eyre::Result, - { - match self { - BuildCli::Job(v) => v.run(make_params, platform, global_params), - BuildCli::RunSingleJob(v) => v.run(make_params, platform, global_params), - BuildCli::Completions(v) => { - v.run(|_, NoArgs {}| unreachable!(), platform, global_params) - } - #[cfg(unix)] - BuildCli::CreateUnixShellScript(v) => v.run(make_params, platform, global_params), + BuildCli::Job(v) => v.run(make_params, cmd), + BuildCli::RunSingleJob(v) => v.run(make_params, cmd), + BuildCli::Completions(v) => v.run(|NoArgs {}| unreachable!(), cmd), + BuildCli::CreateUnixShellScript(v) => v.run(make_params, cmd), } } } -#[cfg(unix)] #[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Subcommand)] enum CreateUnixShellScriptInner { CreateUnixShellScript { @@ -2165,15 +1699,10 @@ enum CreateUnixShellScriptInner { pub struct CreateUnixShellScript(CreateUnixShellScriptInner); impl RunBuild for CreateUnixShellScript { - fn run_without_platform( - self, - make_params: F, - global_params: &GlobalParams, - ) -> eyre::Result<()> + fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> where F: FnOnce(Extra) -> eyre::Result, { - let platform = self.get_platform().cloned(); let CreateUnixShellScriptInner::CreateUnixShellScript { _incomplete: (), inner: @@ -2185,28 +1714,21 @@ impl RunBuild for CreateUnixShellScript { } = self.0; let extra_args = extra.to_interned_args_vec(); let params = make_params(extra)?; - let bin_name = global_params - .top_level_cmd() - .and_then(clap::Command::get_bin_name) - .map(|v| OsStr::new(v).intern()); - let (job, dependencies) = args.args_to_jobs(dependencies_args, ¶ms, global_params)?; + let (job, dependencies) = args.args_to_jobs(dependencies_args, ¶ms)?; let mut job_graph = JobGraph::new(); job_graph.add_jobs([job].into_iter().chain(dependencies)); - std::io::stdout().write_all( - job_graph - .to_unix_shell_script_with_internal_program_prefix( - &[bin_name.unwrap_or_else(|| program_name_for_internal_jobs())], - platform.as_ref(), - &extra_args, - ) - .as_bytes(), - )?; + println!( + "{}", + job_graph.to_unix_shell_script_with_internal_program_prefix( + &[cmd + .get_bin_name() + .map(str::intern) + .unwrap_or_else(|| program_name_for_internal_jobs())], + &extra_args, + ) + ); Ok(()) } - fn get_platform(&self) -> Option<&DynPlatform> { - let CreateUnixShellScriptInner::CreateUnixShellScript { inner, .. } = &self.0; - inner.get_platform() - } } impl clap::FromArgMatches for CreateUnixShellScript { @@ -2227,7 +1749,6 @@ impl clap::FromArgMatches for CreateUnixShellScript { } } -#[cfg(unix)] impl clap::Subcommand for CreateUnixShellScript { fn augment_subcommands(cmd: clap::Command) -> clap::Command { CreateUnixShellScriptInner::::augment_subcommands(cmd) @@ -2373,215 +1894,82 @@ impl clap::FromArgMatches for AnyJobSubcommand { } impl RunBuild for AnyJobSubcommand { - fn run_without_platform( - self, - make_params: F, - global_params: &GlobalParams, - ) -> eyre::Result<()> + fn run(self, make_params: F, cmd: clap::Command) -> eyre::Result<()> where F: FnOnce(Extra) -> eyre::Result, { + let _ = cmd; let Self { args, dependencies_args, extra, } = self; let params = make_params(extra)?; - let (job, dependencies) = args.args_to_jobs(dependencies_args, ¶ms, global_params)?; + let (job, dependencies) = args.args_to_jobs(dependencies_args, ¶ms)?; let mut job_graph = JobGraph::new(); job_graph.add_jobs([job].into_iter().chain(dependencies)); // add all at once to avoid recomputing graph properties multiple times - job_graph.run(¶ms, global_params) - } - fn get_platform(&self) -> Option<&DynPlatform> { - self.args - .base_job_args_dyn(&self.dependencies_args) - .platform - .as_ref() + job_graph.run(¶ms) } } -pub fn program_name_for_internal_jobs() -> Interned { - static PROGRAM_NAME: OnceLock> = OnceLock::new(); - *PROGRAM_NAME.get_or_init(|| { - std::env::args_os() - .next() - .expect("can't get program name") - .intern_deref() - }) +pub fn program_name_for_internal_jobs() -> Interned { + static PROGRAM_NAME: OnceLock> = OnceLock::new(); + *PROGRAM_NAME + .get_or_init(|| str::intern_owned(std::env::args().next().expect("can't get program name"))) } -#[derive(clap::Args, Debug, Clone, Hash, PartialEq, Eq)] -#[group(id = "BaseJob")] -#[non_exhaustive] -pub struct BaseJobArgs { +#[derive(clap::Args, PartialEq, Eq, Hash, Debug, Clone)] +#[group(id = "CreateOutputDir")] +pub struct CreateOutputDirArgs { /// the directory to put the generated main output file and associated files in #[arg(short, long, value_hint = clap::ValueHint::DirPath)] - pub output: Option, + pub output: Option, #[arg(long, env = "FAYALITE_KEEP_TEMP_DIR")] pub keep_temp_dir: bool, - /// the stem of the generated main output file, e.g. to get foo.v, pass --file-stem=foo - #[arg(long)] - pub file_stem: Option, - /// run commands even if their results are already cached - #[arg(long, env = Self::RUN_EVEN_IF_CACHED_ENV_NAME)] - pub run_even_if_cached: bool, - /// platform - #[arg(long)] - pub platform: Option, } -impl BaseJobArgs { - pub const RUN_EVEN_IF_CACHED_ENV_NAME: &'static str = "FAYALITE_RUN_EVEN_IF_CACHED"; - pub fn from_output_dir_and_env(output: PathBuf, platform: Option) -> Self { - Self { - output: Some(output), - keep_temp_dir: false, - file_stem: None, - run_even_if_cached: std::env::var_os(Self::RUN_EVEN_IF_CACHED_ENV_NAME).is_some(), - platform, - } - } -} - -impl ToArgs for BaseJobArgs { +impl ToArgs for CreateOutputDirArgs { fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { let Self { output, keep_temp_dir, - file_stem, - run_even_if_cached, - platform, } = self; if let Some(output) = output { - args.write_long_option_eq("output", output); + args.write_arg(format_args!("--output={output}")); } if *keep_temp_dir { - args.write_arg("--keep-temp-dir"); - } - if let Some(file_stem) = file_stem { - args.write_long_option_eq("file-stem", file_stem); - } - if *run_even_if_cached { - args.write_arg("--run-even-if-cached"); - } - if let Some(platform) = platform { - args.write_long_option_eq("platform", platform.name()); + args.write_str_arg("--keep-temp-dir"); } } } -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct BaseJob { - output_dir: Interned, +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct CreateOutputDir { + output_dir: Interned, #[serde(skip)] temp_dir: Option>, - file_stem: Interned, - run_even_if_cached: bool, - platform: Option, } -impl Hash for BaseJob { - fn hash(&self, state: &mut H) { - let Self { - output_dir, - temp_dir: _, - file_stem, - run_even_if_cached, - platform, - } = self; - output_dir.hash(state); - file_stem.hash(state); - run_even_if_cached.hash(state); - platform.hash(state); - } -} +impl Eq for CreateOutputDir {} -impl Eq for BaseJob {} - -impl PartialEq for BaseJob { +impl PartialEq for CreateOutputDir { fn eq(&self, other: &Self) -> bool { - let Self { - output_dir, - temp_dir: _, - file_stem, - run_even_if_cached, - ref platform, - } = *self; - output_dir == other.output_dir - && file_stem == other.file_stem - && run_even_if_cached == other.run_even_if_cached - && *platform == other.platform + self.compare_key() == other.compare_key() } } -impl BaseJob { - pub fn output_dir(&self) -> Interned { - self.output_dir - } - pub fn temp_dir(&self) -> Option<&Arc> { - self.temp_dir.as_ref() - } - pub fn file_stem(&self) -> Interned { - self.file_stem - } - pub fn file_with_ext(&self, ext: impl AsRef) -> Interned { - let mut retval = self.output_dir().join(self.file_stem()); - retval.set_extension(ext); - retval.intern_deref() - } - pub fn run_even_if_cached(&self) -> bool { - self.run_even_if_cached - } - pub fn platform(&self) -> Option<&DynPlatform> { - self.platform.as_ref() +impl Hash for CreateOutputDir { + fn hash(&self, state: &mut H) { + self.compare_key().hash(state); } } -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] -pub struct BaseJobKind; +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] +pub struct CreateOutputDirJobKind; -impl JobKindHelper for BaseJobKind { - fn base_job<'a>( - self, - job: &'a ::Job, - _dependencies: &'a <::Dependencies as JobDependencies>::JobsAndKinds, - ) -> &'a BaseJob { - job - } - fn base_job_args<'a>( - self, - args: &'a ::Args, - _dependencies: &'a <::Dependencies as JobDependencies>::KindsAndArgs, - ) -> &'a BaseJobArgs { - args - } - #[track_caller] - fn base_job_args_dyn<'a>( - self, - args: &'a ::Args, - dependencies_args: &'a [DynJobArgs], - ) -> &'a BaseJobArgs { - let [] = dependencies_args else { - panic!("wrong number of dependencies"); - }; - args - } - #[track_caller] - fn base_job_dyn<'a>( - self, - job: &'a ::Job, - dependencies: &'a [DynJob], - ) -> &'a BaseJob { - let [] = dependencies else { - panic!("wrong number of dependencies"); - }; - job - } -} - -impl JobKind for BaseJobKind { - type Args = BaseJobArgs; - type Job = BaseJob; +impl JobKind for CreateOutputDirJobKind { + type Args = CreateOutputDirArgs; + type Job = CreateOutputDir; type Dependencies = (); fn dependencies(self) -> Self::Dependencies { @@ -2590,23 +1978,40 @@ impl JobKind for BaseJobKind { fn args_to_jobs( args: JobArgsAndDependencies, - params: &JobParams, - _global_params: &GlobalParams, + _params: &JobParams, ) -> eyre::Result> { - let BaseJobArgs { - output, - keep_temp_dir, - file_stem, - run_even_if_cached, - platform, - } = args.args.args; + let JobArgsAndDependencies { + args: + JobKindAndArgs { + kind, + args: + CreateOutputDirArgs { + output, + keep_temp_dir, + }, + }, + dependencies: (), + } = args; let (output_dir, temp_dir) = if let Some(output) = output { (Intern::intern_owned(output), None) } else { // we create the temp dir here rather than in run so other // jobs can have their paths based on the chosen temp dir let temp_dir = TempDir::new()?; - let output_dir = temp_dir.path().intern(); + let output_dir = temp_dir + .path() + .as_os_str() + .to_str() + .ok_or_else(|| { + std::io::Error::new( + std::io::ErrorKind::InvalidFilename, + format!( + "temporary directory path is not valid UTF-8: {:?}", + temp_dir.path() + ), + ) + })? + .intern(); let temp_dir = if keep_temp_dir { // use TempDir::into_path() to no longer automatically delete the temp dir let temp_dir_path = temp_dir.into_path(); @@ -2617,18 +2022,12 @@ impl JobKind for BaseJobKind { }; (output_dir, temp_dir) }; - let file_stem = file_stem - .map(Intern::intern_deref) - .unwrap_or(params.main_module().name().into()); Ok(JobAndDependencies { job: JobAndKind { - kind: BaseJobKind, - job: BaseJob { + kind, + job: CreateOutputDir { output_dir, temp_dir, - file_stem, - run_even_if_cached, - platform, }, }, dependencies: (), @@ -2642,23 +2041,23 @@ impl JobKind for BaseJobKind { fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { [JobItemName::Path { path: job.output_dir, - }] - .intern_slice() + }][..] + .intern() } fn name(self) -> Interned { - "base-job".intern() + "create-output-dir".intern() } fn external_command_params(self, job: &Self::Job) -> Option { Some(CommandParams { command_line: [ - "mkdir".intern().into(), - "-p".intern().into(), - "--".intern().into(), - job.output_dir.into(), - ] - .intern_slice(), + "mkdir".intern(), + "-p".intern(), + "--".intern(), + job.output_dir, + ][..] + .intern(), current_dir: None, }) } @@ -2668,11 +2067,10 @@ impl JobKind for BaseJobKind { job: &Self::Job, inputs: &[JobItem], _params: &JobParams, - _global_params: &GlobalParams, _acquired_job: &mut AcquiredJob, ) -> eyre::Result> { let [] = inputs else { - panic!("invalid inputs for BaseJob"); + panic!("invalid inputs for CreateOutputDir"); }; std::fs::create_dir_all(&*job.output_dir)?; Ok(vec![JobItem::Path { @@ -2685,119 +2083,222 @@ impl JobKind for BaseJobKind { } } -pub trait GetJob { - fn get_job(this: &Self) -> &J; -} - -impl> GetJob for &'_ T { - fn get_job(this: &Self) -> &J { - T::get_job(this) +impl CreateOutputDir { + pub fn output_dir(&self) -> Interned { + self.output_dir + } + fn compare_key(&self) -> (&str, bool) { + let Self { + output_dir, + temp_dir, + } = self; + (output_dir, temp_dir.is_some()) } } -impl> GetJob for &'_ mut T { - fn get_job(this: &Self) -> &J { - T::get_job(this) +#[derive(clap::Args, Debug, Clone, Hash, PartialEq, Eq)] +#[group(id = "BaseJob")] +#[non_exhaustive] +pub struct BaseJobArgs { + /// rather than having CreateOutputDir be a normal dependency, it's nested in BaseJob to avoid a cyclic dependency + #[command(flatten)] + pub create_output_dir_args: CreateOutputDirArgs, + /// the stem of the generated main output file, e.g. to get foo.v, pass --file-stem=foo + #[arg(long)] + pub file_stem: Option, + /// run commands even if their results are already cached + #[arg(long, env = Self::RUN_EVEN_IF_CACHED_ENV_NAME)] + pub run_even_if_cached: bool, +} + +impl BaseJobArgs { + pub const RUN_EVEN_IF_CACHED_ENV_NAME: &'static str = "FAYALITE_RUN_EVEN_IF_CACHED"; + pub fn from_output_dir_and_env(output: String) -> Self { + Self { + create_output_dir_args: CreateOutputDirArgs { + output: Some(output), + keep_temp_dir: false, + }, + file_stem: None, + run_even_if_cached: std::env::var_os(Self::RUN_EVEN_IF_CACHED_ENV_NAME).is_some(), + } } } -impl> GetJob for Box { - fn get_job(this: &Self) -> &J { - T::get_job(this) +impl ToArgs for BaseJobArgs { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { + let Self { + create_output_dir_args, + file_stem, + run_even_if_cached, + } = self; + create_output_dir_args.to_args(args); + if let Some(file_stem) = file_stem { + args.write_arg(format_args!("--file-stem={file_stem}")); + } + if *run_even_if_cached { + args.write_str_arg("--run-even-if-cached"); + } } } -pub struct GetJobPositionDependencies(PhantomData); +#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct BaseJob { + /// rather than having CreateOutputDir be a normal dependency, it's nested in BaseJob to avoid a cyclic dependency + #[serde(flatten)] + create_output_dir: CreateOutputDir, + file_stem: Interned, + run_even_if_cached: bool, +} -impl Default for GetJobPositionDependencies { - fn default() -> Self { - Self(Default::default()) +impl BaseJob { + pub fn output_dir(&self) -> Interned { + self.create_output_dir.output_dir() + } + pub fn file_stem(&self) -> Interned { + self.file_stem + } + pub fn file_with_ext(&self, ext: &str) -> Interned { + let mut retval = std::path::Path::new(&self.output_dir()).join(self.file_stem()); + retval.set_extension(ext); + intern_known_utf8_path_buf(retval) + } + pub fn run_even_if_cached(&self) -> bool { + self.run_even_if_cached } } -impl fmt::Debug for GetJobPositionDependencies { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!( - f, - "GetJobPositionDependencies<{}>", - std::any::type_name::() - ) +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)] +pub struct BaseJobKind; + +impl JobKind for BaseJobKind { + type Args = BaseJobArgs; + type Job = BaseJob; + type Dependencies = (); + + fn dependencies(self) -> Self::Dependencies { + () } -} -impl Hash for GetJobPositionDependencies { - fn hash(&self, _state: &mut H) {} -} - -impl Ord for GetJobPositionDependencies { - fn cmp(&self, _other: &Self) -> Ordering { - Ordering::Equal + fn args_to_jobs( + args: JobArgsAndDependencies, + params: &JobParams, + ) -> eyre::Result> { + let BaseJobArgs { + create_output_dir_args, + file_stem, + run_even_if_cached, + } = args.args.args; + let create_output_dir_args = JobKindAndArgs { + kind: CreateOutputDirJobKind, + args: create_output_dir_args, + }; + let create_output_dir = create_output_dir_args.args_to_jobs((), params)?.job.job; + let file_stem = file_stem + .map(Intern::intern_owned) + .unwrap_or(params.main_module().name()); + Ok(JobAndDependencies { + job: JobAndKind { + kind: BaseJobKind, + job: BaseJob { + create_output_dir, + file_stem, + run_even_if_cached, + }, + }, + dependencies: (), + }) } -} -impl PartialOrd for GetJobPositionDependencies { - fn partial_cmp(&self, _other: &Self) -> Option { - Some(Ordering::Equal) + fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + CreateOutputDirJobKind.inputs(&job.create_output_dir) } -} -impl Eq for GetJobPositionDependencies {} + fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + CreateOutputDirJobKind.outputs(&job.create_output_dir) + } -impl PartialEq for GetJobPositionDependencies { - fn eq(&self, _other: &Self) -> bool { + fn name(self) -> Interned { + "base-job".intern() + } + + fn external_command_params(self, job: &Self::Job) -> Option { + CreateOutputDirJobKind.external_command_params(&job.create_output_dir) + } + + fn run( + self, + job: &Self::Job, + inputs: &[JobItem], + params: &JobParams, + acquired_job: &mut AcquiredJob, + ) -> eyre::Result> { + CreateOutputDirJobKind.run(&job.create_output_dir, inputs, params, acquired_job) + } + + fn subcommand_hidden(self) -> bool { true } } -impl Clone for GetJobPositionDependencies { - fn clone(&self) -> Self { - Self(PhantomData) +pub trait GetBaseJob { + fn base_job(&self) -> &BaseJob; +} + +impl GetBaseJob for &'_ T { + fn base_job(&self) -> &BaseJob { + T::base_job(self) } } -impl Copy for GetJobPositionDependencies {} +impl GetBaseJob for &'_ mut T { + fn base_job(&self) -> &BaseJob { + T::base_job(self) + } +} -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] -pub struct GetJobPositionJob; +impl GetBaseJob for Box { + fn base_job(&self) -> &BaseJob { + T::base_job(self) + } +} -impl>>> - GetJob> for JobAndDependencies +impl GetBaseJob for BaseJob { + fn base_job(&self) -> &BaseJob { + self + } +} + +impl GetBaseJob for JobAndKind { + fn base_job(&self) -> &BaseJob { + &self.job + } +} + +impl GetBaseJob for JobAndDependencies { + fn base_job(&self) -> &BaseJob { + &self.job.job + } +} + +impl GetBaseJob for JobAndDependencies +where + K::Dependencies: JobDependencies, + ::JobsAndKinds: GetBaseJob, { - fn get_job(this: &Self) -> &J { - GetJob::get_job(&this.dependencies) + fn base_job(&self) -> &BaseJob { + self.dependencies.base_job() } } -impl GetJob for JobAndDependencies { - fn get_job(this: &Self) -> &K::Job { - &this.job.job +impl GetBaseJob for (T, U) { + fn base_job(&self) -> &BaseJob { + self.0.base_job() } } -impl>>> - GetJob> for JobArgsAndDependencies -{ - fn get_job(this: &Self) -> &J { - GetJob::get_job(&this.dependencies) - } -} - -impl GetJob for JobArgsAndDependencies { - fn get_job(this: &Self) -> &K::Args { - &this.args.args - } -} - -impl>> - GetJob> for JobKindAndDependencies -{ - fn get_job(this: &Self) -> &J { - GetJob::get_job(&this.dependencies) - } -} - -impl GetJob for JobKindAndDependencies { - fn get_job(this: &Self) -> &K { - &this.kind +impl GetBaseJob for (T, U, V) { + fn base_job(&self) -> &BaseJob { + self.0.base_job() } } diff --git a/crates/fayalite/src/build/external.rs b/crates/fayalite/src/build/external.rs index e4251a48..021d63d6 100644 --- a/crates/fayalite/src/build/external.rs +++ b/crates/fayalite/src/build/external.rs @@ -3,9 +3,9 @@ use crate::{ build::{ - ArgsWriter, CommandParams, GlobalParams, JobAndDependencies, JobAndKind, - JobArgsAndDependencies, JobDependencies, JobDependenciesHasBase, JobItem, JobItemName, - JobKind, JobKindAndArgs, JobParams, ToArgs, WriteArgs, + ArgsWriter, CommandParams, GetBaseJob, JobAndDependencies, JobAndKind, + JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, JobKindAndArgs, + JobParams, ToArgs, WriteArgs, intern_known_utf8_path_buf, }, intern::{Intern, Interned}, util::{job_server::AcquiredJob, streaming_read_utf8::streaming_read_utf8}, @@ -55,25 +55,6 @@ impl MaybeUtf8 { MaybeUtf8::Binary(v) => v, } } - pub fn as_os_str(&self) -> &OsStr { - #![allow(unreachable_code)] - #[cfg(unix)] - { - return std::os::unix::ffi::OsStrExt::from_bytes(self.as_bytes()); - } - #[cfg(target_os = "wasi")] - { - return std::os::wasi::ffi::OsStrExt::from_bytes(self.as_bytes()); - } - // implementing WTF-8 is too much of a pain so don't have a special case for windows - if let Ok(s) = str::from_utf8(self.as_bytes()) { - return OsStr::new(s); - } - panic!("invalid UTF-8 conversion to OsStr is not implemented on this platform"); - } - pub fn as_path(&self) -> &Path { - Path::new(self.as_os_str()) - } } #[derive(Serialize, Deserialize)] @@ -126,80 +107,31 @@ impl From for MaybeUtf8 { } } -impl From for MaybeUtf8 { - fn from(value: PathBuf) -> Self { - Self::from(value.into_os_string().into_encoded_bytes()) - } -} - -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Serialize, Deserialize)] -#[serde(rename = "File")] -pub struct ExternalJobCacheV2File<'a> { - pub name: MaybeUtf8, - pub contents: Cow<'a, MaybeUtf8>, -} - -#[derive(Clone, PartialEq, Eq, PartialOrd, Ord, Debug)] -pub struct ExternalJobCacheV2Files(pub BTreeMap); - -impl Serialize for ExternalJobCacheV2Files { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - serializer.collect_seq( - self.0 - .iter() - .map(|(name, contents)| ExternalJobCacheV2File { - name: name.clone().into(), - contents: Cow::Borrowed(contents), - }), - ) - } -} - -impl<'de> Deserialize<'de> for ExternalJobCacheV2Files { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - Ok(Self( - Vec::deserialize(deserializer)? - .into_iter() - .map(|ExternalJobCacheV2File { name, contents }| { - (name.as_path().to_path_buf(), contents.into_owned()) - }) - .collect(), - )) - } -} - #[derive(Clone, PartialEq, Eq, Debug, Serialize, Deserialize)] #[serde(rename = "ExternalJobCache")] pub struct ExternalJobCacheV2 { pub version: ExternalJobCacheVersion, pub inputs_hash: blake3::Hash, pub stdout_stderr: String, - pub result: Result, + pub result: Result, String>, } impl ExternalJobCacheV2 { - fn read_from_file(cache_json_path: Interned) -> eyre::Result { + fn read_from_file(cache_json_path: Interned) -> eyre::Result { let cache_str = std::fs::read_to_string(&*cache_json_path) - .wrap_err_with(|| format!("can't read {cache_json_path:?}"))?; - serde_json::from_str(&cache_str) - .wrap_err_with(|| format!("can't decode {cache_json_path:?}")) + .wrap_err_with(|| format!("can't read {cache_json_path}"))?; + serde_json::from_str(&cache_str).wrap_err_with(|| format!("can't decode {cache_json_path}")) } - fn write_to_file(&self, cache_json_path: Interned) -> eyre::Result<()> { + fn write_to_file(&self, cache_json_path: Interned) -> eyre::Result<()> { let cache_str = serde_json::to_string_pretty(&self).expect("serialization can't fail"); std::fs::write(&*cache_json_path, cache_str) - .wrap_err_with(|| format!("can't write {cache_json_path:?}")) + .wrap_err_with(|| format!("can't write {cache_json_path}")) } } #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct ExternalJobCaching { - cache_json_path: Interned, + cache_json_path: Interned, run_even_if_cached: bool, } @@ -216,8 +148,8 @@ impl JobCacheHasher { self.hash_size(bytes.len()); self.0.update(bytes); } - fn hash_sized_os_str(&mut self, s: &OsStr) { - self.hash_sized_bytes(s.as_encoded_bytes()); + fn hash_sized_str(&mut self, s: &str) { + self.hash_sized_bytes(s.as_bytes()); } fn hash_iter>( &mut self, @@ -261,8 +193,8 @@ fn write_file_atomically_no_clobber C, C: AsRef<[u8]>>( } impl ExternalJobCaching { - pub fn get_cache_dir_from_output_dir(output_dir: impl AsRef) -> PathBuf { - output_dir.as_ref().join(".fayalite-job-cache") + pub fn get_cache_dir_from_output_dir(output_dir: &str) -> PathBuf { + Path::join(output_dir.as_ref(), ".fayalite-job-cache") } pub fn make_cache_dir( cache_dir: impl AsRef, @@ -286,18 +218,19 @@ impl ExternalJobCaching { }) } pub fn new( - output_dir: impl AsRef, + output_dir: &str, application_name: &str, - json_file_stem: impl AsRef, + json_file_stem: &str, run_even_if_cached: bool, ) -> std::io::Result { let cache_dir = Self::get_cache_dir_from_output_dir(output_dir); Self::make_cache_dir(&cache_dir, application_name)?; let mut cache_json_path = cache_dir; - cache_json_path.push(json_file_stem.as_ref()); + cache_json_path.push(json_file_stem); cache_json_path.set_extension("json"); + let cache_json_path = intern_known_utf8_path_buf(cache_json_path); Ok(Self { - cache_json_path: Path::intern_owned(cache_json_path), + cache_json_path, run_even_if_cached, }) } @@ -316,7 +249,7 @@ impl ExternalJobCaching { fn run_from_cache( self, inputs_hash: blake3::Hash, - output_file_paths: impl IntoIterator>, + output_file_paths: impl IntoIterator>, ) -> Result, ()> { if self.run_even_if_cached { return Err(()); @@ -336,7 +269,7 @@ impl ExternalJobCaching { match result { Ok(outputs) => { for output_file_path in output_file_paths { - let Some(output_data) = outputs.0.get(&*output_file_path) else { + let Some(output_data) = outputs.get(&*output_file_path) else { if let Ok(true) = std::fs::exists(&*output_file_path) { // assume the existing file is the correct one continue; @@ -357,7 +290,7 @@ impl ExternalJobCaching { } } fn make_command( - command_line: Interned<[Interned]>, + command_line: Interned<[Interned]>, ) -> eyre::Result { ensure!(!command_line.is_empty(), "command line must not be empty"); let mut cmd = std::process::Command::new(&*command_line[0]); @@ -367,26 +300,26 @@ impl ExternalJobCaching { } pub fn run eyre::Result<()>>( self, - command_line: Interned<[Interned]>, - input_file_paths: impl IntoIterator>, - output_file_paths: impl IntoIterator> + Clone, + command_line: Interned<[Interned]>, + input_file_paths: impl IntoIterator>, + output_file_paths: impl IntoIterator> + Clone, run_fn: F, ) -> eyre::Result<()> { let mut hasher = JobCacheHasher::default(); hasher.hash_iter(command_line.iter(), |hasher, arg| { - hasher.hash_sized_os_str(arg) + hasher.hash_sized_str(arg) }); let mut input_file_paths = - Vec::<&Path>::from_iter(input_file_paths.into_iter().map(Interned::into_inner)); + Vec::<&str>::from_iter(input_file_paths.into_iter().map(Interned::into_inner)); input_file_paths.sort_unstable(); input_file_paths.dedup(); hasher.try_hash_iter( &input_file_paths, |hasher, input_file_path| -> eyre::Result<()> { - hasher.hash_sized_os_str(input_file_path.as_ref()); + hasher.hash_sized_str(input_file_path); hasher.hash_sized_bytes( &std::fs::read(input_file_path).wrap_err_with(|| { - format!("can't read job input file: {input_file_path:?}") + format!("can't read job input file: {input_file_path}") })?, ); Ok(()) @@ -405,7 +338,7 @@ impl ExternalJobCaching { let mut stdout_stderr = String::new(); let result = std::thread::scope(|scope| { std::thread::Builder::new() - .name(format!("stdout:{}", command_line[0].display())) + .name(format!("stdout:{}", command_line[0])) .spawn_scoped(scope, || { let _ = streaming_read_utf8(std::io::BufReader::new(pipe_reader), |s| { stdout_stderr.push_str(s); @@ -425,19 +358,17 @@ impl ExternalJobCaching { inputs_hash, stdout_stderr, result: match &result { - Ok(()) => Ok(ExternalJobCacheV2Files(Result::from_iter( - output_file_paths.into_iter().map( - |output_file_path: Interned| -> eyre::Result<_> { - let output_file_path = &*output_file_path; - Ok(( - PathBuf::from(output_file_path), - MaybeUtf8::from(std::fs::read(output_file_path).wrap_err_with( - || format!("can't read job output file: {output_file_path:?}"), - )?), - )) - }, - ), - )?)), + Ok(()) => Ok(Result::from_iter(output_file_paths.into_iter().map( + |output_file_path: Interned| -> eyre::Result<_> { + let output_file_path = &*output_file_path; + Ok(( + String::from(output_file_path), + MaybeUtf8::from(std::fs::read(output_file_path).wrap_err_with( + || format!("can't read job output file: {output_file_path}"), + )?), + )) + }, + ))?), Err(e) => Err(format!("{e:#}")), }, } @@ -446,9 +377,9 @@ impl ExternalJobCaching { } pub fn run_maybe_cached eyre::Result<()>>( this: Option, - command_line: Interned<[Interned]>, - input_file_paths: impl IntoIterator>, - output_file_paths: impl IntoIterator> + Clone, + command_line: Interned<[Interned]>, + input_file_paths: impl IntoIterator>, + output_file_paths: impl IntoIterator> + Clone, run_fn: F, ) -> eyre::Result<()> { match this { @@ -506,22 +437,31 @@ fn parse_which_result( which_result: which::Result, program_name: impl Into, program_path_arg_name: impl FnOnce() -> String, -) -> Result, ResolveProgramPathError> { +) -> Result, ResolveProgramPathError> { let which_result = match which_result { Ok(v) => v, - Err(inner) => { + Err(e) => { return Err(ResolveProgramPathError { - inner, + inner: ResolveProgramPathErrorInner::Which(e), program_name: program_name.into(), program_path_arg_name: program_path_arg_name(), }); } }; - Ok(which_result.intern_deref()) + Ok(str::intern_owned( + which_result + .into_os_string() + .into_string() + .map_err(|_| ResolveProgramPathError { + inner: ResolveProgramPathErrorInner::NotValidUtf8, + program_name: program_name.into(), + program_path_arg_name: program_path_arg_name(), + })?, + )) } impl clap::builder::TypedValueParser for ExternalProgramPathValueParser { - type Value = Interned; + type Value = Interned; fn parse_ref( &self, @@ -555,10 +495,34 @@ pub struct ExternalCommandArgs { pub additional_args: T::AdditionalArgs, } +#[derive(Clone)] +enum ResolveProgramPathErrorInner { + Which(which::Error), + NotValidUtf8, +} + +impl fmt::Debug for ResolveProgramPathErrorInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Which(v) => v.fmt(f), + Self::NotValidUtf8 => f.write_str("NotValidUtf8"), + } + } +} + +impl fmt::Display for ResolveProgramPathErrorInner { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Which(v) => v.fmt(f), + Self::NotValidUtf8 => f.write_str("path is not valid UTF-8"), + } + } +} + #[derive(Clone, Debug)] pub struct ResolveProgramPathError { - inner: which::Error, - program_name: OsString, + inner: ResolveProgramPathErrorInner, + program_name: std::ffi::OsString, program_path_arg_name: String, } @@ -582,7 +546,7 @@ pub fn resolve_program_path( program_name: Option<&OsStr>, default_program_name: impl AsRef, program_path_env_var_name: Option<&OsStr>, -) -> Result, ResolveProgramPathError> { +) -> Result, ResolveProgramPathError> { let default_program_name = default_program_name.as_ref(); let owned_program_name; let program_name = if let Some(program_name) = program_name { @@ -600,7 +564,7 @@ pub fn resolve_program_path( impl ExternalCommandArgs { pub fn with_resolved_program_path( - program_path: Interned, + program_path: Interned, additional_args: T::AdditionalArgs, ) -> Self { Self::new( @@ -638,7 +602,7 @@ impl ToArgs for ExternalCommandArgs { } = *self; program_path.to_args(args); if run_even_if_cached { - args.write_display_arg(format_args!("--{}", T::run_even_if_cached_arg_name())); + args.write_arg(format_args!("--{}", T::run_even_if_cached_arg_name())); } additional_args.to_args(args); } @@ -649,13 +613,13 @@ struct ExternalCommandJobParams { command_params: CommandParams, inputs: Interned<[JobItemName]>, outputs: Interned<[JobItemName]>, - output_paths: Interned<[Interned]>, + output_paths: Interned<[Interned]>, } impl ExternalCommandJobParams { fn new(job: &ExternalCommandJob) -> Self { let output_paths = T::output_paths(job); - let mut command_line = ArgsWriter(vec![job.program_path.as_interned_os_str()]); + let mut command_line = ArgsWriter(vec![job.program_path]); T::command_line_args(job, &mut command_line); Self { command_params: CommandParams { @@ -675,8 +639,8 @@ impl ExternalCommandJobParams { #[derive(Deserialize, Serialize)] pub struct ExternalCommandJob { additional_job_data: T::AdditionalJobData, - program_path: Interned, - output_dir: Interned, + program_path: Interned, + output_dir: Interned, run_even_if_cached: bool, #[serde(skip)] params_cache: OnceLock, @@ -758,10 +722,10 @@ impl ExternalCommandJob { pub fn additional_job_data(&self) -> &T::AdditionalJobData { &self.additional_job_data } - pub fn program_path(&self) -> Interned { + pub fn program_path(&self) -> Interned { self.program_path } - pub fn output_dir(&self) -> Interned { + pub fn output_dir(&self) -> Interned { self.output_dir } pub fn run_even_if_cached(&self) -> bool { @@ -777,7 +741,7 @@ impl ExternalCommandJob { pub fn inputs(&self) -> Interned<[JobItemName]> { self.params().inputs } - pub fn output_paths(&self) -> Interned<[Interned]> { + pub fn output_paths(&self) -> Interned<[Interned]> { self.params().output_paths } pub fn outputs(&self) -> Interned<[JobItemName]> { @@ -787,12 +751,12 @@ impl ExternalCommandJob { #[derive(Copy, Clone, PartialEq, Eq, Hash)] pub struct ExternalProgramPath { - program_path: Interned, + program_path: Interned, _phantom: PhantomData, } impl ExternalProgramPath { - pub fn with_resolved_program_path(program_path: Interned) -> Self { + pub fn with_resolved_program_path(program_path: Interned) -> Self { Self { program_path, _phantom: PhantomData, @@ -816,7 +780,7 @@ impl ExternalProgramPath { _phantom: PhantomData, }) } - pub fn program_path(&self) -> Interned { + pub fn program_path(&self) -> Interned { self.program_path } } @@ -910,8 +874,8 @@ impl ToArgs for ExternalProgramPath { program_path, _phantom: _, } = self; - if args.get_long_option_eq(program_path_arg_name) != Some(program_path.as_os_str()) { - args.write_long_option_eq(program_path_arg_name, program_path); + if args.get_long_option_eq(program_path_arg_name) != Some(&**program_path) { + args.write_arg(format_args!("--{program_path_arg_name}={program_path}")); } } } @@ -989,22 +953,20 @@ pub trait ExternalCommand: 'static + Send + Sync + Hash + Eq + fmt::Debug + Size + fmt::Debug + Serialize + DeserializeOwned; - type BaseJobPosition; - type Dependencies: JobDependenciesHasBase; + type Dependencies: JobDependencies; type ExternalProgram: ExternalProgramTrait; fn dependencies() -> Self::Dependencies; fn args_to_jobs( args: JobArgsAndDependencies>, params: &JobParams, - global_params: &GlobalParams, ) -> eyre::Result<( Self::AdditionalJobData, ::JobsAndKinds, )>; fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]>; - fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]>; + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]>; fn command_line_args(job: &ExternalCommandJob, args: &mut W); - fn current_dir(job: &ExternalCommandJob) -> Option>; + fn current_dir(job: &ExternalCommandJob) -> Option>; fn job_kind_name() -> Interned; fn args_group_id() -> clap::Id { Interned::into_inner(Self::job_kind_name()).into() @@ -1029,7 +991,6 @@ impl JobKind for ExternalCommandJobKind { fn args_to_jobs( args: JobArgsAndDependencies, params: &JobParams, - global_params: &GlobalParams, ) -> eyre::Result> { let JobKindAndArgs { kind, @@ -1044,8 +1005,8 @@ impl JobKind for ExternalCommandJobKind { additional_args: _, }, } = args.args; - let (additional_job_data, dependencies) = T::args_to_jobs(args, params, global_params)?; - let base_job = T::Dependencies::base_job(&dependencies); + let (additional_job_data, dependencies) = T::args_to_jobs(args, params)?; + let base_job = dependencies.base_job(); let job = ExternalCommandJob { additional_job_data, program_path, @@ -1080,8 +1041,7 @@ impl JobKind for ExternalCommandJobKind { self, job: &Self::Job, inputs: &[JobItem], - _params: &JobParams, - global_params: &GlobalParams, + params: &JobParams, acquired_job: &mut AcquiredJob, ) -> eyre::Result> { assert!( @@ -1096,7 +1056,7 @@ impl JobKind for ExternalCommandJobKind { } = job.command_params(); ExternalJobCaching::new( &job.output_dir, - &global_params.application_name(), + ¶ms.application_name(), &T::job_kind_name(), job.run_even_if_cached, )? diff --git a/crates/fayalite/src/build/firrtl.rs b/crates/fayalite/src/build/firrtl.rs index b5574a93..62657f8d 100644 --- a/crates/fayalite/src/build/firrtl.rs +++ b/crates/fayalite/src/build/firrtl.rs @@ -3,17 +3,17 @@ use crate::{ build::{ - BaseJob, BaseJobKind, CommandParams, DynJobKind, GlobalParams, JobAndDependencies, + BaseJob, BaseJobKind, CommandParams, DynJobKind, JobAndDependencies, JobArgsAndDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs, }, firrtl::{ExportOptions, FileBackend}, - intern::{Intern, InternSlice, Interned}, + intern::{Intern, Interned}, util::job_server::AcquiredJob, }; use clap::Args; use serde::{Deserialize, Serialize}; -use std::path::{Path, PathBuf}; +use std::path::PathBuf; #[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Default, Debug)] pub struct FirrtlJobKind; @@ -43,11 +43,11 @@ impl Firrtl { fn make_firrtl_file_backend(&self) -> FileBackend { FileBackend { dir_path: PathBuf::from(&*self.base.output_dir()), - top_fir_file_stem: Some(self.base.file_stem().into()), + top_fir_file_stem: Some(String::from(&*self.base.file_stem())), circuit_name: None, } } - pub fn firrtl_file(&self) -> Interned { + pub fn firrtl_file(&self) -> Interned { self.base.file_with_ext("fir") } } @@ -64,14 +64,12 @@ impl JobKind for FirrtlJobKind { fn args_to_jobs( args: JobArgsAndDependencies, params: &JobParams, - global_params: &GlobalParams, ) -> eyre::Result> { args.args_to_jobs_simple( params, - global_params, |_kind, FirrtlArgs { export_options }, dependencies| { Ok(Firrtl { - base: dependencies.get_job::().clone(), + base: dependencies.job.job.clone(), export_options, }) }, @@ -81,15 +79,15 @@ impl JobKind for FirrtlJobKind { fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> { [JobItemName::Path { path: job.base.output_dir(), - }] - .intern_slice() + }][..] + .intern() } fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { [JobItemName::Path { path: job.firrtl_file(), - }] - .intern_slice() + }][..] + .intern() } fn name(self) -> Interned { @@ -105,7 +103,6 @@ impl JobKind for FirrtlJobKind { job: &Self::Job, inputs: &[JobItem], params: &JobParams, - _global_params: &GlobalParams, _acquired_job: &mut AcquiredJob, ) -> eyre::Result> { let [JobItem::Path { path: input_path }] = *inputs else { diff --git a/crates/fayalite/src/build/formal.rs b/crates/fayalite/src/build/formal.rs index 0708ff0f..a289c811 100644 --- a/crates/fayalite/src/build/formal.rs +++ b/crates/fayalite/src/build/formal.rs @@ -3,26 +3,23 @@ use crate::{ build::{ - BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GlobalParams, - JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, - JobKindAndDependencies, JobParams, ToArgs, WriteArgs, + CommandParams, DynJobKind, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, + JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, + WriteArgs, external::{ ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, }, - verilog::{UnadjustedVerilog, VerilogDialect, VerilogJob, VerilogJobKind}, + interned_known_utf8_method, + verilog::{VerilogDialect, VerilogJob, VerilogJobKind}, }, - intern::{Intern, InternSlice, Interned}, + intern::{Intern, Interned}, module::NameId, util::job_server::AcquiredJob, }; use clap::{Args, ValueEnum}; use eyre::Context; use serde::{Deserialize, Serialize}; -use std::{ - ffi::{OsStr, OsString}, - fmt::{self, Write}, - path::Path, -}; +use std::fmt; #[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Default, Deserialize, Serialize)] #[non_exhaustive] @@ -55,7 +52,7 @@ impl fmt::Display for FormalMode { #[non_exhaustive] pub struct FormalArgs { #[arg(long = "sby-extra-arg", value_name = "ARG")] - pub sby_extra_args: Vec, + pub sby_extra_args: Vec, #[arg(long, default_value_t)] pub formal_mode: FormalMode, #[arg(long, default_value_t = Self::DEFAULT_DEPTH)] @@ -63,7 +60,7 @@ pub struct FormalArgs { #[arg(long, default_value = Self::DEFAULT_SOLVER)] pub formal_solver: String, #[arg(long = "smtbmc-extra-arg", value_name = "ARG")] - pub smtbmc_extra_args: Vec, + pub smtbmc_extra_args: Vec, } impl FormalArgs { @@ -80,17 +77,21 @@ impl ToArgs for FormalArgs { formal_solver, smtbmc_extra_args, } = self; - for arg in sby_extra_args { - args.write_long_option_eq("sby-extra-arg", arg); - } - args.write_display_args([ + args.extend( + sby_extra_args + .iter() + .map(|v| format!("--sby-extra-arg={v}")), + ); + args.extend([ format_args!("--formal-mode={formal_mode}"), format_args!("--formal-depth={formal_depth}"), format_args!("--formal-solver={formal_solver}"), ]); - for arg in smtbmc_extra_args { - args.write_long_option_eq("smtbmc-extra-arg", arg); - } + args.extend( + smtbmc_extra_args + .iter() + .map(|v| format!("--smtbmc-extra-arg={v}")), + ); } } @@ -99,18 +100,18 @@ pub struct WriteSbyFileJobKind; #[derive(Clone, Debug, PartialEq, Eq, Hash, Deserialize, Serialize)] pub struct WriteSbyFileJob { - sby_extra_args: Interned<[Interned]>, + sby_extra_args: Interned<[Interned]>, formal_mode: FormalMode, formal_depth: u64, formal_solver: Interned, - smtbmc_extra_args: Interned<[Interned]>, - sby_file: Interned, - output_dir: Interned, - main_verilog_file: Interned, + smtbmc_extra_args: Interned<[Interned]>, + sby_file: Interned, + output_dir: Interned, + main_verilog_file: Interned, } impl WriteSbyFileJob { - pub fn sby_extra_args(&self) -> Interned<[Interned]> { + pub fn sby_extra_args(&self) -> Interned<[Interned]> { self.sby_extra_args } pub fn formal_mode(&self) -> FormalMode { @@ -122,24 +123,24 @@ impl WriteSbyFileJob { pub fn formal_solver(&self) -> Interned { self.formal_solver } - pub fn smtbmc_extra_args(&self) -> Interned<[Interned]> { + pub fn smtbmc_extra_args(&self) -> Interned<[Interned]> { self.smtbmc_extra_args } - pub fn sby_file(&self) -> Interned { + pub fn sby_file(&self) -> Interned { self.sby_file } - pub fn output_dir(&self) -> Interned { + pub fn output_dir(&self) -> Interned { self.output_dir } - pub fn main_verilog_file(&self) -> Interned { + pub fn main_verilog_file(&self) -> Interned { self.main_verilog_file } - fn write_sby( + fn write_sby( &self, - output: &mut OsString, - additional_files: &[Interned], + output: &mut W, + additional_files: &[Interned], main_module_name_id: NameId, - ) -> eyre::Result<()> { + ) -> Result, fmt::Error> { let Self { sby_extra_args: _, formal_mode, @@ -159,21 +160,23 @@ impl WriteSbyFileJob { \n\ [engines]\n\ smtbmc {formal_solver} -- --" - ) - .expect("writing to OsString can't fail"); + )?; for i in smtbmc_extra_args { - output.push(" "); - output.push(i); + output.write_str(" ")?; + output.write_str(i)?; } - output.push( + output.write_str( "\n\ \n\ [script]\n", - ); - for verilog_file in VerilogJob::all_verilog_files(*main_verilog_file, additional_files)? { - output.push("read_verilog -sv -formal \""); - output.push(verilog_file); - output.push("\"\n"); + )?; + let all_verilog_files = + match VerilogJob::all_verilog_files(*main_verilog_file, additional_files) { + Ok(v) => v, + Err(e) => return Ok(Err(e)), + }; + for verilog_file in all_verilog_files { + writeln!(output, "read_verilog -sv -formal \"{verilog_file}\"")?; } let circuit_name = crate::firrtl::get_circuit_name(main_module_name_id); // workaround for wires disappearing -- set `keep` on all wires @@ -183,9 +186,8 @@ impl WriteSbyFileJob { proc\n\ setattr -set keep 1 w:\\*\n\ prep", - ) - .expect("writing to OsString can't fail"); - Ok(()) + )?; + Ok(Ok(())) } } @@ -201,7 +203,6 @@ impl JobKind for WriteSbyFileJobKind { fn args_to_jobs( mut args: JobArgsAndDependencies, params: &JobParams, - global_params: &GlobalParams, ) -> eyre::Result> { args.dependencies .dependencies @@ -210,7 +211,7 @@ impl JobKind for WriteSbyFileJobKind { .additional_args .verilog_dialect .get_or_insert(VerilogDialect::Yosys); - args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| { + args.args_to_jobs_simple(params, |_kind, args, dependencies| { let FormalArgs { sby_extra_args, formal_mode, @@ -218,16 +219,18 @@ impl JobKind for WriteSbyFileJobKind { formal_solver, smtbmc_extra_args, } = args; - let base_job = dependencies.get_job::(); Ok(WriteSbyFileJob { - sby_extra_args: sby_extra_args.into_iter().map(Interned::from).collect(), + sby_extra_args: sby_extra_args.into_iter().map(str::intern_owned).collect(), formal_mode, formal_depth, - formal_solver: formal_solver.intern_deref(), - smtbmc_extra_args: smtbmc_extra_args.into_iter().map(Interned::from).collect(), - sby_file: base_job.file_with_ext("sby"), - output_dir: base_job.output_dir(), - main_verilog_file: dependencies.get_job::().main_verilog_file(), + formal_solver: str::intern_owned(formal_solver), + smtbmc_extra_args: smtbmc_extra_args + .into_iter() + .map(str::intern_owned) + .collect(), + sby_file: dependencies.base_job().file_with_ext("sby"), + output_dir: dependencies.base_job().output_dir(), + main_verilog_file: dependencies.job.job.main_verilog_file(), }) }) } @@ -235,12 +238,12 @@ impl JobKind for WriteSbyFileJobKind { fn inputs(self, _job: &Self::Job) -> Interned<[JobItemName]> { [JobItemName::DynamicPaths { source_job_name: VerilogJobKind.name(), - }] - .intern_slice() + }][..] + .intern() } fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { - [JobItemName::Path { path: job.sby_file }].intern_slice() + [JobItemName::Path { path: job.sby_file }][..].intern() } fn name(self) -> Interned { @@ -256,7 +259,6 @@ impl JobKind for WriteSbyFileJobKind { job: &Self::Job, inputs: &[JobItem], params: &JobParams, - _global_params: &GlobalParams, _acquired_job: &mut AcquiredJob, ) -> eyre::Result> { assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); @@ -264,16 +266,18 @@ impl JobKind for WriteSbyFileJobKind { unreachable!(); }; let additional_files = VerilogJob::unwrap_additional_files(additional_files); - let mut contents = OsString::new(); - job.write_sby( + let mut contents = String::new(); + match job.write_sby( &mut contents, additional_files, params.main_module().name_id(), - )?; - let path = job.sby_file; - std::fs::write(path, contents.as_encoded_bytes()) - .wrap_err_with(|| format!("writing {path:?} failed"))?; - Ok(vec![JobItem::Path { path }]) + ) { + Ok(result) => result?, + Err(fmt::Error) => unreachable!("writing to String can't fail"), + } + std::fs::write(job.sby_file, contents) + .wrap_err_with(|| format!("writing {} failed", job.sby_file))?; + Ok(vec![JobItem::Path { path: job.sby_file }]) } fn subcommand_hidden(self) -> bool { @@ -285,7 +289,7 @@ impl JobKind for WriteSbyFileJobKind { pub struct Formal { #[serde(flatten)] write_sby_file: WriteSbyFileJob, - sby_file_name: Interned, + sby_file_name: Interned, } impl fmt::Debug for Formal { @@ -338,11 +342,6 @@ impl ToArgs for FormalAdditionalArgs { impl ExternalCommand for Formal { type AdditionalArgs = FormalAdditionalArgs; type AdditionalJobData = Formal; - type BaseJobPosition = GetJobPositionDependencies< - GetJobPositionDependencies< - GetJobPositionDependencies<::BaseJobPosition>, - >, - >; type Dependencies = JobKindAndDependencies; type ExternalProgram = Symbiyosys; @@ -353,20 +352,17 @@ impl ExternalCommand for Formal { fn args_to_jobs( args: JobArgsAndDependencies>, params: &JobParams, - global_params: &GlobalParams, ) -> eyre::Result<( Self::AdditionalJobData, ::JobsAndKinds, )> { - args.args_to_jobs_external_simple(params, global_params, |args, dependencies| { + args.args_to_jobs_external_simple(params, |args, dependencies| { let FormalAdditionalArgs {} = args.additional_args; - let write_sby_file = dependencies.get_job::().clone(); Ok(Formal { - sby_file_name: write_sby_file - .sby_file() - .interned_file_name() - .expect("known to have file name"), - write_sby_file, + write_sby_file: dependencies.job.job.clone(), + sby_file_name: interned_known_utf8_method(dependencies.job.job.sby_file(), |v| { + v.file_name().expect("known to have file name") + }), }) }) } @@ -382,22 +378,22 @@ impl ExternalCommand for Formal { JobItemName::DynamicPaths { source_job_name: VerilogJobKind.name(), }, - ] - .intern_slice() + ][..] + .intern() } - fn output_paths(_job: &ExternalCommandJob) -> Interned<[Interned]> { + fn output_paths(_job: &ExternalCommandJob) -> Interned<[Interned]> { Interned::default() } fn command_line_args(job: &ExternalCommandJob, args: &mut W) { // args.write_str_arg("-j1"); // sby seems not to respect job count in parallel mode - args.write_arg("-f"); + args.write_str_arg("-f"); args.write_interned_arg(job.additional_job_data().sby_file_name); args.write_interned_args(job.additional_job_data().write_sby_file.sby_extra_args()); } - fn current_dir(job: &ExternalCommandJob) -> Option> { + fn current_dir(job: &ExternalCommandJob) -> Option> { Some(job.output_dir()) } diff --git a/crates/fayalite/src/build/graph.rs b/crates/fayalite/src/build/graph.rs index d81b282f..0cf54d51 100644 --- a/crates/fayalite/src/build/graph.rs +++ b/crates/fayalite/src/build/graph.rs @@ -2,11 +2,8 @@ // See Notices.txt for copyright information use crate::{ - build::{ - DynJob, GlobalParams, JobItem, JobItemName, JobParams, program_name_for_internal_jobs, - }, + build::{DynJob, JobItem, JobItemName, JobParams, program_name_for_internal_jobs}, intern::Interned, - platform::DynPlatform, util::{HashMap, HashSet, job_server::AcquiredJob}, }; use eyre::{ContextCompat, eyre}; @@ -19,12 +16,9 @@ use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error, ser::Se use std::{ cell::OnceCell, collections::{BTreeMap, BTreeSet, VecDeque}, - convert::Infallible, - ffi::OsStr, fmt::{self, Write}, panic, rc::Rc, - str::Utf8Error, sync::mpsc, thread::{self, ScopedJoinHandle}, }; @@ -144,8 +138,8 @@ impl<'a> fmt::Display for EscapeForUnixShell<'a> { } impl<'a> EscapeForUnixShell<'a> { - pub fn new(s: &'a (impl ?Sized + AsRef)) -> Self { - Self::from_bytes(s.as_ref().as_encoded_bytes()) + pub fn new(s: &'a str) -> Self { + Self::from_bytes(s.as_bytes()) } fn make_prefix(bytes: &[u8]) -> [u8; 3] { let mut prefix = [0; 3]; @@ -268,7 +262,7 @@ pub enum UnixMakefileEscapeKind { #[derive(Copy, Clone)] pub struct EscapeForUnixMakefile<'a> { - s: &'a OsStr, + s: &'a str, kind: UnixMakefileEscapeKind, } @@ -280,13 +274,9 @@ impl<'a> fmt::Debug for EscapeForUnixMakefile<'a> { impl<'a> fmt::Display for EscapeForUnixMakefile<'a> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.do_write( - f, - fmt::Write::write_str, - fmt::Write::write_char, - |_, _| Ok(()), - |_| unreachable!("already checked that the input causes no UTF-8 errors"), - ) + self.do_write(f, fmt::Write::write_str, fmt::Write::write_char, |_, _| { + Ok(()) + }) } } @@ -297,7 +287,6 @@ impl<'a> EscapeForUnixMakefile<'a> { write_str: impl Fn(&mut S, &str) -> Result<(), E>, write_char: impl Fn(&mut S, char) -> Result<(), E>, add_variable: impl Fn(&mut S, &'static str) -> Result<(), E>, - utf8_error: impl Fn(Utf8Error) -> E, ) -> Result<(), E> { let escape_recipe_char = |c| match c { '$' => write_str(state, "$$"), @@ -307,30 +296,24 @@ impl<'a> EscapeForUnixMakefile<'a> { _ => write_char(state, c), }; match self.kind { - UnixMakefileEscapeKind::NonRecipe => str::from_utf8(self.s.as_encoded_bytes()) - .map_err(&utf8_error)? - .chars() - .try_for_each(|c| match c { - '=' => { - add_variable(state, "EQUALS = =")?; - write_str(state, "$(EQUALS)") - } - ';' => panic!("can't escape a semicolon (;) for Unix Makefile"), - '$' => write_str(state, "$$"), - '\\' | ' ' | '#' | ':' | '%' | '*' | '?' | '[' | ']' | '~' => { - write_char(state, '\\')?; - write_char(state, c) - } - '\0'..='\x1F' | '\x7F' => { - panic!("can't escape a control character for Unix Makefile: {c:?}"); - } - _ => write_char(state, c), - }), + UnixMakefileEscapeKind::NonRecipe => self.s.chars().try_for_each(|c| match c { + '=' => { + add_variable(state, "EQUALS = =")?; + write_str(state, "$(EQUALS)") + } + ';' => panic!("can't escape a semicolon (;) for Unix Makefile"), + '$' => write_str(state, "$$"), + '\\' | ' ' | '#' | ':' | '%' | '*' | '?' | '[' | ']' | '~' => { + write_char(state, '\\')?; + write_char(state, c) + } + '\0'..='\x1F' | '\x7F' => { + panic!("can't escape a control character for Unix Makefile: {c:?}"); + } + _ => write_char(state, c), + }), UnixMakefileEscapeKind::RecipeWithoutShellEscaping => { - str::from_utf8(self.s.as_encoded_bytes()) - .map_err(&utf8_error)? - .chars() - .try_for_each(escape_recipe_char) + self.s.chars().try_for_each(escape_recipe_char) } UnixMakefileEscapeKind::RecipeWithShellEscaping => { EscapeForUnixShell::new(self.s).try_for_each(escape_recipe_char) @@ -338,23 +321,21 @@ impl<'a> EscapeForUnixMakefile<'a> { } } pub fn new( - s: &'a (impl ?Sized + AsRef), + s: &'a str, kind: UnixMakefileEscapeKind, needed_variables: &mut BTreeSet<&'static str>, - ) -> Result { - let s = s.as_ref(); + ) -> Self { let retval = Self { s, kind }; - retval.do_write( + let Ok(()) = retval.do_write( needed_variables, |_, _| Ok(()), |_, _| Ok(()), - |needed_variables, variable| { + |needed_variables, variable| -> Result<(), std::convert::Infallible> { needed_variables.insert(variable); Ok(()) }, - |e| e, - )?; - Ok(retval) + ); + retval } } @@ -492,23 +473,17 @@ impl JobGraph { Err(e) => panic!("error: {e}"), } } - pub fn to_unix_makefile( - &self, - platform: Option<&DynPlatform>, - extra_args: &[Interned], - ) -> Result { + pub fn to_unix_makefile(&self, extra_args: &[Interned]) -> String { self.to_unix_makefile_with_internal_program_prefix( &[program_name_for_internal_jobs()], - platform, extra_args, ) } pub fn to_unix_makefile_with_internal_program_prefix( &self, - internal_program_prefix: &[Interned], - platform: Option<&DynPlatform>, - extra_args: &[Interned], - ) -> Result { + internal_program_prefix: &[Interned], + extra_args: &[Interned], + ) -> String { let mut retval = String::new(); let mut needed_variables = BTreeSet::new(); let mut phony_targets = BTreeSet::new(); @@ -527,10 +502,10 @@ impl JobGraph { retval, "{} ", EscapeForUnixMakefile::new( - &str::from_utf8(path.as_os_str().as_encoded_bytes())?, + &path, UnixMakefileEscapeKind::NonRecipe, &mut needed_variables - )? + ) ); } JobItemName::DynamicPaths { source_job_name } => { @@ -541,7 +516,7 @@ impl JobGraph { &source_job_name, UnixMakefileEscapeKind::NonRecipe, &mut needed_variables - )? + ) ); phony_targets.insert(Interned::into_inner(source_job_name)); } @@ -560,10 +535,10 @@ impl JobGraph { retval, " {}", EscapeForUnixMakefile::new( - &str::from_utf8(path.as_os_str().as_encoded_bytes())?, + &path, UnixMakefileEscapeKind::NonRecipe, &mut needed_variables - )? + ) ); } JobItemName::DynamicPaths { source_job_name } => { @@ -574,30 +549,26 @@ impl JobGraph { &source_job_name, UnixMakefileEscapeKind::NonRecipe, &mut needed_variables - )? + ) ); phony_targets.insert(Interned::into_inner(source_job_name)); } } } retval.push_str("\n\t"); - job.command_params_with_internal_program_prefix( - internal_program_prefix, - platform, - extra_args, - ) - .to_unix_shell_line(&mut retval, |arg, output| { - write_str!( - output, - "{}", - EscapeForUnixMakefile::new( - arg, - UnixMakefileEscapeKind::RecipeWithShellEscaping, - &mut needed_variables - )? - ); - Ok(()) - })?; + job.command_params_with_internal_program_prefix(internal_program_prefix, extra_args) + .to_unix_shell_line(&mut retval, |arg, output| { + write!( + output, + "{}", + EscapeForUnixMakefile::new( + arg, + UnixMakefileEscapeKind::RecipeWithShellEscaping, + &mut needed_variables + ) + ) + }) + .expect("writing to String never fails"); retval.push_str("\n\n"); } if !phony_targets.is_empty() { @@ -610,7 +581,7 @@ impl JobGraph { phony_target, UnixMakefileEscapeKind::NonRecipe, &mut needed_variables - )? + ) ); } retval.push_str("\n"); @@ -621,24 +592,18 @@ impl JobGraph { &String::from_iter(needed_variables.into_iter().map(|v| format!("{v}\n"))), ); } - Ok(retval) + retval } - pub fn to_unix_shell_script( - &self, - platform: Option<&DynPlatform>, - extra_args: &[Interned], - ) -> String { + pub fn to_unix_shell_script(&self, extra_args: &[Interned]) -> String { self.to_unix_shell_script_with_internal_program_prefix( &[program_name_for_internal_jobs()], - platform, extra_args, ) } pub fn to_unix_shell_script_with_internal_program_prefix( &self, - internal_program_prefix: &[Interned], - platform: Option<&DynPlatform>, - extra_args: &[Interned], + internal_program_prefix: &[Interned], + extra_args: &[Interned], ) -> String { let mut retval = String::from( "#!/bin/sh\n\ @@ -648,21 +613,16 @@ impl JobGraph { let JobGraphNode::Job(job) = &self.graph[node_id] else { continue; }; - let Ok(()) = job - .command_params_with_internal_program_prefix( - internal_program_prefix, - platform, - extra_args, - ) - .to_unix_shell_line(&mut retval, |arg, output| -> Result<(), Infallible> { - write_str!(output, "{}", EscapeForUnixShell::new(&arg)); - Ok(()) - }); + job.command_params_with_internal_program_prefix(internal_program_prefix, extra_args) + .to_unix_shell_line(&mut retval, |arg, output| { + write!(output, "{}", EscapeForUnixShell::new(&arg)) + }) + .expect("writing to String never fails"); retval.push_str("\n"); } retval } - pub fn run(&self, params: &JobParams, global_params: &GlobalParams) -> eyre::Result<()> { + pub fn run(&self, params: &JobParams) -> eyre::Result<()> { // use scope to auto-join threads on errors thread::scope(|scope| { struct WaitingJobState { @@ -748,18 +708,13 @@ impl JobGraph { job: DynJob, inputs: Vec, params: &'a JobParams, - global_params: &'a GlobalParams, acquired_job: AcquiredJob, finished_jobs_sender: mpsc::Sender<::NodeId>, } impl RunningJobInThread<'_> { fn run(mut self) -> eyre::Result> { - self.job.run( - &self.inputs, - self.params, - self.global_params, - &mut self.acquired_job, - ) + self.job + .run(&self.inputs, self.params, &mut self.acquired_job) } } impl Drop for RunningJobInThread<'_> { @@ -777,7 +732,6 @@ impl JobGraph { }) }))?, params, - global_params, acquired_job: AcquiredJob::acquire()?, finished_jobs_sender: finished_jobs_sender.clone(), }; diff --git a/crates/fayalite/src/build/registry.rs b/crates/fayalite/src/build/registry.rs index bbd9f2c5..ccb401fc 100644 --- a/crates/fayalite/src/build/registry.rs +++ b/crates/fayalite/src/build/registry.rs @@ -4,9 +4,10 @@ use crate::{ build::{DynJobKind, JobKind, built_in_job_kinds}, intern::Interned, - util::InternedStrCompareAsStr, }; use std::{ + borrow::Borrow, + cmp::Ordering, collections::BTreeMap, fmt, sync::{Arc, OnceLock, RwLock, RwLockWriteGuard}, @@ -22,6 +23,33 @@ impl DynJobKind { } } +#[derive(Copy, Clone, PartialEq, Eq)] +struct InternedStrCompareAsStr(Interned); + +impl fmt::Debug for InternedStrCompareAsStr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.0.fmt(f) + } +} + +impl Ord for InternedStrCompareAsStr { + fn cmp(&self, other: &Self) -> Ordering { + str::cmp(&self.0, &other.0) + } +} + +impl PartialOrd for InternedStrCompareAsStr { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Borrow for InternedStrCompareAsStr { + fn borrow(&self) -> &str { + &self.0 + } +} + #[derive(Clone, Debug)] struct JobKindRegistry { job_kinds: BTreeMap, diff --git a/crates/fayalite/src/vendor.rs b/crates/fayalite/src/build/vendor.rs similarity index 64% rename from crates/fayalite/src/vendor.rs rename to crates/fayalite/src/build/vendor.rs index cdf302d3..56297cf9 100644 --- a/crates/fayalite/src/vendor.rs +++ b/crates/fayalite/src/build/vendor.rs @@ -6,7 +6,3 @@ pub mod xilinx; pub(crate) fn built_in_job_kinds() -> impl IntoIterator { xilinx::built_in_job_kinds() } - -pub(crate) fn built_in_platforms() -> impl IntoIterator { - xilinx::built_in_platforms() -} diff --git a/crates/fayalite/src/build/vendor/xilinx.rs b/crates/fayalite/src/build/vendor/xilinx.rs new file mode 100644 index 00000000..71889e08 --- /dev/null +++ b/crates/fayalite/src/build/vendor/xilinx.rs @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::intern::Interned; + +pub mod yosys_nextpnr_prjxray; + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct XdcIOStandardAnnotation { + pub value: Interned, +} + +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct XdcLocationAnnotation { + pub location: Interned, +} + +pub(crate) fn built_in_job_kinds() -> impl IntoIterator { + yosys_nextpnr_prjxray::built_in_job_kinds() +} diff --git a/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs b/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs new file mode 100644 index 00000000..3db256a6 --- /dev/null +++ b/crates/fayalite/src/build/vendor/xilinx/yosys_nextpnr_prjxray.rs @@ -0,0 +1,910 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{ + annotations::Annotation, + build::{ + CommandParams, DynJobKind, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, + JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, ToArgs, WriteArgs, + external::{ + ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, + }, + interned_known_utf8_method, interned_known_utf8_path_buf_method, + vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation}, + verilog::{VerilogDialect, VerilogJob, VerilogJobKind}, + }, + bundle::Bundle, + firrtl::{ScalarizedModuleABI, ScalarizedModuleABIAnnotations, ScalarizedModuleABIPort}, + intern::{Intern, Interned}, + module::{Module, NameId}, + prelude::JobParams, + util::job_server::AcquiredJob, +}; +use clap::ValueEnum; +use eyre::Context; +use serde::{Deserialize, Serialize}; +use std::{fmt, ops::ControlFlow}; + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)] +pub struct YosysNextpnrXrayWriteYsFileJobKind; + +#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] +pub struct YosysNextpnrXrayWriteYsFileArgs {} + +impl ToArgs for YosysNextpnrXrayWriteYsFileArgs { + fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) { + let Self {} = self; + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub struct YosysNextpnrXrayWriteYsFile { + main_verilog_file: Interned, + ys_file: Interned, + json_file: Interned, + json_file_name: Interned, +} + +impl YosysNextpnrXrayWriteYsFile { + pub fn main_verilog_file(&self) -> Interned { + self.main_verilog_file + } + pub fn ys_file(&self) -> Interned { + self.ys_file + } + pub fn json_file(&self) -> Interned { + self.json_file + } + pub fn json_file_name(&self) -> Interned { + self.json_file_name + } + fn write_ys( + &self, + output: &mut W, + additional_files: &[Interned], + main_module_name_id: NameId, + ) -> Result, fmt::Error> { + let Self { + main_verilog_file, + ys_file: _, + json_file: _, + json_file_name, + } = self; + let all_verilog_files = + match VerilogJob::all_verilog_files(*main_verilog_file, additional_files) { + Ok(v) => v, + Err(e) => return Ok(Err(e)), + }; + for verilog_file in all_verilog_files { + writeln!(output, "read_verilog -sv \"{verilog_file}\"")?; + } + let circuit_name = crate::firrtl::get_circuit_name(main_module_name_id); + writeln!( + output, + "synth_xilinx -flatten -abc9 -nobram -arch xc7 -top {circuit_name}" + )?; + writeln!(output, "write_json \"{json_file_name}\"")?; + Ok(Ok(())) + } +} + +impl JobKind for YosysNextpnrXrayWriteYsFileJobKind { + type Args = YosysNextpnrXrayWriteYsFileArgs; + type Job = YosysNextpnrXrayWriteYsFile; + type Dependencies = JobKindAndDependencies; + + fn dependencies(self) -> Self::Dependencies { + Default::default() + } + + fn args_to_jobs( + mut args: JobArgsAndDependencies, + params: &JobParams, + ) -> eyre::Result> { + args.dependencies + .dependencies + .args + .args + .additional_args + .verilog_dialect + .get_or_insert(VerilogDialect::Yosys); + args.args_to_jobs_simple(params, |_kind, args, dependencies| { + let YosysNextpnrXrayWriteYsFileArgs {} = args; + let json_file = dependencies.base_job().file_with_ext("json"); + Ok(YosysNextpnrXrayWriteYsFile { + main_verilog_file: dependencies.job.job.main_verilog_file(), + ys_file: dependencies.base_job().file_with_ext("ys"), + json_file, + json_file_name: interned_known_utf8_method(json_file, |v| { + v.file_name().expect("known to have file name") + }), + }) + }) + } + + fn inputs(self, _job: &Self::Job) -> Interned<[JobItemName]> { + [JobItemName::DynamicPaths { + source_job_name: VerilogJobKind.name(), + }][..] + .intern() + } + + fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + [JobItemName::Path { path: job.ys_file }][..].intern() + } + + fn name(self) -> Interned { + "yosys-nextpnr-xray-write-ys-file".intern() + } + + fn external_command_params(self, _job: &Self::Job) -> Option { + None + } + + fn run( + self, + job: &Self::Job, + inputs: &[JobItem], + params: &JobParams, + _acquired_job: &mut AcquiredJob, + ) -> eyre::Result> { + assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); + let [additional_files] = inputs else { + unreachable!(); + }; + let additional_files = VerilogJob::unwrap_additional_files(additional_files); + let mut contents = String::new(); + match job.write_ys( + &mut contents, + additional_files, + params.main_module().name_id(), + ) { + Ok(result) => result?, + Err(fmt::Error) => unreachable!("writing to String can't fail"), + } + std::fs::write(job.ys_file, contents) + .wrap_err_with(|| format!("writing {} failed", job.ys_file))?; + Ok(vec![JobItem::Path { path: job.ys_file }]) + } + + fn subcommand_hidden(self) -> bool { + true + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] +pub struct YosysNextpnrXraySynthArgs {} + +impl ToArgs for YosysNextpnrXraySynthArgs { + fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) { + let Self {} = self; + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] +pub struct YosysNextpnrXraySynth { + #[serde(flatten)] + write_ys_file: YosysNextpnrXrayWriteYsFile, + ys_file_name: Interned, +} + +impl fmt::Debug for YosysNextpnrXraySynth { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let Self { + write_ys_file: + YosysNextpnrXrayWriteYsFile { + main_verilog_file, + ys_file, + json_file, + json_file_name, + }, + ys_file_name, + } = self; + f.debug_struct("YosysNextpnrXraySynth") + .field("main_verilog_file", main_verilog_file) + .field("ys_file", ys_file) + .field("ys_file_name", ys_file_name) + .field("json_file", json_file) + .field("json_file_name", json_file_name) + .finish() + } +} + +impl YosysNextpnrXraySynth { + pub fn main_verilog_file(&self) -> Interned { + self.write_ys_file.main_verilog_file() + } + pub fn ys_file(&self) -> Interned { + self.write_ys_file.ys_file() + } + pub fn ys_file_name(&self) -> Interned { + self.ys_file_name + } + pub fn json_file(&self) -> Interned { + self.write_ys_file.json_file() + } + pub fn json_file_name(&self) -> Interned { + self.write_ys_file.json_file_name() + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub struct Yosys; + +impl ExternalProgramTrait for Yosys { + fn default_program_name() -> Interned { + "yosys".intern() + } +} + +impl ExternalCommand for YosysNextpnrXraySynth { + type AdditionalArgs = YosysNextpnrXraySynthArgs; + type AdditionalJobData = Self; + type Dependencies = JobKindAndDependencies; + type ExternalProgram = Yosys; + + fn dependencies() -> Self::Dependencies { + Default::default() + } + + fn args_to_jobs( + args: JobArgsAndDependencies>, + params: &JobParams, + ) -> eyre::Result<( + Self::AdditionalJobData, + ::JobsAndKinds, + )> { + args.args_to_jobs_external_simple(params, |args, dependencies| { + let YosysNextpnrXraySynthArgs {} = args.additional_args; + Ok(Self { + write_ys_file: dependencies.job.job.clone(), + ys_file_name: interned_known_utf8_method(dependencies.job.job.ys_file(), |v| { + v.file_name().expect("known to have file name") + }), + }) + }) + } + + fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]> { + [ + JobItemName::Path { + path: job.additional_job_data().ys_file(), + }, + JobItemName::Path { + path: job.additional_job_data().main_verilog_file(), + }, + JobItemName::DynamicPaths { + source_job_name: VerilogJobKind.name(), + }, + ][..] + .intern() + } + + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { + [job.additional_job_data().json_file()][..].intern() + } + + fn command_line_args(job: &ExternalCommandJob, args: &mut W) { + args.write_str_arg("-s"); + args.write_interned_arg(job.additional_job_data().ys_file_name()); + } + + fn current_dir(job: &ExternalCommandJob) -> Option> { + Some(job.output_dir()) + } + + fn job_kind_name() -> Interned { + "yosys-nextpnr-xray-synth".intern() + } + + fn subcommand_hidden() -> bool { + true + } +} + +#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)] +pub struct YosysNextpnrXrayWriteXdcFileJobKind; + +#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] +pub struct YosysNextpnrXrayWriteXdcFileArgs {} + +impl ToArgs for YosysNextpnrXrayWriteXdcFileArgs { + fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) { + let Self {} = self; + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub struct YosysNextpnrXrayWriteXdcFile { + firrtl_export_options: crate::firrtl::ExportOptions, + output_dir: Interned, + xdc_file: Interned, +} + +struct WriteXdcContentsError(eyre::Report); + +impl From for WriteXdcContentsError { + fn from(v: eyre::Report) -> Self { + Self(v) + } +} + +impl From for WriteXdcContentsError { + fn from(_v: fmt::Error) -> Self { + unreachable!("String write can't fail") + } +} + +fn tcl_escape(s: impl AsRef) -> String { + let s = s.as_ref(); + let mut retval = String::with_capacity(s.len().saturating_add(2)); + retval.push('"'); + for ch in s.chars() { + if let '$' | '\\' | '[' = ch { + retval.push('\\'); + } + retval.push(ch); + } + retval.push('"'); + retval +} + +impl YosysNextpnrXrayWriteXdcFile { + fn write_xdc_contents_for_port_and_annotations( + &self, + output: &mut impl fmt::Write, + port: &ScalarizedModuleABIPort, + annotations: ScalarizedModuleABIAnnotations<'_>, + ) -> Result<(), WriteXdcContentsError> { + for annotation in annotations { + match annotation.annotation() { + Annotation::DontTouch(_) + | Annotation::SVAttribute(_) + | Annotation::BlackBoxInline(_) + | Annotation::BlackBoxPath(_) + | Annotation::DocString(_) + | Annotation::CustomFirrtl(_) => {} + Annotation::XdcLocation(XdcLocationAnnotation { location }) => writeln!( + output, + "set_property LOC {} [get_ports {}]", + tcl_escape(location), + tcl_escape(port.scalarized_name()) + )?, + Annotation::XdcIOStandard(XdcIOStandardAnnotation { value }) => writeln!( + output, + "set_property IOSTANDARD {} [get_ports {}]", + tcl_escape(value), + tcl_escape(port.scalarized_name()) + )?, + } + } + Ok(()) + } + fn write_xdc_contents( + &self, + output: &mut String, + top_module: &Module, + ) -> eyre::Result<()> { + let scalarized_module_abi = + ScalarizedModuleABI::new(top_module, self.firrtl_export_options) + .map_err(eyre::Report::from)?; + match scalarized_module_abi.for_each_port_and_annotations(|port, annotations| { + match self.write_xdc_contents_for_port_and_annotations(output, port, annotations) { + Ok(()) => ControlFlow::Continue(()), + Err(e) => ControlFlow::Break(e), + } + }) { + ControlFlow::Continue(()) => Ok(()), + ControlFlow::Break(e) => Err(e.0), + } + } +} + +impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind { + type Args = YosysNextpnrXrayWriteXdcFileArgs; + type Job = YosysNextpnrXrayWriteXdcFile; + type Dependencies = JobKindAndDependencies>; + + fn dependencies(self) -> Self::Dependencies { + Default::default() + } + + fn args_to_jobs( + args: JobArgsAndDependencies, + params: &JobParams, + ) -> eyre::Result> { + let firrtl_export_options = args + .dependencies + .dependencies + .dependencies + .dependencies + .dependencies + .args + .args + .export_options; + args.args_to_jobs_simple(params, |_kind, args, dependencies| { + let YosysNextpnrXrayWriteXdcFileArgs {} = args; + Ok(YosysNextpnrXrayWriteXdcFile { + firrtl_export_options, + output_dir: dependencies.base_job().output_dir(), + xdc_file: dependencies.base_job().file_with_ext("xdc"), + }) + }) + } + + fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + [JobItemName::Path { + path: job.output_dir, + }][..] + .intern() + } + + fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { + [JobItemName::Path { path: job.xdc_file }][..].intern() + } + + fn name(self) -> Interned { + "yosys-nextpnr-xray-write-xdc-file".intern() + } + + fn external_command_params(self, _job: &Self::Job) -> Option { + None + } + + fn run( + self, + job: &Self::Job, + inputs: &[JobItem], + params: &JobParams, + _acquired_job: &mut AcquiredJob, + ) -> eyre::Result> { + assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); + let mut xdc = String::new(); + job.write_xdc_contents(&mut xdc, params.main_module())?; + // TODO: create actual .xdc from input module + std::fs::write( + job.xdc_file, + r"# autogenerated +set_property LOC G6 [get_ports led] +set_property IOSTANDARD LVCMOS33 [get_ports led] +set_property LOC E3 [get_ports clk] +set_property IOSTANDARD LVCMOS33 [get_ports clk] +set_property LOC C2 [get_ports rst] +set_property IOSTANDARD LVCMOS33 [get_ports rst] +", + )?; + Ok(vec![JobItem::Path { path: job.xdc_file }]) + } + + fn subcommand_hidden(self) -> bool { + true + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub struct NextpnrXilinx; + +impl ExternalProgramTrait for NextpnrXilinx { + fn default_program_name() -> Interned { + "nextpnr-xilinx".intern() + } +} + +macro_rules! make_device_enum { + ($vis:vis enum $Device:ident { + $( + #[ + name = $name:literal, + xray_part = $xray_part:literal, + xray_device = $xray_device:literal, + xray_family = $xray_family:literal, + ] + $variant:ident, + )* + }) => { + #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, ValueEnum)] + $vis enum $Device { + $( + #[value(name = $name, alias = $xray_part)] + $variant, + )* + } + + impl $Device { + $vis fn as_str(self) -> &'static str { + match self { + $(Self::$variant => $name,)* + } + } + $vis fn xray_part(self) -> &'static str { + match self { + $(Self::$variant => $xray_part,)* + } + } + $vis fn xray_device(self) -> &'static str { + match self { + $(Self::$variant => $xray_device,)* + } + } + $vis fn xray_family(self) -> &'static str { + match self { + $(Self::$variant => $xray_family,)* + } + } + } + + struct DeviceVisitor; + + impl<'de> serde::de::Visitor<'de> for DeviceVisitor { + type Value = $Device; + + fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("a Xilinx device string") + } + + fn visit_str(self, v: &str) -> Result + where + E: serde::de::Error, + { + match $Device::from_str(v, false) { + Ok(v) => Ok(v), + Err(_) => Err(E::invalid_value(serde::de::Unexpected::Str(v), &self)), + } + } + + fn visit_bytes(self, v: &[u8]) -> Result + where + E: serde::de::Error, + { + match str::from_utf8(v).ok().and_then(|v| $Device::from_str(v, false).ok()) { + Some(v) => Ok(v), + None => Err(E::invalid_value(serde::de::Unexpected::Bytes(v), &self)), + } + } + } + + impl<'de> Deserialize<'de> for $Device { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_string(DeviceVisitor) + } + } + + impl Serialize for $Device { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + self.as_str().serialize(serializer) + } + } + }; +} + +make_device_enum! { + pub enum Device { + #[ + name = "xc7a35ticsg324-1L", + xray_part = "xc7a35tcsg324-1", + xray_device = "xc7a35t", + xray_family = "artix7", + ] + Xc7a35ticsg324_1l, + #[ + name = "xc7a100ticsg324-1L", + xray_part = "xc7a100tcsg324-1", + xray_device = "xc7a100t", + xray_family = "artix7", + ] + Xc7a100ticsg324_1l, + } +} + +impl fmt::Display for Device { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str(self.as_str()) + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] +pub struct YosysNextpnrXrayRunNextpnrArgs { + #[arg(long, env = "CHIPDB_DIR", value_hint = clap::ValueHint::DirPath)] + pub nextpnr_xilinx_chipdb_dir: String, + #[arg(long)] + pub device: Device, + #[arg(long, default_value_t = 0)] + pub nextpnr_xilinx_seed: i32, +} + +impl ToArgs for YosysNextpnrXrayRunNextpnrArgs { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { + let Self { + nextpnr_xilinx_chipdb_dir, + device, + nextpnr_xilinx_seed, + } = self; + args.write_args([ + format_args!("--nextpnr-xilinx-chipdb-dir={nextpnr_xilinx_chipdb_dir}"), + format_args!("--device={device}"), + format_args!("--nextpnr-xilinx-seed={nextpnr_xilinx_seed}"), + ]); + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub struct YosysNextpnrXrayRunNextpnr { + nextpnr_xilinx_chipdb_dir: Interned, + device: Device, + nextpnr_xilinx_seed: i32, + xdc_file: Interned, + xdc_file_name: Interned, + json_file: Interned, + json_file_name: Interned, + routed_json_file: Interned, + routed_json_file_name: Interned, + fasm_file: Interned, + fasm_file_name: Interned, +} + +impl YosysNextpnrXrayRunNextpnr { + fn chipdb_file(&self) -> Interned { + interned_known_utf8_path_buf_method(self.nextpnr_xilinx_chipdb_dir, |chipdb_dir| { + let mut retval = chipdb_dir.join(self.device.xray_device()); + retval.set_extension("bin"); + retval + }) + } +} + +impl ExternalCommand for YosysNextpnrXrayRunNextpnr { + type AdditionalArgs = YosysNextpnrXrayRunNextpnrArgs; + type AdditionalJobData = Self; + type Dependencies = JobKindAndDependencies; + type ExternalProgram = NextpnrXilinx; + + fn dependencies() -> Self::Dependencies { + Default::default() + } + + fn args_to_jobs( + args: JobArgsAndDependencies>, + params: &JobParams, + ) -> eyre::Result<( + Self::AdditionalJobData, + ::JobsAndKinds, + )> { + args.args_to_jobs_external_simple(params, |args, dependencies| { + let YosysNextpnrXrayRunNextpnrArgs { + nextpnr_xilinx_chipdb_dir, + device, + nextpnr_xilinx_seed, + } = args.additional_args; + let xdc_file = dependencies.job.job.xdc_file; + let routed_json_file = dependencies.base_job().file_with_ext("routed.json"); + let fasm_file = dependencies.base_job().file_with_ext("fasm"); + Ok(Self { + nextpnr_xilinx_chipdb_dir: str::intern_owned(nextpnr_xilinx_chipdb_dir), + device, + nextpnr_xilinx_seed, + xdc_file, + xdc_file_name: interned_known_utf8_method(xdc_file, |v| { + v.file_name().expect("known to have file name") + }), + json_file: dependencies + .dependencies + .job + .job + .additional_job_data() + .json_file(), + json_file_name: dependencies + .dependencies + .job + .job + .additional_job_data() + .json_file_name(), + routed_json_file, + routed_json_file_name: interned_known_utf8_method(routed_json_file, |v| { + v.file_name().expect("known to have file name") + }), + fasm_file, + fasm_file_name: interned_known_utf8_method(fasm_file, |v| { + v.file_name().expect("known to have file name") + }), + }) + }) + } + + fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]> { + [ + JobItemName::Path { + path: job.additional_job_data().json_file, + }, + JobItemName::Path { + path: job.additional_job_data().xdc_file, + }, + ][..] + .intern() + } + + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { + [ + job.additional_job_data().routed_json_file, + job.additional_job_data().fasm_file, + ][..] + .intern() + } + + fn command_line_args(job: &ExternalCommandJob, args: &mut W) { + let job_data @ YosysNextpnrXrayRunNextpnr { + nextpnr_xilinx_seed, + xdc_file_name, + json_file_name, + routed_json_file_name, + fasm_file_name, + .. + } = job.additional_job_data(); + args.write_args([ + format_args!("--chipdb={}", job_data.chipdb_file()), + format_args!("--xdc={xdc_file_name}"), + format_args!("--json={json_file_name}"), + format_args!("--write={routed_json_file_name}"), + format_args!("--fasm={fasm_file_name}"), + format_args!("--seed={nextpnr_xilinx_seed}"), + ]); + } + + fn current_dir(job: &ExternalCommandJob) -> Option> { + Some(job.output_dir()) + } + + fn job_kind_name() -> Interned { + "yosys-nextpnr-xray-run-nextpnr".intern() + } + + fn subcommand_hidden() -> bool { + true + } +} + +#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] +pub struct Xcfasm; + +impl ExternalProgramTrait for Xcfasm { + fn default_program_name() -> Interned { + "xcfasm".intern() + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] +pub struct YosysNextpnrXrayArgs { + #[arg(long, env = "DB_DIR", value_hint = clap::ValueHint::DirPath)] + pub prjxray_db_dir: String, +} + +impl ToArgs for YosysNextpnrXrayArgs { + fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { + let Self { prjxray_db_dir } = self; + args.write_arg(format_args!("--prjxray-db-dir={prjxray_db_dir}")); + } +} + +#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] +pub struct YosysNextpnrXray { + prjxray_db_dir: Interned, + device: Device, + fasm_file: Interned, + fasm_file_name: Interned, + frames_file: Interned, + frames_file_name: Interned, + bit_file: Interned, + bit_file_name: Interned, +} + +impl YosysNextpnrXray { + fn db_root(&self) -> Interned { + interned_known_utf8_path_buf_method(self.prjxray_db_dir, |prjxray_db_dir| { + prjxray_db_dir.join(self.device.xray_family()) + }) + } + fn part_file(&self) -> Interned { + interned_known_utf8_path_buf_method(self.prjxray_db_dir, |prjxray_db_dir| { + let mut retval = prjxray_db_dir.join(self.device.xray_family()); + retval.push(self.device.xray_part()); + retval.push("part.yaml"); + retval + }) + } +} + +impl ExternalCommand for YosysNextpnrXray { + type AdditionalArgs = YosysNextpnrXrayArgs; + type AdditionalJobData = Self; + type Dependencies = JobKindAndDependencies>; + type ExternalProgram = Xcfasm; + + fn dependencies() -> Self::Dependencies { + Default::default() + } + + fn args_to_jobs( + args: JobArgsAndDependencies>, + params: &JobParams, + ) -> eyre::Result<( + Self::AdditionalJobData, + ::JobsAndKinds, + )> { + args.args_to_jobs_external_simple(params, |args, dependencies| { + let YosysNextpnrXrayArgs { prjxray_db_dir } = args.additional_args; + let frames_file = dependencies.base_job().file_with_ext("frames"); + let bit_file = dependencies.base_job().file_with_ext("bit"); + Ok(Self { + prjxray_db_dir: str::intern_owned(prjxray_db_dir), + device: dependencies.job.job.additional_job_data().device, + fasm_file: dependencies.job.job.additional_job_data().fasm_file, + fasm_file_name: dependencies.job.job.additional_job_data().fasm_file_name, + frames_file, + frames_file_name: interned_known_utf8_method(frames_file, |v| { + v.file_name().expect("known to have file name") + }), + bit_file, + bit_file_name: interned_known_utf8_method(bit_file, |v| { + v.file_name().expect("known to have file name") + }), + }) + }) + } + + fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]> { + [JobItemName::Path { + path: job.additional_job_data().fasm_file, + }][..] + .intern() + } + + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { + [ + job.additional_job_data().frames_file, + job.additional_job_data().bit_file, + ][..] + .intern() + } + + fn command_line_args(job: &ExternalCommandJob, args: &mut W) { + let job_data @ YosysNextpnrXray { + device, + fasm_file_name, + frames_file_name, + bit_file_name, + .. + } = job.additional_job_data(); + args.write_args([ + format_args!("--sparse"), + format_args!("--db-root={}", job_data.db_root()), + format_args!("--part={}", device.xray_part()), + format_args!("--part_file={}", job_data.part_file()), + format_args!("--fn_in={fasm_file_name}"), + format_args!("--frm_out={frames_file_name}"), + format_args!("--bit_out={bit_file_name}"), + ]); + } + + fn current_dir(job: &ExternalCommandJob) -> Option> { + Some(job.output_dir()) + } + + fn job_kind_name() -> Interned { + "yosys-nextpnr-xray".intern() + } +} + +pub(crate) fn built_in_job_kinds() -> impl IntoIterator { + [ + DynJobKind::new(YosysNextpnrXrayWriteYsFileJobKind), + DynJobKind::new(ExternalCommandJobKind::::new()), + DynJobKind::new(YosysNextpnrXrayWriteXdcFileJobKind), + DynJobKind::new(ExternalCommandJobKind::::new()), + DynJobKind::new(ExternalCommandJobKind::::new()), + ] +} diff --git a/crates/fayalite/src/build/verilog.rs b/crates/fayalite/src/build/verilog.rs index 7ce77ecb..39334f2c 100644 --- a/crates/fayalite/src/build/verilog.rs +++ b/crates/fayalite/src/build/verilog.rs @@ -3,25 +3,22 @@ use crate::{ build::{ - BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GetJobPositionJob, - GlobalParams, JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, - JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs, + CommandParams, DynJobKind, GetBaseJob, JobAndDependencies, JobArgsAndDependencies, + JobDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, + WriteArgs, external::{ ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, }, - firrtl::{Firrtl, FirrtlJobKind}, + firrtl::FirrtlJobKind, + interned_known_utf8_method, interned_known_utf8_path_buf_method, }, - intern::{Intern, InternSlice, Interned}, + intern::{Intern, Interned}, util::job_server::AcquiredJob, }; use clap::Args; use eyre::{Context, bail}; use serde::{Deserialize, Serialize}; -use std::{ - ffi::{OsStr, OsString}, - fmt, mem, - path::Path, -}; +use std::{fmt, mem}; /// based on [LLVM Circt's recommended lowering options][lowering-options] /// @@ -73,7 +70,7 @@ impl VerilogDialect { #[non_exhaustive] pub struct UnadjustedVerilogArgs { #[arg(long = "firtool-extra-arg", value_name = "ARG")] - pub firtool_extra_args: Vec, + pub firtool_extra_args: Vec, /// adapt the generated Verilog for a particular toolchain #[arg(long)] pub verilog_dialect: Option, @@ -88,14 +85,16 @@ impl ToArgs for UnadjustedVerilogArgs { verilog_dialect, verilog_debug, } = *self; - for arg in firtool_extra_args { - args.write_long_option_eq("firtool-extra-arg", arg); - } + args.extend( + firtool_extra_args + .iter() + .map(|arg| format!("--firtool-extra-arg={arg}")), + ); if let Some(verilog_dialect) = verilog_dialect { - args.write_long_option_eq("verilog-dialect", verilog_dialect.as_str()); + args.write_arg(format_args!("--verilog-dialect={verilog_dialect}")); } if verilog_debug { - args.write_arg("--verilog-debug"); + args.write_str_arg("--verilog-debug"); } } } @@ -111,23 +110,23 @@ impl ExternalProgramTrait for Firtool { #[derive(Clone, PartialEq, Eq, Hash, Debug, Deserialize, Serialize)] pub struct UnadjustedVerilog { - firrtl_file: Interned, - firrtl_file_name: Interned, - unadjusted_verilog_file: Interned, - unadjusted_verilog_file_name: Interned, - firtool_extra_args: Interned<[Interned]>, + firrtl_file: Interned, + firrtl_file_name: Interned, + unadjusted_verilog_file: Interned, + unadjusted_verilog_file_name: Interned, + firtool_extra_args: Interned<[Interned]>, verilog_dialect: Option, verilog_debug: bool, } impl UnadjustedVerilog { - pub fn firrtl_file(&self) -> Interned { + pub fn firrtl_file(&self) -> Interned { self.firrtl_file } - pub fn unadjusted_verilog_file(&self) -> Interned { + pub fn unadjusted_verilog_file(&self) -> Interned { self.unadjusted_verilog_file } - pub fn firtool_extra_args(&self) -> Interned<[Interned]> { + pub fn firtool_extra_args(&self) -> Interned<[Interned]> { self.firtool_extra_args } pub fn verilog_dialect(&self) -> Option { @@ -141,7 +140,6 @@ impl UnadjustedVerilog { impl ExternalCommand for UnadjustedVerilog { type AdditionalArgs = UnadjustedVerilogArgs; type AdditionalJobData = UnadjustedVerilog; - type BaseJobPosition = GetJobPositionDependencies; type Dependencies = JobKindAndDependencies; type ExternalProgram = Firtool; @@ -152,12 +150,11 @@ impl ExternalCommand for UnadjustedVerilog { fn args_to_jobs( args: JobArgsAndDependencies>, params: &JobParams, - global_params: &GlobalParams, ) -> eyre::Result<( Self::AdditionalJobData, ::JobsAndKinds, )> { - args.args_to_jobs_external_simple(params, global_params, |args, dependencies| { + args.args_to_jobs_external_simple(params, |args, dependencies| { let UnadjustedVerilogArgs { firtool_extra_args, verilog_dialect, @@ -168,18 +165,21 @@ impl ExternalCommand for UnadjustedVerilog { .job .job .file_with_ext("unadjusted.v"); - let firrtl_job = dependencies.get_job::(); Ok(UnadjustedVerilog { - firrtl_file: firrtl_job.firrtl_file(), - firrtl_file_name: firrtl_job - .firrtl_file() - .interned_file_name() - .expect("known to have file name"), + firrtl_file: dependencies.job.job.firrtl_file(), + firrtl_file_name: interned_known_utf8_method( + dependencies.job.job.firrtl_file(), + |v| v.file_name().expect("known to have file name"), + ), unadjusted_verilog_file, - unadjusted_verilog_file_name: unadjusted_verilog_file - .interned_file_name() - .expect("known to have file name"), - firtool_extra_args: firtool_extra_args.into_iter().map(Interned::from).collect(), + unadjusted_verilog_file_name: interned_known_utf8_method( + unadjusted_verilog_file, + |v| v.file_name().expect("known to have file name"), + ), + firtool_extra_args: firtool_extra_args + .into_iter() + .map(str::intern_owned) + .collect(), verilog_dialect, verilog_debug, }) @@ -189,12 +189,12 @@ impl ExternalCommand for UnadjustedVerilog { fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]> { [JobItemName::Path { path: job.additional_job_data().firrtl_file, - }] - .intern_slice() + }][..] + .intern() } - fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { - [job.additional_job_data().unadjusted_verilog_file].intern_slice() + fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { + [job.additional_job_data().unadjusted_verilog_file][..].intern() } fn command_line_args(job: &ExternalCommandJob, args: &mut W) { @@ -208,18 +208,18 @@ impl ExternalCommand for UnadjustedVerilog { verilog_debug, } = *job.additional_job_data(); args.write_interned_arg(firrtl_file_name); - args.write_arg("-o"); + args.write_str_arg("-o"); args.write_interned_arg(unadjusted_verilog_file_name); if verilog_debug { - args.write_args(["-g", "--preserve-values=all"]); + args.write_str_args(["-g", "--preserve-values=all"]); } if let Some(dialect) = verilog_dialect { - args.write_args(dialect.firtool_extra_args().iter().copied()); + args.write_str_args(dialect.firtool_extra_args().iter().copied()); } args.write_interned_args(firtool_extra_args); } - fn current_dir(job: &ExternalCommandJob) -> Option> { + fn current_dir(job: &ExternalCommandJob) -> Option> { Some(job.output_dir()) } @@ -251,23 +251,23 @@ impl ToArgs for VerilogJobArgs { #[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)] pub struct VerilogJob { - output_dir: Interned, - unadjusted_verilog_file: Interned, - main_verilog_file: Interned, + output_dir: Interned, + unadjusted_verilog_file: Interned, + main_verilog_file: Interned, } impl VerilogJob { - pub fn output_dir(&self) -> Interned { + pub fn output_dir(&self) -> Interned { self.output_dir } - pub fn unadjusted_verilog_file(&self) -> Interned { + pub fn unadjusted_verilog_file(&self) -> Interned { self.unadjusted_verilog_file } - pub fn main_verilog_file(&self) -> Interned { + pub fn main_verilog_file(&self) -> Interned { self.main_verilog_file } #[track_caller] - pub fn unwrap_additional_files(additional_files: &JobItem) -> &[Interned] { + pub fn unwrap_additional_files(additional_files: &JobItem) -> &[Interned] { match additional_files { JobItem::DynamicPaths { paths, @@ -277,31 +277,31 @@ impl VerilogJob { } } pub fn all_verilog_files( - main_verilog_file: Interned, - additional_files: &[Interned], - ) -> eyre::Result]>> { + main_verilog_file: Interned, + additional_files: &[Interned], + ) -> eyre::Result]>> { let mut retval = Vec::with_capacity(additional_files.len().saturating_add(1)); for verilog_file in [main_verilog_file].iter().chain(additional_files) { - if !["v", "sv"] - .iter() - .any(|extension| verilog_file.extension() == Some(extension.as_ref())) - { + if !(verilog_file.ends_with(".v") || verilog_file.ends_with(".sv")) { continue; } - let verilog_file = std::path::absolute(verilog_file).wrap_err_with(|| { - format!("converting {verilog_file:?} to an absolute path failed") - })?; - if verilog_file - .as_os_str() - .as_encoded_bytes() - .iter() - .any(|&ch| (ch != b' ' && ch != b'\t' && ch.is_ascii_whitespace()) || ch == b'"') - { + let verilog_file = std::path::absolute(verilog_file) + .and_then(|v| { + v.into_os_string().into_string().map_err(|_| { + std::io::Error::new(std::io::ErrorKind::Other, "path is not valid UTF-8") + }) + }) + .wrap_err_with(|| { + format!("converting {verilog_file:?} to an absolute path failed") + })?; + if verilog_file.contains(|ch: char| { + (ch != ' ' && ch != '\t' && ch.is_ascii_whitespace()) || ch == '"' + }) { bail!("verilog file path contains characters that aren't permitted"); } - retval.push(verilog_file.intern_deref()); + retval.push(str::intern_owned(verilog_file)); } - Ok(retval.intern_slice()) + Ok(Intern::intern_owned(retval)) } } @@ -317,19 +317,17 @@ impl JobKind for VerilogJobKind { fn args_to_jobs( args: JobArgsAndDependencies, params: &JobParams, - global_params: &GlobalParams, ) -> eyre::Result> { - args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| { + args.args_to_jobs_simple(params, |_kind, args, dependencies| { let VerilogJobArgs {} = args; - let base_job = dependencies.get_job::(); Ok(VerilogJob { - output_dir: base_job.output_dir(), + output_dir: dependencies.base_job().output_dir(), unadjusted_verilog_file: dependencies .job .job .additional_job_data() .unadjusted_verilog_file(), - main_verilog_file: base_job.file_with_ext("v"), + main_verilog_file: dependencies.base_job().file_with_ext("v"), }) }) } @@ -337,8 +335,8 @@ impl JobKind for VerilogJobKind { fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> { [JobItemName::Path { path: job.unadjusted_verilog_file, - }] - .intern_slice() + }][..] + .intern() } fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { @@ -349,8 +347,8 @@ impl JobKind for VerilogJobKind { JobItemName::DynamicPaths { source_job_name: self.name(), }, - ] - .intern_slice() + ][..] + .intern() } fn name(self) -> Interned { @@ -366,7 +364,6 @@ impl JobKind for VerilogJobKind { job: &Self::Job, inputs: &[JobItem], _params: &JobParams, - _global_params: &GlobalParams, _acquired_job: &mut AcquiredJob, ) -> eyre::Result> { assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); @@ -387,7 +384,8 @@ impl JobKind for VerilogJobKind { ); }; input = rest; - let next_file_name = job.output_dir.join(next_file_name).intern_deref(); + let next_file_name = + interned_known_utf8_path_buf_method(job.output_dir, |v| v.join(next_file_name)); additional_outputs.push(next_file_name); (chunk, Some(next_file_name)) } else { diff --git a/crates/fayalite/src/bundle.rs b/crates/fayalite/src/bundle.rs index a0de189f..55843ea5 100644 --- a/crates/fayalite/src/bundle.rs +++ b/crates/fayalite/src/bundle.rs @@ -7,7 +7,7 @@ use crate::{ ops::{ArrayLiteral, BundleLiteral, ExprPartialEq}, }, int::{Bool, DynSize}, - intern::{Intern, InternSlice, Interned}, + intern::{Intern, Interned}, sim::value::{SimValue, SimValuePartialEq, ToSimValue, ToSimValueWithType}, source_location::SourceLocation, ty::{ @@ -549,7 +549,7 @@ macro_rules! impl_tuples { type FilledBuilder = TupleBuilder<($(Expr<$T>,)*)>; fn fields(&self) -> Interned<[BundleField]> { let ($($var,)*) = self; - [$(BundleField { name: stringify!($num).intern(), flipped: false, ty: $var.canonical() }),*].intern_slice() + [$(BundleField { name: stringify!($num).intern(), flipped: false, ty: $var.canonical() }),*][..].intern() } } impl<$($T: Type,)*> TypeWithDeref for ($($T,)*) { @@ -580,7 +580,7 @@ macro_rules! impl_tuples { $(let $var = $var.to_expr();)* let ty = ($(Expr::ty($var),)*); let field_values = [$(Expr::canonical($var)),*]; - BundleLiteral::new(ty, field_values.intern_slice()).to_expr() + BundleLiteral::new(ty, field_values[..].intern()).to_expr() } } impl<$($T: Type,)*> ToExpr for TupleBuilder<($(Expr<$T>,)*)> { @@ -590,7 +590,7 @@ macro_rules! impl_tuples { let ($($var,)*) = self.0; let ty = ($(Expr::ty($var),)*); let field_values = [$(Expr::canonical($var)),*]; - BundleLiteral::new(ty, field_values.intern_slice()).to_expr() + BundleLiteral::new(ty, field_values[..].intern()).to_expr() } } impl<$($T: ToSimValueWithType,)*> ToSimValueWithType for ($($T,)*) { diff --git a/crates/fayalite/src/firrtl.rs b/crates/fayalite/src/firrtl.rs index cca0d823..a83f2694 100644 --- a/crates/fayalite/src/firrtl.rs +++ b/crates/fayalite/src/firrtl.rs @@ -39,7 +39,6 @@ use crate::{ BitSliceWriteWithBase, DebugAsRawString, GenericConstBool, HashMap, HashSet, const_str_array_is_strictly_ascending, }, - vendor::xilinx::XilinxAnnotation, }; use bitvec::slice::BitSlice; use clap::value_parser; @@ -50,7 +49,6 @@ use std::{ cmp::Ordering, collections::{BTreeMap, VecDeque}, error::Error, - ffi::OsString, fmt::{self, Write}, fs, hash::Hash, @@ -1883,11 +1881,7 @@ impl<'a> Exporter<'a> { } fn annotation(&mut self, path: AnnotationTargetPath, annotation: &Annotation) { let data = match annotation { - Annotation::DontTouch(DontTouchAnnotation {}) => { - // TODO: error if the annotated thing was renamed because of a naming conflict, - // unless Target::base() is one of the ports of the top-level module since that's handled by ScalarizedModuleABI - AnnotationData::DontTouch - } + Annotation::DontTouch(DontTouchAnnotation {}) => AnnotationData::DontTouch, Annotation::SVAttribute(SVAttributeAnnotation { text }) => { AnnotationData::AttributeAnnotation { description: *text } } @@ -1910,9 +1904,7 @@ impl<'a> Exporter<'a> { class: str::to_string(class), additional_fields: (*additional_fields).into(), }, - Annotation::Xilinx(XilinxAnnotation::XdcLocation(_)) - | Annotation::Xilinx(XilinxAnnotation::XdcIOStandard(_)) - | Annotation::Xilinx(XilinxAnnotation::XdcCreateClock(_)) => return, + Annotation::XdcLocation(_) | Annotation::XdcIOStandard(_) => return, }; self.annotations.push(FirrtlAnnotation { data, @@ -2460,7 +2452,7 @@ impl FileBackendTrait for &'_ mut T { pub struct FileBackend { pub dir_path: PathBuf, pub circuit_name: Option, - pub top_fir_file_stem: Option, + pub top_fir_file_stem: Option, } impl FileBackend { @@ -2509,7 +2501,7 @@ impl FileBackendTrait for FileBackend { ) -> Result<(), Self::Error> { let top_fir_file_stem = self .top_fir_file_stem - .get_or_insert_with(|| circuit_name.clone().into()); + .get_or_insert_with(|| circuit_name.clone()); self.circuit_name = Some(circuit_name); let mut path = self.dir_path.join(top_fir_file_stem); if let Some(parent) = path.parent().filter(|v| !v.as_os_str().is_empty()) { @@ -2785,7 +2777,7 @@ impl ToArgs for ExportOptions { __private: ExportOptionsPrivate(()), } = *self; if !simplify_memories { - args.write_arg("--no-simplify-memories"); + args.write_str_arg("--no-simplify-memories"); } let simplify_enums = simplify_enums.map(|v| { clap::ValueEnum::to_possible_value(&v).expect("there are no skipped variants") @@ -2794,7 +2786,7 @@ impl ToArgs for ExportOptions { None => OptionSimplifyEnumsKindValueParser::NONE_NAME, Some(v) => v.get_name(), }; - args.write_long_option_eq("simplify-enums", simplify_enums); + args.write_arg(format_args!("--simplify-enums={simplify_enums}")); } } diff --git a/crates/fayalite/src/int/uint_in_range.rs b/crates/fayalite/src/int/uint_in_range.rs index 39f40517..5ddd38c0 100644 --- a/crates/fayalite/src/int/uint_in_range.rs +++ b/crates/fayalite/src/int/uint_in_range.rs @@ -8,7 +8,7 @@ use crate::{ ops::{ExprCastTo, ExprPartialEq, ExprPartialOrd}, }, int::{Bool, DynSize, KnownSize, Size, SizeType, UInt, UIntType}, - intern::{Intern, InternSlice, Interned}, + intern::{Intern, Interned}, phantom_const::PhantomConst, sim::value::{SimValue, SimValuePartialEq, ToSimValueWithType}, source_location::SourceLocation, @@ -112,8 +112,8 @@ impl BundleType for UIntInRangeMaskType { flipped: false, ty: range.canonical(), }, - ] - .intern_slice() + ][..] + .intern() } } @@ -409,8 +409,8 @@ macro_rules! define_uint_in_range_type { flipped: false, ty: range.canonical(), }, - ] - .intern_slice() + ][..] + .intern() } } diff --git a/crates/fayalite/src/intern.rs b/crates/fayalite/src/intern.rs index b68140b5..9d57146f 100644 --- a/crates/fayalite/src/intern.rs +++ b/crates/fayalite/src/intern.rs @@ -9,13 +9,13 @@ use std::{ any::{Any, TypeId}, borrow::{Borrow, Cow}, cmp::Ordering, - ffi::{OsStr, OsString}, + ffi::OsStr, fmt, hash::{BuildHasher, Hash, Hasher}, iter::FusedIterator, marker::PhantomData, ops::Deref, - path::{Path, PathBuf}, + path::Path, sync::{Mutex, RwLock}, }; @@ -289,266 +289,15 @@ impl InternedCompare for BitSlice { } } -/// Safety: `as_bytes` and `from_bytes_unchecked` must return the same pointer as the input. -/// all values returned by `as_bytes` must be valid to pass to `from_bytes_unchecked`. -/// `into_bytes` must return the exact same thing as `as_bytes`. -/// `Interned` must contain the exact same references as `Interned<[u8]>`, -/// so they can be safely interconverted without needing re-interning. -unsafe trait InternStrLike: ToOwned { - fn as_bytes(this: &Self) -> &[u8]; - fn into_bytes(this: Self::Owned) -> Vec; - /// Safety: `bytes` must be a valid sequence of bytes for this type. All UTF-8 sequences are valid. - unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self; -} - -macro_rules! impl_intern_str_like { - ($ty:ty, owned = $Owned:ty) => { - impl InternedCompare for $ty { - type InternedCompareKey = PtrEqWithMetadata<[u8]>; - fn interned_compare_key_ref(this: &Self) -> Self::InternedCompareKey { - PtrEqWithMetadata(InternStrLike::as_bytes(this)) - } - } - impl Intern for $ty { - fn intern(&self) -> Interned { - Self::intern_cow(Cow::Borrowed(self)) - } - fn intern_cow(this: Cow<'_, Self>) -> Interned { - Interned::cast_unchecked( - <[u8]>::intern_cow(match this { - Cow::Borrowed(v) => Cow::Borrowed(::as_bytes(v)), - Cow::Owned(v) => { - // verify $Owned is correct - let v: $Owned = v; - Cow::Owned(::into_bytes(v)) - } - }), - // Safety: guaranteed safe because we got the bytes from `as_bytes`/`into_bytes` - |v| unsafe { ::from_bytes_unchecked(v) }, - ) - } - } - impl Default for Interned<$ty> { - fn default() -> Self { - // Safety: safe because the empty sequence is valid UTF-8 - unsafe { <$ty as InternStrLike>::from_bytes_unchecked(&[]) }.intern() - } - } - impl<'de> Deserialize<'de> for Interned<$ty> { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - Cow::<'de, $ty>::deserialize(deserializer).map(Intern::intern_cow) - } - } - impl From<$Owned> for Interned<$ty> { - fn from(v: $Owned) -> Self { - v.intern_deref() - } - } - impl From> for $Owned { - fn from(v: Interned<$ty>) -> Self { - Interned::into_inner(v).into() - } - } - impl From> for Box<$ty> { - fn from(v: Interned<$ty>) -> Self { - Interned::into_inner(v).into() - } - } - }; -} - -// Safety: satisfies `InternStrLike`'s requirements where the valid sequences for `from_bytes_unchecked` matches `str` -unsafe impl InternStrLike for str { - fn as_bytes(this: &Self) -> &[u8] { - this.as_bytes() - } - fn into_bytes(this: Self::Owned) -> Vec { - this.into_bytes() - } - unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self { - // Safety: `bytes` is guaranteed UTF-8 by the caller - unsafe { str::from_utf8_unchecked(bytes) } - } -} - -impl_intern_str_like!(str, owned = String); - -// Safety: satisfies `InternStrLike`'s requirements where the valid sequences for `from_bytes_unchecked` matches `OsStr` -unsafe impl InternStrLike for OsStr { - fn as_bytes(this: &Self) -> &[u8] { - this.as_encoded_bytes() - } - fn into_bytes(this: Self::Owned) -> Vec { - this.into_encoded_bytes() - } - unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self { - // Safety: `bytes` is guaranteed valid for `OsStr` by the caller - unsafe { OsStr::from_encoded_bytes_unchecked(bytes) } - } -} - -impl_intern_str_like!(OsStr, owned = OsString); - -// Safety: satisfies `InternStrLike`'s requirements where the valid sequences for `from_bytes_unchecked` matches `OsStr` -unsafe impl InternStrLike for Path { - fn as_bytes(this: &Self) -> &[u8] { - this.as_os_str().as_encoded_bytes() - } - fn into_bytes(this: Self::Owned) -> Vec { - this.into_os_string().into_encoded_bytes() - } - unsafe fn from_bytes_unchecked(bytes: &[u8]) -> &Self { - // Safety: `bytes` is guaranteed valid for `OsStr` by the caller - unsafe { Path::new(OsStr::from_encoded_bytes_unchecked(bytes)) } - } -} - -impl_intern_str_like!(Path, owned = PathBuf); - -impl Interned { - pub fn from_utf8(v: Interned<[u8]>) -> Result { - Interned::try_cast_unchecked(v, str::from_utf8) - } - pub fn as_interned_bytes(self) -> Interned<[u8]> { - Interned::cast_unchecked(self, str::as_bytes) - } - pub fn as_interned_os_str(self) -> Interned { - Interned::cast_unchecked(self, AsRef::as_ref) - } - pub fn as_interned_path(self) -> Interned { - Interned::cast_unchecked(self, AsRef::as_ref) - } -} - -impl From> for Interned { - fn from(value: Interned) -> Self { - value.as_interned_os_str() - } -} - -impl From> for Interned { - fn from(value: Interned) -> Self { - value.as_interned_path() - } -} - -impl Interned { - pub fn as_interned_encoded_bytes(self) -> Interned<[u8]> { - Interned::cast_unchecked(self, OsStr::as_encoded_bytes) - } - pub fn to_interned_str(self) -> Option> { - Interned::try_cast_unchecked(self, |v| v.to_str().ok_or(())).ok() - } - pub fn display(self) -> std::ffi::os_str::Display<'static> { - Self::into_inner(self).display() - } - pub fn as_interned_path(self) -> Interned { - Interned::cast_unchecked(self, AsRef::as_ref) - } -} - -impl From> for Interned { - fn from(value: Interned) -> Self { - value.as_interned_path() - } -} - -impl Interned { - pub fn as_interned_os_str(self) -> Interned { - Interned::cast_unchecked(self, AsRef::as_ref) - } - pub fn to_interned_str(self) -> Option> { - Interned::try_cast_unchecked(self, |v| v.to_str().ok_or(())).ok() - } - pub fn display(self) -> std::path::Display<'static> { - Self::into_inner(self).display() - } - pub fn interned_file_name(self) -> Option> { - Some(self.file_name()?.intern()) - } -} - -impl From> for Interned { - fn from(value: Interned) -> Self { - value.as_interned_os_str() - } -} - -pub trait InternSlice: Sized { - type Element: 'static + Send + Sync + Clone + Hash + Eq; - fn intern_slice(self) -> Interned<[Self::Element]>; -} - -impl InternSlice for Box<[T]> { - type Element = T; - fn intern_slice(self) -> Interned<[Self::Element]> { - self.into_vec().intern_slice() - } -} - -impl InternSlice for Vec { - type Element = T; - fn intern_slice(self) -> Interned<[Self::Element]> { - self.intern_deref() - } -} - -impl InternSlice for &'_ [T] { - type Element = T; - fn intern_slice(self) -> Interned<[Self::Element]> { - self.intern() - } -} - -impl InternSlice for &'_ mut [T] { - type Element = T; - fn intern_slice(self) -> Interned<[Self::Element]> { - self.intern() - } -} - -impl InternSlice for [T; N] { - type Element = T; - fn intern_slice(self) -> Interned<[Self::Element]> { - (&self).intern_slice() - } -} - -impl InternSlice for Box<[T; N]> { - type Element = T; - fn intern_slice(self) -> Interned<[Self::Element]> { - let this: Box<[T]> = self; - this.intern_slice() - } -} - -impl InternSlice for &'_ [T; N] { - type Element = T; - fn intern_slice(self) -> Interned<[Self::Element]> { - let this: &[T] = self; - this.intern() - } -} - -impl InternSlice for &'_ mut [T; N] { - type Element = T; - fn intern_slice(self) -> Interned<[Self::Element]> { - let this: &[T] = self; - this.intern() +impl InternedCompare for str { + type InternedCompareKey = PtrEqWithMetadata; + fn interned_compare_key_ref(this: &Self) -> Self::InternedCompareKey { + PtrEqWithMetadata(this) } } pub trait Intern: Any + Send + Sync { fn intern(&self) -> Interned; - fn intern_deref(self) -> Interned - where - Self: Sized + Deref>, - { - Self::Target::intern_owned(self) - } fn intern_sized(self) -> Interned where Self: Clone, @@ -569,30 +318,6 @@ pub trait Intern: Any + Send + Sync { } } -impl From> for Interned { - fn from(value: Cow<'_, T>) -> Self { - Intern::intern_cow(value) - } -} - -impl From<&'_ T> for Interned { - fn from(value: &'_ T) -> Self { - Intern::intern(value) - } -} - -impl From for Interned { - fn from(value: T) -> Self { - Intern::intern_sized(value) - } -} - -impl From> for Cow<'_, T> { - fn from(value: Interned) -> Self { - Cow::Borrowed(Interned::into_inner(value)) - } -} - struct InternerState { table: HashTable<&'static T>, hasher: DefaultBuildHasher, @@ -658,6 +383,12 @@ impl Interner { } } +impl Interner { + fn intern_str(&self, value: Cow<'_, str>) -> Interned { + self.intern(|value| value.into_owned().leak(), value) + } +} + pub struct Interned { inner: &'static T, } @@ -687,9 +418,9 @@ forward_fmt_trait!(Pointer); forward_fmt_trait!(UpperExp); forward_fmt_trait!(UpperHex); -impl, U: ?Sized> AsRef for Interned { - fn as_ref(&self) -> &U { - T::as_ref(self) +impl AsRef for Interned { + fn as_ref(&self) -> &T { + self } } @@ -767,25 +498,19 @@ where String: FromIterator, { fn from_iter>(iter: T) -> Self { - String::from_iter(iter).intern_deref() + str::intern_owned(FromIterator::from_iter(iter)) } } -impl FromIterator for Interned -where - PathBuf: FromIterator, -{ - fn from_iter>(iter: T) -> Self { - PathBuf::from_iter(iter).intern_deref() +impl AsRef for Interned { + fn as_ref(&self) -> &OsStr { + str::as_ref(self) } } -impl FromIterator for Interned -where - OsString: FromIterator, -{ - fn from_iter>(iter: T) -> Self { - OsString::from_iter(iter).intern_deref() +impl AsRef for Interned { + fn as_ref(&self) -> &Path { + str::as_ref(self) } } @@ -825,12 +550,24 @@ impl From> for Box<[T]> { } } +impl From> for String { + fn from(value: Interned) -> Self { + String::from(&*value) + } +} + impl Default for Interned<[I]> where [I]: Intern, { fn default() -> Self { - Intern::intern(&[]) + [][..].intern() + } +} + +impl Default for Interned { + fn default() -> Self { + "".intern() } } @@ -961,6 +698,15 @@ impl<'de> Deserialize<'de> for Interned { } } +impl<'de> Deserialize<'de> for Interned { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + String::deserialize(deserializer).map(Intern::intern_owned) + } +} + impl Intern for T { fn intern(&self) -> Interned { Self::intern_cow(Cow::Borrowed(self)) @@ -1021,6 +767,26 @@ impl Intern for BitSlice { } } +impl Intern for str { + fn intern(&self) -> Interned { + Self::intern_cow(Cow::Borrowed(self)) + } + + fn intern_owned(this: ::Owned) -> Interned + where + Self: ToOwned, + { + Self::intern_cow(Cow::Owned(this)) + } + + fn intern_cow(this: Cow<'_, Self>) -> Interned + where + Self: ToOwned, + { + Interner::get().intern_str(this) + } +} + pub trait MemoizeGeneric: 'static + Send + Sync + Hash + Eq + Copy { type InputRef<'a>: 'a + Send + Sync + Hash + Copy; type InputOwned: 'static + Send + Sync; diff --git a/crates/fayalite/src/lib.rs b/crates/fayalite/src/lib.rs index 326d44bb..e5323f3b 100644 --- a/crates/fayalite/src/lib.rs +++ b/crates/fayalite/src/lib.rs @@ -99,14 +99,13 @@ pub mod intern; pub mod memory; pub mod module; pub mod phantom_const; -pub mod platform; pub mod prelude; pub mod reg; pub mod reset; pub mod sim; pub mod source_location; +pub mod target; pub mod testing; pub mod ty; pub mod util; -pub mod vendor; pub mod wire; diff --git a/crates/fayalite/src/module.rs b/crates/fayalite/src/module.rs index 6527043c..aa7a673f 100644 --- a/crates/fayalite/src/module.rs +++ b/crates/fayalite/src/module.rs @@ -19,7 +19,6 @@ use crate::{ int::{Bool, DynSize, Size}, intern::{Intern, Interned}, memory::{Mem, MemBuilder, MemBuilderTarget, PortName}, - platform::PlatformIOBuilder, reg::Reg, reset::{AsyncReset, Reset, ResetType, ResetTypeDispatch, SyncReset}, sim::{ExternModuleSimGenerator, ExternModuleSimulation}, @@ -2120,27 +2119,6 @@ impl ModuleBuilder { self.output_with_loc(implicit_name.0, SourceLocation::caller(), ty) } #[track_caller] - pub fn add_platform_io_with_loc( - &self, - name: &str, - source_location: SourceLocation, - platform_io_builder: PlatformIOBuilder<'_>, - ) -> Expr { - platform_io_builder.add_platform_io(name, source_location, self) - } - #[track_caller] - pub fn add_platform_io( - &self, - implicit_name: ImplicitName<'_>, - platform_io_builder: PlatformIOBuilder<'_>, - ) -> Expr { - self.add_platform_io_with_loc( - implicit_name.0, - SourceLocation::caller(), - platform_io_builder, - ) - } - #[track_caller] pub fn run( name: &str, module_kind: ModuleKind, @@ -2765,22 +2743,6 @@ impl ModuleIO { source_location, } } - pub fn from_canonical(canonical_module_io: ModuleIO) -> Self { - let ModuleIO { - containing_module_name, - bundle_field, - id, - ty, - source_location, - } = canonical_module_io; - Self { - containing_module_name, - bundle_field, - id, - ty: T::from_canonical(ty), - source_location, - } - } pub fn bundle_field(&self) -> BundleField { self.bundle_field } diff --git a/crates/fayalite/src/module/transform/deduce_resets.rs b/crates/fayalite/src/module/transform/deduce_resets.rs index e84d835e..4fa931ec 100644 --- a/crates/fayalite/src/module/transform/deduce_resets.rs +++ b/crates/fayalite/src/module/transform/deduce_resets.rs @@ -1802,7 +1802,6 @@ impl_run_pass_clone!([] ExternModuleParameter); impl_run_pass_clone!([] SIntValue); impl_run_pass_clone!([] std::ops::Range); impl_run_pass_clone!([] UIntValue); -impl_run_pass_clone!([] crate::vendor::xilinx::XilinxAnnotation); impl_run_pass_copy!([] BlackBoxInlineAnnotation); impl_run_pass_copy!([] BlackBoxPathAnnotation); impl_run_pass_copy!([] bool); @@ -1818,6 +1817,8 @@ impl_run_pass_copy!([] UInt); impl_run_pass_copy!([] usize); impl_run_pass_copy!([] FormalKind); impl_run_pass_copy!([] PhantomConst); +impl_run_pass_copy!([] crate::build::vendor::xilinx::XdcIOStandardAnnotation); +impl_run_pass_copy!([] crate::build::vendor::xilinx::XdcLocationAnnotation); macro_rules! impl_run_pass_for_struct { ( @@ -2218,7 +2219,8 @@ impl_run_pass_for_enum! { BlackBoxPath(v), DocString(v), CustomFirrtl(v), - Xilinx(v), + XdcLocation(v), + XdcIOStandard(v), } } diff --git a/crates/fayalite/src/module/transform/simplify_enums.rs b/crates/fayalite/src/module/transform/simplify_enums.rs index bd5f7d52..0839881b 100644 --- a/crates/fayalite/src/module/transform/simplify_enums.rs +++ b/crates/fayalite/src/module/transform/simplify_enums.rs @@ -10,7 +10,7 @@ use crate::{ }, hdl, int::UInt, - intern::{Intern, InternSlice, Interned, Memoize}, + intern::{Intern, Interned, Memoize}, memory::{DynPortType, Mem, MemPort}, module::{ Block, Id, Module, NameId, ScopedNameId, Stmt, StmtConnect, StmtIf, StmtMatch, StmtWire, @@ -620,7 +620,7 @@ fn match_int_tag( block, Block { memories: Default::default(), - stmts: [Stmt::from(retval)].intern_slice(), + stmts: [Stmt::from(retval)][..].intern(), }, ], }; diff --git a/crates/fayalite/src/module/transform/visit.rs b/crates/fayalite/src/module/transform/visit.rs index 2c33a76b..d08ac108 100644 --- a/crates/fayalite/src/module/transform/visit.rs +++ b/crates/fayalite/src/module/transform/visit.rs @@ -7,6 +7,7 @@ use crate::{ DocStringAnnotation, DontTouchAnnotation, SVAttributeAnnotation, TargetedAnnotation, }, array::ArrayType, + build::vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation}, bundle::{Bundle, BundleField, BundleType}, clock::Clock, enum_::{Enum, EnumType, EnumVariant}, @@ -33,9 +34,6 @@ use crate::{ sim::{ExternModuleSimulation, value::DynSimOnly}, source_location::SourceLocation, ty::{CanonicalType, Type}, - vendor::xilinx::{ - XdcCreateClockAnnotation, XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation, - }, wire::Wire, }; use num_bigint::{BigInt, BigUint}; diff --git a/crates/fayalite/src/platform.rs b/crates/fayalite/src/platform.rs deleted file mode 100644 index 194aa6e3..00000000 --- a/crates/fayalite/src/platform.rs +++ /dev/null @@ -1,1923 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// See Notices.txt for copyright information - -use crate::{ - bundle::{Bundle, BundleField, BundleType}, - expr::{Expr, ExprEnum}, - intern::{Intern, Interned}, - module::{Module, ModuleBuilder, ModuleIO, connect_with_loc, instance_with_loc, wire_with_loc}, - source_location::SourceLocation, - ty::{CanonicalType, Type}, - util::{HashMap, HashSet, InternedStrCompareAsStr}, -}; -use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error}; -use std::{ - any::{Any, TypeId}, - borrow::Cow, - cmp::Ordering, - collections::{BTreeMap, BTreeSet}, - convert::Infallible, - fmt, - hash::{Hash, Hasher}, - iter::FusedIterator, - marker::PhantomData, - mem, - sync::{Arc, Mutex, MutexGuard, OnceLock, RwLock, RwLockWriteGuard}, -}; - -pub mod peripherals; - -trait DynPlatformTrait: 'static + Send + Sync + fmt::Debug { - fn as_any(&self) -> &dyn Any; - fn eq_dyn(&self, other: &dyn DynPlatformTrait) -> bool; - fn hash_dyn(&self, state: &mut dyn Hasher); - fn name_dyn(&self) -> Interned; - fn new_peripherals_dyn<'builder>( - &self, - builder_factory: PeripheralsBuilderFactory<'builder>, - ) -> (DynPeripherals, PeripheralsBuilderFinished<'builder>); - fn source_location_dyn(&self) -> SourceLocation; - #[track_caller] - fn add_peripherals_in_wrapper_module_dyn(&self, m: &ModuleBuilder, peripherals: DynPeripherals); - fn aspects_dyn(&self) -> PlatformAspectSet; -} - -impl DynPlatformTrait for T { - fn as_any(&self) -> &dyn Any { - self - } - - fn eq_dyn(&self, other: &dyn DynPlatformTrait) -> bool { - other - .as_any() - .downcast_ref::() - .is_some_and(|other| self == other) - } - - fn hash_dyn(&self, mut state: &mut dyn Hasher) { - self.hash(&mut state); - } - - fn name_dyn(&self) -> Interned { - self.name() - } - - fn new_peripherals_dyn<'builder>( - &self, - builder_factory: PeripheralsBuilderFactory<'builder>, - ) -> (DynPeripherals, PeripheralsBuilderFinished<'builder>) { - let (peripherals, finished) = self.new_peripherals(builder_factory); - (DynPeripherals(Box::new(peripherals)), finished) - } - - fn source_location_dyn(&self) -> SourceLocation { - self.source_location() - } - - #[track_caller] - fn add_peripherals_in_wrapper_module_dyn( - &self, - m: &ModuleBuilder, - peripherals: DynPeripherals, - ) { - if DynPeripheralsTrait::type_id(&*peripherals.0) != TypeId::of::() { - panic!( - "wrong DynPeripherals value type, expected type: <{}>::Peripherals, got value:\n{peripherals:?}", - std::any::type_name::() - ); - } - let Ok(peripherals) = peripherals.0.into_box_any().downcast() else { - unreachable!(); - }; - self.add_peripherals_in_wrapper_module(m, *peripherals) - } - - fn aspects_dyn(&self) -> PlatformAspectSet { - self.aspects() - } -} - -#[derive(Clone)] -pub struct DynPlatform(Arc); - -impl PartialEq for DynPlatform { - fn eq(&self, other: &Self) -> bool { - DynPlatformTrait::eq_dyn(&*self.0, &*other.0) - } -} - -impl Eq for DynPlatform {} - -impl Hash for DynPlatform { - fn hash(&self, state: &mut H) { - DynPlatformTrait::hash_dyn(&*self.0, state); - } -} - -impl fmt::Debug for DynPlatform { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl DynPlatform { - pub fn new(platform: T) -> Self { - if let Some(platform) = ::downcast_ref::(&platform) { - platform.clone() - } else { - Self(Arc::new(platform)) - } - } -} - -trait DynPeripheralsTrait: fmt::Debug + 'static + Send + Sync { - fn type_id(&self) -> TypeId; - fn into_box_any(self: Box) -> Box; - fn append_peripherals_dyn<'a>( - &'a self, - peripherals: &mut Vec>, - ); -} - -impl DynPeripheralsTrait for T { - fn type_id(&self) -> TypeId { - TypeId::of::() - } - fn into_box_any(self: Box) -> Box { - self - } - fn append_peripherals_dyn<'a>( - &'a self, - peripherals: &mut Vec>, - ) { - self.append_peripherals(peripherals); - } -} - -pub struct DynPeripherals(Box); - -impl fmt::Debug for DynPeripherals { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl Peripherals for DynPeripherals { - fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { - self.0.append_peripherals_dyn(peripherals); - } -} - -impl Platform for DynPlatform { - type Peripherals = DynPeripherals; - fn name(&self) -> Interned { - DynPlatformTrait::name_dyn(&*self.0) - } - fn new_peripherals<'a>( - &self, - builder_factory: PeripheralsBuilderFactory<'a>, - ) -> (Self::Peripherals, PeripheralsBuilderFinished<'a>) { - DynPlatformTrait::new_peripherals_dyn(&*self.0, builder_factory) - } - fn source_location(&self) -> SourceLocation { - DynPlatformTrait::source_location_dyn(&*self.0) - } - #[track_caller] - fn add_peripherals_in_wrapper_module(&self, m: &ModuleBuilder, peripherals: Self::Peripherals) { - DynPlatformTrait::add_peripherals_in_wrapper_module_dyn(&*self.0, m, peripherals); - } - fn aspects(&self) -> PlatformAspectSet { - DynPlatformTrait::aspects_dyn(&*self.0) - } -} - -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct PeripheralId { - pub name: Interned, -} - -impl PartialOrd for PeripheralId { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl Ord for PeripheralId { - fn cmp(&self, other: &Self) -> Ordering { - if self == other { - Ordering::Equal - } else { - let Self { name } = self; - str::cmp(name, &other.name) - } - } -} - -struct CollectingPeripherals { - conflicts_graph: BTreeMap>, - on_use_state: PeripheralsOnUseState, -} - -pub trait PeripheralsOnUseSharedState: 'static + Send + fmt::Debug { - fn as_any(&mut self) -> &mut dyn Any; -} - -impl PeripheralsOnUseSharedState for T { - fn as_any(&mut self) -> &mut dyn Any { - self - } -} - -type DynPeripheralsOnUse = dyn FnOnce( - &mut dyn PeripheralsOnUseSharedState, - PeripheralRef<'_, CanonicalType>, - Expr, - ) + Send - + 'static; - -struct PeripheralsOnUseState { - shared_state: Box, - main_module_io_fields: Vec, - main_module_io_wires: Vec>, - on_use_functions: BTreeMap>, -} - -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub enum PeripheralAvailability { - Available, - Used, - ConflictsWithUsed(PeripheralId), -} - -impl PeripheralAvailability { - pub fn is_available(self) -> bool { - matches!(self, Self::Available) - } - pub fn is_used(&self) -> bool { - matches!(self, Self::Used) - } -} - -struct PeripheralsStateBuildingModule { - conflicts_graph: Interned>>>, - availabilities: Mutex>, - on_use_state: Mutex, -} - -impl From for PeripheralsStateBuildingModule { - fn from(value: CollectingPeripherals) -> Self { - let CollectingPeripherals { - conflicts_graph, - on_use_state, - } = value; - let conflicts_graph = BTreeMap::from_iter( - conflicts_graph - .into_iter() - .map(|(k, v)| (k, v.intern_sized())), - ) - .intern_sized(); - Self { - conflicts_graph, - availabilities: Mutex::new( - on_use_state - .on_use_functions - .keys() - .map(|&id| (id, PeripheralAvailability::Available)) - .collect(), - ), - on_use_state: Mutex::new(on_use_state), - } - } -} - -struct PeripheralsStateBuildingWrapperModule { - output_module_io: ModuleIO, - output: Option>, -} - -enum PeripheralsStateEnum { - Initial, - CollectingPeripherals(CollectingPeripherals), - BuildingModule, - BuildingWrapperModule(PeripheralsStateBuildingWrapperModule), -} - -struct PeripheralsState { - will_build_wrapper: bool, - state: Mutex, - building_module: OnceLock, -} - -impl PeripheralsState { - fn finish_collecting_peripherals(&self) { - let mut state = self.state.lock().expect("shouldn't be poison"); - let building_module = match mem::replace(&mut *state, PeripheralsStateEnum::BuildingModule) - { - PeripheralsStateEnum::CollectingPeripherals(v) => v.into(), - PeripheralsStateEnum::Initial - | PeripheralsStateEnum::BuildingModule - | PeripheralsStateEnum::BuildingWrapperModule(_) => unreachable!(), - }; - self.building_module.get_or_init(|| building_module); - } -} - -struct PeripheralCommon { - type_id: TypeId, - id: PeripheralId, - is_input: bool, - peripherals_state: Arc, -} - -#[must_use] -pub struct PeripheralsBuilderFactory<'a> { - peripherals_state: Arc, - _phantom: PhantomData &'a ()>, -} - -impl fmt::Debug for PeripheralsBuilderFactory<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("PeripheralsBuilderFactory") - .finish_non_exhaustive() - } -} - -impl PeripheralsBuilderFactory<'_> { - fn new(will_build_wrapper: bool) -> Self { - Self { - peripherals_state: Arc::new(PeripheralsState { - will_build_wrapper, - state: Mutex::new(PeripheralsStateEnum::Initial), - building_module: OnceLock::new(), - }), - _phantom: PhantomData, - } - } -} - -impl<'a> PeripheralsBuilderFactory<'a> { - pub fn builder(self) -> PeripheralsBuilder<'a> { - self.builder_with_default_state() - } - pub fn builder_with_default_state( - self, - ) -> PeripheralsBuilder<'a, S> { - self.builder_with_boxed_state(Box::default()) - } - pub fn builder_with_state( - self, - shared_state: S, - ) -> PeripheralsBuilder<'a, S> { - self.builder_with_boxed_state(Box::new(shared_state)) - } - pub fn builder_with_boxed_state( - self, - shared_state: Box, - ) -> PeripheralsBuilder<'a, S> { - let Self { - peripherals_state, - _phantom: PhantomData, - } = self; - match *peripherals_state.state.lock().expect("shouldn't be poison") { - ref mut state @ PeripheralsStateEnum::Initial => { - *state = PeripheralsStateEnum::CollectingPeripherals(CollectingPeripherals { - conflicts_graph: BTreeMap::new(), - on_use_state: PeripheralsOnUseState { - shared_state, - main_module_io_fields: Vec::new(), - main_module_io_wires: Vec::new(), - on_use_functions: BTreeMap::new(), - }, - }) - } - PeripheralsStateEnum::CollectingPeripherals(_) - | PeripheralsStateEnum::BuildingModule - | PeripheralsStateEnum::BuildingWrapperModule(_) => unreachable!(), - } - PeripheralsBuilder { - peripherals_state, - _phantom: PhantomData, - } - } -} - -#[must_use] -pub struct PeripheralsBuilder<'a, S: PeripheralsOnUseSharedState = ()> { - peripherals_state: Arc, - _phantom: PhantomData<(Arc, fn(&'a ()) -> &'a ())>, -} - -#[must_use] -pub struct PeripheralsBuilderFinished<'a> { - _private: PhantomData &'a ()>, -} - -impl fmt::Debug for PeripheralsBuilderFinished<'_> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("PeripheralsBuilderFinished") - .finish_non_exhaustive() - } -} - -impl<'a, S: PeripheralsOnUseSharedState> PeripheralsBuilder<'a, S> { - fn state_enum(&mut self) -> MutexGuard<'_, PeripheralsStateEnum> { - self.peripherals_state - .state - .lock() - .expect("shouldn't be poison") - } - #[track_caller] - pub fn peripheral( - &mut self, - id_name: impl AsRef, - is_input: bool, - ty: T, - ) -> Peripheral { - self.peripheral_with_on_use(id_name, is_input, ty, |_, _, _| {}) - } - #[track_caller] - pub fn peripheral_with_on_use( - &mut self, - id_name: impl AsRef, - is_input: bool, - ty: T, - on_use: impl FnOnce(&mut S, PeripheralRef<'_, T>, Expr) + Send + 'static, - ) -> Peripheral { - let mut state_enum = self.state_enum(); - let PeripheralsStateEnum::CollectingPeripherals(CollectingPeripherals { - conflicts_graph, - on_use_state: - PeripheralsOnUseState { - shared_state: _, - main_module_io_fields: _, - main_module_io_wires: _, - on_use_functions, - }, - }) = &mut *state_enum - else { - unreachable!(); - }; - let id = PeripheralId { - name: id_name.as_ref().intern(), - }; - let std::collections::btree_map::Entry::Vacant(entry) = conflicts_graph.entry(id) else { - drop(state_enum); // don't poison - panic!("duplicate peripheral: {id:?}"); - }; - entry.insert(BTreeSet::new()); - on_use_functions.insert( - id, - Box::new(move |state, peripheral_ref, wire| { - on_use( - ::downcast_mut::(PeripheralsOnUseSharedState::as_any(state)) - .expect("known to be correct type"), - PeripheralRef::from_canonical(peripheral_ref), - Expr::from_canonical(wire), - ) - }), - ); - drop(state_enum); - Peripheral { - ty, - common: PeripheralCommon { - type_id: TypeId::of::(), - id, - is_input, - peripherals_state: self.peripherals_state.clone(), - }, - } - } - #[track_caller] - pub fn input_peripheral_with_on_use( - &mut self, - id_name: impl AsRef, - ty: T, - on_use: impl FnOnce(&mut S, PeripheralRef<'_, T>, Expr) + Send + 'static, - ) -> Peripheral { - self.peripheral_with_on_use(id_name, true, ty, on_use) - } - #[track_caller] - pub fn output_peripheral_with_on_use( - &mut self, - id_name: impl AsRef, - ty: T, - on_use: impl FnOnce(&mut S, PeripheralRef<'_, T>, Expr) + Send + 'static, - ) -> Peripheral { - self.peripheral_with_on_use(id_name, false, ty, on_use) - } - #[track_caller] - pub fn input_peripheral(&mut self, id_name: impl AsRef, ty: T) -> Peripheral { - self.peripheral(id_name, true, ty) - } - #[track_caller] - pub fn output_peripheral(&mut self, id_name: impl AsRef, ty: T) -> Peripheral { - self.peripheral(id_name, false, ty) - } - #[track_caller] - pub fn add_conflicts(&mut self, conflicts: impl AsRef<[PeripheralId]>) { - let mut state_enum = self.state_enum(); - let PeripheralsStateEnum::CollectingPeripherals(collecting_peripherals) = &mut *state_enum - else { - unreachable!(); - }; - let conflicts = conflicts.as_ref(); - for &id in conflicts { - let Some(conflicts_for_id) = collecting_peripherals.conflicts_graph.get_mut(&id) else { - drop(state_enum); // don't poison - panic!("unknown peripheral: {id:?}"); - }; - conflicts_for_id.extend(conflicts.iter().copied().filter(|&v| v != id)); - } - } - pub fn finish(self) -> PeripheralsBuilderFinished<'a> { - self.peripherals_state.finish_collecting_peripherals(); - PeripheralsBuilderFinished { - _private: PhantomData, - } - } -} - -#[must_use] -pub struct Peripheral { - ty: T, - common: PeripheralCommon, -} - -impl Peripheral { - pub fn as_ref<'a>(&'a self) -> PeripheralRef<'a, T> { - let Self { ty, ref common } = *self; - PeripheralRef { ty, common } - } - pub fn ty(&self) -> T { - self.as_ref().ty() - } - pub fn id(&self) -> PeripheralId { - self.as_ref().id() - } - pub fn name(&self) -> Interned { - self.as_ref().name() - } - pub fn is_input(&self) -> bool { - self.as_ref().is_input() - } - pub fn is_output(&self) -> bool { - self.as_ref().is_output() - } - pub fn conflicts_with(&self) -> Interned> { - self.as_ref().conflicts_with() - } - pub fn availability(&self) -> PeripheralAvailability { - self.as_ref().availability() - } - pub fn is_available(&self) -> bool { - self.as_ref().is_available() - } - pub fn is_used(&self) -> bool { - self.as_ref().is_used() - } - pub fn try_into_used(self) -> Result, Self> { - let Some(building_module) = self.common.peripherals_state.building_module.get() else { - return Err(self); - }; - let building_module = building_module - .availabilities - .lock() - .expect("shouldn't be poison"); - match building_module[&self.common.id] { - PeripheralAvailability::Used => {} - PeripheralAvailability::Available | PeripheralAvailability::ConflictsWithUsed(_) => { - drop(building_module); - return Err(self); - } - } - drop(building_module); - let state = self - .common - .peripherals_state - .state - .lock() - .expect("shouldn't be poison"); - let output = match *state { - PeripheralsStateEnum::Initial | PeripheralsStateEnum::CollectingPeripherals(_) => { - unreachable!() - } - PeripheralsStateEnum::BuildingModule => { - drop(state); - return Err(self); - } - PeripheralsStateEnum::BuildingWrapperModule( - PeripheralsStateBuildingWrapperModule { - output: Some(output), - .. - }, - ) => output, - PeripheralsStateEnum::BuildingWrapperModule(_) => unreachable!(), - }; - drop(state); - let Self { ty, common } = self; - let instance_io_field = Expr::field(output, &common.id.name); - assert_eq!(ty, Expr::ty(instance_io_field)); - Ok(UsedPeripheral { - instance_io_field, - common, - }) - } - pub fn into_used(self) -> Option> { - self.try_into_used().ok() - } -} - -impl fmt::Debug for Peripheral { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.as_ref().debug_common_fields("Peripheral", f).finish() - } -} - -pub struct UsedPeripheral { - instance_io_field: Expr, - common: PeripheralCommon, -} - -impl fmt::Debug for UsedPeripheral { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.as_ref() - .debug_common_fields("UsedPeripheral", f) - .field("instance_io_field", &self.instance_io_field()) - .finish() - } -} - -impl UsedPeripheral { - pub fn as_ref<'a>(&'a self) -> PeripheralRef<'a, T> { - let Self { - instance_io_field, - ref common, - } = *self; - PeripheralRef { - ty: Expr::ty(instance_io_field), - common, - } - } - pub fn instance_io_field(&self) -> Expr { - self.instance_io_field - } - pub fn ty(&self) -> T { - self.as_ref().ty() - } - pub fn id(&self) -> PeripheralId { - self.as_ref().id() - } - pub fn name(&self) -> Interned { - self.as_ref().name() - } - pub fn is_input(&self) -> bool { - self.as_ref().is_input() - } - pub fn is_output(&self) -> bool { - self.as_ref().is_output() - } - pub fn conflicts_with(&self) -> Interned> { - self.as_ref().conflicts_with() - } -} - -#[derive(Copy, Clone)] -pub struct PeripheralRef<'a, T: Type> { - ty: T, - common: &'a PeripheralCommon, -} - -impl<'a, T: Type> fmt::Debug for PeripheralRef<'a, T> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.debug_common_fields("PeripheralRef", f).finish() - } -} - -#[derive(Debug, Clone)] -pub enum PeripheralUnavailableError { - PeripheralAlreadyUsed { - id: PeripheralId, - }, - PeripheralConflict { - id: PeripheralId, - conflicting_id: PeripheralId, - }, - PeripheralsWillNotBeUsedToBuildWrapper, -} - -impl fmt::Display for PeripheralUnavailableError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::PeripheralAlreadyUsed { id } => { - write!(f, "peripherals can only be used once: {id:?}") - } - Self::PeripheralConflict { id, conflicting_id } => { - write!(f, "peripheral {id:?} conflicts with {conflicting_id:?}") - } - Self::PeripheralsWillNotBeUsedToBuildWrapper => { - write!(f, "peripherals will not be used to build wrapper") - } - } - } -} - -impl std::error::Error for PeripheralUnavailableError {} - -impl<'a, T: Type> PeripheralRef<'a, T> { - fn debug_common_fields<'f1, 'f2>( - &self, - struct_name: &str, - f: &'f1 mut fmt::Formatter<'f2>, - ) -> fmt::DebugStruct<'f1, 'f2> { - let Self { - ty, - common: - PeripheralCommon { - type_id: _, - id, - is_input, - peripherals_state: _, - }, - } = self; - let mut retval = f.debug_struct(struct_name); - retval - .field("ty", ty) - .field("id", id) - .field("is_input", is_input) - .field("availability", &self.availability()); - retval - } - pub fn ty(&self) -> T { - self.ty - } - pub fn id(&self) -> PeripheralId { - self.common.id - } - pub fn name(&self) -> Interned { - self.id().name - } - pub fn is_input(&self) -> bool { - self.common.is_input - } - pub fn is_output(&self) -> bool { - !self.common.is_input - } - pub fn conflicts_with(&self) -> Interned> { - match self.common.peripherals_state.building_module.get() { - Some(building_module) => building_module.conflicts_graph[&self.common.id], - None => match &*self - .common - .peripherals_state - .state - .lock() - .expect("shouldn't be poison") - { - PeripheralsStateEnum::CollectingPeripherals(v) => { - v.conflicts_graph[&self.common.id].intern() - } - PeripheralsStateEnum::Initial - | PeripheralsStateEnum::BuildingModule - | PeripheralsStateEnum::BuildingWrapperModule(_) => unreachable!(), - }, - } - } - pub fn availability(&self) -> PeripheralAvailability { - match self.common.peripherals_state.building_module.get() { - None => PeripheralAvailability::Available, - Some(building_module) => building_module - .availabilities - .lock() - .expect("shouldn't be poison")[&self.common.id], - } - } - pub fn is_available(&self) -> bool { - match self.common.peripherals_state.building_module.get() { - None => true, - Some(building_module) => match building_module - .availabilities - .lock() - .expect("shouldn't be poison")[&self.common.id] - { - PeripheralAvailability::Available => true, - PeripheralAvailability::Used | PeripheralAvailability::ConflictsWithUsed(_) => { - false - } - }, - } - } - pub fn is_used(&self) -> bool { - match self.common.peripherals_state.building_module.get() { - None => false, - Some(building_module) => match building_module - .availabilities - .lock() - .expect("shouldn't be poison")[&self.common.id] - { - PeripheralAvailability::Used => true, - PeripheralAvailability::Available - | PeripheralAvailability::ConflictsWithUsed(_) => false, - }, - } - } - pub fn canonical(self) -> PeripheralRef<'a, CanonicalType> { - let Self { ty, common } = self; - PeripheralRef { - ty: ty.canonical(), - common, - } - } - pub fn from_canonical(peripheral_ref: PeripheralRef<'a, CanonicalType>) -> Self { - let PeripheralRef { ty, common } = peripheral_ref; - Self { - ty: T::from_canonical(ty), - common, - } - } - #[track_caller] - pub fn try_use_peripheral(self) -> Result, PeripheralUnavailableError> { - self.try_use_peripheral_with_loc(SourceLocation::caller()) - } - #[track_caller] - pub fn try_use_peripheral_with_loc( - self, - source_location: SourceLocation, - ) -> Result, PeripheralUnavailableError> { - let PeripheralsState { - will_build_wrapper, - ref state, - ref building_module, - } = *self.common.peripherals_state; - if !will_build_wrapper { - return Err(PeripheralUnavailableError::PeripheralsWillNotBeUsedToBuildWrapper); - } - let Some(PeripheralsStateBuildingModule { - conflicts_graph, - availabilities, - on_use_state, - }) = building_module.get() - else { - panic!("can't use peripherals in a module before the PeripheralsBuilder is finished"); - }; - let state = state.lock().expect("shouldn't be poison"); - match *state { - PeripheralsStateEnum::Initial | PeripheralsStateEnum::CollectingPeripherals(_) => { - unreachable!() - } - PeripheralsStateEnum::BuildingModule => {} - PeripheralsStateEnum::BuildingWrapperModule(_) => { - panic!("can't add new peripherals after calling m.add_platform_io()") - } - } - drop(state); - let mut availabilities = availabilities.lock().expect("shouldn't be poison"); - let Some(availability) = availabilities.get_mut(&self.common.id) else { - unreachable!(); - }; - match *availability { - PeripheralAvailability::Available => { - *availability = PeripheralAvailability::Used; - } - PeripheralAvailability::Used => { - return Err(PeripheralUnavailableError::PeripheralAlreadyUsed { - id: self.common.id, - }); - } - PeripheralAvailability::ConflictsWithUsed(conflicting_id) => { - return Err(PeripheralUnavailableError::PeripheralConflict { - id: self.common.id, - conflicting_id, - }); - } - } - for conflict in conflicts_graph[&self.common.id].iter() { - let Some(availability) = availabilities.get_mut(conflict) else { - unreachable!(); - }; - *availability = PeripheralAvailability::ConflictsWithUsed(self.common.id); - } - drop(availabilities); - let wire = wire_with_loc(&self.name(), source_location, self.ty()); - let mut on_use_state = on_use_state.lock().expect("shouldn't be poison"); - let PeripheralsOnUseState { - shared_state, - main_module_io_fields, - main_module_io_wires, - on_use_functions, - } = &mut *on_use_state; - let Some(on_use_function) = on_use_functions.remove(&self.common.id) else { - unreachable!(); - }; - for conflict in conflicts_graph[&self.common.id].iter() { - on_use_functions.remove(conflict); - } - let canonical_wire = Expr::canonical(wire); - main_module_io_wires.push(canonical_wire); - main_module_io_fields.push(BundleField { - name: self.name(), - flipped: self.is_input(), - ty: Expr::ty(canonical_wire), - }); - on_use_function(&mut **shared_state, self.canonical(), canonical_wire); - drop(on_use_state); - Ok(wire) - } - #[track_caller] - pub fn use_peripheral_with_loc(self, source_location: SourceLocation) -> Expr { - match self.try_use_peripheral_with_loc(source_location) { - Ok(wire) => wire, - Err(e) => panic!("{e}"), - } - } - #[track_caller] - pub fn use_peripheral(self) -> Expr { - match self.try_use_peripheral() { - Ok(wire) => wire, - Err(e) => panic!("{e}"), - } - } -} - -pub trait Peripherals: 'static + Send + Sync + fmt::Debug { - fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>); - fn to_peripherals_vec<'a>(&'a self) -> Vec> { - let mut peripherals = Vec::new(); - self.append_peripherals(&mut peripherals); - peripherals - } -} - -impl Peripherals for Peripheral { - fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { - peripherals.push(self.as_ref().canonical()); - } -} - -impl Peripherals for Vec { - fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { - for v in self { - v.append_peripherals(peripherals); - } - } -} - -impl Peripherals for Box { - fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { - T::append_peripherals(self, peripherals); - } -} - -impl Peripherals for [T] { - fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { - for v in self { - v.append_peripherals(peripherals); - } - } -} - -impl Peripherals for [T; N] { - fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { - for v in self { - v.append_peripherals(peripherals); - } - } -} - -macro_rules! impl_peripherals { - (@impl $(($v:ident: $T:ident),)*) => { - impl<$($T: Peripherals),*> Peripherals for ($($T,)*) { - fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { - #![allow(unused_variables)] - let ($($v,)*) = self; - $(Peripherals::append_peripherals($v, peripherals);)* - } - } - }; - ($($first:tt, $($rest:tt,)*)?) => { - impl_peripherals!(@impl $($first, $($rest,)*)?); - $(impl_peripherals!($($rest,)*);)? - }; -} - -impl_peripherals! { - (v0: T0), - (v1: T1), - (v2: T2), - (v3: T3), - (v4: T4), - (v5: T5), - (v6: T6), - (v7: T7), - (v8: T8), - (v9: T9), - (v10: T10), - (v11: T11), -} - -pub struct PlatformIOBuilder<'a> { - peripherals: Vec>, - peripherals_by_type_id: HashMap>>, - peripherals_by_id: BTreeMap>, - peripherals_state: &'a PeripheralsState, -} - -impl<'a> PlatformIOBuilder<'a> { - pub fn peripherals(&self) -> &[PeripheralRef<'a, CanonicalType>] { - &self.peripherals - } - pub fn peripherals_with_type(&self) -> Vec> { - let Some(peripherals) = self.peripherals_by_type_id.get(&TypeId::of::()) else { - return Vec::new(); - }; - peripherals - .iter() - .map(|&peripheral_ref| PeripheralRef::from_canonical(peripheral_ref)) - .collect() - } - pub fn peripherals_by_id(&self) -> &BTreeMap> { - &self.peripherals_by_id - } - #[track_caller] - pub(crate) fn add_platform_io( - self, - name: &str, - source_location: SourceLocation, - m: &ModuleBuilder, - ) -> Expr { - if !ModuleBuilder::with(|m2| std::ptr::eq(m, m2)) { - panic!("m.add_platform_io() must be called in the same module as m"); - } - let PeripheralsState { - will_build_wrapper: _, - state, - building_module, - } = self.peripherals_state; - let building_module = building_module.get().expect("shouldn't be None"); - let mut on_use_state = building_module - .on_use_state - .lock() - .expect("shouldn't be poison"); - let output_ty = - Bundle::new(mem::take(&mut on_use_state.main_module_io_fields).intern_deref()); - let main_module_wires = mem::take(&mut on_use_state.main_module_io_wires); - drop(on_use_state); - let output = m.output_with_loc(name, source_location, output_ty); - for (field, wire_expr) in output_ty.fields().iter().zip(main_module_wires) { - let ExprEnum::Wire(wire) = *Expr::expr_enum(wire_expr) else { - unreachable!(); - }; - let field_expr = Expr::field(output, &field.name); - if field.flipped { - connect_with_loc(wire, field_expr, wire.source_location()); - } else { - connect_with_loc(field_expr, wire, wire.source_location()); - } - } - let ExprEnum::ModuleIO(output_module_io) = *Expr::expr_enum(output) else { - unreachable!(); - }; - *state.lock().expect("shouldn't be poison") = - PeripheralsStateEnum::BuildingWrapperModule(PeripheralsStateBuildingWrapperModule { - output_module_io, - output: None, - }); - output - } -} - -impl<'a> fmt::Debug for PlatformIOBuilder<'a> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Self { - peripherals, - peripherals_by_type_id: _, - peripherals_by_id: _, - peripherals_state: _, - } = self; - f.debug_struct("PlatformIOBuilder") - .field("peripherals", peripherals) - .finish_non_exhaustive() - } -} - -trait PlatformAspectTrait: 'static + Send + Sync + fmt::Debug { - fn any_ref(&self) -> &dyn Any; - fn any_arc(self: Arc) -> Arc; - fn eq_dyn(&self, other: &dyn PlatformAspectTrait) -> bool; - fn hash_dyn(&self, state: &mut dyn Hasher); -} - -impl PlatformAspectTrait for T { - fn any_ref(&self) -> &dyn Any { - self - } - - fn any_arc(self: Arc) -> Arc { - self - } - - fn eq_dyn(&self, other: &dyn PlatformAspectTrait) -> bool { - other - .any_ref() - .downcast_ref::() - .is_some_and(|other| self == other) - } - - fn hash_dyn(&self, mut state: &mut dyn Hasher) { - self.hash(&mut state); - } -} - -#[derive(Clone)] -pub struct PlatformAspect { - type_id: TypeId, - type_name: &'static str, - value: Arc, -} - -impl Hash for PlatformAspect { - fn hash(&self, state: &mut H) { - PlatformAspectTrait::hash_dyn(&*self.value, state); - } -} - -impl Eq for PlatformAspect {} - -impl PartialEq for PlatformAspect { - fn eq(&self, other: &Self) -> bool { - self.type_id == other.type_id && PlatformAspectTrait::eq_dyn(&*self.value, &*other.value) - } -} - -impl fmt::Debug for PlatformAspect { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Self { - type_id: _, - type_name, - value, - } = self; - write!(f, "PlatformAspect<{type_name}>")?; - f.debug_tuple("").field(value).finish() - } -} - -impl PlatformAspect { - pub fn new_arc(value: Arc) -> Self { - Self { - type_id: TypeId::of::(), - type_name: std::any::type_name::(), - value, - } - } - pub fn new(value: T) -> Self { - Self::new_arc(Arc::new(value)) - } - pub fn type_id(&self) -> TypeId { - self.type_id - } - pub fn downcast_arc(self) -> Result, Self> { - if self.type_id == TypeId::of::() { - let Ok(retval) = self.value.any_arc().downcast() else { - unreachable!(); - }; - Ok(retval) - } else { - Err(self) - } - } - pub fn downcast_unwrap_or_clone( - self, - ) -> Result { - Ok(Arc::unwrap_or_clone(self.downcast_arc()?)) - } - pub fn downcast_ref(&self) -> Option<&T> { - PlatformAspectTrait::any_ref(&*self.value).downcast_ref() - } -} - -#[derive(Clone, Default)] -pub struct PlatformAspectSet { - aspects_by_type_id: Arc>>, - aspects: Arc>, -} - -impl PlatformAspectSet { - pub fn new() -> Self { - Self::default() - } - pub fn insert_new( - &mut self, - value: T, - ) -> bool { - self.insert(PlatformAspect::new(value)) - } - pub fn insert_new_arc( - &mut self, - value: Arc, - ) -> bool { - self.insert(PlatformAspect::new_arc(value)) - } - fn insert_inner( - aspects_by_type_id: &mut HashMap>, - aspects: &mut Vec, - value: PlatformAspect, - ) -> bool { - if aspects_by_type_id - .entry(value.type_id) - .or_default() - .insert(value.clone()) - { - aspects.push(value); - true - } else { - false - } - } - pub fn insert(&mut self, value: PlatformAspect) -> bool { - Self::insert_inner( - Arc::make_mut(&mut self.aspects_by_type_id), - Arc::make_mut(&mut self.aspects), - value, - ) - } - pub fn contains(&self, value: &PlatformAspect) -> bool { - self.aspects_by_type_id - .get(&value.type_id) - .is_some_and(|aspects| aspects.contains(value)) - } - pub fn get_aspects_by_type<'a, T: 'static + Send + Sync + fmt::Debug + Hash + Eq>( - &'a self, - ) -> impl Clone + Iterator + FusedIterator + ExactSizeIterator + 'a - { - self.aspects_by_type_id - .get(&TypeId::of::()) - .map(|aspects| aspects.iter()) - .unwrap_or_default() - } - pub fn get_by_type<'a, T: 'static + Send + Sync + fmt::Debug + Hash + Eq>( - &'a self, - ) -> impl Clone + Iterator + FusedIterator + ExactSizeIterator + 'a { - self.get_aspects_by_type::() - .map(|aspect| aspect.downcast_ref().expect("already checked type")) - } - pub fn get_single_by_type<'a, T: 'static + Send + Sync + fmt::Debug + Hash + Eq>( - &'a self, - ) -> Option<&'a T> { - let mut aspects = self.get_by_type::(); - if aspects.len() == 1 { - aspects.next() - } else { - None - } - } - pub fn get_arcs_by_type<'a, T: 'static + Send + Sync + fmt::Debug + Hash + Eq>( - &'a self, - ) -> impl Clone + Iterator> + FusedIterator + ExactSizeIterator + 'a { - self.get_aspects_by_type::().map(|aspect| { - aspect - .clone() - .downcast_arc() - .ok() - .expect("already checked type") - }) - } -} - -impl<'a> Extend<&'a PlatformAspect> for PlatformAspectSet { - fn extend>(&mut self, iter: T) { - self.extend(iter.into_iter().cloned()); - } -} - -impl Extend for PlatformAspectSet { - fn extend>(&mut self, iter: T) { - let Self { - aspects_by_type_id, - aspects, - } = self; - let aspects_by_type_id = Arc::make_mut(aspects_by_type_id); - let aspects = Arc::make_mut(aspects); - iter.into_iter().for_each(|value| { - Self::insert_inner(aspects_by_type_id, aspects, value); - }); - } -} - -impl<'a> FromIterator<&'a PlatformAspect> for PlatformAspectSet { - fn from_iter>(iter: T) -> Self { - let mut retval = Self::default(); - retval.extend(iter); - retval - } -} - -impl FromIterator for PlatformAspectSet { - fn from_iter>(iter: T) -> Self { - let mut retval = Self::default(); - retval.extend(iter); - retval - } -} - -impl std::ops::Deref for PlatformAspectSet { - type Target = [PlatformAspect]; - - fn deref(&self) -> &Self::Target { - &self.aspects - } -} - -impl fmt::Debug for PlatformAspectSet { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_set().entries(self).finish() - } -} - -impl IntoIterator for PlatformAspectSet { - type Item = PlatformAspect; - type IntoIter = PlatformAspectsIntoIter; - - fn into_iter(self) -> Self::IntoIter { - PlatformAspectsIntoIter { - indexes: 0..self.aspects.len(), - aspects: self.aspects, - } - } -} - -impl<'a> IntoIterator for &'a PlatformAspectSet { - type Item = &'a PlatformAspect; - type IntoIter = std::slice::Iter<'a, PlatformAspect>; - - fn into_iter(self) -> Self::IntoIter { - self.aspects.iter() - } -} - -#[derive(Clone, Debug)] -pub struct PlatformAspectsIntoIter { - aspects: Arc>, - indexes: std::ops::Range, -} - -impl Iterator for PlatformAspectsIntoIter { - type Item = PlatformAspect; - - fn next(&mut self) -> Option { - self.indexes.next().map(|index| self.aspects[index].clone()) - } - - fn size_hint(&self) -> (usize, Option) { - self.indexes.size_hint() - } - - fn count(self) -> usize { - self.indexes.len() - } - - fn last(mut self) -> Option { - self.next_back() - } - - fn nth(&mut self, n: usize) -> Option { - self.indexes.nth(n).map(|index| self.aspects[index].clone()) - } - - fn fold(self, init: B, mut f: F) -> B - where - F: FnMut(B, Self::Item) -> B, - { - self.indexes - .fold(init, |v, index| f(v, self.aspects[index].clone())) - } -} - -impl FusedIterator for PlatformAspectsIntoIter {} - -impl ExactSizeIterator for PlatformAspectsIntoIter {} - -impl DoubleEndedIterator for PlatformAspectsIntoIter { - fn next_back(&mut self) -> Option { - self.indexes - .next_back() - .map(|index| self.aspects[index].clone()) - } - - fn nth_back(&mut self, n: usize) -> Option { - self.indexes - .nth_back(n) - .map(|index| self.aspects[index].clone()) - } - - fn rfold(self, init: B, mut f: F) -> B - where - F: FnMut(B, Self::Item) -> B, - { - self.indexes - .rfold(init, |v, index| f(v, self.aspects[index].clone())) - } -} - -pub trait Platform: Clone + 'static + Send + Sync + fmt::Debug + Hash + Eq { - type Peripherals: Peripherals; - fn name(&self) -> Interned; - fn new_peripherals<'builder>( - &self, - builder_factory: PeripheralsBuilderFactory<'builder>, - ) -> (Self::Peripherals, PeripheralsBuilderFinished<'builder>); - /// gets peripherals that can be used for inspecting them, but not for building a main module - fn get_peripherals(&self) -> Self::Peripherals { - let ( - retval, - PeripheralsBuilderFinished { - _private: PhantomData, - }, - ) = self.new_peripherals(PeripheralsBuilderFactory::new(false)); - retval - } - fn source_location(&self) -> SourceLocation; - fn add_peripherals_in_wrapper_module(&self, m: &ModuleBuilder, peripherals: Self::Peripherals); - #[track_caller] - fn try_wrap_main_module< - T: BundleType, - E, - M: AsRef>, - F: for<'a> FnOnce(PlatformIOBuilder<'a>) -> Result, - >( - &self, - make_main_module: F, - ) -> Result>, E> { - let builder_factory = PeripheralsBuilderFactory::new(true); - let peripherals_state = builder_factory.peripherals_state.clone(); - let ( - peripherals, - PeripheralsBuilderFinished { - _private: PhantomData, - }, - ) = self.new_peripherals(builder_factory); - let peripherals_vec = peripherals.to_peripherals_vec(); - let mut peripherals_by_id = BTreeMap::new(); - let mut peripherals_by_type_id = HashMap::<_, Vec<_>>::default(); - for &peripheral in &peripherals_vec { - peripherals_by_id.insert(peripheral.id(), peripheral); - peripherals_by_type_id - .entry(peripheral.common.type_id) - .or_default() - .push(peripheral); - } - let main_module = Module::canonical( - *make_main_module(PlatformIOBuilder { - peripherals: peripherals_vec, - peripherals_by_type_id, - peripherals_by_id, - peripherals_state: &peripherals_state, - })? - .as_ref(), - ); - let state = peripherals_state.state.lock().expect("shouldn't be poison"); - let PeripheralsStateEnum::BuildingWrapperModule(PeripheralsStateBuildingWrapperModule { - output_module_io, - output: _, - }) = *state - else { - drop(state); - panic!( - "you need to call m.add_platform_io() inside the main module you're trying to use peripherals in.\nat: {}", - main_module.source_location() - ); - }; - drop(state); - for module_io in main_module.module_io() { - if module_io.module_io != output_module_io { - panic!( - "when you're using m.add_platform_io(), you can't have any other inputs/outputs.\nat: {}", - module_io.module_io.source_location() - ); - } - } - Ok(ModuleBuilder::run_with_loc( - &main_module.name(), - self.source_location(), - crate::module::ModuleKind::Normal, - |m| { - let instance = - instance_with_loc("main", main_module.intern(), self.source_location()); - let output_expr = Expr::field(instance, &output_module_io.bundle_field().name); - let mut state = peripherals_state.state.lock().expect("shouldn't be poison"); - let PeripheralsStateEnum::BuildingWrapperModule( - PeripheralsStateBuildingWrapperModule { - output_module_io: _, - output, - }, - ) = &mut *state - else { - unreachable!(); - }; - *output = Some(output_expr); - drop(state); - self.add_peripherals_in_wrapper_module(m, peripherals) - }, - )) - } - #[track_caller] - fn wrap_main_module< - T: BundleType, - M: AsRef>, - F: for<'a> FnOnce(PlatformIOBuilder<'a>) -> M, - >( - &self, - make_main_module: F, - ) -> Interned> { - self.try_wrap_main_module(|p| Ok(make_main_module(p))) - .unwrap_or_else(|e: Infallible| match e {}) - } - fn aspects(&self) -> PlatformAspectSet; -} - -impl DynPlatform { - pub fn registry() -> PlatformRegistrySnapshot { - PlatformRegistrySnapshot(PlatformRegistry::get()) - } - #[track_caller] - pub fn register(self) { - PlatformRegistry::register(PlatformRegistry::lock(), self); - } -} - -#[derive(Clone, Debug)] -struct PlatformRegistry { - platforms: BTreeMap, -} - -enum PlatformRegisterError { - SameName { - name: InternedStrCompareAsStr, - old_platform: DynPlatform, - new_platform: DynPlatform, - }, -} - -impl fmt::Display for PlatformRegisterError { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - Self::SameName { - name, - old_platform, - new_platform, - } => write!( - f, - "two different `Platform` can't share the same name:\n\ - {name:?}\n\ - old platform:\n\ - {old_platform:?}\n\ - new platform:\n\ - {new_platform:?}", - ), - } - } -} - -trait PlatformRegistryRegisterLock { - type Locked; - fn lock(self) -> Self::Locked; - fn make_mut(locked: &mut Self::Locked) -> &mut PlatformRegistry; -} - -impl PlatformRegistryRegisterLock for &'static RwLock> { - type Locked = RwLockWriteGuard<'static, Arc>; - fn lock(self) -> Self::Locked { - self.write().expect("shouldn't be poisoned") - } - fn make_mut(locked: &mut Self::Locked) -> &mut PlatformRegistry { - Arc::make_mut(locked) - } -} - -impl PlatformRegistryRegisterLock for &'_ mut PlatformRegistry { - type Locked = Self; - - fn lock(self) -> Self::Locked { - self - } - - fn make_mut(locked: &mut Self::Locked) -> &mut PlatformRegistry { - locked - } -} - -impl PlatformRegistry { - fn lock() -> &'static RwLock> { - static REGISTRY: OnceLock>> = OnceLock::new(); - REGISTRY.get_or_init(Default::default) - } - fn try_register( - lock: L, - platform: DynPlatform, - ) -> Result<(), PlatformRegisterError> { - use std::collections::btree_map::Entry; - let name = InternedStrCompareAsStr(platform.name()); - // run user code only outside of lock - let mut locked = lock.lock(); - let this = L::make_mut(&mut locked); - let result = match this.platforms.entry(name) { - Entry::Occupied(entry) => Err(PlatformRegisterError::SameName { - name, - old_platform: entry.get().clone(), - new_platform: platform, - }), - Entry::Vacant(entry) => { - entry.insert(platform); - Ok(()) - } - }; - drop(locked); - // outside of lock now, so we can test if it's the same DynPlatform - match result { - Err(PlatformRegisterError::SameName { - name: _, - old_platform, - new_platform, - }) if old_platform == new_platform => Ok(()), - result => result, - } - } - #[track_caller] - fn register(lock: L, platform: DynPlatform) { - match Self::try_register(lock, platform) { - Err(e) => panic!("{e}"), - Ok(()) => {} - } - } - fn get() -> Arc { - Self::lock().read().expect("shouldn't be poisoned").clone() - } -} - -impl Default for PlatformRegistry { - fn default() -> Self { - let mut retval = Self { - platforms: BTreeMap::new(), - }; - for platform in built_in_platforms() { - Self::register(&mut retval, platform); - } - retval - } -} - -#[derive(Clone, Debug)] -pub struct PlatformRegistrySnapshot(Arc); - -impl PlatformRegistrySnapshot { - pub fn get() -> Self { - PlatformRegistrySnapshot(PlatformRegistry::get()) - } - pub fn get_by_name<'a>(&'a self, name: &str) -> Option<&'a DynPlatform> { - self.0.platforms.get(name) - } - pub fn iter_with_names(&self) -> PlatformRegistryIterWithNames<'_> { - PlatformRegistryIterWithNames(self.0.platforms.iter()) - } - pub fn iter(&self) -> PlatformRegistryIter<'_> { - PlatformRegistryIter(self.0.platforms.values()) - } -} - -impl<'a> IntoIterator for &'a PlatformRegistrySnapshot { - type Item = &'a DynPlatform; - type IntoIter = PlatformRegistryIter<'a>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -impl<'a> IntoIterator for &'a mut PlatformRegistrySnapshot { - type Item = &'a DynPlatform; - type IntoIter = PlatformRegistryIter<'a>; - - fn into_iter(self) -> Self::IntoIter { - self.iter() - } -} - -#[derive(Clone, Debug)] -pub struct PlatformRegistryIter<'a>( - std::collections::btree_map::Values<'a, InternedStrCompareAsStr, DynPlatform>, -); - -impl<'a> Iterator for PlatformRegistryIter<'a> { - type Item = &'a DynPlatform; - - fn next(&mut self) -> Option { - self.0.next() - } - - fn size_hint(&self) -> (usize, Option) { - self.0.size_hint() - } - - fn count(self) -> usize - where - Self: Sized, - { - self.0.count() - } - - fn last(self) -> Option { - self.0.last() - } - - fn nth(&mut self, n: usize) -> Option { - self.0.nth(n) - } - - fn fold(self, init: B, f: F) -> B - where - F: FnMut(B, Self::Item) -> B, - { - self.0.fold(init, f) - } -} - -impl<'a> std::iter::FusedIterator for PlatformRegistryIter<'a> {} - -impl<'a> ExactSizeIterator for PlatformRegistryIter<'a> {} - -impl<'a> DoubleEndedIterator for PlatformRegistryIter<'a> { - fn next_back(&mut self) -> Option { - self.0.next_back() - } - - fn nth_back(&mut self, n: usize) -> Option { - self.0.nth_back(n) - } - - fn rfold(self, init: B, f: F) -> B - where - F: FnMut(B, Self::Item) -> B, - { - self.0.rfold(init, f) - } -} - -#[derive(Clone, Debug)] -pub struct PlatformRegistryIterWithNames<'a>( - std::collections::btree_map::Iter<'a, InternedStrCompareAsStr, DynPlatform>, -); - -impl<'a> Iterator for PlatformRegistryIterWithNames<'a> { - type Item = (Interned, &'a DynPlatform); - - fn next(&mut self) -> Option { - self.0.next().map(|(name, platform)| (name.0, platform)) - } - - fn size_hint(&self) -> (usize, Option) { - self.0.size_hint() - } - - fn count(self) -> usize - where - Self: Sized, - { - self.0.count() - } - - fn last(self) -> Option { - self.0.last().map(|(name, platform)| (name.0, platform)) - } - - fn nth(&mut self, n: usize) -> Option { - self.0.nth(n).map(|(name, platform)| (name.0, platform)) - } - - fn fold(self, init: B, f: F) -> B - where - F: FnMut(B, Self::Item) -> B, - { - self.0 - .map(|(name, platform)| (name.0, platform)) - .fold(init, f) - } -} - -impl<'a> std::iter::FusedIterator for PlatformRegistryIterWithNames<'a> {} - -impl<'a> ExactSizeIterator for PlatformRegistryIterWithNames<'a> {} - -impl<'a> DoubleEndedIterator for PlatformRegistryIterWithNames<'a> { - fn next_back(&mut self) -> Option { - self.0 - .next_back() - .map(|(name, platform)| (name.0, platform)) - } - - fn nth_back(&mut self, n: usize) -> Option { - self.0 - .nth_back(n) - .map(|(name, platform)| (name.0, platform)) - } - - fn rfold(self, init: B, f: F) -> B - where - F: FnMut(B, Self::Item) -> B, - { - self.0 - .map(|(name, platform)| (name.0, platform)) - .rfold(init, f) - } -} - -#[track_caller] -pub fn register_platform(kind: K) { - DynPlatform::new(kind).register(); -} - -impl Serialize for DynPlatform { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - self.name().serialize(serializer) - } -} - -impl<'de> Deserialize<'de> for DynPlatform { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - let name = Cow::::deserialize(deserializer)?; - match Self::registry().get_by_name(&name) { - Some(retval) => Ok(retval.clone()), - None => Err(D::Error::custom(format_args!( - "unknown platform: name not found in registry: {name:?}" - ))), - } - } -} - -#[derive(Copy, Clone, Debug, Default)] -pub struct DynPlatformValueParser; - -#[derive(Clone, PartialEq, Eq, Hash)] -struct DynPlatformValueEnum { - name: Interned, - platform: DynPlatform, -} - -impl clap::ValueEnum for DynPlatformValueEnum { - fn value_variants<'a>() -> &'a [Self] { - Interned::into_inner( - PlatformRegistrySnapshot::get() - .iter_with_names() - .map(|(name, platform)| Self { - name, - platform: platform.clone(), - }) - .collect(), - ) - } - - fn to_possible_value(&self) -> Option { - Some(clap::builder::PossibleValue::new(Interned::into_inner( - self.name, - ))) - } -} - -impl clap::builder::TypedValueParser for DynPlatformValueParser { - type Value = DynPlatform; - - fn parse_ref( - &self, - cmd: &clap::Command, - arg: Option<&clap::Arg>, - value: &std::ffi::OsStr, - ) -> clap::error::Result { - clap::builder::EnumValueParser::::new() - .parse_ref(cmd, arg, value) - .map(|v| v.platform) - } - - fn possible_values( - &self, - ) -> Option + '_>> { - static ENUM_VALUE_PARSER: OnceLock> = - OnceLock::new(); - ENUM_VALUE_PARSER - .get_or_init(clap::builder::EnumValueParser::::new) - .possible_values() - } -} - -impl clap::builder::ValueParserFactory for DynPlatform { - type Parser = DynPlatformValueParser; - - fn value_parser() -> Self::Parser { - DynPlatformValueParser::default() - } -} - -pub(crate) fn built_in_platforms() -> impl IntoIterator { - crate::vendor::built_in_platforms() -} diff --git a/crates/fayalite/src/platform/peripherals.rs b/crates/fayalite/src/platform/peripherals.rs deleted file mode 100644 index 90c66402..00000000 --- a/crates/fayalite/src/platform/peripherals.rs +++ /dev/null @@ -1,62 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// See Notices.txt for copyright information - -use crate::{intern::Intern, prelude::*}; -use ordered_float::NotNan; -use serde::{Deserialize, Serialize}; - -#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] -#[non_exhaustive] -pub struct ClockInputProperties { - pub frequency: NotNan, -} - -#[hdl(no_runtime_generics, no_static)] -pub struct ClockInput { - pub clk: Clock, - pub properties: PhantomConst, -} - -impl ClockInput { - #[track_caller] - pub fn new(frequency: f64) -> Self { - assert!( - frequency > 0.0 && frequency.is_finite(), - "invalid clock frequency: {frequency}" - ); - Self { - clk: Clock, - properties: PhantomConst::new( - ClockInputProperties { - frequency: NotNan::new(frequency).expect("just checked"), - } - .intern_sized(), - ), - } - } - pub fn frequency(self) -> f64 { - self.properties.get().frequency.into_inner() - } -} - -#[hdl] -pub struct Led { - pub on: Bool, -} - -#[hdl] -pub struct RgbLed { - pub r: Bool, - pub g: Bool, - pub b: Bool, -} - -#[hdl] -/// UART, used as an output from the FPGA -pub struct Uart { - /// transmit from the FPGA's perspective - pub tx: Bool, - /// receive from the FPGA's perspective - #[hdl(flip)] - pub rx: Bool, -} diff --git a/crates/fayalite/src/prelude.rs b/crates/fayalite/src/prelude.rs index 216f94e8..5ff3a64a 100644 --- a/crates/fayalite/src/prelude.rs +++ b/crates/fayalite/src/prelude.rs @@ -28,7 +28,6 @@ pub use crate::{ memory, memory_array, memory_with_init, reg_builder, wire, }, phantom_const::PhantomConst, - platform::{DynPlatform, Platform, PlatformIOBuilder, peripherals}, reg::Reg, reset::{AsyncReset, Reset, SyncReset, ToAsyncReset, ToReset, ToSyncReset}, sim::{ diff --git a/crates/fayalite/src/sim.rs b/crates/fayalite/src/sim.rs index fabe6d8c..1717aec6 100644 --- a/crates/fayalite/src/sim.rs +++ b/crates/fayalite/src/sim.rs @@ -12,9 +12,7 @@ use crate::{ }, }, int::BoolOrIntType, - intern::{ - Intern, InternSlice, Interned, InternedCompare, PtrEqWithTypeId, SupportsPtrEqWithTypeId, - }, + intern::{Intern, Interned, InternedCompare, PtrEqWithTypeId, SupportsPtrEqWithTypeId}, module::{ ModuleIO, transform::visit::{Fold, Folder, Visit, Visitor}, @@ -264,7 +262,7 @@ impl_trace_decl! { }), Instance(TraceInstance { fn children(self) -> _ { - [self.instance_io.into(), self.module.into()].intern_slice() + [self.instance_io.into(), self.module.into()][..].intern() } name: Interned, instance_io: TraceBundle, @@ -284,7 +282,7 @@ impl_trace_decl! { }), MemPort(TraceMemPort { fn children(self) -> _ { - [self.bundle.into()].intern_slice() + [self.bundle.into()][..].intern() } name: Interned, bundle: TraceBundle, @@ -292,7 +290,7 @@ impl_trace_decl! { }), Wire(TraceWire { fn children(self) -> _ { - [*self.child].intern_slice() + [*self.child][..].intern() } name: Interned, child: Interned, @@ -300,7 +298,7 @@ impl_trace_decl! { }), Reg(TraceReg { fn children(self) -> _ { - [*self.child].intern_slice() + [*self.child][..].intern() } name: Interned, child: Interned, @@ -308,7 +306,7 @@ impl_trace_decl! { }), ModuleIO(TraceModuleIO { fn children(self) -> _ { - [*self.child].intern_slice() + [*self.child][..].intern() } name: Interned, child: Interned, diff --git a/crates/fayalite/src/sim/compiler.rs b/crates/fayalite/src/sim/compiler.rs index fbede7b4..2b291bea 100644 --- a/crates/fayalite/src/sim/compiler.rs +++ b/crates/fayalite/src/sim/compiler.rs @@ -14,7 +14,7 @@ use crate::{ }, }, int::BoolOrIntType, - intern::{Intern, InternSlice, Interned, Memoize}, + intern::{Intern, Interned, Memoize}, memory::PortKind, module::{ AnnotatedModuleIO, Block, ExternModuleBody, Id, InstantiatedModule, ModuleBody, NameId, @@ -3950,8 +3950,8 @@ impl Compiler { [Cond { body: CondBody::IfTrue { cond }, source_location: reg.source_location(), - }] - .intern_slice(), + }][..] + .intern(), lhs, init, reg.source_location(), diff --git a/crates/fayalite/src/target.rs b/crates/fayalite/src/target.rs new file mode 100644 index 00000000..33ffee9a --- /dev/null +++ b/crates/fayalite/src/target.rs @@ -0,0 +1,202 @@ +// SPDX-License-Identifier: LGPL-3.0-or-later +// See Notices.txt for copyright information + +use crate::{intern::Interned, util::job_server::AcquiredJob}; +use std::{ + any::Any, + fmt, + iter::FusedIterator, + sync::{Arc, Mutex}, +}; + +pub trait Peripheral: Any + Send + Sync + fmt::Debug {} + +pub trait Tool: Any + Send + Sync + fmt::Debug { + fn name(&self) -> Interned; + fn run(&self, acquired_job: &mut AcquiredJob); +} + +pub trait Target: Any + Send + Sync + fmt::Debug { + fn name(&self) -> Interned; + fn peripherals(&self) -> Interned<[Interned]>; +} + +#[derive(Clone)] +struct TargetsMap(Vec<(Interned, Interned)>); + +impl TargetsMap { + fn sort(&mut self) { + self.0.sort_by(|(k1, _), (k2, _)| str::cmp(k1, k2)); + self.0.dedup_by_key(|(k, _)| *k); + } + fn from_unsorted_vec(unsorted_vec: Vec<(Interned, Interned)>) -> Self { + let mut retval = Self(unsorted_vec); + retval.sort(); + retval + } + fn extend_from_unsorted_slice(&mut self, additional: &[(Interned, Interned)]) { + self.0.extend_from_slice(additional); + self.sort(); + } +} + +impl Default for TargetsMap { + fn default() -> Self { + Self::from_unsorted_vec(vec![ + // TODO: add default targets here + ]) + } +} + +fn access_targets>) -> R, R>(f: F) -> R { + static TARGETS: Mutex>> = Mutex::new(None); + let mut targets_lock = TARGETS.lock().expect("shouldn't be poisoned"); + f(&mut targets_lock) +} + +pub fn add_targets>>(additional: I) { + // run iterator and target methods outside of lock + let additional = Vec::from_iter(additional.into_iter().map(|v| (v.name(), v))); + access_targets(|targets| { + Arc::make_mut(targets.get_or_insert_default()).extend_from_unsorted_slice(&additional); + }); +} + +pub fn targets() -> TargetsSnapshot { + access_targets(|targets| match targets { + Some(targets) => TargetsSnapshot { + targets: targets.clone(), + }, + None => { + let new_targets = Arc::::default(); + *targets = Some(new_targets.clone()); + TargetsSnapshot { + targets: new_targets, + } + } + }) +} + +#[derive(Clone)] +pub struct TargetsSnapshot { + targets: Arc, +} + +impl TargetsSnapshot { + pub fn get(&self, key: &str) -> Option> { + let index = self + .targets + .0 + .binary_search_by_key(&key, |(k, _v)| k) + .ok()?; + Some(self.targets.0[index].1) + } + pub fn iter(&self) -> TargetsIter { + self.into_iter() + } + pub fn len(&self) -> usize { + self.targets.0.len() + } +} + +impl fmt::Debug for TargetsSnapshot { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("TargetsSnapshot ")?; + f.debug_map().entries(self).finish() + } +} + +impl IntoIterator for &'_ mut TargetsSnapshot { + type Item = (Interned, Interned); + type IntoIter = TargetsIter; + + fn into_iter(self) -> Self::IntoIter { + self.clone().into_iter() + } +} + +impl IntoIterator for &'_ TargetsSnapshot { + type Item = (Interned, Interned); + type IntoIter = TargetsIter; + + fn into_iter(self) -> Self::IntoIter { + self.clone().into_iter() + } +} + +impl IntoIterator for TargetsSnapshot { + type Item = (Interned, Interned); + type IntoIter = TargetsIter; + + fn into_iter(self) -> Self::IntoIter { + TargetsIter { + indexes: 0..self.targets.0.len(), + targets: self.targets, + } + } +} + +#[derive(Clone)] +pub struct TargetsIter { + targets: Arc, + indexes: std::ops::Range, +} + +impl fmt::Debug for TargetsIter { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("TargetsIter ")?; + f.debug_map().entries(self.clone()).finish() + } +} + +impl Iterator for TargetsIter { + type Item = (Interned, Interned); + + fn next(&mut self) -> Option { + Some(self.targets.0[self.indexes.next()?]) + } + + fn size_hint(&self) -> (usize, Option) { + self.indexes.size_hint() + } + + fn count(self) -> usize { + self.indexes.len() + } + + fn last(mut self) -> Option { + self.next_back() + } + + fn nth(&mut self, n: usize) -> Option { + Some(self.targets.0[self.indexes.nth(n)?]) + } + + fn fold B>(self, init: B, mut f: F) -> B { + self.indexes + .fold(init, move |retval, index| f(retval, self.targets.0[index])) + } +} + +impl FusedIterator for TargetsIter {} + +impl DoubleEndedIterator for TargetsIter { + fn next_back(&mut self) -> Option { + Some(self.targets.0[self.indexes.next_back()?]) + } + + fn nth_back(&mut self, n: usize) -> Option { + Some(self.targets.0[self.indexes.nth_back(n)?]) + } + + fn rfold B>(self, init: B, mut f: F) -> B { + self.indexes + .rfold(init, move |retval, index| f(retval, self.targets.0[index])) + } +} + +impl ExactSizeIterator for TargetsIter { + fn len(&self) -> usize { + self.indexes.len() + } +} diff --git a/crates/fayalite/src/testing.rs b/crates/fayalite/src/testing.rs index c7feb5e4..47c7cfa3 100644 --- a/crates/fayalite/src/testing.rs +++ b/crates/fayalite/src/testing.rs @@ -2,8 +2,8 @@ // See Notices.txt for copyright information use crate::{ build::{ - BaseJobArgs, BaseJobKind, GlobalParams, JobArgsAndDependencies, JobKindAndArgs, JobParams, - NoArgs, RunBuild, + BaseJobArgs, BaseJobKind, JobArgsAndDependencies, JobKindAndArgs, JobParams, NoArgs, + RunBuild, external::{ExternalCommandArgs, ExternalCommandJobKind}, firrtl::{FirrtlArgs, FirrtlJobKind}, formal::{Formal, FormalAdditionalArgs, FormalArgs, FormalMode, WriteSbyFileJobKind}, @@ -14,6 +14,7 @@ use crate::{ module::Module, util::HashMap, }; +use eyre::eyre; use serde::Deserialize; use std::{ fmt::Write, @@ -106,7 +107,12 @@ fn make_assert_formal_args( ) -> eyre::Result>> { let args = JobKindAndArgs { kind: BaseJobKind, - args: BaseJobArgs::from_output_dir_and_env(get_assert_formal_target_path(&test_name), None), + args: BaseJobArgs::from_output_dir_and_env( + get_assert_formal_target_path(&test_name) + .into_os_string() + .into_string() + .map_err(|_| eyre!("path is not valid UTF-8"))?, + ), }; let dependencies = JobArgsAndDependencies { args, @@ -168,9 +174,9 @@ pub fn try_assert_formal>, T: BundleType>( solver, export_options, )? - .run_without_platform( - |NoArgs {}| Ok(JobParams::new(module)), - &GlobalParams::new(None, APP_NAME), + .run( + |NoArgs {}| Ok(JobParams::new(module, APP_NAME)), + clap::Command::new(APP_NAME), // not actually used, so we can use an arbitrary value ) } diff --git a/crates/fayalite/src/util.rs b/crates/fayalite/src/util.rs index 9796488b..23a68521 100644 --- a/crates/fayalite/src/util.rs +++ b/crates/fayalite/src/util.rs @@ -33,15 +33,15 @@ pub use const_cmp::{ #[doc(inline)] pub use scoped_ref::ScopedRef; +pub(crate) use misc::chain; #[doc(inline)] pub use misc::{ BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice, RcWriter, SerdeJsonEscapeIf, SerdeJsonEscapeIfFormatter, SerdeJsonEscapeIfTest, - SerdeJsonEscapeIfTestResult, interned_bit, iter_eq_by, os_str_strip_prefix, - os_str_strip_suffix, serialize_to_json_ascii, serialize_to_json_ascii_pretty, - serialize_to_json_ascii_pretty_with_indent, slice_range, try_slice_range, + SerdeJsonEscapeIfTestResult, interned_bit, iter_eq_by, serialize_to_json_ascii, + serialize_to_json_ascii_pretty, serialize_to_json_ascii_pretty_with_indent, slice_range, + try_slice_range, }; -pub(crate) use misc::{InternedStrCompareAsStr, chain}; pub mod job_server; pub mod prefix_sum; diff --git a/crates/fayalite/src/util/misc.rs b/crates/fayalite/src/util/misc.rs index 165ab3a4..6c9aceef 100644 --- a/crates/fayalite/src/util/misc.rs +++ b/crates/fayalite/src/util/misc.rs @@ -4,7 +4,6 @@ use crate::intern::{Intern, Interned}; use bitvec::{bits, order::Lsb0, slice::BitSlice, view::BitView}; use std::{ cell::Cell, - ffi::OsStr, fmt::{self, Debug, Write}, io, ops::{Bound, Range, RangeBounds}, @@ -565,50 +564,3 @@ pub fn serialize_to_json_ascii_pretty_with_indent( serde_json::ser::PrettyFormatter::with_indent(indent.as_bytes()), ) } - -pub fn os_str_strip_prefix<'a>(os_str: &'a OsStr, prefix: impl AsRef) -> Option<&'a OsStr> { - os_str - .as_encoded_bytes() - .strip_prefix(prefix.as_ref().as_bytes()) - .map(|bytes| { - // Safety: we removed a UTF-8 prefix so bytes starts with a valid boundary - unsafe { OsStr::from_encoded_bytes_unchecked(bytes) } - }) -} - -pub fn os_str_strip_suffix<'a>(os_str: &'a OsStr, suffix: impl AsRef) -> Option<&'a OsStr> { - os_str - .as_encoded_bytes() - .strip_suffix(suffix.as_ref().as_bytes()) - .map(|bytes| { - // Safety: we removed a UTF-8 suffix so bytes ends with a valid boundary - unsafe { OsStr::from_encoded_bytes_unchecked(bytes) } - }) -} - -#[derive(Copy, Clone, PartialEq, Eq)] -pub(crate) struct InternedStrCompareAsStr(pub(crate) Interned); - -impl fmt::Debug for InternedStrCompareAsStr { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - self.0.fmt(f) - } -} - -impl Ord for InternedStrCompareAsStr { - fn cmp(&self, other: &Self) -> std::cmp::Ordering { - str::cmp(&self.0, &other.0) - } -} - -impl PartialOrd for InternedStrCompareAsStr { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl std::borrow::Borrow for InternedStrCompareAsStr { - fn borrow(&self) -> &str { - &self.0 - } -} diff --git a/crates/fayalite/src/vendor/xilinx.rs b/crates/fayalite/src/vendor/xilinx.rs deleted file mode 100644 index d80f3888..00000000 --- a/crates/fayalite/src/vendor/xilinx.rs +++ /dev/null @@ -1,207 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// See Notices.txt for copyright information - -use crate::{ - annotations::make_annotation_enum, - build::{GlobalParams, ToArgs, WriteArgs}, - intern::Interned, - prelude::{DynPlatform, Platform}, -}; -use clap::ValueEnum; -use ordered_float::NotNan; -use serde::{Deserialize, Serialize}; -use std::fmt; - -pub mod arty_a7; -pub mod primitives; -pub mod yosys_nextpnr_prjxray; - -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct XdcIOStandardAnnotation { - pub value: Interned, -} - -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct XdcLocationAnnotation { - pub location: Interned, -} - -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct XdcCreateClockAnnotation { - /// clock period in nanoseconds - pub period: NotNan, -} - -make_annotation_enum! { - #[non_exhaustive] - pub enum XilinxAnnotation { - XdcIOStandard(XdcIOStandardAnnotation), - XdcLocation(XdcLocationAnnotation), - XdcCreateClock(XdcCreateClockAnnotation), - } -} - -#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] -pub struct XilinxArgs { - #[arg(long)] - pub device: Option, -} - -impl XilinxArgs { - pub fn require_device( - &self, - platform: Option<&DynPlatform>, - global_params: &GlobalParams, - ) -> clap::error::Result { - if let Some(device) = self.device { - return Ok(device); - } - if let Some(device) = - platform.and_then(|platform| platform.aspects().get_single_by_type::().copied()) - { - return Ok(device); - } - Err(global_params.clap_error( - clap::error::ErrorKind::MissingRequiredArgument, - "missing --device option", - )) - } -} - -impl ToArgs for XilinxArgs { - fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { - if let Some(device) = self.device { - args.write_long_option_eq("device", device.as_str()); - } - } -} - -macro_rules! make_device_enum { - ($vis:vis enum $Device:ident { - $( - #[ - name = $name:literal, - xray_part = $xray_part:literal, - xray_device = $xray_device:literal, - xray_family = $xray_family:literal, - ] - $variant:ident, - )* - }) => { - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, ValueEnum)] - $vis enum $Device { - $( - #[value(name = $name, alias = $xray_part)] - $variant, - )* - } - - impl $Device { - $vis fn as_str(self) -> &'static str { - match self { - $(Self::$variant => $name,)* - } - } - $vis fn xray_part(self) -> &'static str { - match self { - $(Self::$variant => $xray_part,)* - } - } - $vis fn xray_device(self) -> &'static str { - match self { - $(Self::$variant => $xray_device,)* - } - } - $vis fn xray_family(self) -> &'static str { - match self { - $(Self::$variant => $xray_family,)* - } - } - } - - struct DeviceVisitor; - - impl<'de> serde::de::Visitor<'de> for DeviceVisitor { - type Value = $Device; - - fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("a Xilinx device string") - } - - fn visit_str(self, v: &str) -> Result - where - E: serde::de::Error, - { - match $Device::from_str(v, false) { - Ok(v) => Ok(v), - Err(_) => Err(E::invalid_value(serde::de::Unexpected::Str(v), &self)), - } - } - - fn visit_bytes(self, v: &[u8]) -> Result - where - E: serde::de::Error, - { - match str::from_utf8(v).ok().and_then(|v| $Device::from_str(v, false).ok()) { - Some(v) => Ok(v), - None => Err(E::invalid_value(serde::de::Unexpected::Bytes(v), &self)), - } - } - } - - impl<'de> Deserialize<'de> for $Device { - fn deserialize(deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - deserializer.deserialize_string(DeviceVisitor) - } - } - - impl Serialize for $Device { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - self.as_str().serialize(serializer) - } - } - }; -} - -make_device_enum! { - pub enum Device { - #[ - name = "xc7a35ticsg324-1L", - xray_part = "xc7a35tcsg324-1", - xray_device = "xc7a35t", - xray_family = "artix7", - ] - Xc7a35ticsg324_1l, - #[ - name = "xc7a100ticsg324-1L", - xray_part = "xc7a100tcsg324-1", - xray_device = "xc7a100t", - xray_family = "artix7", - ] - Xc7a100ticsg324_1l, - } -} - -impl fmt::Display for Device { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str(self.as_str()) - } -} - -pub(crate) fn built_in_job_kinds() -> impl IntoIterator { - arty_a7::built_in_job_kinds() - .into_iter() - .chain(yosys_nextpnr_prjxray::built_in_job_kinds()) -} - -pub(crate) fn built_in_platforms() -> impl IntoIterator { - arty_a7::built_in_platforms() - .into_iter() - .chain(yosys_nextpnr_prjxray::built_in_platforms()) -} diff --git a/crates/fayalite/src/vendor/xilinx/arty_a7.rs b/crates/fayalite/src/vendor/xilinx/arty_a7.rs deleted file mode 100644 index 0ba214e0..00000000 --- a/crates/fayalite/src/vendor/xilinx/arty_a7.rs +++ /dev/null @@ -1,341 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// See Notices.txt for copyright information - -use crate::{ - intern::{Intern, Interned}, - module::{instance_with_loc, wire_with_loc}, - platform::{ - DynPlatform, Peripheral, PeripheralRef, Peripherals, PeripheralsBuilderFactory, - PeripheralsBuilderFinished, Platform, PlatformAspectSet, - peripherals::{ClockInput, Led, RgbLed, Uart}, - }, - prelude::*, - vendor::xilinx::{ - Device, XdcCreateClockAnnotation, XdcIOStandardAnnotation, XdcLocationAnnotation, - primitives::{self, BUFGCE, STARTUPE2_default_inputs}, - }, -}; -use ordered_float::NotNan; -use std::sync::OnceLock; - -macro_rules! arty_a7_platform { - ( - $vis:vis enum $ArtyA7Platform:ident { - $(#[name = $name:literal, device = $device:ident] - $Variant:ident,)* - } - ) => { - #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] - #[non_exhaustive] - $vis enum $ArtyA7Platform { - $($Variant,)* - } - - impl $ArtyA7Platform { - $vis const VARIANTS: &'static [Self] = &[$(Self::$Variant,)*]; - $vis fn device(self) -> Device { - match self { - $(Self::$Variant => Device::$device,)* - } - } - $vis const fn as_str(self) -> &'static str { - match self { - $(Self::$Variant => $name,)* - } - } - fn get_aspects(self) -> &'static PlatformAspectSet { - match self { - $(Self::$Variant => { - static ASPECTS_SET: OnceLock = OnceLock::new(); - ASPECTS_SET.get_or_init(|| self.make_aspects()) - })* - } - } - } - }; -} - -arty_a7_platform! { - pub enum ArtyA7Platform { - #[name = "arty-a7-35t", device = Xc7a35ticsg324_1l] - ArtyA7_35T, - #[name = "arty-a7-100t", device = Xc7a100ticsg324_1l] - ArtyA7_100T, - } -} - -#[derive(Debug)] -pub struct ArtyA7Peripherals { - clk100: Peripheral, - rst: Peripheral, - rst_sync: Peripheral, - ld0: Peripheral, - ld1: Peripheral, - ld2: Peripheral, - ld3: Peripheral, - ld4: Peripheral, - ld5: Peripheral, - ld6: Peripheral, - ld7: Peripheral, - uart: Peripheral, - // TODO: add rest of peripherals when we need them -} - -impl Peripherals for ArtyA7Peripherals { - fn append_peripherals<'a>(&'a self, peripherals: &mut Vec>) { - let Self { - clk100, - rst, - rst_sync, - ld0, - ld1, - ld2, - ld3, - ld4, - ld5, - ld6, - ld7, - uart, - } = self; - clk100.append_peripherals(peripherals); - rst.append_peripherals(peripherals); - rst_sync.append_peripherals(peripherals); - ld0.append_peripherals(peripherals); - ld1.append_peripherals(peripherals); - ld2.append_peripherals(peripherals); - ld3.append_peripherals(peripherals); - ld4.append_peripherals(peripherals); - ld5.append_peripherals(peripherals); - ld6.append_peripherals(peripherals); - ld7.append_peripherals(peripherals); - uart.append_peripherals(peripherals); - } -} - -impl ArtyA7Platform { - fn make_aspects(self) -> PlatformAspectSet { - let mut retval = PlatformAspectSet::new(); - retval.insert_new(self.device()); - retval - } -} - -#[hdl_module(extern)] -fn reset_sync() { - #[hdl] - let clk: Clock = m.input(); - #[hdl] - let inp: Bool = m.input(); - #[hdl] - let out: SyncReset = m.output(); - m.annotate_module(BlackBoxInlineAnnotation { - path: "fayalite_arty_a7_reset_sync.v".intern(), - text: r#"module __fayalite_arty_a7_reset_sync(input clk, input inp, output out); - wire reset_0_out; - (* ASYNC_REG = "TRUE" *) - FDPE #( - .INIT(1'b1) - ) reset_0 ( - .Q(reset_0_out), - .C(clk), - .CE(1'b1), - .PRE(inp), - .D(1'b0) - ); - (* ASYNC_REG = "TRUE" *) - FDPE #( - .INIT(1'b1) - ) reset_1 ( - .Q(out), - .C(clk), - .CE(1'b1), - .PRE(inp), - .D(reset_0_out) - ); -endmodule -"# - .intern(), - }); - m.verilog_name("__fayalite_arty_a7_reset_sync"); -} - -impl Platform for ArtyA7Platform { - type Peripherals = ArtyA7Peripherals; - - fn name(&self) -> Interned { - self.as_str().intern() - } - - fn new_peripherals<'builder>( - &self, - builder_factory: PeripheralsBuilderFactory<'builder>, - ) -> (Self::Peripherals, PeripheralsBuilderFinished<'builder>) { - let mut builder = builder_factory.builder(); - ( - ArtyA7Peripherals { - clk100: builder.input_peripheral("clk100", ClockInput::new(100e6)), - rst: builder.input_peripheral("rst", Reset), - rst_sync: builder.input_peripheral("rst_sync", SyncReset), - ld0: builder.output_peripheral("ld0", RgbLed), - ld1: builder.output_peripheral("ld1", RgbLed), - ld2: builder.output_peripheral("ld2", RgbLed), - ld3: builder.output_peripheral("ld3", RgbLed), - ld4: builder.output_peripheral("ld4", Led), - ld5: builder.output_peripheral("ld5", Led), - ld6: builder.output_peripheral("ld6", Led), - ld7: builder.output_peripheral("ld7", Led), - uart: builder.output_peripheral("uart", Uart), - }, - builder.finish(), - ) - } - - fn source_location(&self) -> SourceLocation { - SourceLocation::builtin() - } - - fn add_peripherals_in_wrapper_module(&self, m: &ModuleBuilder, peripherals: Self::Peripherals) { - let ArtyA7Peripherals { - clk100, - rst, - rst_sync, - ld0, - ld1, - ld2, - ld3, - ld4, - ld5, - ld6, - ld7, - uart, - } = peripherals; - let make_buffered_input = |name: &str, location: &str, io_standard: &str, invert: bool| { - let pin = m.input_with_loc(name, SourceLocation::builtin(), Bool); - annotate( - pin, - XdcLocationAnnotation { - location: location.intern(), - }, - ); - annotate( - pin, - XdcIOStandardAnnotation { - value: io_standard.intern(), - }, - ); - let buf = instance_with_loc( - &format!("{name}_buf"), - primitives::IBUF(), - SourceLocation::builtin(), - ); - connect(buf.I, pin); - if invert { !buf.O } else { buf.O } - }; - let make_buffered_output = |name: &str, location: &str, io_standard: &str| { - let pin = m.output_with_loc(name, SourceLocation::builtin(), Bool); - annotate( - pin, - XdcLocationAnnotation { - location: location.intern(), - }, - ); - annotate( - pin, - XdcIOStandardAnnotation { - value: io_standard.intern(), - }, - ); - let buf = instance_with_loc( - &format!("{name}_buf"), - primitives::OBUFT(), - SourceLocation::builtin(), - ); - connect(pin, buf.O); - connect(buf.T, false); - buf.I - }; - let clock_annotation = XdcCreateClockAnnotation { - period: NotNan::new(1e9 / clk100.ty().frequency()).expect("known to be valid"), - }; - let clk100_buf = make_buffered_input("clk100", "E3", "LVCMOS33", false); - let startup = instance_with_loc( - "startup", - STARTUPE2_default_inputs(), - SourceLocation::builtin(), - ); - let clk100_sync = instance_with_loc("clk100_sync", BUFGCE(), SourceLocation::builtin()); - connect(clk100_sync.CE, startup.EOS); - connect(clk100_sync.I, clk100_buf); - let clk100_out = wire_with_loc("clk100_out", SourceLocation::builtin(), Clock); - connect(clk100_out, clk100_sync.O); - annotate(clk100_out, clock_annotation); - annotate(clk100_out, DontTouchAnnotation); - if let Some(clk100) = clk100.into_used() { - connect(clk100.instance_io_field().clk, clk100_out); - } - let rst_value = { - let rst_buf = make_buffered_input("rst", "C2", "LVCMOS33", true); - let rst_sync = instance_with_loc("rst_sync", reset_sync(), SourceLocation::builtin()); - connect(rst_sync.clk, clk100_sync.O); - connect(rst_sync.inp, rst_buf); - rst_sync.out - }; - if let Some(rst) = rst.into_used() { - connect(rst.instance_io_field(), rst_value.to_reset()); - } - if let Some(rst_sync) = rst_sync.into_used() { - connect(rst_sync.instance_io_field(), rst_value); - } - let rgb_leds = [ - (ld0, ("G6", "F6", "E1")), - (ld1, ("G3", "J4", "G4")), - (ld2, ("J3", "J2", "H4")), - (ld3, ("K1", "H6", "K2")), - ]; - for (rgb_led, (r_loc, g_loc, b_loc)) in rgb_leds { - let r = make_buffered_output(&format!("{}_r", rgb_led.name()), r_loc, "LVCMOS33"); - let g = make_buffered_output(&format!("{}_g", rgb_led.name()), g_loc, "LVCMOS33"); - let b = make_buffered_output(&format!("{}_b", rgb_led.name()), b_loc, "LVCMOS33"); - if let Some(rgb_led) = rgb_led.into_used() { - connect(r, rgb_led.instance_io_field().r); - connect(g, rgb_led.instance_io_field().g); - connect(b, rgb_led.instance_io_field().b); - } else { - connect(r, false); - connect(g, false); - connect(b, false); - } - } - let leds = [(ld4, "H5"), (ld5, "J5"), (ld6, "T9"), (ld7, "T10")]; - for (led, loc) in leds { - let o = make_buffered_output(&led.name(), loc, "LVCMOS33"); - if let Some(led) = led.into_used() { - connect(o, led.instance_io_field().on); - } else { - connect(o, false); - } - } - let uart_tx = make_buffered_output("uart_tx", "D10", "LVCMOS33"); - let uart_rx = make_buffered_input("uart_rx", "A9", "LVCMOS33", false); - if let Some(uart) = uart.into_used() { - connect(uart_tx, uart.instance_io_field().tx); - connect(uart.instance_io_field().rx, uart_rx); - } else { - connect(uart_tx, true); // idle - } - } - - fn aspects(&self) -> PlatformAspectSet { - self.get_aspects().clone() - } -} - -pub(crate) fn built_in_job_kinds() -> impl IntoIterator { - [] -} - -pub(crate) fn built_in_platforms() -> impl IntoIterator { - ArtyA7Platform::VARIANTS - .iter() - .map(|&v| DynPlatform::new(v)) -} diff --git a/crates/fayalite/src/vendor/xilinx/primitives.rs b/crates/fayalite/src/vendor/xilinx/primitives.rs deleted file mode 100644 index 5dc25672..00000000 --- a/crates/fayalite/src/vendor/xilinx/primitives.rs +++ /dev/null @@ -1,50 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// See Notices.txt for copyright information - -#![allow(non_snake_case)] - -use crate::prelude::*; - -#[hdl_module(extern)] -pub fn IBUF() { - m.verilog_name("IBUF"); - #[hdl] - let O: Bool = m.output(); - #[hdl] - let I: Bool = m.input(); -} - -#[hdl_module(extern)] -pub fn OBUFT() { - m.verilog_name("OBUFT"); - #[hdl] - let O: Bool = m.output(); - #[hdl] - let I: Bool = m.input(); - #[hdl] - let T: Bool = m.input(); -} - -#[hdl_module(extern)] -pub fn BUFGCE() { - m.verilog_name("BUFGCE"); - #[hdl] - let O: Clock = m.output(); - #[hdl] - let CE: Bool = m.input(); - #[hdl] - let I: Bool = m.input(); -} - -#[hdl_module(extern)] -pub fn STARTUPE2_default_inputs() { - m.verilog_name("STARTUPE2"); - #[hdl] - let CFGCLK: Clock = m.output(); - #[hdl] - let CFGMCLK: Clock = m.output(); - #[hdl] - let EOS: Bool = m.output(); - #[hdl] - let PREQ: Bool = m.output(); -} diff --git a/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs b/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs deleted file mode 100644 index 3e1ac0c7..00000000 --- a/crates/fayalite/src/vendor/xilinx/yosys_nextpnr_prjxray.rs +++ /dev/null @@ -1,1043 +0,0 @@ -// SPDX-License-Identifier: LGPL-3.0-or-later -// See Notices.txt for copyright information - -use crate::{ - annotations::{Annotation, TargetedAnnotation}, - build::{ - BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GlobalParams, - JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, - JobKindAndDependencies, ToArgs, WriteArgs, - external::{ - ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait, - }, - verilog::{UnadjustedVerilog, VerilogDialect, VerilogJob, VerilogJobKind}, - }, - bundle::{Bundle, BundleType}, - expr::target::{Target, TargetBase}, - firrtl::{ScalarizedModuleABI, ScalarizedModuleABIAnnotations, ScalarizedModuleABIPort}, - intern::{Intern, InternSlice, Interned}, - module::{ - NameId, ScopedNameId, TargetName, - transform::visit::{Visit, Visitor}, - }, - prelude::*, - source_location::SourceLocation, - util::{HashSet, job_server::AcquiredJob}, - vendor::xilinx::{ - Device, XdcCreateClockAnnotation, XdcIOStandardAnnotation, XdcLocationAnnotation, - XilinxAnnotation, XilinxArgs, - }, -}; -use eyre::Context; -use serde::{Deserialize, Serialize}; -use std::{ - convert::Infallible, - ffi::{OsStr, OsString}, - fmt::{self, Write}, - ops::ControlFlow, - path::{Path, PathBuf}, -}; - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)] -pub struct YosysNextpnrXrayWriteYsFileJobKind; - -#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] -pub struct YosysNextpnrXrayWriteYsFileArgs {} - -impl ToArgs for YosysNextpnrXrayWriteYsFileArgs { - fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) { - let Self {} = self; - } -} - -#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] -pub struct YosysNextpnrXrayWriteYsFile { - main_verilog_file: Interned, - ys_file: Interned, - json_file: Interned, - json_file_name: Interned, -} - -impl YosysNextpnrXrayWriteYsFile { - pub fn main_verilog_file(&self) -> Interned { - self.main_verilog_file - } - pub fn ys_file(&self) -> Interned { - self.ys_file - } - pub fn json_file(&self) -> Interned { - self.json_file - } - pub fn json_file_name(&self) -> Interned { - self.json_file_name - } - fn write_ys( - &self, - output: &mut OsString, - additional_files: &[Interned], - main_module_name_id: NameId, - ) -> eyre::Result<()> { - let Self { - main_verilog_file, - ys_file: _, - json_file: _, - json_file_name, - } = self; - for verilog_file in VerilogJob::all_verilog_files(*main_verilog_file, additional_files)? { - output.push("read_verilog -sv \""); - output.push(verilog_file); - output.push("\"\n"); - } - let circuit_name = crate::firrtl::get_circuit_name(main_module_name_id); - writeln!( - output, - "synth_xilinx -flatten -abc9 -nobram -arch xc7 -top {circuit_name}" - ) - .expect("writing to OsString can't fail"); - output.push("write_json \""); - output.push(json_file_name); - output.push("\"\n"); - Ok(()) - } -} - -impl JobKind for YosysNextpnrXrayWriteYsFileJobKind { - type Args = YosysNextpnrXrayWriteYsFileArgs; - type Job = YosysNextpnrXrayWriteYsFile; - type Dependencies = JobKindAndDependencies; - - fn dependencies(self) -> Self::Dependencies { - Default::default() - } - - fn args_to_jobs( - mut args: JobArgsAndDependencies, - params: &JobParams, - global_params: &GlobalParams, - ) -> eyre::Result> { - args.dependencies - .dependencies - .args - .args - .additional_args - .verilog_dialect - .get_or_insert(VerilogDialect::Yosys); - args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| { - let YosysNextpnrXrayWriteYsFileArgs {} = args; - let base_job = dependencies.get_job::(); - let verilog_job = dependencies.get_job::(); - let json_file = base_job.file_with_ext("json"); - Ok(YosysNextpnrXrayWriteYsFile { - main_verilog_file: verilog_job.main_verilog_file(), - ys_file: base_job.file_with_ext("ys"), - json_file, - json_file_name: json_file - .interned_file_name() - .expect("known to have file name"), - }) - }) - } - - fn inputs(self, _job: &Self::Job) -> Interned<[JobItemName]> { - [JobItemName::DynamicPaths { - source_job_name: VerilogJobKind.name(), - }] - .intern_slice() - } - - fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { - [JobItemName::Path { path: job.ys_file }].intern_slice() - } - - fn name(self) -> Interned { - "yosys-nextpnr-xray-write-ys-file".intern() - } - - fn external_command_params(self, _job: &Self::Job) -> Option { - None - } - - fn run( - self, - job: &Self::Job, - inputs: &[JobItem], - params: &JobParams, - _global_params: &GlobalParams, - _acquired_job: &mut AcquiredJob, - ) -> eyre::Result> { - assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); - let [additional_files] = inputs else { - unreachable!(); - }; - let additional_files = VerilogJob::unwrap_additional_files(additional_files); - let mut contents = OsString::new(); - job.write_ys( - &mut contents, - additional_files, - params.main_module().name_id(), - )?; - let path = job.ys_file; - std::fs::write(path, contents.as_encoded_bytes()) - .wrap_err_with(|| format!("writing {path:?} failed"))?; - Ok(vec![JobItem::Path { path }]) - } - - fn subcommand_hidden(self) -> bool { - true - } -} - -#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] -pub struct YosysNextpnrXraySynthArgs {} - -impl ToArgs for YosysNextpnrXraySynthArgs { - fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) { - let Self {} = self; - } -} - -#[derive(Clone, PartialEq, Eq, Hash, Deserialize, Serialize)] -pub struct YosysNextpnrXraySynth { - #[serde(flatten)] - write_ys_file: YosysNextpnrXrayWriteYsFile, - ys_file_name: Interned, -} - -impl fmt::Debug for YosysNextpnrXraySynth { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let Self { - write_ys_file: - YosysNextpnrXrayWriteYsFile { - main_verilog_file, - ys_file, - json_file, - json_file_name, - }, - ys_file_name, - } = self; - f.debug_struct("YosysNextpnrXraySynth") - .field("main_verilog_file", main_verilog_file) - .field("ys_file", ys_file) - .field("ys_file_name", ys_file_name) - .field("json_file", json_file) - .field("json_file_name", json_file_name) - .finish() - } -} - -impl YosysNextpnrXraySynth { - pub fn main_verilog_file(&self) -> Interned { - self.write_ys_file.main_verilog_file() - } - pub fn ys_file(&self) -> Interned { - self.write_ys_file.ys_file() - } - pub fn ys_file_name(&self) -> Interned { - self.ys_file_name - } - pub fn json_file(&self) -> Interned { - self.write_ys_file.json_file() - } - pub fn json_file_name(&self) -> Interned { - self.write_ys_file.json_file_name() - } -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] -pub struct Yosys; - -impl ExternalProgramTrait for Yosys { - fn default_program_name() -> Interned { - "yosys".intern() - } -} - -impl ExternalCommand for YosysNextpnrXraySynth { - type AdditionalArgs = YosysNextpnrXraySynthArgs; - type AdditionalJobData = Self; - type BaseJobPosition = GetJobPositionDependencies< - GetJobPositionDependencies< - GetJobPositionDependencies<::BaseJobPosition>, - >, - >; - type Dependencies = JobKindAndDependencies; - type ExternalProgram = Yosys; - - fn dependencies() -> Self::Dependencies { - Default::default() - } - - fn args_to_jobs( - args: JobArgsAndDependencies>, - params: &JobParams, - global_params: &GlobalParams, - ) -> eyre::Result<( - Self::AdditionalJobData, - ::JobsAndKinds, - )> { - args.args_to_jobs_external_simple(params, global_params, |args, dependencies| { - let YosysNextpnrXraySynthArgs {} = args.additional_args; - Ok(Self { - write_ys_file: dependencies.job.job.clone(), - ys_file_name: dependencies - .job - .job - .ys_file() - .interned_file_name() - .expect("known to have file name"), - }) - }) - } - - fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]> { - [ - JobItemName::Path { - path: job.additional_job_data().ys_file(), - }, - JobItemName::Path { - path: job.additional_job_data().main_verilog_file(), - }, - JobItemName::DynamicPaths { - source_job_name: VerilogJobKind.name(), - }, - ] - .intern_slice() - } - - fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { - [job.additional_job_data().json_file()].intern_slice() - } - - fn command_line_args(job: &ExternalCommandJob, args: &mut W) { - args.write_arg("-s"); - args.write_interned_arg(job.additional_job_data().ys_file_name()); - } - - fn current_dir(job: &ExternalCommandJob) -> Option> { - Some(job.output_dir()) - } - - fn job_kind_name() -> Interned { - "yosys-nextpnr-xray-synth".intern() - } - - fn subcommand_hidden() -> bool { - true - } -} - -#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)] -pub struct YosysNextpnrXrayWriteXdcFileJobKind; - -#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] -pub struct YosysNextpnrXrayWriteXdcFileArgs {} - -impl ToArgs for YosysNextpnrXrayWriteXdcFileArgs { - fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) { - let Self {} = self; - } -} - -#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] -pub struct YosysNextpnrXrayWriteXdcFile { - firrtl_export_options: crate::firrtl::ExportOptions, - output_dir: Interned, - xdc_file: Interned, -} - -struct WriteXdcContentsError(eyre::Report); - -impl From for WriteXdcContentsError { - fn from(v: eyre::Report) -> Self { - Self(v) - } -} - -impl From for WriteXdcContentsError { - fn from(_v: fmt::Error) -> Self { - unreachable!("String write can't fail") - } -} - -fn tcl_escape(s: impl AsRef) -> String { - let s = s.as_ref(); - if !s.contains(|ch: char| !ch.is_alphanumeric() && ch != '_') { - return s.into(); - } - let mut retval = String::with_capacity(s.len().saturating_add(2)); - retval.push('"'); - for ch in s.chars() { - if let '$' | '\\' | '[' = ch { - retval.push('\\'); - } - retval.push(ch); - } - retval.push('"'); - retval -} - -#[derive(Copy, Clone, Debug)] -enum AnnotationTarget { - None, - Module(Module), - Mem(Mem), - Target(Interned), -} - -impl AnnotationTarget { - fn source_location(self) -> SourceLocation { - match self { - AnnotationTarget::None => unreachable!(), - AnnotationTarget::Module(module) => module.source_location(), - AnnotationTarget::Mem(mem) => mem.source_location(), - AnnotationTarget::Target(target) => target.base().source_location(), - } - } -} - -struct XdcFileWriter { - output: W, - module_depth: usize, - annotation_target: AnnotationTarget, - dont_touch_targets: HashSet>, - required_dont_touch_targets: HashSet>, -} - -impl XdcFileWriter { - fn run(output: W, top_module: Module) -> Result<(), WriteXdcContentsError> { - let mut this = Self { - output, - module_depth: 0, - annotation_target: AnnotationTarget::None, - dont_touch_targets: HashSet::default(), - required_dont_touch_targets: HashSet::default(), - }; - top_module.visit(&mut this)?; - let Self { - output: _, - module_depth: _, - annotation_target: _, - dont_touch_targets, - required_dont_touch_targets, - } = this; - for &target in required_dont_touch_targets.difference(&dont_touch_targets) { - return Err(eyre::eyre!( - "a DontTouchAnnotation is required since the target is also annotated with a XilinxAnnotation:\ntarget: {target:?}\nat: {}", - target.base().source_location(), - ).into()); - } - Ok(()) - } - fn default_visit_with>( - &mut self, - module_depth: usize, - annotation_target: AnnotationTarget, - v: &T, - ) -> Result<(), WriteXdcContentsError> { - let Self { - output: _, - module_depth: old_module_depth, - annotation_target: old_annotation_target, - dont_touch_targets: _, - required_dont_touch_targets: _, - } = *self; - self.module_depth = module_depth; - self.annotation_target = annotation_target; - let retval = v.default_visit(self); - self.module_depth = old_module_depth; - self.annotation_target = old_annotation_target; - retval - } -} - -impl Visitor for XdcFileWriter { - type Error = WriteXdcContentsError; - - fn visit_targeted_annotation(&mut self, v: &TargetedAnnotation) -> Result<(), Self::Error> { - self.default_visit_with(self.module_depth, AnnotationTarget::Target(v.target()), v) - } - - fn visit_module(&mut self, v: &Module) -> Result<(), Self::Error> { - self.default_visit_with( - self.module_depth + 1, - AnnotationTarget::Module(v.canonical()), - v, - ) - } - - fn visit_mem( - &mut self, - v: &Mem, - ) -> Result<(), Self::Error> - where - Element: Visit, - { - self.default_visit_with( - self.module_depth + 1, - AnnotationTarget::Mem(v.canonical()), - v, - ) - } - - fn visit_dont_touch_annotation(&mut self, _v: &DontTouchAnnotation) -> Result<(), Self::Error> { - if let AnnotationTarget::Target(target) = self.annotation_target { - self.dont_touch_targets.insert(target); - } - Ok(()) - } - - fn visit_xilinx_annotation(&mut self, v: &XilinxAnnotation) -> Result<(), Self::Error> { - fn todo( - msg: &str, - annotation: &XilinxAnnotation, - source_location: SourceLocation, - ) -> Result { - Err(WriteXdcContentsError(eyre::eyre!( - "{msg}\nannotation: {annotation:?}\nat: {source_location}" - ))) - } - if self.module_depth != 1 { - match todo( - "annotations are not yet supported outside of the top module since the logic to figure out the correct name isn't implemented", - v, - self.annotation_target.source_location(), - )? {} - } - match self.annotation_target { - AnnotationTarget::None => unreachable!(), - AnnotationTarget::Module(module) => match v { - XilinxAnnotation::XdcIOStandard(_) - | XilinxAnnotation::XdcLocation(_) - | XilinxAnnotation::XdcCreateClock(_) => { - return Err(WriteXdcContentsError(eyre::eyre!( - "annotation not allowed on a module: {v:?}\nat: {}", - module.source_location(), - ))); - } - }, - AnnotationTarget::Mem(mem) => match todo( - "xilinx annotations are not yet supported on memories since the logic to figure out the correct name isn't implemented", - v, - mem.source_location(), - )? {}, - AnnotationTarget::Target(target) => { - let base = target.base(); - match *base { - TargetBase::ModuleIO(_) => { - // already handled by write_xdc_contents handling the main module's ScalarizedModuleABI - Ok(()) - } - TargetBase::MemPort(mem_port) => { - match todo( - "xilinx annotations are not yet supported on memory ports since the logic to figure out the correct name isn't implemented", - v, - mem_port.source_location(), - )? {} - } - TargetBase::Reg(_) - | TargetBase::RegSync(_) - | TargetBase::RegAsync(_) - | TargetBase::Wire(_) => { - match *target { - Target::Base(_) => {} - Target::Child(_) => match todo( - "xilinx annotations are not yet supported on parts of registers/wires since the logic to figure out the correct name isn't implemented", - v, - base.source_location(), - )? {}, - } - match base.canonical_ty() { - CanonicalType::UInt(_) - | CanonicalType::SInt(_) - | CanonicalType::Bool(_) - | CanonicalType::AsyncReset(_) - | CanonicalType::SyncReset(_) - | CanonicalType::Reset(_) - | CanonicalType::Clock(_) => {} - CanonicalType::Enum(_) - | CanonicalType::Array(_) - | CanonicalType::Bundle(_) - | CanonicalType::PhantomConst(_) - | CanonicalType::DynSimOnly(_) => match todo( - "xilinx annotations are not yet supported on types other than integers, Bool, resets, or Clock since the logic to figure out the correct name isn't implemented", - v, - base.source_location(), - )? {}, - } - self.required_dont_touch_targets.insert(target); - match v { - XilinxAnnotation::XdcIOStandard(_) - | XilinxAnnotation::XdcLocation(_) => { - return Err(WriteXdcContentsError(eyre::eyre!( - "annotation must be on a ModuleIO: {v:?}\nat: {}", - base.source_location(), - ))); - } - XilinxAnnotation::XdcCreateClock(XdcCreateClockAnnotation { - period, - }) => { - let TargetName(ScopedNameId(_, NameId(name, _)), _) = - base.target_name(); - writeln!( - self.output, - "create_clock -period {period} [get_nets {}]", - tcl_escape(name), - )?; - Ok(()) - } - } - } - TargetBase::Instance(instance) => match todo( - "xilinx annotations are not yet supported on instances' IO since the logic to figure out the correct name isn't implemented", - v, - instance.source_location(), - )? {}, - } - } - } - } -} - -impl YosysNextpnrXrayWriteXdcFile { - fn write_xdc_contents_for_port_and_annotations( - &self, - output: &mut impl fmt::Write, - port: &ScalarizedModuleABIPort, - annotations: ScalarizedModuleABIAnnotations<'_>, - ) -> Result<(), WriteXdcContentsError> { - for annotation in annotations { - match annotation.annotation() { - Annotation::DontTouch(_) - | Annotation::SVAttribute(_) - | Annotation::BlackBoxInline(_) - | Annotation::BlackBoxPath(_) - | Annotation::DocString(_) - | Annotation::CustomFirrtl(_) => {} - Annotation::Xilinx(XilinxAnnotation::XdcLocation(XdcLocationAnnotation { - location, - })) => writeln!( - output, - "set_property LOC {} [get_ports {}]", - tcl_escape(location), - tcl_escape(port.scalarized_name()), - )?, - Annotation::Xilinx(XilinxAnnotation::XdcIOStandard(XdcIOStandardAnnotation { - value, - })) => writeln!( - output, - "set_property IOSTANDARD {} [get_ports {}]", - tcl_escape(value), - tcl_escape(port.scalarized_name()), - )?, - Annotation::Xilinx(XilinxAnnotation::XdcCreateClock( - XdcCreateClockAnnotation { period }, - )) => writeln!( - output, - "create_clock -period {period} [get_ports {}]", - tcl_escape(port.scalarized_name()), - )?, - } - } - Ok(()) - } - fn write_xdc_contents( - &self, - output: &mut String, - top_module: &Module, - ) -> eyre::Result<()> { - let scalarized_module_abi = - ScalarizedModuleABI::new(top_module, self.firrtl_export_options) - .map_err(eyre::Report::from)?; - match scalarized_module_abi.for_each_port_and_annotations(|port, annotations| { - match self.write_xdc_contents_for_port_and_annotations(output, port, annotations) { - Ok(()) => ControlFlow::Continue(()), - Err(e) => ControlFlow::Break(e), - } - }) { - ControlFlow::Continue(()) => {} - ControlFlow::Break(e) => return Err(e.0), - } - XdcFileWriter::run(output, *top_module).map_err(|e| e.0) - } -} - -impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind { - type Args = YosysNextpnrXrayWriteXdcFileArgs; - type Job = YosysNextpnrXrayWriteXdcFile; - type Dependencies = JobKindAndDependencies>; - - fn dependencies(self) -> Self::Dependencies { - Default::default() - } - - fn args_to_jobs( - args: JobArgsAndDependencies, - params: &JobParams, - global_params: &GlobalParams, - ) -> eyre::Result> { - let firrtl_export_options = args - .dependencies - .dependencies - .dependencies - .dependencies - .dependencies - .args - .args - .export_options; - args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| { - let YosysNextpnrXrayWriteXdcFileArgs {} = args; - let base_job = dependencies.get_job::(); - Ok(YosysNextpnrXrayWriteXdcFile { - firrtl_export_options, - output_dir: base_job.output_dir(), - xdc_file: base_job.file_with_ext("xdc"), - }) - }) - } - - fn inputs(self, job: &Self::Job) -> Interned<[JobItemName]> { - [JobItemName::Path { - path: job.output_dir, - }] - .intern_slice() - } - - fn outputs(self, job: &Self::Job) -> Interned<[JobItemName]> { - [JobItemName::Path { path: job.xdc_file }].intern_slice() - } - - fn name(self) -> Interned { - "yosys-nextpnr-xray-write-xdc-file".intern() - } - - fn external_command_params(self, _job: &Self::Job) -> Option { - None - } - - fn run( - self, - job: &Self::Job, - inputs: &[JobItem], - params: &JobParams, - _global_params: &GlobalParams, - _acquired_job: &mut AcquiredJob, - ) -> eyre::Result> { - assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job))); - let mut xdc = String::new(); - job.write_xdc_contents(&mut xdc, params.main_module())?; - std::fs::write(job.xdc_file, xdc)?; - Ok(vec![JobItem::Path { path: job.xdc_file }]) - } - - fn subcommand_hidden(self) -> bool { - true - } -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] -pub struct NextpnrXilinx; - -impl ExternalProgramTrait for NextpnrXilinx { - fn default_program_name() -> Interned { - "nextpnr-xilinx".intern() - } -} - -#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] -pub struct YosysNextpnrXrayRunNextpnrArgs { - #[command(flatten)] - pub common: XilinxArgs, - #[arg(long, env = "CHIPDB_DIR", value_hint = clap::ValueHint::DirPath)] - pub nextpnr_xilinx_chipdb_dir: PathBuf, - #[arg(long, default_value_t = 0)] - pub nextpnr_xilinx_seed: i32, -} - -impl ToArgs for YosysNextpnrXrayRunNextpnrArgs { - fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { - let Self { - common, - nextpnr_xilinx_chipdb_dir, - nextpnr_xilinx_seed, - } = self; - common.to_args(args); - args.write_long_option_eq("nextpnr-xilinx-chipdb-dir", nextpnr_xilinx_chipdb_dir); - args.write_display_arg(format_args!("--nextpnr-xilinx-seed={nextpnr_xilinx_seed}")); - } -} - -#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] -pub struct YosysNextpnrXrayRunNextpnr { - nextpnr_xilinx_chipdb_dir: Interned, - device: Device, - nextpnr_xilinx_seed: i32, - xdc_file: Interned, - xdc_file_name: Interned, - json_file: Interned, - json_file_name: Interned, - routed_json_file: Interned, - routed_json_file_name: Interned, - fasm_file: Interned, - fasm_file_name: Interned, -} - -impl YosysNextpnrXrayRunNextpnr { - fn chipdb_file(&self) -> Interned { - let mut retval = self - .nextpnr_xilinx_chipdb_dir - .join(self.device.xray_device()); - retval.set_extension("bin"); - retval.intern_deref() - } -} - -impl ExternalCommand for YosysNextpnrXrayRunNextpnr { - type AdditionalArgs = YosysNextpnrXrayRunNextpnrArgs; - type AdditionalJobData = Self; - type BaseJobPosition = GetJobPositionDependencies< - GetJobPositionDependencies<::BaseJobPosition>, - >; - type Dependencies = JobKindAndDependencies; - type ExternalProgram = NextpnrXilinx; - - fn dependencies() -> Self::Dependencies { - Default::default() - } - - fn args_to_jobs( - args: JobArgsAndDependencies>, - params: &JobParams, - global_params: &GlobalParams, - ) -> eyre::Result<( - Self::AdditionalJobData, - ::JobsAndKinds, - )> { - args.args_to_jobs_external_simple(params, global_params, |args, dependencies| { - let YosysNextpnrXrayRunNextpnrArgs { - common, - nextpnr_xilinx_chipdb_dir, - nextpnr_xilinx_seed, - } = args.additional_args; - let base_job = dependencies.get_job::(); - let write_xdc_file = dependencies.get_job::(); - let synth = dependencies.get_job::, _>(); - let routed_json_file = base_job.file_with_ext("routed.json"); - let fasm_file = base_job.file_with_ext("fasm"); - Ok(Self { - nextpnr_xilinx_chipdb_dir: nextpnr_xilinx_chipdb_dir.intern_deref(), - device: common.require_device(base_job.platform(), global_params)?, - nextpnr_xilinx_seed, - xdc_file: write_xdc_file.xdc_file, - xdc_file_name: write_xdc_file - .xdc_file - .interned_file_name() - .expect("known to have file name"), - json_file: synth.additional_job_data().json_file(), - json_file_name: synth.additional_job_data().json_file_name(), - routed_json_file, - routed_json_file_name: routed_json_file - .interned_file_name() - .expect("known to have file name"), - fasm_file, - fasm_file_name: fasm_file - .interned_file_name() - .expect("known to have file name"), - }) - }) - } - - fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]> { - [ - JobItemName::Path { - path: job.additional_job_data().json_file, - }, - JobItemName::Path { - path: job.additional_job_data().xdc_file, - }, - ] - .intern_slice() - } - - fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { - [ - job.additional_job_data().routed_json_file, - job.additional_job_data().fasm_file, - ] - .intern_slice() - } - - fn command_line_args(job: &ExternalCommandJob, args: &mut W) { - let job_data @ YosysNextpnrXrayRunNextpnr { - nextpnr_xilinx_seed, - xdc_file_name, - json_file_name, - routed_json_file_name, - fasm_file_name, - .. - } = job.additional_job_data(); - args.write_long_option_eq("chipdb", job_data.chipdb_file()); - args.write_long_option_eq("xdc", xdc_file_name); - args.write_long_option_eq("json", json_file_name); - args.write_long_option_eq("write", routed_json_file_name); - args.write_long_option_eq("fasm", fasm_file_name); - args.write_display_arg(format_args!("--seed={nextpnr_xilinx_seed}")); - } - - fn current_dir(job: &ExternalCommandJob) -> Option> { - Some(job.output_dir()) - } - - fn job_kind_name() -> Interned { - "yosys-nextpnr-xray-run-nextpnr".intern() - } - - fn subcommand_hidden() -> bool { - true - } -} - -#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)] -pub struct Xcfasm; - -impl ExternalProgramTrait for Xcfasm { - fn default_program_name() -> Interned { - "xcfasm".intern() - } -} - -#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)] -pub struct YosysNextpnrXrayArgs { - #[arg(long, env = "DB_DIR", value_hint = clap::ValueHint::DirPath)] - pub prjxray_db_dir: PathBuf, -} - -impl ToArgs for YosysNextpnrXrayArgs { - fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) { - let Self { prjxray_db_dir } = self; - args.write_long_option_eq("prjxray-db-dir", prjxray_db_dir); - } -} - -#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)] -pub struct YosysNextpnrXray { - prjxray_db_dir: Interned, - device: Device, - fasm_file: Interned, - fasm_file_name: Interned, - frames_file: Interned, - frames_file_name: Interned, - bit_file: Interned, - bit_file_name: Interned, -} - -impl YosysNextpnrXray { - fn db_root(&self) -> Interned { - self.prjxray_db_dir - .join(self.device.xray_family()) - .intern_deref() - } - fn part_file(&self) -> Interned { - let mut retval = self.prjxray_db_dir.join(self.device.xray_family()); - retval.push(self.device.xray_part()); - retval.push("part.yaml"); - retval.intern_deref() - } -} - -impl ExternalCommand for YosysNextpnrXray { - type AdditionalArgs = YosysNextpnrXrayArgs; - type AdditionalJobData = Self; - type BaseJobPosition = GetJobPositionDependencies< - ::BaseJobPosition, - >; - type Dependencies = JobKindAndDependencies>; - type ExternalProgram = Xcfasm; - - fn dependencies() -> Self::Dependencies { - Default::default() - } - - fn args_to_jobs( - args: JobArgsAndDependencies>, - params: &JobParams, - global_params: &GlobalParams, - ) -> eyre::Result<( - Self::AdditionalJobData, - ::JobsAndKinds, - )> { - args.args_to_jobs_external_simple(params, global_params, |args, dependencies| { - let YosysNextpnrXrayArgs { prjxray_db_dir } = args.additional_args; - let base_job = dependencies.get_job::(); - let frames_file = base_job.file_with_ext("frames"); - let bit_file = base_job.file_with_ext("bit"); - Ok(Self { - prjxray_db_dir: prjxray_db_dir.intern_deref(), - device: dependencies.job.job.additional_job_data().device, - fasm_file: dependencies.job.job.additional_job_data().fasm_file, - fasm_file_name: dependencies.job.job.additional_job_data().fasm_file_name, - frames_file, - frames_file_name: frames_file - .interned_file_name() - .expect("known to have file name"), - bit_file, - bit_file_name: bit_file - .interned_file_name() - .expect("known to have file name"), - }) - }) - } - - fn inputs(job: &ExternalCommandJob) -> Interned<[JobItemName]> { - [JobItemName::Path { - path: job.additional_job_data().fasm_file, - }] - .intern_slice() - } - - fn output_paths(job: &ExternalCommandJob) -> Interned<[Interned]> { - [ - job.additional_job_data().frames_file, - job.additional_job_data().bit_file, - ] - .intern_slice() - } - - fn command_line_args(job: &ExternalCommandJob, args: &mut W) { - let job_data @ YosysNextpnrXray { - device, - fasm_file_name, - frames_file_name, - bit_file_name, - .. - } = job.additional_job_data(); - args.write_arg("--sparse"); - args.write_long_option_eq("db-root", job_data.db_root()); - args.write_long_option_eq("part", device.xray_part()); - args.write_long_option_eq("part_file", job_data.part_file()); - args.write_long_option_eq("fn_in", fasm_file_name); - args.write_long_option_eq("frm_out", frames_file_name); - args.write_long_option_eq("bit_out", bit_file_name); - } - - fn current_dir(job: &ExternalCommandJob) -> Option> { - Some(job.output_dir()) - } - - fn job_kind_name() -> Interned { - "yosys-nextpnr-xray".intern() - } -} - -pub(crate) fn built_in_job_kinds() -> impl IntoIterator { - [ - DynJobKind::new(YosysNextpnrXrayWriteYsFileJobKind), - DynJobKind::new(ExternalCommandJobKind::::new()), - DynJobKind::new(YosysNextpnrXrayWriteXdcFileJobKind), - DynJobKind::new(ExternalCommandJobKind::::new()), - DynJobKind::new(ExternalCommandJobKind::::new()), - ] -} - -pub(crate) fn built_in_platforms() -> impl IntoIterator { - [] -} diff --git a/crates/fayalite/tests/module.rs b/crates/fayalite/tests/module.rs index a0392503..c2dc24ea 100644 --- a/crates/fayalite/tests/module.rs +++ b/crates/fayalite/tests/module.rs @@ -6,7 +6,6 @@ use fayalite::{ int::{UIntInRange, UIntInRangeInclusive}, intern::Intern, module::transform::simplify_enums::SimplifyEnumsKind, - platform::PlatformIOBuilder, prelude::*, reset::ResetType, ty::StaticType, @@ -4632,55 +4631,3 @@ circuit check_uint_in_range: ", }; } - -#[hdl_module(outline_generated)] -pub fn check_platform_io(platform_io_builder: PlatformIOBuilder<'_>) { - #[hdl] - let io = m.add_platform_io(platform_io_builder); -} - -#[cfg(todo)] -#[test] -fn test_platform_io() { - let _n = SourceLocation::normalize_files_for_tests(); - let m = check_platform_io(todo!()); - dbg!(m); - #[rustfmt::skip] // work around https://github.com/rust-lang/rustfmt/issues/6161 - assert_export_firrtl! { - m => - "/test/check_platform_io.fir": r"FIRRTL version 3.2.0 -circuit check_platform_io: - type Ty0 = {value: UInt<0>, range: {}} - type Ty1 = {value: UInt<1>, range: {}} - type Ty2 = {value: UInt<2>, range: {}} - type Ty3 = {value: UInt<2>, range: {}} - type Ty4 = {value: UInt<3>, range: {}} - type Ty5 = {value: UInt<3>, range: {}} - type Ty6 = {value: UInt<4>, range: {}} - type Ty7 = {value: UInt<0>, range: {}} - type Ty8 = {value: UInt<1>, range: {}} - type Ty9 = {value: UInt<2>, range: {}} - type Ty10 = {value: UInt<2>, range: {}} - type Ty11 = {value: UInt<3>, range: {}} - type Ty12 = {value: UInt<3>, range: {}} - type Ty13 = {value: UInt<4>, range: {}} - type Ty14 = {value: UInt<4>, range: {}} - module check_platform_io: @[module-XXXXXXXXXX.rs 1:1] - input i_0_to_1: Ty0 @[module-XXXXXXXXXX.rs 2:1] - input i_0_to_2: Ty1 @[module-XXXXXXXXXX.rs 3:1] - input i_0_to_3: Ty2 @[module-XXXXXXXXXX.rs 4:1] - input i_0_to_4: Ty3 @[module-XXXXXXXXXX.rs 5:1] - input i_0_to_7: Ty4 @[module-XXXXXXXXXX.rs 6:1] - input i_0_to_8: Ty5 @[module-XXXXXXXXXX.rs 7:1] - input i_0_to_9: Ty6 @[module-XXXXXXXXXX.rs 8:1] - input i_0_through_0: Ty7 @[module-XXXXXXXXXX.rs 9:1] - input i_0_through_1: Ty8 @[module-XXXXXXXXXX.rs 10:1] - input i_0_through_2: Ty9 @[module-XXXXXXXXXX.rs 11:1] - input i_0_through_3: Ty10 @[module-XXXXXXXXXX.rs 12:1] - input i_0_through_4: Ty11 @[module-XXXXXXXXXX.rs 13:1] - input i_0_through_7: Ty12 @[module-XXXXXXXXXX.rs 14:1] - input i_0_through_8: Ty13 @[module-XXXXXXXXXX.rs 15:1] - input i_0_through_9: Ty14 @[module-XXXXXXXXXX.rs 16:1] -", - }; -} diff --git a/crates/fayalite/tests/ui/module.rs b/crates/fayalite/tests/ui/module.rs index ee0f9887..36649aa2 100644 --- a/crates/fayalite/tests/ui/module.rs +++ b/crates/fayalite/tests/ui/module.rs @@ -11,20 +11,4 @@ pub fn my_module(a: i32, m: u32, (m, _): (i32, u32)) { let o: UInt<8> = m.output(); } -#[hdl_module] -pub fn my_module2(platform_io_builder: PlatformIOBuilder<'_>) { - #[hdl] - let a: UInt<8> = m.input(); - #[hdl] - let b: UInt<8> = m.output(); - #[hdl] - let io = m.add_platform_io(platform_io_builder); - #[hdl] - let c: UInt<8> = m.input(); - #[hdl] - let d: UInt<8> = m.output(); - #[hdl] - let io = m.add_platform_io(platform_io_builder); -} - fn main() {} diff --git a/crates/fayalite/tests/ui/module.stderr b/crates/fayalite/tests/ui/module.stderr index 979e76fe..efbd1fea 100644 --- a/crates/fayalite/tests/ui/module.stderr +++ b/crates/fayalite/tests/ui/module.stderr @@ -1,47 +1,17 @@ -error: name conflicts with implicit `m: &ModuleBuilder` +error: name conflicts with implicit `m: &mut ModuleBuilder<_>` --> tests/ui/module.rs:7:26 | 7 | pub fn my_module(a: i32, m: u32, (m, _): (i32, u32)) { | ^ -error: name conflicts with implicit `m: &ModuleBuilder` +error: name conflicts with implicit `m: &mut ModuleBuilder<_>` --> tests/ui/module.rs:7:35 | 7 | pub fn my_module(a: i32, m: u32, (m, _): (i32, u32)) { | ^ -error: name conflicts with implicit `m: &ModuleBuilder` +error: name conflicts with implicit `m: &mut ModuleBuilder<_>` --> tests/ui/module.rs:9:9 | 9 | let m: UInt<8> = m.input(); | ^ - -error: can't have other inputs/outputs in a module using m.add_platform_io() - --> tests/ui/module.rs:17:24 - | -17 | let a: UInt<8> = m.input(); - | ^^^^^ - -error: can't have other inputs/outputs in a module using m.add_platform_io() - --> tests/ui/module.rs:19:24 - | -19 | let b: UInt<8> = m.output(); - | ^^^^^^ - -error: can't have other inputs/outputs in a module using m.add_platform_io() - --> tests/ui/module.rs:23:24 - | -23 | let c: UInt<8> = m.input(); - | ^^^^^ - -error: can't have other inputs/outputs in a module using m.add_platform_io() - --> tests/ui/module.rs:25:24 - | -25 | let d: UInt<8> = m.output(); - | ^^^^^^ - -error: can't use m.add_platform_io() more than once in a single module - --> tests/ui/module.rs:27:16 - | -27 | let io = m.add_platform_io(platform_io_builder); - | ^^^^^^^^^^^^^^^ diff --git a/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr b/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr index 1fd291c2..03c62bf6 100644 --- a/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr +++ b/crates/fayalite/tests/ui/simvalue_is_not_internable.stderr @@ -156,7 +156,7 @@ note: required by a bound in `intern_sized` | | pub trait Intern: Any + Send + Sync { | ^^^^ required by this bound in `Intern::intern_sized` -... + | fn intern(&self) -> Interned; | fn intern_sized(self) -> Interned | ------------ required by a bound in this associated function help: consider dereferencing here @@ -188,7 +188,7 @@ note: required by a bound in `intern_sized` | | pub trait Intern: Any + Send + Sync { | ^^^^ required by this bound in `Intern::intern_sized` -... + | fn intern(&self) -> Interned; | fn intern_sized(self) -> Interned | ------------ required by a bound in this associated function help: consider dereferencing here @@ -255,7 +255,7 @@ note: required by a bound in `intern_sized` | | pub trait Intern: Any + Send + Sync { | ^^^^ required by this bound in `Intern::intern_sized` -... + | fn intern(&self) -> Interned; | fn intern_sized(self) -> Interned | ------------ required by a bound in this associated function help: consider dereferencing here diff --git a/crates/fayalite/visit_types.json b/crates/fayalite/visit_types.json index 04227efe..f47ca589 100644 --- a/crates/fayalite/visit_types.json +++ b/crates/fayalite/visit_types.json @@ -1177,7 +1177,8 @@ "BlackBoxPath": "Visible", "DocString": "Visible", "CustomFirrtl": "Visible", - "Xilinx": "Visible" + "XdcLocation": "Visible", + "XdcIOStandard": "Visible" } }, "DontTouchAnnotation": { @@ -1215,14 +1216,6 @@ "$kind": "Opaque" } }, - "XilinxAnnotation": { - "data": { - "$kind": "Enum", - "XdcLocation": "Visible", - "XdcIOStandard": "Visible", - "XdcCreateClock": "Visible" - } - }, "XdcLocationAnnotation": { "data": { "$kind": "Opaque" @@ -1233,11 +1226,6 @@ "$kind": "Opaque" } }, - "XdcCreateClockAnnotation": { - "data": { - "$kind": "Opaque" - } - }, "Target": { "data": { "$kind": "Enum",