Compare commits
1 commit
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
34b4a57507 |
8 changed files with 1560 additions and 2 deletions
21
Makefile
Normal file
21
Makefile
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
DIRU=/home/alex/Desktop/Hacking/libre-chip/fayalite-wip/target/blinky-out
|
||||
NEXTPNR_DENSITY:=--25k
|
||||
|
||||
all:
|
||||
cp $(DIRU)/*.pcf /tmp
|
||||
RUST_BACKTRACE=full cargo run --example blinky yosys-nextpnr-ecp5 \
|
||||
--nextpnr /home/alex/.guix-profile/bin/nextpnr-ecp5 \
|
||||
--platform orangecrab-85k -o target/blinky-out \
|
||||
--ecppack /home/alex/.guix-profile/bin/ecppack \
|
||||
--placeholder-dir /tmp/anyPathBuf/orangecrab_r0.2.1.pcf
|
||||
ls -1 $(DIRU)/*.bit
|
||||
ls:
|
||||
ls -1 $(DIRU)
|
||||
clean:
|
||||
rm $(DIRU)/*
|
||||
nextpnr:
|
||||
cd $(DIRU) && nextpnr-ecp5 --json blinky.json --textcfg blinky.nextpnr.out $(NEXTPNR_DENSITY) \
|
||||
--package CSFBGA285 --lpf orangecrab_r0.2.1.pcf --lpf-allow-unconstrained
|
||||
pack:
|
||||
cd $(DIRU) && ecppack --compress --freq 38.8 --input blinky.nextpnr.out --bit blinky.nextpnr.bit
|
||||
cd $(DIRU) && file *.bit
|
||||
|
|
@ -2,11 +2,14 @@
|
|||
// See Notices.txt for copyright information
|
||||
|
||||
pub mod xilinx;
|
||||
pub mod lattice;
|
||||
|
||||
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = crate::build::DynJobKind> {
|
||||
xilinx::built_in_job_kinds()
|
||||
xilinx::built_in_job_kinds();
|
||||
lattice::built_in_job_kinds()
|
||||
}
|
||||
|
||||
pub(crate) fn built_in_platforms() -> impl IntoIterator<Item = crate::platform::DynPlatform> {
|
||||
xilinx::built_in_platforms()
|
||||
xilinx::built_in_platforms();
|
||||
lattice::built_in_platforms()
|
||||
}
|
||||
|
|
|
|||
187
crates/fayalite/src/vendor/lattice.rs
vendored
Normal file
187
crates/fayalite/src/vendor/lattice.rs
vendored
Normal file
|
|
@ -0,0 +1,187 @@
|
|||
// 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 orangecrab;
|
||||
pub mod primitives;
|
||||
pub mod yosys_nextpnr;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)]
|
||||
pub struct LatticeArgs {
|
||||
#[arg(long)]
|
||||
pub device: Option<Device>,
|
||||
}
|
||||
|
||||
impl LatticeArgs {
|
||||
pub fn require_device(
|
||||
&self,
|
||||
platform: Option<&DynPlatform>,
|
||||
global_params: &GlobalParams,
|
||||
) -> clap::error::Result<Device> {
|
||||
if let Some(device) = self.device {
|
||||
return Ok(device);
|
||||
}
|
||||
if let Some(device) =
|
||||
platform.and_then(|platform| platform.aspects().get_single_by_type::<Device>().copied())
|
||||
{
|
||||
return Ok(device);
|
||||
}
|
||||
Err(global_params.clap_error(
|
||||
clap::error::ErrorKind::MissingRequiredArgument,
|
||||
"missing --device option",
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
impl ToArgs for LatticeArgs {
|
||||
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,
|
||||
lattice_part = $lattice_part:literal,
|
||||
lattice_device = $lattice_device:literal,
|
||||
lattice_family = $lattice_family:literal,
|
||||
]
|
||||
$variant:ident,
|
||||
)*
|
||||
}) => {
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, ValueEnum)]
|
||||
$vis enum $Device {
|
||||
$(
|
||||
#[value(name = $name, alias = $lattice_part)]
|
||||
$variant,
|
||||
)*
|
||||
}
|
||||
|
||||
impl $Device {
|
||||
$vis fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
$(Self::$variant => $name,)*
|
||||
}
|
||||
}
|
||||
$vis fn lattice_part(self) -> &'static str {
|
||||
match self {
|
||||
$(Self::$variant => $lattice_part,)*
|
||||
}
|
||||
}
|
||||
$vis fn lattice_device(self) -> &'static str {
|
||||
match self {
|
||||
$(Self::$variant => $lattice_device,)*
|
||||
}
|
||||
}
|
||||
$vis fn lattice_family(self) -> &'static str {
|
||||
match self {
|
||||
$(Self::$variant => $lattice_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 Lattice device string")
|
||||
}
|
||||
|
||||
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
|
||||
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<E>(self, v: &[u8]) -> Result<Self::Value, E>
|
||||
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<D>(deserializer: D) -> Result<Self, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
deserializer.deserialize_string(DeviceVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
impl Serialize for $Device {
|
||||
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
self.as_str().serialize(serializer)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//ECP5 variants
|
||||
make_device_enum! {
|
||||
pub enum Device {
|
||||
#[
|
||||
name = "placeholder25k",
|
||||
lattice_part = "fixme",
|
||||
lattice_device = "fixme",
|
||||
lattice_family = "fixme",
|
||||
]
|
||||
Placeholder25k,
|
||||
#[
|
||||
name = "placeholder85k",
|
||||
lattice_part = "fixme",
|
||||
lattice_device = "fimxe",
|
||||
lattice_family = "fixme",
|
||||
]
|
||||
Placeholder85k,
|
||||
}
|
||||
}
|
||||
|
||||
//rest looks good
|
||||
|
||||
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<Item = crate::build::DynJobKind> {
|
||||
orangecrab::built_in_job_kinds()
|
||||
.into_iter()
|
||||
.chain(yosys_nextpnr::built_in_job_kinds())
|
||||
}
|
||||
|
||||
pub(crate) fn built_in_platforms() -> impl IntoIterator<Item = crate::platform::DynPlatform> {
|
||||
orangecrab::built_in_platforms()
|
||||
.into_iter()
|
||||
.chain(yosys_nextpnr::built_in_platforms())
|
||||
}
|
||||
|
||||
//first step yosys -p "read_verilog $<; synth_ecp5 -json $@"
|
||||
396
crates/fayalite/src/vendor/lattice/orangecrab.rs
vendored
Normal file
396
crates/fayalite/src/vendor/lattice/orangecrab.rs
vendored
Normal file
|
|
@ -0,0 +1,396 @@
|
|||
// 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::lattice::{
|
||||
Device,
|
||||
primitives,
|
||||
},
|
||||
};
|
||||
use ordered_float::NotNan;
|
||||
use std::sync::OnceLock;
|
||||
|
||||
//keep unchanged
|
||||
macro_rules! orangecrab_platform {
|
||||
(
|
||||
$vis:vis enum $OrangeCrabPlatform:ident {
|
||||
$(#[name = $name:literal, device = $device:ident]
|
||||
$Variant:ident,)*
|
||||
}
|
||||
) => {
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
#[non_exhaustive]
|
||||
$vis enum $OrangeCrabPlatform {
|
||||
$($Variant,)*
|
||||
}
|
||||
|
||||
impl $OrangeCrabPlatform {
|
||||
$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())
|
||||
})*
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//untested
|
||||
orangecrab_platform! {
|
||||
pub enum OrangeCrabPlatform {
|
||||
#[name = "orangecrab-25k", device = Placeholder25k]
|
||||
OrangeCrab_25k,
|
||||
#[name = "orangecrab-85k", device = Placeholder85k]
|
||||
OrangeCrab_85k,
|
||||
}
|
||||
}
|
||||
|
||||
//FIXME
|
||||
#[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 OrangeCrabPlatform {
|
||||
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_orangecrab_reset_sync.v".intern(),
|
||||
text: r#"module __fayalite_orangecrab_reset_sync(input clk, input inp, output out);
|
||||
wire reset_0_out;
|
||||
always @(posedge clk) begin
|
||||
reset_0_out <= inp;
|
||||
outp <= reset_0_out;
|
||||
end
|
||||
endmodule
|
||||
"#
|
||||
.intern(),
|
||||
});
|
||||
m.verilog_name("__fayalite_orangecrab_reset_sync");
|
||||
}
|
||||
|
||||
impl Platform for OrangeCrabPlatform {
|
||||
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);
|
||||
/* fixme annotate(
|
||||
pin,
|
||||
XdcLocationAnnotation {
|
||||
location: location.intern(),
|
||||
},
|
||||
); */
|
||||
/* fixme 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 }
|
||||
if invert { !pin } else { pin }
|
||||
};
|
||||
let make_buffered_output = |name: &str, location: &str, io_standard: &str| {
|
||||
let pin = m.output_with_loc(name, SourceLocation::builtin(), Bool);
|
||||
/* fixme 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
|
||||
pin
|
||||
};
|
||||
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);
|
||||
/* fixme
|
||||
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);
|
||||
//FIXME 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);
|
||||
connect(clk_out, clk_global_buf_in);
|
||||
/* fixme 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);
|
||||
}
|
||||
//undo 1
|
||||
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*/); //FIXME
|
||||
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> {
|
||||
OrangeCrabPlatform::VARIANTS
|
||||
.iter()
|
||||
.map(|&v| DynPlatform::new(v))
|
||||
}
|
||||
50
crates/fayalite/src/vendor/lattice/primitives.rs
vendored
Normal file
50
crates/fayalite/src/vendor/lattice/primitives.rs
vendored
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
// 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: Clock = m.input();
|
||||
//}
|
||||
|
||||
#[hdl_module(extern)]
|
||||
pub fn FIXME_PLACEHOLDER() {
|
||||
m.verilog_name("FIXME_PLACEHOLDER");
|
||||
#[hdl]
|
||||
let CFGCLK: Clock = m.output();
|
||||
#[hdl]
|
||||
let CFGMCLK: Clock = m.output();
|
||||
#[hdl]
|
||||
let EOS: Bool = m.output();
|
||||
#[hdl]
|
||||
let PREQ: Bool = m.output();
|
||||
}
|
||||
896
crates/fayalite/src/vendor/lattice/yosys_nextpnr.rs
vendored
Normal file
896
crates/fayalite/src/vendor/lattice/yosys_nextpnr.rs
vendored
Normal file
|
|
@ -0,0 +1,896 @@
|
|||
// 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::lattice::{
|
||||
Device,
|
||||
/* fixme LatticeAnnotation,*/ LatticeArgs,
|
||||
},
|
||||
};
|
||||
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 YosysNextpnrWriteYsFileJobKind;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)]
|
||||
pub struct YosysNextpnrWriteYsFileArgs {}
|
||||
|
||||
impl ToArgs for YosysNextpnrWriteYsFileArgs {
|
||||
fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) {
|
||||
let Self {} = self;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
|
||||
pub struct YosysNextpnrWriteYsFile {
|
||||
main_verilog_file: Interned<Path>,
|
||||
ys_file: Interned<Path>,
|
||||
json_file: Interned<Path>,
|
||||
json_file_name: Interned<OsStr>,
|
||||
}
|
||||
|
||||
impl YosysNextpnrWriteYsFile {
|
||||
pub fn main_verilog_file(&self) -> Interned<Path> {
|
||||
self.main_verilog_file
|
||||
}
|
||||
pub fn ys_file(&self) -> Interned<Path> {
|
||||
self.ys_file
|
||||
}
|
||||
pub fn json_file(&self) -> Interned<Path> {
|
||||
self.json_file
|
||||
}
|
||||
pub fn json_file_name(&self) -> Interned<OsStr> {
|
||||
self.json_file_name
|
||||
}
|
||||
fn write_ys(
|
||||
&self,
|
||||
output: &mut OsString,
|
||||
additional_files: &[Interned<Path>],
|
||||
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_ecp5 -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 YosysNextpnrWriteYsFileJobKind {
|
||||
type Args = YosysNextpnrWriteYsFileArgs;
|
||||
type Job = YosysNextpnrWriteYsFile;
|
||||
type Dependencies = JobKindAndDependencies<VerilogJobKind>;
|
||||
|
||||
fn dependencies(self) -> Self::Dependencies {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn args_to_jobs(
|
||||
mut args: JobArgsAndDependencies<Self>,
|
||||
params: &JobParams,
|
||||
global_params: &GlobalParams,
|
||||
) -> eyre::Result<JobAndDependencies<Self>> {
|
||||
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 YosysNextpnrWriteYsFileArgs {} = args;
|
||||
let base_job = dependencies.get_job::<BaseJob, _>();
|
||||
let verilog_job = dependencies.get_job::<VerilogJob, _>();
|
||||
let json_file = base_job.file_with_ext("json");
|
||||
Ok(YosysNextpnrWriteYsFile {
|
||||
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<str> {
|
||||
"yosys-nextpnr-ecp5-write-ys-file".intern()
|
||||
}
|
||||
|
||||
fn external_command_params(self, _job: &Self::Job) -> Option<CommandParams> {
|
||||
None
|
||||
}
|
||||
|
||||
fn run(
|
||||
self,
|
||||
job: &Self::Job,
|
||||
inputs: &[JobItem],
|
||||
params: &JobParams,
|
||||
_global_params: &GlobalParams,
|
||||
_acquired_job: &mut AcquiredJob,
|
||||
) -> eyre::Result<Vec<JobItem>> {
|
||||
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 YosysNextpnrSynthArgs {}
|
||||
|
||||
impl ToArgs for YosysNextpnrSynthArgs {
|
||||
fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) {
|
||||
let Self {} = self;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Deserialize, Serialize)]
|
||||
pub struct YosysNextpnrSynth {
|
||||
#[serde(flatten)]
|
||||
write_ys_file: YosysNextpnrWriteYsFile,
|
||||
ys_file_name: Interned<OsStr>,
|
||||
}
|
||||
|
||||
impl fmt::Debug for YosysNextpnrSynth {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let Self {
|
||||
write_ys_file:
|
||||
YosysNextpnrWriteYsFile {
|
||||
main_verilog_file,
|
||||
ys_file,
|
||||
json_file,
|
||||
json_file_name,
|
||||
},
|
||||
ys_file_name,
|
||||
} = self;
|
||||
f.debug_struct("YosysNextpnrSynth")
|
||||
.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 YosysNextpnrSynth {
|
||||
pub fn main_verilog_file(&self) -> Interned<Path> {
|
||||
self.write_ys_file.main_verilog_file()
|
||||
}
|
||||
pub fn ys_file(&self) -> Interned<Path> {
|
||||
self.write_ys_file.ys_file()
|
||||
}
|
||||
pub fn ys_file_name(&self) -> Interned<OsStr> {
|
||||
self.ys_file_name
|
||||
}
|
||||
pub fn json_file(&self) -> Interned<Path> {
|
||||
self.write_ys_file.json_file()
|
||||
}
|
||||
pub fn json_file_name(&self) -> Interned<OsStr> {
|
||||
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<str> {
|
||||
"yosys".intern() //must be yosys
|
||||
}
|
||||
}
|
||||
|
||||
impl ExternalCommand for YosysNextpnrSynth {
|
||||
type AdditionalArgs = YosysNextpnrSynthArgs;
|
||||
type AdditionalJobData = Self;
|
||||
type BaseJobPosition = GetJobPositionDependencies<
|
||||
GetJobPositionDependencies<
|
||||
GetJobPositionDependencies<<UnadjustedVerilog as ExternalCommand>::BaseJobPosition>,
|
||||
>,
|
||||
>;
|
||||
type Dependencies = JobKindAndDependencies<YosysNextpnrWriteYsFileJobKind>;
|
||||
type ExternalProgram = Yosys;
|
||||
|
||||
fn dependencies() -> Self::Dependencies {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn args_to_jobs(
|
||||
args: JobArgsAndDependencies<ExternalCommandJobKind<Self>>,
|
||||
params: &JobParams,
|
||||
global_params: &GlobalParams,
|
||||
) -> eyre::Result<(
|
||||
Self::AdditionalJobData,
|
||||
<Self::Dependencies as JobDependencies>::JobsAndKinds,
|
||||
)> {
|
||||
args.args_to_jobs_external_simple(params, global_params, |args, dependencies| {
|
||||
let YosysNextpnrSynthArgs {} = 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<Self>) -> 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<Self>) -> Interned<[Interned<Path>]> {
|
||||
[job.additional_job_data().json_file()].intern_slice()
|
||||
}
|
||||
|
||||
fn command_line_args<W: ?Sized + WriteArgs>(job: &ExternalCommandJob<Self>, args: &mut W) {
|
||||
args.write_arg("-s");
|
||||
args.write_interned_arg(job.additional_job_data().ys_file_name());
|
||||
}
|
||||
|
||||
fn current_dir(job: &ExternalCommandJob<Self>) -> Option<Interned<Path>> {
|
||||
Some(job.output_dir())
|
||||
}
|
||||
|
||||
fn job_kind_name() -> Interned<str> {
|
||||
"yosys-nextpnr-ecp5-synth".intern()
|
||||
}
|
||||
|
||||
fn subcommand_hidden() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Debug, Hash, Default)]
|
||||
pub struct YosysNextpnrWritePcfFileJobKind;
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)]
|
||||
pub struct YosysNextpnrWritePcfFileArgs {}
|
||||
|
||||
impl ToArgs for YosysNextpnrWritePcfFileArgs {
|
||||
fn to_args(&self, _args: &mut (impl WriteArgs + ?Sized)) {
|
||||
let Self {} = self;
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
|
||||
pub struct YosysNextpnrWritePcfFile {
|
||||
firrtl_export_options: crate::firrtl::ExportOptions,
|
||||
output_dir: Interned<Path>,
|
||||
pcf_file: Interned<Path>,
|
||||
}
|
||||
|
||||
struct WritePcfContentsError(eyre::Report);
|
||||
|
||||
impl From<eyre::Report> for WritePcfContentsError {
|
||||
fn from(v: eyre::Report) -> Self {
|
||||
Self(v)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<fmt::Error> for WritePcfContentsError {
|
||||
fn from(_v: fmt::Error) -> Self {
|
||||
unreachable!("String write can't fail")
|
||||
}
|
||||
}
|
||||
|
||||
fn tcl_escape(s: impl AsRef<str>) -> 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<Bundle>),
|
||||
Mem(Mem),
|
||||
Target(Interned<Target>),
|
||||
}
|
||||
|
||||
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 PcfFileWriter<W: fmt::Write> { //TODO
|
||||
output: W,
|
||||
module_depth: usize,
|
||||
annotation_target: AnnotationTarget,
|
||||
dont_touch_targets: HashSet<Interned<Target>>,
|
||||
required_dont_touch_targets: HashSet<Interned<Target>>,
|
||||
}
|
||||
|
||||
impl<W: fmt::Write> PcfFileWriter<W> {
|
||||
fn run(output: W, top_module: Module<Bundle>) -> Result<(), WritePcfContentsError> {
|
||||
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 LatticeAnnotation:\ntarget: {target:?}\nat: {}",
|
||||
target.base().source_location(),
|
||||
).into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
fn default_visit_with<T: ?Sized + Visit<Self>>(
|
||||
&mut self,
|
||||
module_depth: usize,
|
||||
annotation_target: AnnotationTarget,
|
||||
v: &T,
|
||||
) -> Result<(), WritePcfContentsError> {
|
||||
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<W: fmt::Write> Visitor for PcfFileWriter<W> {
|
||||
type Error = WritePcfContentsError;
|
||||
|
||||
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<T: BundleType>(&mut self, v: &Module<T>) -> Result<(), Self::Error> {
|
||||
self.default_visit_with(
|
||||
self.module_depth + 1,
|
||||
AnnotationTarget::Module(v.canonical()),
|
||||
v,
|
||||
)
|
||||
}
|
||||
|
||||
fn visit_mem<Element: Type, Len: Size>(
|
||||
&mut self,
|
||||
v: &Mem<Element, Len>,
|
||||
) -> Result<(), Self::Error>
|
||||
where
|
||||
Element: Visit<Self>,
|
||||
{
|
||||
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(())
|
||||
}
|
||||
|
||||
|
||||
/* FIXME fn visit_lattice_annotation(&mut self, v: &LatticeAnnotation) -> Result<(), Self::Error> */
|
||||
}
|
||||
|
||||
impl YosysNextpnrWritePcfFile {
|
||||
fn write_pcf_contents_for_port_and_annotations(
|
||||
&self,
|
||||
output: &mut impl fmt::Write,
|
||||
port: &ScalarizedModuleABIPort,
|
||||
annotations: ScalarizedModuleABIAnnotations<'_>,
|
||||
) -> Result<(), WritePcfContentsError> {
|
||||
/* fixme for annotation in annotations .. */
|
||||
Ok(())
|
||||
}
|
||||
fn write_pcf_contents(
|
||||
&self,
|
||||
output: &mut String,
|
||||
top_module: &Module<Bundle>,
|
||||
) -> 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_pcf_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),
|
||||
}
|
||||
PcfFileWriter::run(output, *top_module).map_err(|e| e.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl JobKind for YosysNextpnrWritePcfFileJobKind {
|
||||
type Args = YosysNextpnrWritePcfFileArgs;
|
||||
type Job = YosysNextpnrWritePcfFile;
|
||||
type Dependencies = JobKindAndDependencies<ExternalCommandJobKind<YosysNextpnrSynth>>;
|
||||
|
||||
fn dependencies(self) -> Self::Dependencies {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn args_to_jobs(
|
||||
args: JobArgsAndDependencies<Self>,
|
||||
params: &JobParams,
|
||||
global_params: &GlobalParams,
|
||||
) -> eyre::Result<JobAndDependencies<Self>> {
|
||||
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 YosysNextpnrWritePcfFileArgs {} = args;
|
||||
let base_job = dependencies.get_job::<BaseJob, _>();
|
||||
Ok(YosysNextpnrWritePcfFile {
|
||||
firrtl_export_options,
|
||||
output_dir: base_job.output_dir(),
|
||||
pcf_file: base_job.file_with_ext("pcf"),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
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.pcf_file }].intern_slice()
|
||||
}
|
||||
|
||||
fn name(self) -> Interned<str> {
|
||||
"yosys-nextpnr-ecp5-write-pcf-file".intern()
|
||||
}
|
||||
|
||||
fn external_command_params(self, _job: &Self::Job) -> Option<CommandParams> {
|
||||
None
|
||||
}
|
||||
|
||||
fn run(
|
||||
self,
|
||||
job: &Self::Job,
|
||||
inputs: &[JobItem],
|
||||
params: &JobParams,
|
||||
_global_params: &GlobalParams,
|
||||
_acquired_job: &mut AcquiredJob,
|
||||
) -> eyre::Result<Vec<JobItem>> {
|
||||
assert!(inputs.iter().map(JobItem::name).eq(self.inputs(job)));
|
||||
let mut pcf = String::new();
|
||||
job.write_pcf_contents(&mut pcf, params.main_module())?;
|
||||
std::fs::write(job.pcf_file, pcf)?;
|
||||
Ok(vec![JobItem::Path { path: job.pcf_file }])
|
||||
}
|
||||
|
||||
fn subcommand_hidden(self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
|
||||
pub struct NextpnrLattice;
|
||||
|
||||
impl ExternalProgramTrait for NextpnrLattice {
|
||||
fn default_program_name() -> Interned<str> {
|
||||
"nextpnr".intern()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)]
|
||||
pub struct YosysNextpnrRunNextpnrArgs {
|
||||
#[command(flatten)]
|
||||
pub common: LatticeArgs,
|
||||
#[arg(long, default_value_t = 0)]
|
||||
pub nextpnr_lattice_seed: i32,
|
||||
}
|
||||
|
||||
impl ToArgs for YosysNextpnrRunNextpnrArgs {
|
||||
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
|
||||
let Self {
|
||||
common,
|
||||
nextpnr_lattice_seed,
|
||||
} = self;
|
||||
common.to_args(args);
|
||||
args.write_display_arg(format_args!("--nextpnr-lattice-seed={nextpnr_lattice_seed}"));
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
|
||||
pub struct YosysNextpnrRunNextpnr {
|
||||
device: Device,
|
||||
nextpnr_lattice_seed: i32,
|
||||
pcf_file: Interned<Path>,
|
||||
json_file: Interned<Path>,
|
||||
json_file_name: Interned<OsStr>,
|
||||
routed_json_file: Interned<Path>,
|
||||
routed_json_file_name: Interned<OsStr>,
|
||||
textcfg_file: Interned<Path>,
|
||||
textcfg_file_name: Interned<OsStr>,
|
||||
}
|
||||
|
||||
impl ExternalCommand for YosysNextpnrRunNextpnr {
|
||||
type AdditionalArgs = YosysNextpnrRunNextpnrArgs;
|
||||
type AdditionalJobData = Self;
|
||||
type BaseJobPosition = GetJobPositionDependencies<
|
||||
GetJobPositionDependencies<<YosysNextpnrSynth as ExternalCommand>::BaseJobPosition>,
|
||||
>;
|
||||
type Dependencies = JobKindAndDependencies<YosysNextpnrWritePcfFileJobKind>;
|
||||
type ExternalProgram = NextpnrLattice;
|
||||
|
||||
fn dependencies() -> Self::Dependencies {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn args_to_jobs(
|
||||
args: JobArgsAndDependencies<ExternalCommandJobKind<Self>>,
|
||||
params: &JobParams,
|
||||
global_params: &GlobalParams,
|
||||
) -> eyre::Result<(
|
||||
Self::AdditionalJobData,
|
||||
<Self::Dependencies as JobDependencies>::JobsAndKinds,
|
||||
)> {
|
||||
args.args_to_jobs_external_simple(params, global_params, |args, dependencies| {
|
||||
let YosysNextpnrRunNextpnrArgs {
|
||||
common,
|
||||
nextpnr_lattice_seed,
|
||||
} = args.additional_args;
|
||||
let base_job = dependencies.get_job::<BaseJob, _>();
|
||||
let write_pcf_file = dependencies.get_job::<YosysNextpnrWritePcfFile, _>();
|
||||
let synth = dependencies.get_job::<ExternalCommandJob<YosysNextpnrSynth>, _>();
|
||||
let routed_json_file = base_job.file_with_ext("routed.json");
|
||||
let textcfg_file = base_job.file_with_ext("config"); //file must exist
|
||||
Ok(Self {
|
||||
device: common.require_device(base_job.platform(), global_params)?,
|
||||
nextpnr_lattice_seed,
|
||||
pcf_file: write_pcf_file.pcf_file,
|
||||
json_file: synth.additional_job_data().json_file(),
|
||||
json_file_name: synth.additional_job_data().json_file_name(),
|
||||
routed_json_file: routed_json_file,
|
||||
routed_json_file_name: routed_json_file
|
||||
.interned_file_name()
|
||||
.expect("known to have file name"),
|
||||
textcfg_file:textcfg_file,
|
||||
textcfg_file_name: textcfg_file
|
||||
.interned_file_name()
|
||||
.expect("known to have file name"),
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
fn inputs(job: &ExternalCommandJob<Self>) -> Interned<[JobItemName]> {
|
||||
[
|
||||
JobItemName::Path {
|
||||
path: job.additional_job_data().json_file,
|
||||
},
|
||||
JobItemName::Path {
|
||||
path: job.additional_job_data().pcf_file,
|
||||
},
|
||||
]
|
||||
.intern_slice()
|
||||
}
|
||||
|
||||
fn output_paths(job: &ExternalCommandJob<Self>) -> Interned<[Interned<Path>]> {
|
||||
[
|
||||
job.additional_job_data().routed_json_file,
|
||||
]
|
||||
.intern_slice()
|
||||
}
|
||||
|
||||
fn command_line_args<W: ?Sized + WriteArgs>(job: &ExternalCommandJob<Self>, args: &mut W) {
|
||||
let job_data @ YosysNextpnrRunNextpnr {
|
||||
nextpnr_lattice_seed,
|
||||
json_file_name,
|
||||
routed_json_file_name,
|
||||
textcfg_file_name,
|
||||
..
|
||||
} = job.additional_job_data();
|
||||
args.write_long_option_eq("json", json_file_name);
|
||||
args.write_long_option_eq("textcfg",textcfg_file_name);
|
||||
args.write_arg("--25k");
|
||||
args.write_long_option_eq("package","CSFBGA285");
|
||||
args.write_long_option_eq("lpf","/tmp/orangecrab_r0.2.1.pcf");
|
||||
args.write_arg("--lpf-allow-unconstrained");
|
||||
//???args.write_display_arg(format_args!("--seed={nextpnr_lattice_seed}"));
|
||||
|
||||
}
|
||||
|
||||
fn current_dir(job: &ExternalCommandJob<Self>) -> Option<Interned<Path>> {
|
||||
Some(job.output_dir())
|
||||
}
|
||||
|
||||
fn job_kind_name() -> Interned<str> {
|
||||
"yosys-nextpnr-ecp5-run-nextpnr".intern()
|
||||
}
|
||||
|
||||
fn subcommand_hidden() -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)]
|
||||
pub struct YosysNextpnrArgs {
|
||||
#[arg(long, env = "DB_DIR", value_hint = clap::ValueHint::DirPath)]
|
||||
pub pcf1: PathBuf,
|
||||
}
|
||||
|
||||
impl ToArgs for YosysNextpnrArgs {
|
||||
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
|
||||
let Self { pcf1 } = self;
|
||||
args.write_long_option_eq("pcf1", pcf1);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
|
||||
pub struct YosysNextpnr {
|
||||
pcf1: Interned<Path>,
|
||||
device: Device,
|
||||
frames_file: Interned<Path>,
|
||||
frames_file_name: Interned<OsStr>,
|
||||
bit_file: Interned<Path>,
|
||||
bit_file_name: Interned<OsStr>,
|
||||
}
|
||||
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
|
||||
pub struct Ecppack;
|
||||
|
||||
impl ExternalProgramTrait for Ecppack {
|
||||
fn default_program_name() -> Interned<str> {
|
||||
"ecppack".intern()
|
||||
}
|
||||
}
|
||||
|
||||
//begin
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)]
|
||||
pub struct YosysNextpnrRunEcpPackArgs {
|
||||
#[arg(long, env = "DB_DIR", value_hint = clap::ValueHint::DirPath)]
|
||||
pub placeholder_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl ToArgs for YosysNextpnrRunEcpPackArgs {
|
||||
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
|
||||
let Self { placeholder_dir } = self;
|
||||
args.write_long_option_eq("placeholder-dir", placeholder_dir);
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
|
||||
pub struct YosysNextpnrRunEcpPack {
|
||||
placeholder_dir: Interned<Path>,
|
||||
device: Device,
|
||||
routed_json_file: Interned<Path>,
|
||||
routed_json_file_name: Interned<OsStr>,
|
||||
textcfg_file: Interned<Path>,
|
||||
textcfg_file_name: Interned<OsStr>,
|
||||
frames_file: Interned<Path>,
|
||||
frames_file_name: Interned<OsStr>,
|
||||
bit_file: Interned<Path>,
|
||||
bit_file_name: Interned<OsStr>,
|
||||
}
|
||||
|
||||
impl ExternalCommand for YosysNextpnrRunEcpPack {
|
||||
type AdditionalArgs = YosysNextpnrRunEcpPackArgs;
|
||||
type AdditionalJobData = Self;
|
||||
type BaseJobPosition = GetJobPositionDependencies<
|
||||
<YosysNextpnrRunNextpnr as ExternalCommand>::BaseJobPosition,
|
||||
>;
|
||||
type Dependencies = JobKindAndDependencies<ExternalCommandJobKind<YosysNextpnrRunNextpnr>>;
|
||||
type ExternalProgram = Ecppack;
|
||||
|
||||
fn dependencies() -> Self::Dependencies {
|
||||
Default::default()
|
||||
}
|
||||
|
||||
fn args_to_jobs(
|
||||
args: JobArgsAndDependencies<ExternalCommandJobKind<Self>>,
|
||||
params: &JobParams,
|
||||
global_params: &GlobalParams,
|
||||
) -> eyre::Result<(
|
||||
Self::AdditionalJobData,
|
||||
<Self::Dependencies as JobDependencies>::JobsAndKinds,
|
||||
)> {
|
||||
args.args_to_jobs_external_simple(params, global_params, |args, dependencies| {
|
||||
let YosysNextpnrRunEcpPackArgs { placeholder_dir } = args.additional_args;
|
||||
let base_job = dependencies.get_job::<BaseJob, _>();
|
||||
let frames_file = base_job.file_with_ext("frames");
|
||||
let bit_file = base_job.file_with_ext("bit");
|
||||
Ok(Self {
|
||||
placeholder_dir: placeholder_dir.intern_deref(),
|
||||
device: dependencies.job.job.additional_job_data().device,
|
||||
//fixme glue code
|
||||
routed_json_file: dependencies.job.job.additional_job_data().routed_json_file,
|
||||
routed_json_file_name: dependencies.job.job.additional_job_data().routed_json_file_name,
|
||||
textcfg_file: dependencies.job.job.additional_job_data().textcfg_file,
|
||||
textcfg_file_name: dependencies.job.job.additional_job_data().textcfg_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<Self>) -> Interned<[JobItemName]> {
|
||||
[JobItemName::Path {
|
||||
path: job.additional_job_data().routed_json_file,
|
||||
}]
|
||||
.intern_slice()
|
||||
}
|
||||
|
||||
fn output_paths(job: &ExternalCommandJob<Self>) -> Interned<[Interned<Path>]> {
|
||||
[
|
||||
job.additional_job_data().bit_file,
|
||||
]
|
||||
.intern_slice()
|
||||
}
|
||||
|
||||
fn command_line_args<W: ?Sized + WriteArgs>(job: &ExternalCommandJob<Self>, args: &mut W) {
|
||||
let job_data @ YosysNextpnrRunEcpPack {
|
||||
placeholder_dir,
|
||||
device,
|
||||
routed_json_file_name,
|
||||
textcfg_file_name,
|
||||
frames_file_name,
|
||||
bit_file_name,
|
||||
..
|
||||
} = job.additional_job_data();
|
||||
args.write_arg("--compress");
|
||||
args.write_long_option_eq("freq", "38.8"); //FIXME do not hardcode
|
||||
args.write_long_option_eq("input",textcfg_file_name);
|
||||
args.write_long_option_eq("bit",bit_file_name);
|
||||
}
|
||||
|
||||
fn current_dir(job: &ExternalCommandJob<Self>) -> Option<Interned<Path>> {
|
||||
Some(job.output_dir())
|
||||
}
|
||||
|
||||
fn job_kind_name() -> Interned<str> {
|
||||
"yosys-nextpnr-ecp5".intern()
|
||||
}
|
||||
}
|
||||
//end
|
||||
|
||||
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = DynJobKind> {
|
||||
[
|
||||
DynJobKind::new(YosysNextpnrWriteYsFileJobKind), //working
|
||||
DynJobKind::new(ExternalCommandJobKind::<YosysNextpnrSynth>::new()), //working
|
||||
DynJobKind::new(YosysNextpnrWritePcfFileJobKind), //TODO
|
||||
DynJobKind::new(ExternalCommandJobKind::<YosysNextpnrRunNextpnr>::new()), //working
|
||||
DynJobKind::new(ExternalCommandJobKind::<YosysNextpnrRunEcpPack>::new()), //working
|
||||
]
|
||||
}
|
||||
|
||||
pub(crate) fn built_in_platforms() -> impl IntoIterator<Item = crate::platform::DynPlatform> {
|
||||
[]
|
||||
}
|
||||
5
nextpnr.txt
Normal file
5
nextpnr.txt
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
nextpnr-ecp5 for orangecrab
|
||||
nextpnr-ice40 for other one*
|
||||
|
||||
modified: Makefile
|
||||
modified: crates/fayalite/src/vendor/lattice/yosys_nextpnr.rs
|
||||
BIN
tools/firrtl.tar.gz
Normal file
BIN
tools/firrtl.tar.gz
Normal file
Binary file not shown.
Loading…
Add table
Add a link
Reference in a new issue