1
0
Fork 0

Compare commits

...

10 commits

29 changed files with 2953 additions and 570 deletions

View file

@ -21,4 +21,5 @@ jobs:
- run: cargo test --doc --features=unstable-doc
- run: cargo doc --features=unstable-doc
- run: FAYALITE_TEST_HASHER=always_zero cargo test --test=module --features=unstable-doc,unstable-test-hasher
- run: cargo run --example blinky yosys-nextpnr-xray --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db --device xc7a100ticsg324-1L -o target/blinky-out --clock-frequency=$((1000*1000*100))
- run: cargo run --example blinky yosys-nextpnr-xray --platform=arty-a7-100t --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db -o target/blinky-out
- run: cargo run --example tx_only_uart yosys-nextpnr-xray --platform=arty-a7-100t --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db -o target/tx_only_uart-out

31
Cargo.lock generated
View file

@ -319,6 +319,7 @@ dependencies = [
"jobslot",
"num-bigint",
"num-traits",
"ordered-float",
"petgraph",
"serde",
"serde_json",
@ -524,6 +525,17 @@ version = "1.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92"
[[package]]
name = "ordered-float"
version = "5.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d"
dependencies = [
"num-traits",
"rand",
"serde",
]
[[package]]
name = "petgraph"
version = "0.8.1"
@ -576,6 +588,25 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"rand_core",
"serde",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"serde",
]
[[package]]
name = "rustix"
version = "0.38.31"

View file

@ -30,6 +30,7 @@ indexmap = { version = "2.5.0", features = ["serde"] }
jobslot = "0.2.23"
num-bigint = "0.4.6"
num-traits = "0.2.16"
ordered-float = { version = "5.1.0", features = ["serde"] }
petgraph = "0.8.1"
prettyplease = "0.2.20"
proc-macro2 = "1.0.83"

View file

@ -8,6 +8,73 @@ Fayalite is a library for designing digital hardware -- a hardware description l
[FIRRTL]: https://github.com/chipsalliance/firrtl-spec
# Building the [Blinky example] for the Arty A7 100T on Linux
[Blinky example]: crates/fayalite/examples/blinky.rs
This uses the container image containing all the external programs and files that Fayalite needs to build for FPGAs, the sources for the container image are in https://git.libre-chip.org/libre-chip/fayalite-deps
Steps:
Install podman (or docker).
Run:
```bash
podman run --rm --security-opt label=disable --volume="$(pwd):$(pwd)" -w="$(pwd)" -it git.libre-chip.org/libre-chip/fayalite-deps:latest cargo run --example blinky yosys-nextpnr-xray --nextpnr-xilinx-chipdb-dir /opt/fayalite-deps/nextpnr-xilinx/xilinx --prjxray-db-dir /opt/fayalite-deps/prjxray-db --platform arty-a7-100t -o target/blinky-out
```
To actually program the FPGA, you'll need to install [openFPGALoader] on your host OS:
[openFPGALoader]: https://github.com/trabucayre/openFPGALoader
On Debian 12:
```bash
sudo apt update && sudo apt install openfpgaloader
```
Then program the FPGA:
```bash
sudo openFPGALoader --board arty_a7_100t target/blinky-out/blinky.bit
```
This will program the FPGA but leave the Flash chip unmodified, so the FPGA will revert when the board is power-cycled.
To program the Flash also, so it stays programmed when power-cycling the board:
```bash
sudo openFPGALoader --board arty_a7_100t -f target/blinky-out/blinky.bit
```
# Building the [Transmit-only UART example] for the Arty A7 100T on Linux
[Transmit-only UART example]: crates/fayalite/examples/tx_only_uart.rs
Follow the steps above of building the Blinky example, but replace `blinky` with `tx_only_uart`.
View the output using [tio](https://github.com/tio/tio) which you can install in Debian using `apt`.
Find the correct USB device:
```bash
sudo tio --list
```
You want the device with a name like (note the `if01`, `if00` is presumably the JTAG port):
`/dev/serial/by-id/usb-Digilent_Digilent_USB_Device_210319B4A51E-if01-port0`
Connect to the serial port:
```bash
sudo tio -b115200 /dev/serial/by-id/put-your-device-id-here
```
You'll see (repeating endlessly):
```text
Hello World from Fayalite!!!
Hello World from Fayalite!!!
Hello World from Fayalite!!!
```
Press Ctrl+T then `q` to exit tio.
# Funding
## NLnet Grants

View file

@ -26,6 +26,7 @@ hashbrown.workspace = true
jobslot.workspace = true
num-bigint.workspace = true
num-traits.workspace = true
ordered-float.workspace = true
petgraph.workspace = true
serde_json.workspace = true
serde.workspace = true

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

@ -0,0 +1,188 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use clap::builder::TypedValueParser;
use fayalite::{
build::{ToArgs, WriteArgs},
platform::PeripheralRef,
prelude::*,
};
use ordered_float::NotNan;
fn pick_clock<'a>(
platform_io_builder: &PlatformIOBuilder<'a>,
) -> PeripheralRef<'a, peripherals::ClockInput> {
let mut clks = platform_io_builder.peripherals_with_type::<peripherals::ClockInput>();
clks.sort_by_key(|clk| {
// sort clocks by preference, smaller return values means higher preference
let mut frequency = clk.ty().frequency();
let priority;
if frequency < 10e6 {
frequency = -frequency; // prefer bigger frequencies
priority = 1;
} else if frequency > 50e6 {
// prefer smaller frequencies
priority = 2; // least preferred
} else {
priority = 0; // most preferred
frequency = (frequency - 25e6).abs(); // prefer closer to 25MHz
}
(priority, NotNan::new(frequency).expect("should be valid"))
});
clks[0]
}
#[hdl_module]
fn tx_only_uart(
platform_io_builder: PlatformIOBuilder<'_>,
divisor: f64,
message: impl AsRef<[u8]>,
) {
let message = message.as_ref();
let clk_input = pick_clock(&platform_io_builder).use_peripheral();
let rst = platform_io_builder.peripherals_with_type::<Reset>()[0].use_peripheral();
let cd = #[hdl]
ClockDomain {
clk: clk_input.clk,
rst,
};
let numerator = 1u128 << 16;
let denominator = (divisor * numerator as f64).round() as u128;
#[hdl]
let remainder_reg: UInt<128> = reg_builder().clock_domain(cd).reset(0u128);
#[hdl]
let sum: UInt<128> = wire();
connect_any(sum, remainder_reg + numerator);
#[hdl]
let tick_reg = reg_builder().clock_domain(cd).reset(false);
connect(tick_reg, false);
#[hdl]
let next_remainder: UInt<128> = wire();
connect(remainder_reg, next_remainder);
#[hdl]
if sum.cmp_ge(denominator) {
connect_any(next_remainder, sum - denominator);
connect(tick_reg, true);
} else {
connect(next_remainder, sum);
}
#[hdl]
let uart_state_reg = reg_builder().clock_domain(cd).reset(0_hdl_u4);
#[hdl]
let next_uart_state: UInt<4> = wire();
connect_any(next_uart_state, uart_state_reg + 1u8);
#[hdl]
let message_mem: Array<UInt<8>> = wire(Array[UInt::new_static()][message.len()]);
for (message, message_mem) in message.iter().zip(message_mem) {
connect(message_mem, *message);
}
#[hdl]
let addr_reg: UInt<32> = reg_builder().clock_domain(cd).reset(0u32);
#[hdl]
let next_addr: UInt<32> = wire();
connect(next_addr, addr_reg);
#[hdl]
let tx = reg_builder().clock_domain(cd).reset(true);
#[hdl]
let tx_bits: Array<Bool, 10> = wire();
connect(tx_bits[0], false); // start bit
connect(tx_bits[9], true); // stop bit
for i in 0..8 {
connect(tx_bits[i + 1], message_mem[addr_reg][i]); // data bits
}
connect(tx, tx_bits[uart_state_reg]);
#[hdl]
if uart_state_reg.cmp_eq(Expr::ty(tx_bits).len() - 1) {
connect(next_uart_state, 0_hdl_u4);
let next_addr_val = addr_reg + 1u8;
#[hdl]
if next_addr_val.cmp_lt(message.len()) {
connect_any(next_addr, next_addr_val);
} else {
connect(next_addr, 0u32);
}
}
#[hdl]
if tick_reg {
connect(uart_state_reg, next_uart_state);
connect(addr_reg, next_addr);
}
for uart in platform_io_builder.peripherals_with_type::<peripherals::Uart>() {
connect(uart.use_peripheral().tx, tx);
}
#[hdl]
let io = m.add_platform_io(platform_io_builder);
}
fn parse_baud_rate(
v: impl AsRef<str>,
) -> Result<NotNan<f64>, Box<dyn std::error::Error + Send + Sync>> {
let retval: NotNan<f64> = v
.as_ref()
.parse()
.map_err(|_| "invalid baud rate, must be a finite positive floating-point value")?;
if *retval > 0.0 && retval.is_finite() {
Ok(retval)
} else {
Err("baud rate must be finite and positive".into())
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)]
pub struct ExtraArgs {
#[arg(long, value_parser = clap::builder::StringValueParser::new().try_map(parse_baud_rate), default_value = "115200")]
pub baud_rate: NotNan<f64>,
#[arg(long, default_value = "Hello World from Fayalite!!!\r\n", value_parser = clap::builder::NonEmptyStringValueParser::new())]
pub message: String,
}
impl ToArgs for ExtraArgs {
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
let Self { baud_rate, message } = self;
args.write_display_arg(format_args!("--baud-rate={baud_rate}"));
args.write_long_option_eq("message", message);
}
}
fn main() {
type Cli = BuildCli<ExtraArgs>;
Cli::main(
"tx_only_uart",
|_, platform, ExtraArgs { baud_rate, message }| {
Ok(JobParams::new(platform.try_wrap_main_module(|io| {
let clk = pick_clock(&io).ty();
let divisor = clk.frequency() / *baud_rate;
let baud_rate_error = |msg| {
<Cli as clap::CommandFactory>::command()
.error(clap::error::ErrorKind::ValueValidation, msg)
};
const HUGE_DIVISOR: f64 = u64::MAX as f64;
match divisor {
divisor if !divisor.is_finite() => {
return Err(baud_rate_error("bad baud rate"));
}
HUGE_DIVISOR.. => return Err(baud_rate_error("baud rate is too small")),
4.0.. => {}
_ => return Err(baud_rate_error("baud rate is too large")),
}
Ok(tx_only_uart(io, divisor, message))
})?))
},
);
}

