404 lines
13 KiB
Rust
404 lines
13 KiB
Rust
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
// See Notices.txt for copyright information
|
|
|
|
use crate::{
|
|
intern::{Intern, Interned},
|
|
module::{instance_with_loc, reg_builder_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,
|
|
},
|
|
};
|
|
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<PlatformAspectSet> = 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_div_pow2: [Peripheral<ClockInput>; 4],
|
|
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>,
|
|
uart: Peripheral<Uart>,
|
|
// 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_div_pow2,
|
|
rst,
|
|
rst_sync,
|
|
ld0,
|
|
ld1,
|
|
ld2,
|
|
ld3,
|
|
ld4,
|
|
ld5,
|
|
ld6,
|
|
ld7,
|
|
uart,
|
|
} = self;
|
|
clk100_div_pow2.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<str> {
|
|
self.as_str().intern()
|
|
}
|
|
|
|
fn new_peripherals<'builder>(
|
|
&self,
|
|
builder_factory: PeripheralsBuilderFactory<'builder>,
|
|
) -> (Self::Peripherals, PeripheralsBuilderFinished<'builder>) {
|
|
let mut builder = builder_factory.builder();
|
|
|
|
let clk100_div_pow2 = std::array::from_fn(|log2_divisor| {
|
|
let divisor = 1u64 << log2_divisor;
|
|
let name = if divisor != 1 {
|
|
format!("clk100_div_{divisor}")
|
|
} else {
|
|
"clk100".into()
|
|
};
|
|
builder.input_peripheral(name, ClockInput::new(100e6 / divisor as f64))
|
|
});
|
|
builder.add_conflicts(Vec::from_iter(clk100_div_pow2.iter().map(|v| v.id())));
|
|
(
|
|
ArtyA7Peripherals {
|
|
clk100_div_pow2,
|
|
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_div_pow2,
|
|
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 mut frequency = clk100_div_pow2[0].ty().frequency();
|
|
let mut log2_divisor = 0;
|
|
let mut clk = None;
|
|
for (cur_log2_divisor, p) in clk100_div_pow2.into_iter().enumerate() {
|
|
let Some(p) = p.into_used() else {
|
|
continue;
|
|
};
|
|
debug_assert!(
|
|
clk.is_none(),
|
|
"conflict-handling logic should ensure at most one clock is used",
|
|
);
|
|
frequency = p.ty().frequency();
|
|
clk = Some(p);
|
|
log2_divisor = cur_log2_divisor;
|
|
}
|
|
let clk100_buf = make_buffered_input("clk100", "E3", "LVCMOS33", false);
|
|
let startup = instance_with_loc(
|
|
"startup",
|
|
primitives::STARTUPE2_default_inputs(),
|
|
SourceLocation::builtin(),
|
|
);
|
|
let clk_global_buf = instance_with_loc(
|
|
"clk_global_buf",
|
|
primitives::BUFGCE(),
|
|
SourceLocation::builtin(),
|
|
);
|
|
connect(clk_global_buf.CE, startup.EOS);
|
|
let mut clk_global_buf_in = clk100_buf.to_clock();
|
|
for prev_log2_divisor in 0..log2_divisor {
|
|
let prev_divisor = 1u64 << prev_log2_divisor;
|
|
let clk_in = wire_with_loc(
|
|
&format!("clk_div_{prev_divisor}"),
|
|
SourceLocation::builtin(),
|
|
Clock,
|
|
);
|
|
connect(clk_in, clk_global_buf_in);
|
|
annotate(
|
|
clk_in,
|
|
XdcCreateClockAnnotation {
|
|
period: NotNan::new(1e9 / (100e6 / prev_divisor as f64))
|
|
.expect("known to be valid"),
|
|
},
|
|
);
|
|
annotate(clk_in, DontTouchAnnotation);
|
|
let cd = wire_with_loc(
|
|
&format!("clk_div_{prev_divisor}_in"),
|
|
SourceLocation::builtin(),
|
|
ClockDomain[AsyncReset],
|
|
);
|
|
connect(cd.clk, clk_in);
|
|
connect(cd.rst, (!startup.EOS).to_async_reset());
|
|
let divider = reg_builder_with_loc("divider", SourceLocation::builtin())
|
|
.clock_domain(cd)
|
|
.reset(false)
|
|
.build();
|
|
connect(divider, !divider);
|
|
clk_global_buf_in = divider.to_clock();
|
|
}
|
|
connect(clk_global_buf.I, clk_global_buf_in);
|
|
let clk_out = wire_with_loc("clk_out", SourceLocation::builtin(), Clock);
|
|
connect(clk_out, clk_global_buf.O);
|
|
annotate(
|
|
clk_out,
|
|
XdcCreateClockAnnotation {
|
|
period: NotNan::new(1e9 / frequency).expect("known to be valid"),
|
|
},
|
|
);
|
|
annotate(clk_out, DontTouchAnnotation);
|
|
if let Some(clk) = clk {
|
|
connect(clk.instance_io_field().clk, clk_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, clk_out);
|
|
connect(rst_sync.inp, rst_buf | !startup.EOS);
|
|
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<Item = crate::build::DynJobKind> {
|
|
[]
|
|
}
|
|
|
|
pub(crate) fn built_in_platforms() -> impl IntoIterator<Item = DynPlatform> {
|
|
ArtyA7Platform::VARIANTS
|
|
.iter()
|
|
.map(|&v| DynPlatform::new(v))
|
|
}
|