diff --git a/crates/fayalite/examples/blinky.rs b/crates/fayalite/examples/blinky.rs index 8682a33..7384e0d 100644 --- a/crates/fayalite/examples/blinky.rs +++ b/crates/fayalite/examples/blinky.rs @@ -1,55 +1,64 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use fayalite::{ - build::{ToArgs, WriteArgs}, - prelude::*, -}; +use fayalite::prelude::*; #[hdl_module] -fn blinky(clock_frequency: u64) { - #[hdl] - let clk: Clock = m.input(); - #[hdl] - let rst: SyncReset = m.input(); +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(); let cd = #[hdl] ClockDomain { - clk, - rst: rst.to_reset(), + clk: clk_input.clk, + rst, }; - let max_value = clock_frequency / 2 - 1; + let max_value = (Expr::ty(clk_input).frequency() / 2.0).round_ties_even() as u64 - 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); } - #[hdl] - 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!("--clock-frequency={clock_frequency}")); + 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); } fn main() { - BuildCli::main(|_cli, ExtraArgs { clock_frequency }| { - Ok(JobParams::new(blinky(clock_frequency), "blinky")) + BuildCli::main("blinky", |_, platform, _| { + Ok(JobParams::new(platform.wrap_main_module(blinky))) }); } diff --git a/crates/fayalite/src/platform.rs b/crates/fayalite/src/platform.rs index be675b8..1018055 100644 --- a/crates/fayalite/src/platform.rs +++ b/crates/fayalite/src/platform.rs @@ -530,6 +530,7 @@ impl<'a, S: PeripheralsOnUseSharedState> PeripheralsBuilder<'a, S> { } } +#[must_use] pub struct Peripheral { ty: T, common: PeripheralCommon, @@ -540,6 +541,27 @@ impl Peripheral { 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() } @@ -629,6 +651,24 @@ impl UsedPeripheral { 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)] diff --git a/crates/fayalite/src/platform/peripherals.rs b/crates/fayalite/src/platform/peripherals.rs index 68db8d2..3ff4d6c 100644 --- a/crates/fayalite/src/platform/peripherals.rs +++ b/crates/fayalite/src/platform/peripherals.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information -use crate::prelude::*; +use crate::{intern::Intern, prelude::*}; use ordered_float::NotNan; use serde::{Deserialize, Serialize}; @@ -11,10 +11,42 @@ pub struct ClockInputProperties { pub frequency: NotNan, } -type PhantomConstClockInputProperties = PhantomConst; - -#[hdl(no_static)] +#[hdl(no_runtime_generics, no_static)] pub struct ClockInput { pub clk: Clock, - pub properties: PhantomConstClockInputProperties, + 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, } diff --git a/crates/fayalite/src/prelude.rs b/crates/fayalite/src/prelude.rs index 5ff3a64..216f94e 100644 --- a/crates/fayalite/src/prelude.rs +++ b/crates/fayalite/src/prelude.rs @@ -28,6 +28,7 @@ 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/testing.rs b/crates/fayalite/src/testing.rs index 30a8243..c7feb5e 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, JobArgsAndDependencies, JobKindAndArgs, JobParams, NoArgs, - RunBuild, + BaseJobArgs, BaseJobKind, GlobalParams, JobArgsAndDependencies, JobKindAndArgs, JobParams, + NoArgs, RunBuild, external::{ExternalCommandArgs, ExternalCommandJobKind}, firrtl::{FirrtlArgs, FirrtlJobKind}, formal::{Formal, FormalAdditionalArgs, FormalArgs, FormalMode, WriteSbyFileJobKind}, @@ -106,7 +106,7 @@ 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)), + args: BaseJobArgs::from_output_dir_and_env(get_assert_formal_target_path(&test_name), None), }; let dependencies = JobArgsAndDependencies { args, @@ -168,9 +168,9 @@ pub fn try_assert_formal>, T: BundleType>( solver, export_options, )? - .run( - |NoArgs {}| Ok(JobParams::new(module, APP_NAME)), - clap::Command::new(APP_NAME), // not actually used, so we can use an arbitrary value + .run_without_platform( + |NoArgs {}| Ok(JobParams::new(module)), + &GlobalParams::new(None, APP_NAME), ) } diff --git a/crates/fayalite/src/vendor/xilinx.rs b/crates/fayalite/src/vendor/xilinx.rs index 11b6048..2298541 100644 --- a/crates/fayalite/src/vendor/xilinx.rs +++ b/crates/fayalite/src/vendor/xilinx.rs @@ -11,6 +11,7 @@ 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)] diff --git a/crates/fayalite/src/vendor/xilinx/arty_a7.rs b/crates/fayalite/src/vendor/xilinx/arty_a7.rs index e8affa2..5a1123b 100644 --- a/crates/fayalite/src/vendor/xilinx/arty_a7.rs +++ b/crates/fayalite/src/vendor/xilinx/arty_a7.rs @@ -3,9 +3,17 @@ use crate::{ intern::{Intern, Interned}, - platform::{DynPlatform, PeripheralsBuilderFactory, PeripheralsBuilderFinished, Platform}, - prelude::{ModuleBuilder, SourceLocation}, - vendor::xilinx::Device, + module::instance_with_loc, + platform::{ + DynPlatform, Peripheral, PeripheralRef, Peripherals, PeripheralsBuilderFactory, + PeripheralsBuilderFinished, Platform, + peripherals::{ClockInput, Led, RgbLed}, + }, + prelude::*, + vendor::xilinx::{ + Device, XdcIOStandardAnnotation, XdcLocationAnnotation, + primitives::{self, BUFGCE, FDPE, STARTUPE2_default_inputs}, + }, }; macro_rules! arty_a7_platform { @@ -46,8 +54,53 @@ arty_a7_platform! { } } +#[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, + // 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, + } = 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); + } +} + impl Platform for ArtyA7Platform { - type Peripherals = (); + type Peripherals = ArtyA7Peripherals; fn name(&self) -> Interned { self.as_str().intern() @@ -57,8 +110,23 @@ impl Platform for ArtyA7Platform { &self, builder_factory: PeripheralsBuilderFactory<'builder>, ) -> (Self::Peripherals, PeripheralsBuilderFinished<'builder>) { - let builder = builder_factory.builder(); - (todo!(), builder.finish()) + 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.input_peripheral("ld0", RgbLed), + ld1: builder.input_peripheral("ld1", RgbLed), + ld2: builder.input_peripheral("ld2", RgbLed), + ld3: builder.input_peripheral("ld3", RgbLed), + ld4: builder.input_peripheral("ld4", Led), + ld5: builder.input_peripheral("ld5", Led), + ld6: builder.input_peripheral("ld6", Led), + ld7: builder.input_peripheral("ld7", Led), + }, + builder.finish(), + ) } fn source_location(&self) -> SourceLocation { @@ -66,7 +134,132 @@ impl Platform for ArtyA7Platform { } fn add_peripherals_in_wrapper_module(&self, m: &ModuleBuilder, peripherals: Self::Peripherals) { - let () = peripherals; + let ArtyA7Peripherals { + clk100, + rst, + rst_sync, + ld0, + ld1, + ld2, + ld3, + ld4, + ld5, + ld6, + ld7, + } = 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 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); + if let Some(clk100) = clk100.into_used() { + connect(clk100.instance_io_field().clk, clk100_sync.O); + } + let rst_buf = make_buffered_input("rst", "C2", "LVCMOS33", true); + let [rst_sync_0, rst_sync_1] = std::array::from_fn(|index| { + let rst_sync = instance_with_loc( + &format!("rst_sync_{index}"), + FDPE(true), + SourceLocation::builtin(), + ); + annotate( + rst_sync, + SVAttributeAnnotation { + text: "ASYNC_REG = \"TRUE\"".intern(), + }, + ); + connect(rst_sync.C, clk100_sync.O); + connect(rst_sync.CE, true); + connect(rst_sync.PRE, rst_buf.to_async_reset()); + rst_sync + }); + connect(rst_sync_0.D, false); + connect(rst_sync_1.D, rst_sync_0.Q); + let rst_value = rst_sync_1.Q.to_sync_reset(); + 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); + } + } } } diff --git a/crates/fayalite/src/vendor/xilinx/primitives.rs b/crates/fayalite/src/vendor/xilinx/primitives.rs new file mode 100644 index 0000000..6e2bfb9 --- /dev/null +++ b/crates/fayalite/src/vendor/xilinx/primitives.rs @@ -0,0 +1,66 @@ +// 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(); +} + +#[hdl_module(extern)] +pub fn FDPE(init: bool) { + m.verilog_name("FDPE"); + m.parameter_raw_verilog("INIT", if init { "1'b1" } else { "1'b0" }); + #[hdl] + let Q: Bool = m.output(); + #[hdl] + let C: Clock = m.input(); + #[hdl] + let CE: Bool = m.input(); + #[hdl] + let PRE: AsyncReset = m.input(); + #[hdl] + let D: Bool = m.input(); +}