Compare commits

..

1 commit

Author SHA1 Message Date
d2efb94686
WIP adding peripherals
Some checks failed
/ test (pull_request) Failing after 1m5s
2025-10-18 23:24:21 -07:00
8 changed files with 390 additions and 48 deletions

View file

@ -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::<peripherals::ClockInput>()[0].use_peripheral();
let rst = platform_io_builder.peripherals_with_type::<Reset>()[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::<peripherals::Led>() {
if let Ok(led) = led.try_use_peripheral() {
connect(led.on, output_reg);
}
}
for rgb_led in platform_io_builder.peripherals_with_type::<peripherals::RgbLed>() {
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)))
});
}

View file

@ -530,6 +530,7 @@ impl<'a, S: PeripheralsOnUseSharedState> PeripheralsBuilder<'a, S> {
}
}
#[must_use]
pub struct Peripheral<T: Type> {
ty: T,
common: PeripheralCommon,
@ -540,6 +541,27 @@ impl<T: Type> Peripheral<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<str> {
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<BTreeSet<PeripheralId>> {
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<T: Type> UsedPeripheral<T> {
pub fn instance_io_field(&self) -> Expr<T> {
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<str> {
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<BTreeSet<PeripheralId>> {
self.as_ref().conflicts_with()
}
}
#[derive(Copy, Clone)]

View file

@ -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<f64>,
}
type PhantomConstClockInputProperties = PhantomConst<ClockInputProperties>;
#[hdl(no_static)]
#[hdl(no_runtime_generics, no_static)]
pub struct ClockInput {
pub clk: Clock,
pub properties: PhantomConstClockInputProperties,
pub properties: PhantomConst<ClockInputProperties>,
}
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,
}

View file

@ -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::{

View file

@ -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<JobArgsAndDependencies<ExternalCommandJobKind<Formal>>> {
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<M: AsRef<Module<T>>, 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),
)
}

View file

@ -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)]

View file

@ -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<ClockInput>,
rst: Peripheral<Reset>,
rst_sync: Peripheral<SyncReset>,
ld0: Peripheral<RgbLed>,
ld1: Peripheral<RgbLed>,
ld2: Peripheral<RgbLed>,
ld3: Peripheral<RgbLed>,
ld4: Peripheral<Led>,
ld5: Peripheral<Led>,
ld6: Peripheral<Led>,
ld7: Peripheral<Led>,
// TODO: add rest of peripherals when we need them
}
impl Peripherals for ArtyA7Peripherals {
fn append_peripherals<'a>(&'a self, peripherals: &mut Vec<PeripheralRef<'a, CanonicalType>>) {
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<str> {
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);
}
}
}
}

View file

@ -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();
}