File diff suppressed because it is too large Load diff

View file

@ -3,9 +3,9 @@
use crate::{
build::{
ArgsWriter, BaseJob, CommandParams, GetJob, JobAndDependencies, JobAndKind,
JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind, JobKindAndArgs,
JobParams, ToArgs, WriteArgs,
ArgsWriter, CommandParams, GlobalParams, JobAndDependencies, JobAndKind,
JobArgsAndDependencies, JobDependencies, JobDependenciesHasBase, JobItem, JobItemName,
JobKind, JobKindAndArgs, JobParams, ToArgs, WriteArgs,
},
intern::{Intern, Interned},
util::{job_server::AcquiredJob, streaming_read_utf8::streaming_read_utf8},
@ -990,12 +990,13 @@ pub trait ExternalCommand: 'static + Send + Sync + Hash + Eq + fmt::Debug + Size
+ Serialize
+ DeserializeOwned;
type BaseJobPosition;
type Dependencies: JobDependencies<JobsAndKinds: GetJob<BaseJob, Self::BaseJobPosition>>;
type Dependencies: JobDependenciesHasBase;
type ExternalProgram: ExternalProgramTrait;
fn dependencies() -> Self::Dependencies;
fn args_to_jobs(
args: JobArgsAndDependencies<ExternalCommandJobKind<Self>>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<(
Self::AdditionalJobData,
<Self::Dependencies as JobDependencies>::JobsAndKinds,
@ -1028,6 +1029,7 @@ impl<T: ExternalCommand> JobKind for ExternalCommandJobKind<T> {
fn args_to_jobs(
args: JobArgsAndDependencies<Self>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<JobAndDependencies<Self>> {
let JobKindAndArgs {
kind,
@ -1042,8 +1044,8 @@ impl<T: ExternalCommand> JobKind for ExternalCommandJobKind<T> {
additional_args: _,
},
} = args.args;
let (additional_job_data, dependencies) = T::args_to_jobs(args, params)?;
let base_job = GetJob::<BaseJob, _>::get_job(&dependencies);
let (additional_job_data, dependencies) = T::args_to_jobs(args, params, global_params)?;
let base_job = T::Dependencies::base_job(&dependencies);
let job = ExternalCommandJob {
additional_job_data,
program_path,
@ -1078,7 +1080,8 @@ impl<T: ExternalCommand> JobKind for ExternalCommandJobKind<T> {
self,
job: &Self::Job,
inputs: &[JobItem],
params: &JobParams,
_params: &JobParams,
global_params: &GlobalParams,
acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>> {
assert!(
@ -1093,7 +1096,7 @@ impl<T: ExternalCommand> JobKind for ExternalCommandJobKind<T> {
} = job.command_params();
ExternalJobCaching::new(
&job.output_dir,
&params.application_name(),
&global_params.application_name(),
&T::job_kind_name(),
job.run_even_if_cached,
)?

View file

@ -3,7 +3,7 @@
use crate::{
build::{
BaseJob, BaseJobKind, CommandParams, DynJobKind, JobAndDependencies,
BaseJob, BaseJobKind, CommandParams, DynJobKind, GlobalParams, JobAndDependencies,
JobArgsAndDependencies, JobItem, JobItemName, JobKind, JobKindAndDependencies, JobParams,
ToArgs, WriteArgs,
},
@ -64,9 +64,11 @@ impl JobKind for FirrtlJobKind {
fn args_to_jobs(
args: JobArgsAndDependencies<Self>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<JobAndDependencies<Self>> {
args.args_to_jobs_simple(
params,
global_params,
|_kind, FirrtlArgs { export_options }, dependencies| {
Ok(Firrtl {
base: dependencies.get_job::<BaseJob, _>().clone(),
@ -103,6 +105,7 @@ impl JobKind for FirrtlJobKind {
job: &Self::Job,
inputs: &[JobItem],
params: &JobParams,
_global_params: &GlobalParams,
_acquired_job: &mut AcquiredJob,
) -> eyre::Result<Vec<JobItem>> {
let [JobItem::Path { path: input_path }] = *inputs else {

View file

@ -3,8 +3,8 @@
use crate::{
build::{
BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, JobAndDependencies,
JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind,
BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GlobalParams,
JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind,
JobKindAndDependencies, JobParams, ToArgs, WriteArgs,
external::{
ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait,
@ -201,6 +201,7 @@ impl JobKind for WriteSbyFileJobKind {
fn args_to_jobs(
mut args: JobArgsAndDependencies<Self>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<JobAndDependencies<Self>> {
args.dependencies
.dependencies
@ -209,7 +210,7 @@ impl JobKind for WriteSbyFileJobKind {
.additional_args
.verilog_dialect
.get_or_insert(VerilogDialect::Yosys);
args.args_to_jobs_simple(params, |_kind, args, dependencies| {
args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| {
let FormalArgs {
sby_extra_args,
formal_mode,
@ -255,6 +256,7 @@ impl JobKind for WriteSbyFileJobKind {
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)));
@ -351,11 +353,12 @@ impl ExternalCommand for Formal {
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, |args, dependencies| {
args.args_to_jobs_external_simple(params, global_params, |args, dependencies| {
let FormalAdditionalArgs {} = args.additional_args;
let write_sby_file = dependencies.get_job::<WriteSbyFileJob, _>().clone();
Ok(Formal {

View file

@ -2,8 +2,11 @@
// See Notices.txt for copyright information
use crate::{
build::{DynJob, JobItem, JobItemName, JobParams, program_name_for_internal_jobs},
build::{
DynJob, GlobalParams, JobItem, JobItemName, JobParams, program_name_for_internal_jobs,
},
intern::Interned,
platform::DynPlatform,
util::{HashMap, HashSet, job_server::AcquiredJob},
};
use eyre::{ContextCompat, eyre};
@ -489,15 +492,21 @@ impl JobGraph {
Err(e) => panic!("error: {e}"),
}
}
pub fn to_unix_makefile(&self, extra_args: &[Interned<OsStr>]) -> Result<String, Utf8Error> {
pub fn to_unix_makefile(
&self,
platform: Option<&DynPlatform>,
extra_args: &[Interned<OsStr>],
) -> Result<String, Utf8Error> {
self.to_unix_makefile_with_internal_program_prefix(
&[program_name_for_internal_jobs()],
platform,
extra_args,
)
}
pub fn to_unix_makefile_with_internal_program_prefix(
&self,
internal_program_prefix: &[Interned<OsStr>],
platform: Option<&DynPlatform>,
extra_args: &[Interned<OsStr>],
) -> Result<String, Utf8Error> {
let mut retval = String::new();
@ -572,19 +581,23 @@ impl JobGraph {
}
}
retval.push_str("\n\t");
job.command_params_with_internal_program_prefix(internal_program_prefix, extra_args)
.to_unix_shell_line(&mut retval, |arg, output| {
write_str!(
output,
"{}",
EscapeForUnixMakefile::new(
arg,
UnixMakefileEscapeKind::RecipeWithShellEscaping,
&mut needed_variables
)?
);
Ok(())
})?;
job.command_params_with_internal_program_prefix(
internal_program_prefix,
platform,
extra_args,
)
.to_unix_shell_line(&mut retval, |arg, output| {
write_str!(
output,
"{}",
EscapeForUnixMakefile::new(
arg,
UnixMakefileEscapeKind::RecipeWithShellEscaping,
&mut needed_variables
)?
);
Ok(())
})?;
retval.push_str("\n\n");
}
if !phony_targets.is_empty() {
@ -610,15 +623,21 @@ impl JobGraph {
}
Ok(retval)
}
pub fn to_unix_shell_script(&self, extra_args: &[Interned<OsStr>]) -> String {
pub fn to_unix_shell_script(
&self,
platform: Option<&DynPlatform>,
extra_args: &[Interned<OsStr>],
) -> String {
self.to_unix_shell_script_with_internal_program_prefix(
&[program_name_for_internal_jobs()],
platform,
extra_args,
)
}
pub fn to_unix_shell_script_with_internal_program_prefix(
&self,
internal_program_prefix: &[Interned<OsStr>],
platform: Option<&DynPlatform>,
extra_args: &[Interned<OsStr>],
) -> String {
let mut retval = String::from(
@ -630,7 +649,11 @@ impl JobGraph {
continue;
};
let Ok(()) = job
.command_params_with_internal_program_prefix(internal_program_prefix, extra_args)
.command_params_with_internal_program_prefix(
internal_program_prefix,
platform,
extra_args,
)
.to_unix_shell_line(&mut retval, |arg, output| -> Result<(), Infallible> {
write_str!(output, "{}", EscapeForUnixShell::new(&arg));
Ok(())
@ -639,7 +662,7 @@ impl JobGraph {
}
retval
}
pub fn run(&self, params: &JobParams) -> eyre::Result<()> {
pub fn run(&self, params: &JobParams, global_params: &GlobalParams) -> eyre::Result<()> {
// use scope to auto-join threads on errors
thread::scope(|scope| {
struct WaitingJobState {
@ -725,13 +748,18 @@ impl JobGraph {
job: DynJob,
inputs: Vec<JobItem>,
params: &'a JobParams,
global_params: &'a GlobalParams,
acquired_job: AcquiredJob,
finished_jobs_sender: mpsc::Sender<<JobGraphInner as GraphBase>::NodeId>,
}
impl RunningJobInThread<'_> {
fn run(mut self) -> eyre::Result<Vec<JobItem>> {
self.job
.run(&self.inputs, self.params, &mut self.acquired_job)
self.job.run(
&self.inputs,
self.params,
self.global_params,
&mut self.acquired_job,
)
}
}
impl Drop for RunningJobInThread<'_> {
@ -749,6 +777,7 @@ impl JobGraph {
})
}))?,
params,
global_params,
acquired_job: AcquiredJob::acquire()?,
finished_jobs_sender: finished_jobs_sender.clone(),
};

View file

@ -4,10 +4,9 @@
use crate::{
build::{DynJobKind, JobKind, built_in_job_kinds},
intern::Interned,
util::InternedStrCompareAsStr,
};
use std::{
borrow::Borrow,
cmp::Ordering,
collections::BTreeMap,
fmt,
sync::{Arc, OnceLock, RwLock, RwLockWriteGuard},
@ -23,33 +22,6 @@ impl DynJobKind {
}
}
#[derive(Copy, Clone, PartialEq, Eq)]
struct InternedStrCompareAsStr(Interned<str>);
impl fmt::Debug for InternedStrCompareAsStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl Ord for InternedStrCompareAsStr {
fn cmp(&self, other: &Self) -> Ordering {
str::cmp(&self.0, &other.0)
}
}
impl PartialOrd for InternedStrCompareAsStr {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Borrow<str> for InternedStrCompareAsStr {
fn borrow(&self) -> &str {
&self.0
}
}
#[derive(Clone, Debug)]
struct JobKindRegistry {
job_kinds: BTreeMap<InternedStrCompareAsStr, DynJobKind>,

View file

@ -4,8 +4,8 @@
use crate::{
build::{
BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, GetJobPositionJob,
JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind,
JobKindAndDependencies, JobParams, ToArgs, WriteArgs,
GlobalParams, JobAndDependencies, JobArgsAndDependencies, JobDependencies, JobItem,
JobItemName, JobKind, JobKindAndDependencies, JobParams, ToArgs, WriteArgs,
external::{
ExternalCommand, ExternalCommandJob, ExternalCommandJobKind, ExternalProgramTrait,
},
@ -152,11 +152,12 @@ impl ExternalCommand for UnadjustedVerilog {
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, |args, dependencies| {
args.args_to_jobs_external_simple(params, global_params, |args, dependencies| {
let UnadjustedVerilogArgs {
firtool_extra_args,
verilog_dialect,
@ -316,8 +317,9 @@ impl JobKind for VerilogJobKind {
fn args_to_jobs(
args: JobArgsAndDependencies<Self>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<JobAndDependencies<Self>> {
args.args_to_jobs_simple(params, |_kind, args, dependencies| {
args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| {
let VerilogJobArgs {} = args;
let base_job = dependencies.get_job::<BaseJob, _>();
Ok(VerilogJob {
@ -364,6 +366,7 @@ impl JobKind for VerilogJobKind {
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)));

View file

@ -1883,7 +1883,11 @@ impl<'a> Exporter<'a> {
}
fn annotation(&mut self, path: AnnotationTargetPath, annotation: &Annotation) {
let data = match annotation {
Annotation::DontTouch(DontTouchAnnotation {}) => AnnotationData::DontTouch,
Annotation::DontTouch(DontTouchAnnotation {}) => {
// TODO: error if the annotated thing was renamed because of a naming conflict,
// unless Target::base() is one of the ports of the top-level module since that's handled by ScalarizedModuleABI
AnnotationData::DontTouch
}
Annotation::SVAttribute(SVAttributeAnnotation { text }) => {
AnnotationData::AttributeAnnotation { description: *text }
}
@ -1907,7 +1911,8 @@ impl<'a> Exporter<'a> {
additional_fields: (*additional_fields).into(),
},
Annotation::Xilinx(XilinxAnnotation::XdcLocation(_))
| Annotation::Xilinx(XilinxAnnotation::XdcIOStandard(_)) => return,
| Annotation::Xilinx(XilinxAnnotation::XdcIOStandard(_))
| Annotation::Xilinx(XilinxAnnotation::XdcCreateClock(_)) => return,
};
self.annotations.push(FirrtlAnnotation {
data,

View file

@ -304,6 +304,15 @@ macro_rules! define_uint_in_range_type {
$SerdeRange { start, end }.intern_sized(),
))
}
pub fn bit_width(self) -> usize {
self.value.width()
}
pub fn start(self) -> Start::SizeType {
self.range.get().start
}
pub fn end(self) -> End::SizeType {
self.range.get().end
}
}
impl<Start: Size, End: Size> fmt::Debug for $UIntInRangeType<Start, End> {
@ -477,18 +486,22 @@ macro_rules! define_uint_in_range_type {
}
}
impl<Start: Size, End: Size> ExprCastTo<UInt> for $UIntInRangeType<Start, End> {
fn cast_to(src: Expr<Self>, to_type: UInt) -> Expr<UInt> {
impl<Start: Size, End: Size, Width: Size> ExprCastTo<UIntType<Width>>
for $UIntInRangeType<Start, End>
{
fn cast_to(src: Expr<Self>, to_type: UIntType<Width>) -> Expr<UIntType<Width>> {
src.cast_to_bits().cast_to(to_type)
}
}
impl<Start: Size, End: Size> ExprCastTo<$UIntInRangeType<Start, End>> for UInt {
impl<Start: Size, End: Size, Width: Size> ExprCastTo<$UIntInRangeType<Start, End>>
for UIntType<Width>
{
fn cast_to(
src: Expr<Self>,
to_type: $UIntInRangeType<Start, End>,
) -> Expr<$UIntInRangeType<Start, End>> {
src.cast_bits_to(to_type)
src.cast_to(to_type.value).cast_bits_to(to_type)
}
}

View file

@ -33,7 +33,9 @@ use crate::{
sim::{ExternModuleSimulation, value::DynSimOnly},
source_location::SourceLocation,
ty::{CanonicalType, Type},
vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation},
vendor::xilinx::{
XdcCreateClockAnnotation, XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation,
},
wire::Wire,
};
use num_bigint::{BigInt, BigUint};

View file

@ -8,24 +8,30 @@ use crate::{
module::{Module, ModuleBuilder, ModuleIO, connect_with_loc, instance_with_loc, wire_with_loc},
source_location::SourceLocation,
ty::{CanonicalType, Type},
util::HashMap,
util::{HashMap, HashSet, InternedStrCompareAsStr},
};
use serde::{Deserialize, Deserializer, Serialize, Serializer, de::Error};
use std::{
any::{Any, TypeId},
borrow::Cow,
cmp::Ordering,
collections::{BTreeMap, BTreeSet},
convert::Infallible,
fmt,
hash::{Hash, Hasher},
iter::FusedIterator,
marker::PhantomData,
mem,
sync::{Arc, Mutex, MutexGuard, OnceLock},
sync::{Arc, Mutex, MutexGuard, OnceLock, RwLock, RwLockWriteGuard},
};
pub mod peripherals;
trait DynPlatformTrait: 'static + Send + Sync + fmt::Debug {
fn as_any(&self) -> &dyn Any;
fn eq_dyn(&self, other: &dyn DynPlatformTrait) -> bool;
fn hash_dyn(&self, state: &mut dyn Hasher);
fn name_dyn(&self) -> Interned<str>;
fn new_peripherals_dyn<'builder>(
&self,
builder_factory: PeripheralsBuilderFactory<'builder>,
@ -33,6 +39,7 @@ trait DynPlatformTrait: 'static + Send + Sync + fmt::Debug {
fn source_location_dyn(&self) -> SourceLocation;
#[track_caller]
fn add_peripherals_in_wrapper_module_dyn(&self, m: &ModuleBuilder, peripherals: DynPeripherals);
fn aspects_dyn(&self) -> PlatformAspectSet;
}
impl<T: Platform> DynPlatformTrait for T {
@ -51,6 +58,10 @@ impl<T: Platform> DynPlatformTrait for T {
self.hash(&mut state);
}
fn name_dyn(&self) -> Interned<str> {
self.name()
}
fn new_peripherals_dyn<'builder>(
&self,
builder_factory: PeripheralsBuilderFactory<'builder>,
@ -80,6 +91,10 @@ impl<T: Platform> DynPlatformTrait for T {
};
self.add_peripherals_in_wrapper_module(m, *peripherals)
}
fn aspects_dyn(&self) -> PlatformAspectSet {
self.aspects()
}
}
#[derive(Clone)]
@ -155,6 +170,9 @@ impl Peripherals for DynPeripherals {
impl Platform for DynPlatform {
type Peripherals = DynPeripherals;
fn name(&self) -> Interned<str> {
DynPlatformTrait::name_dyn(&*self.0)
}
fn new_peripherals<'a>(
&self,
builder_factory: PeripheralsBuilderFactory<'a>,
@ -168,6 +186,9 @@ impl Platform for DynPlatform {
fn add_peripherals_in_wrapper_module(&self, m: &ModuleBuilder, peripherals: Self::Peripherals) {
DynPlatformTrait::add_peripherals_in_wrapper_module_dyn(&*self.0, m, peripherals);
}
fn aspects(&self) -> PlatformAspectSet {
DynPlatformTrait::aspects_dyn(&*self.0)
}
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
@ -448,9 +469,7 @@ impl<'a, S: PeripheralsOnUseSharedState> PeripheralsBuilder<'a, S> {
id,
Box::new(move |state, peripheral_ref, wire| {
on_use(
state
.as_any()
.downcast_mut()
<dyn Any>::downcast_mut::<S>(PeripheralsOnUseSharedState::as_any(state))
.expect("known to be correct type"),
PeripheralRef::from_canonical(peripheral_ref),
Expr::from_canonical(wire),
@ -518,6 +537,7 @@ impl<'a, S: PeripheralsOnUseSharedState> PeripheralsBuilder<'a, S> {
}
}
#[must_use]
pub struct Peripheral<T: Type> {
ty: T,
common: PeripheralCommon,
@ -528,6 +548,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()
}
@ -588,7 +629,7 @@ impl<T: Type> Peripheral<T> {
impl<T: Type> fmt::Debug for Peripheral<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_ref().debug_fmt("Peripheral", f)
self.as_ref().debug_common_fields("Peripheral", f).finish()
}
}
@ -599,7 +640,10 @@ pub struct UsedPeripheral<T: Type> {
impl<T: Type> fmt::Debug for UsedPeripheral<T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.as_ref().debug_fmt("UsedPeripheral", f)
self.as_ref()
.debug_common_fields("UsedPeripheral", f)
.field("instance_io_field", &self.instance_io_field())
.finish()
}
}
@ -617,6 +661,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)]
@ -627,7 +689,7 @@ pub struct PeripheralRef<'a, T: Type> {
impl<'a, T: Type> fmt::Debug for PeripheralRef<'a, T> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.debug_fmt("PeripheralRef", f)
self.debug_common_fields("PeripheralRef", f).finish()
}
}
@ -662,7 +724,11 @@ impl fmt::Display for PeripheralUnavailableError {
impl std::error::Error for PeripheralUnavailableError {}
impl<'a, T: Type> PeripheralRef<'a, T> {
fn debug_fmt(&self, struct_name: &str, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fn debug_common_fields<'f1, 'f2>(
&self,
struct_name: &str,
f: &'f1 mut fmt::Formatter<'f2>,
) -> fmt::DebugStruct<'f1, 'f2> {
let Self {
ty,
common:
@ -673,12 +739,13 @@ impl<'a, T: Type> PeripheralRef<'a, T> {
peripherals_state: _,
},
} = self;
f.debug_struct(struct_name)
let mut retval = f.debug_struct(struct_name);
retval
.field("ty", ty)
.field("id", id)
.field("is_input", is_input)
.field("availability", &self.availability())
.finish()
.field("availability", &self.availability());
retval
}
pub fn ty(&self) -> T {
self.ty
@ -850,7 +917,7 @@ impl<'a, T: Type> PeripheralRef<'a, T> {
flipped: self.is_input(),
ty: Expr::ty(canonical_wire),
});
on_use_function(shared_state, self.canonical(), canonical_wire);
on_use_function(&mut **shared_state, self.canonical(), canonical_wire);
drop(on_use_state);
Ok(wire)
}
@ -1031,8 +1098,327 @@ impl<'a> fmt::Debug for PlatformIOBuilder<'a> {
}
}
trait PlatformAspectTrait: 'static + Send + Sync + fmt::Debug {
fn any_ref(&self) -> &dyn Any;
fn any_arc(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
fn eq_dyn(&self, other: &dyn PlatformAspectTrait) -> bool;
fn hash_dyn(&self, state: &mut dyn Hasher);
}
impl<T: 'static + Send + Sync + fmt::Debug + Eq + Hash> PlatformAspectTrait for T {
fn any_ref(&self) -> &dyn Any {
self
}
fn any_arc(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
self
}
fn eq_dyn(&self, other: &dyn PlatformAspectTrait) -> bool {
other
.any_ref()
.downcast_ref::<T>()
.is_some_and(|other| self == other)
}
fn hash_dyn(&self, mut state: &mut dyn Hasher) {
self.hash(&mut state);
}
}
#[derive(Clone)]
pub struct PlatformAspect {
type_id: TypeId,
type_name: &'static str,
value: Arc<dyn PlatformAspectTrait>,
}
impl Hash for PlatformAspect {
fn hash<H: Hasher>(&self, state: &mut H) {
PlatformAspectTrait::hash_dyn(&*self.value, state);
}
}
impl Eq for PlatformAspect {}
impl PartialEq for PlatformAspect {
fn eq(&self, other: &Self) -> bool {
self.type_id == other.type_id && PlatformAspectTrait::eq_dyn(&*self.value, &*other.value)
}
}
impl fmt::Debug for PlatformAspect {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let Self {
type_id: _,
type_name,
value,
} = self;
write!(f, "PlatformAspect<{type_name}>")?;
f.debug_tuple("").field(value).finish()
}
}
impl PlatformAspect {
pub fn new_arc<T: 'static + Send + Sync + fmt::Debug + Hash + Eq>(value: Arc<T>) -> Self {
Self {
type_id: TypeId::of::<T>(),
type_name: std::any::type_name::<T>(),
value,
}
}
pub fn new<T: 'static + Send + Sync + fmt::Debug + Hash + Eq>(value: T) -> Self {
Self::new_arc(Arc::new(value))
}
pub fn type_id(&self) -> TypeId {
self.type_id
}
pub fn downcast_arc<T: 'static + Send + Sync + fmt::Debug>(self) -> Result<Arc<T>, Self> {
if self.type_id == TypeId::of::<T>() {
let Ok(retval) = self.value.any_arc().downcast() else {
unreachable!();
};
Ok(retval)
} else {
Err(self)
}
}
pub fn downcast_unwrap_or_clone<T: 'static + Send + Sync + fmt::Debug + Clone>(
self,
) -> Result<T, Self> {
Ok(Arc::unwrap_or_clone(self.downcast_arc()?))
}
pub fn downcast_ref<T: 'static + Send + Sync + fmt::Debug>(&self) -> Option<&T> {
PlatformAspectTrait::any_ref(&*self.value).downcast_ref()
}
}
#[derive(Clone, Default)]
pub struct PlatformAspectSet {
aspects_by_type_id: Arc<HashMap<TypeId, HashSet<PlatformAspect>>>,
aspects: Arc<Vec<PlatformAspect>>,
}
impl PlatformAspectSet {
pub fn new() -> Self {
Self::default()
}
pub fn insert_new<T: 'static + Send + Sync + fmt::Debug + Hash + Eq>(
&mut self,
value: T,
) -> bool {
self.insert(PlatformAspect::new(value))
}
pub fn insert_new_arc<T: 'static + Send + Sync + fmt::Debug + Hash + Eq>(
&mut self,
value: Arc<T>,
) -> bool {
self.insert(PlatformAspect::new_arc(value))
}
fn insert_inner(
aspects_by_type_id: &mut HashMap<TypeId, HashSet<PlatformAspect>>,
aspects: &mut Vec<PlatformAspect>,
value: PlatformAspect,
) -> bool {
if aspects_by_type_id
.entry(value.type_id)
.or_default()
.insert(value.clone())
{
aspects.push(value);
true
} else {
false
}
}
pub fn insert(&mut self, value: PlatformAspect) -> bool {
Self::insert_inner(
Arc::make_mut(&mut self.aspects_by_type_id),
Arc::make_mut(&mut self.aspects),
value,
)
}
pub fn contains(&self, value: &PlatformAspect) -> bool {
self.aspects_by_type_id
.get(&value.type_id)
.is_some_and(|aspects| aspects.contains(value))
}
pub fn get_aspects_by_type<'a, T: 'static + Send + Sync + fmt::Debug + Hash + Eq>(
&'a self,
) -> impl Clone + Iterator<Item = &'a PlatformAspect> + FusedIterator + ExactSizeIterator + 'a
{
self.aspects_by_type_id
.get(&TypeId::of::<T>())
.map(|aspects| aspects.iter())
.unwrap_or_default()
}
pub fn get_by_type<'a, T: 'static + Send + Sync + fmt::Debug + Hash + Eq>(
&'a self,
) -> impl Clone + Iterator<Item = &'a T> + FusedIterator + ExactSizeIterator + 'a {
self.get_aspects_by_type::<T>()
.map(|aspect| aspect.downcast_ref().expect("already checked type"))
}
pub fn get_single_by_type<'a, T: 'static + Send + Sync + fmt::Debug + Hash + Eq>(
&'a self,
) -> Option<&'a T> {
let mut aspects = self.get_by_type::<T>();
if aspects.len() == 1 {
aspects.next()
} else {
None
}
}
pub fn get_arcs_by_type<'a, T: 'static + Send + Sync + fmt::Debug + Hash + Eq>(
&'a self,
) -> impl Clone + Iterator<Item = Arc<T>> + FusedIterator + ExactSizeIterator + 'a {
self.get_aspects_by_type::<T>().map(|aspect| {
aspect
.clone()
.downcast_arc()
.ok()
.expect("already checked type")
})
}
}
impl<'a> Extend<&'a PlatformAspect> for PlatformAspectSet {
fn extend<T: IntoIterator<Item = &'a PlatformAspect>>(&mut self, iter: T) {
self.extend(iter.into_iter().cloned());
}
}
impl Extend<PlatformAspect> for PlatformAspectSet {
fn extend<T: IntoIterator<Item = PlatformAspect>>(&mut self, iter: T) {
let Self {
aspects_by_type_id,
aspects,
} = self;
let aspects_by_type_id = Arc::make_mut(aspects_by_type_id);
let aspects = Arc::make_mut(aspects);
iter.into_iter().for_each(|value| {
Self::insert_inner(aspects_by_type_id, aspects, value);
});
}
}
impl<'a> FromIterator<&'a PlatformAspect> for PlatformAspectSet {
fn from_iter<T: IntoIterator<Item = &'a PlatformAspect>>(iter: T) -> Self {
let mut retval = Self::default();
retval.extend(iter);
retval
}
}
impl FromIterator<PlatformAspect> for PlatformAspectSet {
fn from_iter<T: IntoIterator<Item = PlatformAspect>>(iter: T) -> Self {
let mut retval = Self::default();
retval.extend(iter);
retval
}
}
impl std::ops::Deref for PlatformAspectSet {
type Target = [PlatformAspect];
fn deref(&self) -> &Self::Target {
&self.aspects
}
}
impl fmt::Debug for PlatformAspectSet {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_set().entries(self).finish()
}
}
impl IntoIterator for PlatformAspectSet {
type Item = PlatformAspect;
type IntoIter = PlatformAspectsIntoIter;
fn into_iter(self) -> Self::IntoIter {
PlatformAspectsIntoIter {
indexes: 0..self.aspects.len(),
aspects: self.aspects,
}
}
}
impl<'a> IntoIterator for &'a PlatformAspectSet {
type Item = &'a PlatformAspect;
type IntoIter = std::slice::Iter<'a, PlatformAspect>;
fn into_iter(self) -> Self::IntoIter {
self.aspects.iter()
}
}
#[derive(Clone, Debug)]
pub struct PlatformAspectsIntoIter {
aspects: Arc<Vec<PlatformAspect>>,
indexes: std::ops::Range<usize>,
}
impl Iterator for PlatformAspectsIntoIter {
type Item = PlatformAspect;
fn next(&mut self) -> Option<Self::Item> {
self.indexes.next().map(|index| self.aspects[index].clone())
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.indexes.size_hint()
}
fn count(self) -> usize {
self.indexes.len()
}
fn last(mut self) -> Option<Self::Item> {
self.next_back()
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.indexes.nth(n).map(|index| self.aspects[index].clone())
}
fn fold<B, F>(self, init: B, mut f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
self.indexes
.fold(init, |v, index| f(v, self.aspects[index].clone()))
}
}
impl FusedIterator for PlatformAspectsIntoIter {}
impl ExactSizeIterator for PlatformAspectsIntoIter {}
impl DoubleEndedIterator for PlatformAspectsIntoIter {
fn next_back(&mut self) -> Option<Self::Item> {
self.indexes
.next_back()
.map(|index| self.aspects[index].clone())
}
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
self.indexes
.nth_back(n)
.map(|index| self.aspects[index].clone())
}
fn rfold<B, F>(self, init: B, mut f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
self.indexes
.rfold(init, |v, index| f(v, self.aspects[index].clone()))
}
}
pub trait Platform: Clone + 'static + Send + Sync + fmt::Debug + Hash + Eq {
type Peripherals: Peripherals;
fn name(&self) -> Interned<str>;
fn new_peripherals<'builder>(
&self,
builder_factory: PeripheralsBuilderFactory<'builder>,
@ -1113,7 +1499,7 @@ pub trait Platform: Clone + 'static + Send + Sync + fmt::Debug + Hash + Eq {
crate::module::ModuleKind::Normal,
|m| {
let instance =
instance_with_loc("main", main_module.intern(), SourceLocation::caller());
instance_with_loc("main", main_module.intern(), self.source_location());
let output_expr = Expr::field(instance, &output_module_io.bundle_field().name);
let mut state = peripherals_state.state.lock().expect("shouldn't be poison");
let PeripheralsStateEnum::BuildingWrapperModule(
@ -1143,4 +1529,395 @@ pub trait Platform: Clone + 'static + Send + Sync + fmt::Debug + Hash + Eq {
self.try_wrap_main_module(|p| Ok(make_main_module(p)))
.unwrap_or_else(|e: Infallible| match e {})
}
fn aspects(&self) -> PlatformAspectSet;
}
impl DynPlatform {
pub fn registry() -> PlatformRegistrySnapshot {
PlatformRegistrySnapshot(PlatformRegistry::get())
}
#[track_caller]
pub fn register(self) {
PlatformRegistry::register(PlatformRegistry::lock(), self);
}
}
#[derive(Clone, Debug)]
struct PlatformRegistry {
platforms: BTreeMap<InternedStrCompareAsStr, DynPlatform>,
}
enum PlatformRegisterError {
SameName {
name: InternedStrCompareAsStr,
old_platform: DynPlatform,
new_platform: DynPlatform,
},
}
impl fmt::Display for PlatformRegisterError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::SameName {
name,
old_platform,
new_platform,
} => write!(
f,
"two different `Platform` can't share the same name:\n\
{name:?}\n\
old platform:\n\
{old_platform:?}\n\
new platform:\n\
{new_platform:?}",
),
}
}
}
trait PlatformRegistryRegisterLock {
type Locked;
fn lock(self) -> Self::Locked;
fn make_mut(locked: &mut Self::Locked) -> &mut PlatformRegistry;
}
impl PlatformRegistryRegisterLock for &'static RwLock<Arc<PlatformRegistry>> {
type Locked = RwLockWriteGuard<'static, Arc<PlatformRegistry>>;
fn lock(self) -> Self::Locked {
self.write().expect("shouldn't be poisoned")
}
fn make_mut(locked: &mut Self::Locked) -> &mut PlatformRegistry {
Arc::make_mut(locked)
}
}
impl PlatformRegistryRegisterLock for &'_ mut PlatformRegistry {
type Locked = Self;
fn lock(self) -> Self::Locked {
self
}
fn make_mut(locked: &mut Self::Locked) -> &mut PlatformRegistry {
locked
}
}
impl PlatformRegistry {
fn lock() -> &'static RwLock<Arc<Self>> {
static REGISTRY: OnceLock<RwLock<Arc<PlatformRegistry>>> = OnceLock::new();
REGISTRY.get_or_init(Default::default)
}
fn try_register<L: PlatformRegistryRegisterLock>(
lock: L,
platform: DynPlatform,
) -> Result<(), PlatformRegisterError> {
use std::collections::btree_map::Entry;
let name = InternedStrCompareAsStr(platform.name());
// run user code only outside of lock
let mut locked = lock.lock();
let this = L::make_mut(&mut locked);
let result = match this.platforms.entry(name) {
Entry::Occupied(entry) => Err(PlatformRegisterError::SameName {
name,
old_platform: entry.get().clone(),
new_platform: platform,
}),
Entry::Vacant(entry) => {
entry.insert(platform);
Ok(())
}
};
drop(locked);
// outside of lock now, so we can test if it's the same DynPlatform
match result {
Err(PlatformRegisterError::SameName {
name: _,
old_platform,
new_platform,
}) if old_platform == new_platform => Ok(()),
result => result,
}
}
#[track_caller]
fn register<L: PlatformRegistryRegisterLock>(lock: L, platform: DynPlatform) {
match Self::try_register(lock, platform) {
Err(e) => panic!("{e}"),
Ok(()) => {}
}
}
fn get() -> Arc<Self> {
Self::lock().read().expect("shouldn't be poisoned").clone()
}
}
impl Default for PlatformRegistry {
fn default() -> Self {
let mut retval = Self {
platforms: BTreeMap::new(),
};
for platform in built_in_platforms() {
Self::register(&mut retval, platform);
}
retval
}
}
#[derive(Clone, Debug)]
pub struct PlatformRegistrySnapshot(Arc<PlatformRegistry>);
impl PlatformRegistrySnapshot {
pub fn get() -> Self {
PlatformRegistrySnapshot(PlatformRegistry::get())
}
pub fn get_by_name<'a>(&'a self, name: &str) -> Option<&'a DynPlatform> {
self.0.platforms.get(name)
}
pub fn iter_with_names(&self) -> PlatformRegistryIterWithNames<'_> {
PlatformRegistryIterWithNames(self.0.platforms.iter())
}
pub fn iter(&self) -> PlatformRegistryIter<'_> {
PlatformRegistryIter(self.0.platforms.values())
}
}
impl<'a> IntoIterator for &'a PlatformRegistrySnapshot {
type Item = &'a DynPlatform;
type IntoIter = PlatformRegistryIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl<'a> IntoIterator for &'a mut PlatformRegistrySnapshot {
type Item = &'a DynPlatform;
type IntoIter = PlatformRegistryIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
#[derive(Clone, Debug)]
pub struct PlatformRegistryIter<'a>(
std::collections::btree_map::Values<'a, InternedStrCompareAsStr, DynPlatform>,
);
impl<'a> Iterator for PlatformRegistryIter<'a> {
type Item = &'a DynPlatform;
fn next(&mut self) -> Option<Self::Item> {
self.0.next()
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
fn count(self) -> usize
where
Self: Sized,
{
self.0.count()
}
fn last(self) -> Option<Self::Item> {
self.0.last()
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.0.nth(n)
}
fn fold<B, F>(self, init: B, f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
self.0.fold(init, f)
}
}
impl<'a> std::iter::FusedIterator for PlatformRegistryIter<'a> {}
impl<'a> ExactSizeIterator for PlatformRegistryIter<'a> {}
impl<'a> DoubleEndedIterator for PlatformRegistryIter<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
self.0.next_back()
}
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
self.0.nth_back(n)
}
fn rfold<B, F>(self, init: B, f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
self.0.rfold(init, f)
}
}
#[derive(Clone, Debug)]
pub struct PlatformRegistryIterWithNames<'a>(
std::collections::btree_map::Iter<'a, InternedStrCompareAsStr, DynPlatform>,
);
impl<'a> Iterator for PlatformRegistryIterWithNames<'a> {
type Item = (Interned<str>, &'a DynPlatform);
fn next(&mut self) -> Option<Self::Item> {
self.0.next().map(|(name, platform)| (name.0, platform))
}
fn size_hint(&self) -> (usize, Option<usize>) {
self.0.size_hint()
}
fn count(self) -> usize
where
Self: Sized,
{
self.0.count()
}
fn last(self) -> Option<Self::Item> {
self.0.last().map(|(name, platform)| (name.0, platform))
}
fn nth(&mut self, n: usize) -> Option<Self::Item> {
self.0.nth(n).map(|(name, platform)| (name.0, platform))
}
fn fold<B, F>(self, init: B, f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
self.0
.map(|(name, platform)| (name.0, platform))
.fold(init, f)
}
}
impl<'a> std::iter::FusedIterator for PlatformRegistryIterWithNames<'a> {}
impl<'a> ExactSizeIterator for PlatformRegistryIterWithNames<'a> {}
impl<'a> DoubleEndedIterator for PlatformRegistryIterWithNames<'a> {
fn next_back(&mut self) -> Option<Self::Item> {
self.0
.next_back()
.map(|(name, platform)| (name.0, platform))
}
fn nth_back(&mut self, n: usize) -> Option<Self::Item> {
self.0
.nth_back(n)
.map(|(name, platform)| (name.0, platform))
}
fn rfold<B, F>(self, init: B, f: F) -> B
where
F: FnMut(B, Self::Item) -> B,
{
self.0
.map(|(name, platform)| (name.0, platform))
.rfold(init, f)
}
}
#[track_caller]
pub fn register_platform<K: Platform>(kind: K) {
DynPlatform::new(kind).register();
}
impl Serialize for DynPlatform {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
self.name().serialize(serializer)
}
}
impl<'de> Deserialize<'de> for DynPlatform {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let name = Cow::<str>::deserialize(deserializer)?;
match Self::registry().get_by_name(&name) {
Some(retval) => Ok(retval.clone()),
None => Err(D::Error::custom(format_args!(
"unknown platform: name not found in registry: {name:?}"
))),
}
}
}
#[derive(Copy, Clone, Debug, Default)]
pub struct DynPlatformValueParser;
#[derive(Clone, PartialEq, Eq, Hash)]
struct DynPlatformValueEnum {
name: Interned<str>,
platform: DynPlatform,
}
impl clap::ValueEnum for DynPlatformValueEnum {
fn value_variants<'a>() -> &'a [Self] {
Interned::into_inner(
PlatformRegistrySnapshot::get()
.iter_with_names()
.map(|(name, platform)| Self {
name,
platform: platform.clone(),
})
.collect(),
)
}
fn to_possible_value(&self) -> Option<clap::builder::PossibleValue> {
Some(clap::builder::PossibleValue::new(Interned::into_inner(
self.name,
)))
}
}
impl clap::builder::TypedValueParser for DynPlatformValueParser {
type Value = DynPlatform;
fn parse_ref(
&self,
cmd: &clap::Command,
arg: Option<&clap::Arg>,
value: &std::ffi::OsStr,
) -> clap::error::Result<Self::Value> {
clap::builder::EnumValueParser::<DynPlatformValueEnum>::new()
.parse_ref(cmd, arg, value)
.map(|v| v.platform)
}
fn possible_values(
&self,
) -> Option<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
static ENUM_VALUE_PARSER: OnceLock<clap::builder::EnumValueParser<DynPlatformValueEnum>> =
OnceLock::new();
ENUM_VALUE_PARSER
.get_or_init(clap::builder::EnumValueParser::<DynPlatformValueEnum>::new)
.possible_values()
}
}
impl clap::builder::ValueParserFactory for DynPlatform {
type Parser = DynPlatformValueParser;
fn value_parser() -> Self::Parser {
DynPlatformValueParser::default()
}
}
pub(crate) fn built_in_platforms() -> impl IntoIterator<Item = DynPlatform> {
crate::vendor::built_in_platforms()
}

View file

@ -0,0 +1,62 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{intern::Intern, prelude::*};
use ordered_float::NotNan;
use serde::{Deserialize, Serialize};
#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
#[non_exhaustive]
pub struct ClockInputProperties {
pub frequency: NotNan<f64>,
}
#[hdl(no_runtime_generics, no_static)]
pub struct ClockInput {
pub clk: Clock,
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,
}
#[hdl]
/// UART, used as an output from the FPGA
pub struct Uart {
/// transmit from the FPGA's perspective
pub tx: Bool,
/// receive from the FPGA's perspective
#[hdl(flip)]
pub rx: Bool,
}

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

@ -33,7 +33,6 @@ pub use const_cmp::{
#[doc(inline)]
pub use scoped_ref::ScopedRef;
pub(crate) use misc::chain;
#[doc(inline)]
pub use misc::{
BitSliceWriteWithBase, DebugAsDisplay, DebugAsRawString, MakeMutSlice, RcWriter,
@ -42,6 +41,7 @@ pub use misc::{
os_str_strip_suffix, serialize_to_json_ascii, serialize_to_json_ascii_pretty,
serialize_to_json_ascii_pretty_with_indent, slice_range, try_slice_range,
};
pub(crate) use misc::{InternedStrCompareAsStr, chain};
pub mod job_server;
pub mod prefix_sum;

View file

@ -585,3 +585,30 @@ pub fn os_str_strip_suffix<'a>(os_str: &'a OsStr, suffix: impl AsRef<str>) -> Op
unsafe { OsStr::from_encoded_bytes_unchecked(bytes) }
})
}
#[derive(Copy, Clone, PartialEq, Eq)]
pub(crate) struct InternedStrCompareAsStr(pub(crate) Interned<str>);
impl fmt::Debug for InternedStrCompareAsStr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl Ord for InternedStrCompareAsStr {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
str::cmp(&self.0, &other.0)
}
}
impl PartialOrd for InternedStrCompareAsStr {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl std::borrow::Borrow<str> for InternedStrCompareAsStr {
fn borrow(&self) -> &str {
&self.0
}
}

View file

@ -6,3 +6,7 @@ pub mod xilinx;
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = crate::build::DynJobKind> {
xilinx::built_in_job_kinds()
}
pub(crate) fn built_in_platforms() -> impl IntoIterator<Item = crate::platform::DynPlatform> {
xilinx::built_in_platforms()
}

View file

@ -1,8 +1,19 @@
// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use crate::{annotations::make_annotation_enum, intern::Interned};
use crate::{
annotations::make_annotation_enum,
build::{GlobalParams, ToArgs, WriteArgs},
intern::Interned,
prelude::{DynPlatform, Platform},
};
use clap::ValueEnum;
use ordered_float::NotNan;
use serde::{Deserialize, Serialize};
use std::fmt;
pub mod arty_a7;
pub mod primitives;
pub mod yosys_nextpnr_prjxray;
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
@ -15,14 +26,182 @@ pub struct XdcLocationAnnotation {
pub location: Interned<str>,
}
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
pub struct XdcCreateClockAnnotation {
/// clock period in nanoseconds
pub period: NotNan<f64>,
}
make_annotation_enum! {
#[non_exhaustive]
pub enum XilinxAnnotation {
XdcIOStandard(XdcIOStandardAnnotation),
XdcLocation(XdcLocationAnnotation),
XdcCreateClock(XdcCreateClockAnnotation),
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)]
pub struct XilinxArgs {
#[arg(long)]
pub device: Option<Device>,
}
impl XilinxArgs {
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 XilinxArgs {
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
if let Some(device) = self.device {
args.write_long_option_eq("device", device.as_str());
}
}
}
macro_rules! make_device_enum {
($vis:vis enum $Device:ident {
$(
#[
name = $name:literal,
xray_part = $xray_part:literal,
xray_device = $xray_device:literal,
xray_family = $xray_family:literal,
]
$variant:ident,
)*
}) => {
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, ValueEnum)]
$vis enum $Device {
$(
#[value(name = $name, alias = $xray_part)]
$variant,
)*
}
impl $Device {
$vis fn as_str(self) -> &'static str {
match self {
$(Self::$variant => $name,)*
}
}
$vis fn xray_part(self) -> &'static str {
match self {
$(Self::$variant => $xray_part,)*
}
}
$vis fn xray_device(self) -> &'static str {
match self {
$(Self::$variant => $xray_device,)*
}
}
$vis fn xray_family(self) -> &'static str {
match self {
$(Self::$variant => $xray_family,)*
}
}
}
struct DeviceVisitor;
impl<'de> serde::de::Visitor<'de> for DeviceVisitor {
type Value = $Device;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a Xilinx device string")
}
fn visit_str<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)
}
}
};
}
make_device_enum! {
pub enum Device {
#[
name = "xc7a35ticsg324-1L",
xray_part = "xc7a35tcsg324-1",
xray_device = "xc7a35t",
xray_family = "artix7",
]
Xc7a35ticsg324_1l,
#[
name = "xc7a100ticsg324-1L",
xray_part = "xc7a100tcsg324-1",
xray_device = "xc7a100t",
xray_family = "artix7",
]
Xc7a100ticsg324_1l,
}
}
impl fmt::Display for Device {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = crate::build::DynJobKind> {
yosys_nextpnr_prjxray::built_in_job_kinds()
arty_a7::built_in_job_kinds()
.into_iter()
.chain(yosys_nextpnr_prjxray::built_in_job_kinds())
}
pub(crate) fn built_in_platforms() -> impl IntoIterator<Item = crate::platform::DynPlatform> {
arty_a7::built_in_platforms()
.into_iter()
.chain(yosys_nextpnr_prjxray::built_in_platforms())
}

View file

@ -0,0 +1,404 @@
// 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))
}

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

View file

@ -2,28 +2,36 @@
// See Notices.txt for copyright information
use crate::{
annotations::Annotation,
annotations::{Annotation, TargetedAnnotation},
build::{
BaseJob, CommandParams, DynJobKind, GetJobPositionDependencies, JobAndDependencies,
JobArgsAndDependencies, JobDependencies, JobItem, JobItemName, JobKind,
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,
bundle::{Bundle, BundleType},
expr::target::{Target, TargetBase},
firrtl::{ScalarizedModuleABI, ScalarizedModuleABIAnnotations, ScalarizedModuleABIPort},
intern::{Intern, InternSlice, Interned},
module::{Module, NameId},
prelude::JobParams,
util::job_server::AcquiredJob,
vendor::xilinx::{XdcIOStandardAnnotation, XdcLocationAnnotation, XilinxAnnotation},
module::{
NameId, ScopedNameId, TargetName,
transform::visit::{Visit, Visitor},
},
prelude::*,
source_location::SourceLocation,
util::{HashSet, job_server::AcquiredJob},
vendor::xilinx::{
Device, XdcCreateClockAnnotation, XdcIOStandardAnnotation, XdcLocationAnnotation,
XilinxAnnotation, XilinxArgs,
},
};
use clap::ValueEnum;
use eyre::Context;
use serde::{Deserialize, Serialize};
use std::{
convert::Infallible,
ffi::{OsStr, OsString},
fmt::{self, Write},
ops::ControlFlow,
@ -105,6 +113,7 @@ impl JobKind for YosysNextpnrXrayWriteYsFileJobKind {
fn args_to_jobs(
mut args: JobArgsAndDependencies<Self>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<JobAndDependencies<Self>> {
args.dependencies
.dependencies
@ -113,7 +122,7 @@ impl JobKind for YosysNextpnrXrayWriteYsFileJobKind {
.additional_args
.verilog_dialect
.get_or_insert(VerilogDialect::Yosys);
args.args_to_jobs_simple(params, |_kind, args, dependencies| {
args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| {
let YosysNextpnrXrayWriteYsFileArgs {} = args;
let base_job = dependencies.get_job::<BaseJob, _>();
let verilog_job = dependencies.get_job::<VerilogJob, _>();
@ -153,6 +162,7 @@ impl JobKind for YosysNextpnrXrayWriteYsFileJobKind {
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)));
@ -260,11 +270,12 @@ impl ExternalCommand for YosysNextpnrXraySynth {
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, |args, dependencies| {
args.args_to_jobs_external_simple(params, global_params, |args, dependencies| {
let YosysNextpnrXraySynthArgs {} = args.additional_args;
Ok(Self {
write_ys_file: dependencies.job.job.clone(),
@ -350,6 +361,9 @@ impl From<fmt::Error> for WriteXdcContentsError {
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() {
@ -362,6 +376,228 @@ fn tcl_escape(s: impl AsRef<str>) -> String {
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 XdcFileWriter<W: fmt::Write> {
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> XdcFileWriter<W> {
fn run(output: W, top_module: Module<Bundle>) -> Result<(), WriteXdcContentsError> {
let mut this = Self {
output,
module_depth: 0,
annotation_target: AnnotationTarget::None,
dont_touch_targets: HashSet::default(),
required_dont_touch_targets: HashSet::default(),
};
top_module.visit(&mut this)?;
let Self {
output: _,
module_depth: _,
annotation_target: _,
dont_touch_targets,
required_dont_touch_targets,
} = this;
for &target in required_dont_touch_targets.difference(&dont_touch_targets) {
return Err(eyre::eyre!(
"a DontTouchAnnotation is required since the target is also annotated with a XilinxAnnotation:\ntarget: {target:?}\nat: {}",
target.base().source_location(),
).into());
}
Ok(())
}
fn default_visit_with<T: ?Sized + Visit<Self>>(
&mut self,
module_depth: usize,
annotation_target: AnnotationTarget,
v: &T,
) -> Result<(), WriteXdcContentsError> {
let Self {
output: _,
module_depth: old_module_depth,
annotation_target: old_annotation_target,
dont_touch_targets: _,
required_dont_touch_targets: _,
} = *self;
self.module_depth = module_depth;
self.annotation_target = annotation_target;
let retval = v.default_visit(self);
self.module_depth = old_module_depth;
self.annotation_target = old_annotation_target;
retval
}
}
impl<W: fmt::Write> Visitor for XdcFileWriter<W> {
type Error = WriteXdcContentsError;
fn visit_targeted_annotation(&mut self, v: &TargetedAnnotation) -> Result<(), Self::Error> {
self.default_visit_with(self.module_depth, AnnotationTarget::Target(v.target()), v)
}
fn visit_module<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(())
}
fn visit_xilinx_annotation(&mut self, v: &XilinxAnnotation) -> Result<(), Self::Error> {
fn todo(
msg: &str,
annotation: &XilinxAnnotation,
source_location: SourceLocation,
) -> Result<Infallible, WriteXdcContentsError> {
Err(WriteXdcContentsError(eyre::eyre!(
"{msg}\nannotation: {annotation:?}\nat: {source_location}"
)))
}
if self.module_depth != 1 {
match todo(
"annotations are not yet supported outside of the top module since the logic to figure out the correct name isn't implemented",
v,
self.annotation_target.source_location(),
)? {}
}
match self.annotation_target {
AnnotationTarget::None => unreachable!(),
AnnotationTarget::Module(module) => match v {
XilinxAnnotation::XdcIOStandard(_)
| XilinxAnnotation::XdcLocation(_)
| XilinxAnnotation::XdcCreateClock(_) => {
return Err(WriteXdcContentsError(eyre::eyre!(
"annotation not allowed on a module: {v:?}\nat: {}",
module.source_location(),
)));
}
},
AnnotationTarget::Mem(mem) => match todo(
"xilinx annotations are not yet supported on memories since the logic to figure out the correct name isn't implemented",
v,
mem.source_location(),
)? {},
AnnotationTarget::Target(target) => {
let base = target.base();
match *base {
TargetBase::ModuleIO(_) => {
// already handled by write_xdc_contents handling the main module's ScalarizedModuleABI
Ok(())
}
TargetBase::MemPort(mem_port) => {
match todo(
"xilinx annotations are not yet supported on memory ports since the logic to figure out the correct name isn't implemented",
v,
mem_port.source_location(),
)? {}
}
TargetBase::Reg(_)
| TargetBase::RegSync(_)
| TargetBase::RegAsync(_)
| TargetBase::Wire(_) => {
match *target {
Target::Base(_) => {}
Target::Child(_) => match todo(
"xilinx annotations are not yet supported on parts of registers/wires since the logic to figure out the correct name isn't implemented",
v,
base.source_location(),
)? {},
}
match base.canonical_ty() {
CanonicalType::UInt(_)
| CanonicalType::SInt(_)
| CanonicalType::Bool(_)
| CanonicalType::AsyncReset(_)
| CanonicalType::SyncReset(_)
| CanonicalType::Reset(_)
| CanonicalType::Clock(_) => {}
CanonicalType::Enum(_)
| CanonicalType::Array(_)
| CanonicalType::Bundle(_)
| CanonicalType::PhantomConst(_)
| CanonicalType::DynSimOnly(_) => match todo(
"xilinx annotations are not yet supported on types other than integers, Bool, resets, or Clock since the logic to figure out the correct name isn't implemented",
v,
base.source_location(),
)? {},
}
self.required_dont_touch_targets.insert(target);
match v {
XilinxAnnotation::XdcIOStandard(_)
| XilinxAnnotation::XdcLocation(_) => {
return Err(WriteXdcContentsError(eyre::eyre!(
"annotation must be on a ModuleIO: {v:?}\nat: {}",
base.source_location(),
)));
}
XilinxAnnotation::XdcCreateClock(XdcCreateClockAnnotation {
period,
}) => {
let TargetName(ScopedNameId(_, NameId(name, _)), _) =
base.target_name();
writeln!(
self.output,
"create_clock -period {period} [get_nets {}]",
tcl_escape(name),
)?;
Ok(())
}
}
}
TargetBase::Instance(instance) => match todo(
"xilinx annotations are not yet supported on instances' IO since the logic to figure out the correct name isn't implemented",
v,
instance.source_location(),
)? {},
}
}
}
}
}
impl YosysNextpnrXrayWriteXdcFile {
fn write_xdc_contents_for_port_and_annotations(
&self,
@ -383,7 +619,7 @@ impl YosysNextpnrXrayWriteXdcFile {
output,
"set_property LOC {} [get_ports {}]",
tcl_escape(location),
tcl_escape(port.scalarized_name())
tcl_escape(port.scalarized_name()),
)?,
Annotation::Xilinx(XilinxAnnotation::XdcIOStandard(XdcIOStandardAnnotation {
value,
@ -391,7 +627,14 @@ impl YosysNextpnrXrayWriteXdcFile {
output,
"set_property IOSTANDARD {} [get_ports {}]",
tcl_escape(value),
tcl_escape(port.scalarized_name())
tcl_escape(port.scalarized_name()),
)?,
Annotation::Xilinx(XilinxAnnotation::XdcCreateClock(
XdcCreateClockAnnotation { period },
)) => writeln!(
output,
"create_clock -period {period} [get_ports {}]",
tcl_escape(port.scalarized_name()),
)?,
}
}
@ -411,9 +654,10 @@ impl YosysNextpnrXrayWriteXdcFile {
Err(e) => ControlFlow::Break(e),
}
}) {
ControlFlow::Continue(()) => Ok(()),
ControlFlow::Break(e) => Err(e.0),
ControlFlow::Continue(()) => {}
ControlFlow::Break(e) => return Err(e.0),
}
XdcFileWriter::run(output, *top_module).map_err(|e| e.0)
}
}
@ -429,6 +673,7 @@ impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind {
fn args_to_jobs(
args: JobArgsAndDependencies<Self>,
params: &JobParams,
global_params: &GlobalParams,
) -> eyre::Result<JobAndDependencies<Self>> {
let firrtl_export_options = args
.dependencies
@ -439,7 +684,7 @@ impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind {
.args
.args
.export_options;
args.args_to_jobs_simple(params, |_kind, args, dependencies| {
args.args_to_jobs_simple(params, global_params, |_kind, args, dependencies| {
let YosysNextpnrXrayWriteXdcFileArgs {} = args;
let base_job = dependencies.get_job::<BaseJob, _>();
Ok(YosysNextpnrXrayWriteXdcFile {
@ -474,23 +719,13 @@ impl JobKind for YosysNextpnrXrayWriteXdcFileJobKind {
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 xdc = String::new();
job.write_xdc_contents(&mut xdc, params.main_module())?;
// TODO: create actual .xdc from input module
std::fs::write(
job.xdc_file,
r"# autogenerated
set_property LOC G6 [get_ports led]
set_property IOSTANDARD LVCMOS33 [get_ports led]
set_property LOC E3 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
set_property LOC C2 [get_ports rst]
set_property IOSTANDARD LVCMOS33 [get_ports rst]
",
)?;
std::fs::write(job.xdc_file, xdc)?;
Ok(vec![JobItem::Path { path: job.xdc_file }])
}
@ -508,130 +743,12 @@ impl ExternalProgramTrait for NextpnrXilinx {
}
}
macro_rules! make_device_enum {
($vis:vis enum $Device:ident {
$(
#[
name = $name:literal,
xray_part = $xray_part:literal,
xray_device = $xray_device:literal,
xray_family = $xray_family:literal,
]
$variant:ident,
)*
}) => {
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, ValueEnum)]
$vis enum $Device {
$(
#[value(name = $name, alias = $xray_part)]
$variant,
)*
}
impl $Device {
$vis fn as_str(self) -> &'static str {
match self {
$(Self::$variant => $name,)*
}
}
$vis fn xray_part(self) -> &'static str {
match self {
$(Self::$variant => $xray_part,)*
}
}
$vis fn xray_device(self) -> &'static str {
match self {
$(Self::$variant => $xray_device,)*
}
}
$vis fn xray_family(self) -> &'static str {
match self {
$(Self::$variant => $xray_family,)*
}
}
}
struct DeviceVisitor;
impl<'de> serde::de::Visitor<'de> for DeviceVisitor {
type Value = $Device;
fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("a Xilinx device string")
}
fn visit_str<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)
}
}
};
}
make_device_enum! {
pub enum Device {
#[
name = "xc7a35ticsg324-1L",
xray_part = "xc7a35tcsg324-1",
xray_device = "xc7a35t",
xray_family = "artix7",
]
Xc7a35ticsg324_1l,
#[
name = "xc7a100ticsg324-1L",
xray_part = "xc7a100tcsg324-1",
xray_device = "xc7a100t",
xray_family = "artix7",
]
Xc7a100ticsg324_1l,
}
}
impl fmt::Display for Device {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.as_str())
}
}
#[derive(Clone, PartialEq, Eq, Hash, Debug, clap::Args)]
pub struct YosysNextpnrXrayRunNextpnrArgs {
#[command(flatten)]
pub common: XilinxArgs,
#[arg(long, env = "CHIPDB_DIR", value_hint = clap::ValueHint::DirPath)]
pub nextpnr_xilinx_chipdb_dir: PathBuf,
#[arg(long)]
pub device: Device,
#[arg(long, default_value_t = 0)]
pub nextpnr_xilinx_seed: i32,
}
@ -639,12 +756,12 @@ pub struct YosysNextpnrXrayRunNextpnrArgs {
impl ToArgs for YosysNextpnrXrayRunNextpnrArgs {
fn to_args(&self, args: &mut (impl WriteArgs + ?Sized)) {
let Self {
common,
nextpnr_xilinx_chipdb_dir,
device,
nextpnr_xilinx_seed,
} = self;
common.to_args(args);
args.write_long_option_eq("nextpnr-xilinx-chipdb-dir", nextpnr_xilinx_chipdb_dir);
args.write_long_option_eq("device", device.as_str());
args.write_display_arg(format_args!("--nextpnr-xilinx-seed={nextpnr_xilinx_seed}"));
}
}
@ -690,14 +807,15 @@ impl ExternalCommand for YosysNextpnrXrayRunNextpnr {
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, |args, dependencies| {
args.args_to_jobs_external_simple(params, global_params, |args, dependencies| {
let YosysNextpnrXrayRunNextpnrArgs {
common,
nextpnr_xilinx_chipdb_dir,
device,
nextpnr_xilinx_seed,
} = args.additional_args;
let base_job = dependencies.get_job::<BaseJob, _>();
@ -707,7 +825,7 @@ impl ExternalCommand for YosysNextpnrXrayRunNextpnr {
let fasm_file = base_job.file_with_ext("fasm");
Ok(Self {
nextpnr_xilinx_chipdb_dir: nextpnr_xilinx_chipdb_dir.intern_deref(),
device,
device: common.require_device(base_job.platform(), global_params)?,
nextpnr_xilinx_seed,
xdc_file: write_xdc_file.xdc_file,
xdc_file_name: write_xdc_file
@ -842,11 +960,12 @@ impl ExternalCommand for YosysNextpnrXray {
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, |args, dependencies| {
args.args_to_jobs_external_simple(params, global_params, |args, dependencies| {
let YosysNextpnrXrayArgs { prjxray_db_dir } = args.additional_args;
let base_job = dependencies.get_job::<BaseJob, _>();
let frames_file = base_job.file_with_ext("frames");
@ -918,3 +1037,7 @@ pub(crate) fn built_in_job_kinds() -> impl IntoIterator<Item = DynJobKind> {
DynJobKind::new(ExternalCommandJobKind::<YosysNextpnrXray>::new()),
]
}
pub(crate) fn built_in_platforms() -> impl IntoIterator<Item = crate::platform::DynPlatform> {
[]
}

View file

@ -1219,7 +1219,8 @@
"data": {
"$kind": "Enum",
"XdcLocation": "Visible",
"XdcIOStandard": "Visible"
"XdcIOStandard": "Visible",
"XdcCreateClock": "Visible"
}
},
"XdcLocationAnnotation": {
@ -1232,6 +1233,11 @@
"$kind": "Opaque"
}
},
"XdcCreateClockAnnotation": {
"data": {
"$kind": "Opaque"
}
},
"Target": {
"data": {
"$kind": "Enum",