forked from libre-chip/fayalite
Compare commits
10 commits
c6feea6d51
...
edcc5927a5
| Author | SHA1 | Date | |
|---|---|---|---|
| edcc5927a5 | |||
| 7dc4417874 | |||
| 838bd469ce | |||
| b6e4cd0614 | |||
| 3e5b2f126a | |||
| 040cefea21 | |||
| 3267cb38c4 | |||
| b3cc28e2b6 | |||
| 26840daf13 | |||
| 4d9e8d3b47 |
17 changed files with 11155 additions and 93 deletions
|
|
@ -22,3 +22,4 @@ jobs:
|
|||
- 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 --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
|
||||
|
|
|
|||
67
README.md
67
README.md
|
|
@ -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
|
||||
|
|
|
|||
188
crates/fayalite/examples/tx_only_uart.rs
Normal file
188
crates/fayalite/examples/tx_only_uart.rs
Normal 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))
|
||||
})?))
|
||||
},
|
||||
);
|
||||
}
|
||||
|
|
@ -12,7 +12,7 @@ use crate::{
|
|||
};
|
||||
use base64::{Engine, prelude::BASE64_URL_SAFE_NO_PAD};
|
||||
use clap::builder::OsStringValueParser;
|
||||
use eyre::{Context, bail, ensure, eyre};
|
||||
use eyre::{Context, ensure, eyre};
|
||||
use serde::{
|
||||
Deserialize, Deserializer, Serialize, Serializer,
|
||||
de::{DeserializeOwned, Error},
|
||||
|
|
@ -26,6 +26,7 @@ use std::{
|
|||
io::Write,
|
||||
marker::PhantomData,
|
||||
path::{Path, PathBuf},
|
||||
process::ExitStatus,
|
||||
sync::OnceLock,
|
||||
};
|
||||
|
||||
|
|
@ -365,13 +366,17 @@ impl ExternalJobCaching {
|
|||
.stdin(std::process::Stdio::null());
|
||||
Ok(cmd)
|
||||
}
|
||||
pub fn run<F: FnOnce(std::process::Command) -> eyre::Result<()>>(
|
||||
pub fn run<F>(
|
||||
self,
|
||||
command_line: Interned<[Interned<OsStr>]>,
|
||||
input_file_paths: impl IntoIterator<Item = Interned<Path>>,
|
||||
output_file_paths: impl IntoIterator<Item = Interned<Path>> + Clone,
|
||||
run_fn: F,
|
||||
) -> eyre::Result<()> {
|
||||
exit_status_to_error: impl FnOnce(ExitStatus) -> eyre::Report,
|
||||
) -> eyre::Result<()>
|
||||
where
|
||||
F: FnOnce(std::process::Command) -> eyre::Result<Result<(), ExitStatus>>,
|
||||
{
|
||||
let mut hasher = JobCacheHasher::default();
|
||||
hasher.hash_iter(command_line.iter(), |hasher, arg| {
|
||||
hasher.hash_sized_os_str(arg)
|
||||
|
|
@ -419,7 +424,26 @@ impl ExternalJobCaching {
|
|||
})
|
||||
.expect("spawn shouldn't fail");
|
||||
run_fn(cmd)
|
||||
});
|
||||
})?;
|
||||
if let Err(exit_status) = result {
|
||||
// check if the user may have terminated it or something, don't cache the failure
|
||||
let user_maybe_terminated;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
user_maybe_terminated = std::os::unix::process::ExitStatusExt::signal(&exit_status)
|
||||
.is_some()
|
||||
|| exit_status.code().is_none_or(|code| code > 1);
|
||||
}
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
user_maybe_terminated = !exit_status.success();
|
||||
}
|
||||
if user_maybe_terminated {
|
||||
let _ = std::fs::remove_file(self.cache_json_path);
|
||||
return Err(exit_status_to_error(exit_status));
|
||||
}
|
||||
}
|
||||
let result = result.map_err(exit_status_to_error);
|
||||
ExternalJobCacheV2 {
|
||||
version: ExternalJobCacheVersion::CURRENT,
|
||||
inputs_hash,
|
||||
|
|
@ -444,16 +468,26 @@ impl ExternalJobCaching {
|
|||
.write_to_file(self.cache_json_path)?;
|
||||
result
|
||||
}
|
||||
pub fn run_maybe_cached<F: FnOnce(std::process::Command) -> eyre::Result<()>>(
|
||||
pub fn run_maybe_cached<F>(
|
||||
this: Option<Self>,
|
||||
command_line: Interned<[Interned<OsStr>]>,
|
||||
input_file_paths: impl IntoIterator<Item = Interned<Path>>,
|
||||
output_file_paths: impl IntoIterator<Item = Interned<Path>> + Clone,
|
||||
run_fn: F,
|
||||
) -> eyre::Result<()> {
|
||||
exit_status_to_error: impl FnOnce(ExitStatus) -> eyre::Report,
|
||||
) -> eyre::Result<()>
|
||||
where
|
||||
F: FnOnce(std::process::Command) -> eyre::Result<Result<(), ExitStatus>>,
|
||||
{
|
||||
match this {
|
||||
Some(this) => this.run(command_line, input_file_paths, output_file_paths, run_fn),
|
||||
None => run_fn(Self::make_command(command_line)?),
|
||||
Some(this) => this.run(
|
||||
command_line,
|
||||
input_file_paths,
|
||||
output_file_paths,
|
||||
run_fn,
|
||||
exit_status_to_error,
|
||||
),
|
||||
None => run_fn(Self::make_command(command_line)?)?.map_err(exit_status_to_error),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1119,10 +1153,12 @@ impl<T: ExternalCommand> JobKind for ExternalCommandJobKind<T> {
|
|||
}
|
||||
let status = acquired_job.run_command(cmd, |cmd| cmd.status())?;
|
||||
if !status.success() {
|
||||
bail!("running {command_line:?} failed: {status}")
|
||||
Ok(Err(status))
|
||||
} else {
|
||||
Ok(Ok(()))
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
|status| eyre!("running {command_line:?} failed: {status}"),
|
||||
)?;
|
||||
Ok(job
|
||||
.output_paths()
|
||||
|
|
|
|||
|
|
@ -13,9 +13,10 @@ use crate::{
|
|||
},
|
||||
intern::{Intern, InternSlice, Interned},
|
||||
module::NameId,
|
||||
testing::FormalMode,
|
||||
util::job_server::AcquiredJob,
|
||||
};
|
||||
use clap::{Args, ValueEnum};
|
||||
use clap::Args;
|
||||
use eyre::Context;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
|
|
@ -24,33 +25,6 @@ use std::{
|
|||
path::Path,
|
||||
};
|
||||
|
||||
#[derive(ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Default, Deserialize, Serialize)]
|
||||
#[non_exhaustive]
|
||||
pub enum FormalMode {
|
||||
#[default]
|
||||
BMC,
|
||||
Prove,
|
||||
Live,
|
||||
Cover,
|
||||
}
|
||||
|
||||
impl FormalMode {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
FormalMode::BMC => "bmc",
|
||||
FormalMode::Prove => "prove",
|
||||
FormalMode::Live => "live",
|
||||
FormalMode::Cover => "cover",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FormalMode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Args, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
#[non_exhaustive]
|
||||
pub struct FormalArgs {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -50,3 +50,13 @@ pub struct RgbLed {
|
|||
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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ pub use crate::{
|
|||
value::{SimOnly, SimOnlyValue, SimValue, ToSimValue, ToSimValueWithType},
|
||||
},
|
||||
source_location::SourceLocation,
|
||||
testing::assert_formal,
|
||||
testing::{FormalMode, assert_formal},
|
||||
ty::{AsMask, CanonicalType, Type},
|
||||
util::{ConstUsize, GenericConstUsize},
|
||||
wire::Wire,
|
||||
|
|
|
|||
|
|
@ -1522,7 +1522,7 @@ struct SimulationImpl {
|
|||
state_ready_to_run: bool,
|
||||
trace_decls: TraceModule,
|
||||
traces: SimTraces<Box<[SimTrace<SimTraceKind, SimTraceState>]>>,
|
||||
trace_memories: HashMap<StatePartIndex<StatePartKindMemories>, TraceMem>,
|
||||
trace_memories: BTreeMap<StatePartIndex<StatePartKindMemories>, TraceMem>,
|
||||
trace_writers: Vec<TraceWriterState<DynTraceWriterDecls>>,
|
||||
instant: SimInstant,
|
||||
clocks_triggered: Interned<[StatePartIndex<StatePartKindSmallSlots>]>,
|
||||
|
|
@ -1622,7 +1622,7 @@ impl SimulationImpl {
|
|||
last_state: kind.make_state(),
|
||||
},
|
||||
))),
|
||||
trace_memories: HashMap::from_iter(compiled.trace_memories.iter().copied()),
|
||||
trace_memories: BTreeMap::from_iter(compiled.trace_memories.iter().copied()),
|
||||
trace_writers: vec![],
|
||||
instant: SimInstant::START,
|
||||
clocks_triggered: compiled.clocks_triggered,
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ use crate::{
|
|||
NoArgs, RunBuild,
|
||||
external::{ExternalCommandArgs, ExternalCommandJobKind},
|
||||
firrtl::{FirrtlArgs, FirrtlJobKind},
|
||||
formal::{Formal, FormalAdditionalArgs, FormalArgs, FormalMode, WriteSbyFileJobKind},
|
||||
formal::{Formal, FormalAdditionalArgs, FormalArgs, WriteSbyFileJobKind},
|
||||
verilog::{UnadjustedVerilogArgs, VerilogJobArgs, VerilogJobKind},
|
||||
},
|
||||
bundle::BundleType,
|
||||
|
|
@ -14,14 +14,43 @@ use crate::{
|
|||
module::Module,
|
||||
util::HashMap,
|
||||
};
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{
|
||||
fmt::Write,
|
||||
fmt::{self, Write},
|
||||
path::{Path, PathBuf},
|
||||
process::Command,
|
||||
sync::{Mutex, OnceLock},
|
||||
};
|
||||
|
||||
#[derive(
|
||||
clap::ValueEnum, Copy, Clone, Debug, PartialEq, Eq, Hash, Default, Deserialize, Serialize,
|
||||
)]
|
||||
#[non_exhaustive]
|
||||
pub enum FormalMode {
|
||||
#[default]
|
||||
BMC,
|
||||
Prove,
|
||||
Live,
|
||||
Cover,
|
||||
}
|
||||
|
||||
impl FormalMode {
|
||||
pub fn as_str(self) -> &'static str {
|
||||
match self {
|
||||
FormalMode::BMC => "bmc",
|
||||
FormalMode::Prove => "prove",
|
||||
FormalMode::Live => "live",
|
||||
FormalMode::Cover => "cover",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for FormalMode {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str(self.as_str())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct CargoMetadata {
|
||||
target_directory: String,
|
||||
|
|
|
|||
|
|
@ -212,9 +212,7 @@ pub fn queue<T: Type>(
|
|||
mod tests {
|
||||
use super::*;
|
||||
use crate::{
|
||||
build::formal::FormalMode, firrtl::ExportOptions,
|
||||
module::transform::simplify_enums::SimplifyEnumsKind, testing::assert_formal,
|
||||
ty::StaticType,
|
||||
firrtl::ExportOptions, module::transform::simplify_enums::SimplifyEnumsKind, ty::StaticType,
|
||||
};
|
||||
use std::num::NonZero;
|
||||
|
||||
|
|
|
|||
122
crates/fayalite/src/vendor/xilinx/arty_a7.rs
vendored
122
crates/fayalite/src/vendor/xilinx/arty_a7.rs
vendored
|
|
@ -3,16 +3,16 @@
|
|||
|
||||
use crate::{
|
||||
intern::{Intern, Interned},
|
||||
module::{instance_with_loc, wire_with_loc},
|
||||
module::{instance_with_loc, reg_builder_with_loc, wire_with_loc},
|
||||
platform::{
|
||||
DynPlatform, Peripheral, PeripheralRef, Peripherals, PeripheralsBuilderFactory,
|
||||
PeripheralsBuilderFinished, Platform, PlatformAspectSet,
|
||||
peripherals::{ClockInput, Led, RgbLed},
|
||||
peripherals::{ClockInput, Led, RgbLed, Uart},
|
||||
},
|
||||
prelude::*,
|
||||
vendor::xilinx::{
|
||||
Device, XdcCreateClockAnnotation, XdcIOStandardAnnotation, XdcLocationAnnotation,
|
||||
primitives::{self, BUFGCE, STARTUPE2_default_inputs},
|
||||
primitives,
|
||||
},
|
||||
};
|
||||
use ordered_float::NotNan;
|
||||
|
|
@ -66,7 +66,7 @@ arty_a7_platform! {
|
|||
|
||||
#[derive(Debug)]
|
||||
pub struct ArtyA7Peripherals {
|
||||
clk100: Peripheral<ClockInput>,
|
||||
clk100_div_pow2: [Peripheral<ClockInput>; 4],
|
||||
rst: Peripheral<Reset>,
|
||||
rst_sync: Peripheral<SyncReset>,
|
||||
ld0: Peripheral<RgbLed>,
|
||||
|
|
@ -77,13 +77,14 @@ pub struct ArtyA7Peripherals {
|
|||
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,
|
||||
clk100_div_pow2,
|
||||
rst,
|
||||
rst_sync,
|
||||
ld0,
|
||||
|
|
@ -94,8 +95,9 @@ impl Peripherals for ArtyA7Peripherals {
|
|||
ld5,
|
||||
ld6,
|
||||
ld7,
|
||||
uart,
|
||||
} = self;
|
||||
clk100.append_peripherals(peripherals);
|
||||
clk100_div_pow2.append_peripherals(peripherals);
|
||||
rst.append_peripherals(peripherals);
|
||||
rst_sync.append_peripherals(peripherals);
|
||||
ld0.append_peripherals(peripherals);
|
||||
|
|
@ -106,6 +108,7 @@ impl Peripherals for ArtyA7Peripherals {
|
|||
ld5.append_peripherals(peripherals);
|
||||
ld6.append_peripherals(peripherals);
|
||||
ld7.append_peripherals(peripherals);
|
||||
uart.append_peripherals(peripherals);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -168,9 +171,20 @@ impl Platform for ArtyA7Platform {
|
|||
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: builder.input_peripheral("clk100", ClockInput::new(100e6)),
|
||||
clk100_div_pow2,
|
||||
rst: builder.input_peripheral("rst", Reset),
|
||||
rst_sync: builder.input_peripheral("rst_sync", SyncReset),
|
||||
ld0: builder.output_peripheral("ld0", RgbLed),
|
||||
|
|
@ -181,6 +195,7 @@ impl Platform for ArtyA7Platform {
|
|||
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(),
|
||||
)
|
||||
|
|
@ -192,7 +207,7 @@ impl Platform for ArtyA7Platform {
|
|||
|
||||
fn add_peripherals_in_wrapper_module(&self, m: &ModuleBuilder, peripherals: Self::Peripherals) {
|
||||
let ArtyA7Peripherals {
|
||||
clk100,
|
||||
clk100_div_pow2,
|
||||
rst,
|
||||
rst_sync,
|
||||
ld0,
|
||||
|
|
@ -203,6 +218,7 @@ impl Platform for ArtyA7Platform {
|
|||
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);
|
||||
|
|
@ -249,30 +265,82 @@ impl Platform for ArtyA7Platform {
|
|||
connect(buf.T, false);
|
||||
buf.I
|
||||
};
|
||||
let clock_annotation = XdcCreateClockAnnotation {
|
||||
period: NotNan::new(1e9 / clk100.ty().frequency()).expect("known to be valid"),
|
||||
};
|
||||
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",
|
||||
STARTUPE2_default_inputs(),
|
||||
primitives::STARTUPE2_default_inputs(),
|
||||
SourceLocation::builtin(),
|
||||
);
|
||||
let clk100_sync = instance_with_loc("clk100_sync", BUFGCE(), SourceLocation::builtin());
|
||||
connect(clk100_sync.CE, startup.EOS);
|
||||
connect(clk100_sync.I, clk100_buf);
|
||||
let clk100_out = wire_with_loc("clk100_out", SourceLocation::builtin(), Clock);
|
||||
connect(clk100_out, clk100_sync.O);
|
||||
annotate(clk100_out, clock_annotation);
|
||||
annotate(clk100_out, DontTouchAnnotation);
|
||||
if let Some(clk100) = clk100.into_used() {
|
||||
connect(clk100.instance_io_field().clk, clk100_out);
|
||||
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, clk100_sync.O);
|
||||
connect(rst_sync.inp, rst_buf);
|
||||
connect(rst_sync.clk, clk_out);
|
||||
connect(rst_sync.inp, rst_buf | !startup.EOS);
|
||||
rst_sync.out
|
||||
};
|
||||
if let Some(rst) = rst.into_used() {
|
||||
|
|
@ -310,6 +378,14 @@ impl Platform for ArtyA7Platform {
|
|||
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 {
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ pub fn BUFGCE() {
|
|||
#[hdl]
|
||||
let CE: Bool = m.input();
|
||||
#[hdl]
|
||||
let I: Bool = m.input();
|
||||
let I: Clock = m.input();
|
||||
}
|
||||
|
||||
#[hdl_module(extern)]
|
||||
|
|
|
|||
|
|
@ -2,19 +2,7 @@
|
|||
// See Notices.txt for copyright information
|
||||
//! Formal tests in Fayalite
|
||||
|
||||
use fayalite::{
|
||||
build::formal::FormalMode,
|
||||
clock::{Clock, ClockDomain},
|
||||
expr::{CastTo, HdlPartialEq},
|
||||
firrtl::ExportOptions,
|
||||
formal::{any_const, any_seq, formal_reset, hdl_assert, hdl_assume},
|
||||
hdl, hdl_module,
|
||||
int::{Bool, DynSize, Size, UInt, UIntType},
|
||||
module::{connect, connect_any, instance, memory, reg_builder, wire},
|
||||
reset::ToReset,
|
||||
testing::assert_formal,
|
||||
ty::StaticType,
|
||||
};
|
||||
use fayalite::prelude::*;
|
||||
|
||||
/// Test hidden state
|
||||
///
|
||||
|
|
@ -119,7 +107,7 @@ mod hidden_state {
|
|||
FormalMode::Prove,
|
||||
16,
|
||||
None,
|
||||
ExportOptions::default(),
|
||||
Default::default(),
|
||||
);
|
||||
// here a couple of cycles is enough
|
||||
assert_formal(
|
||||
|
|
@ -128,7 +116,7 @@ mod hidden_state {
|
|||
FormalMode::Prove,
|
||||
2,
|
||||
None,
|
||||
ExportOptions::default(),
|
||||
Default::default(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
@ -242,7 +230,7 @@ mod memory {
|
|||
#[hdl]
|
||||
let wr: WritePort<DynSize> = wire(WritePort[n]);
|
||||
connect(wr.addr, any_seq(UInt[n]));
|
||||
connect(wr.data, any_seq(UInt::<8>::TYPE));
|
||||
connect(wr.data, any_seq(UInt::<8>::new_static()));
|
||||
connect(wr.en, any_seq(Bool));
|
||||
#[hdl]
|
||||
let dut = instance(example_sram(n));
|
||||
|
|
@ -289,7 +277,7 @@ mod memory {
|
|||
FormalMode::Prove,
|
||||
2,
|
||||
None,
|
||||
ExportOptions::default(),
|
||||
Default::default(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
|
||||
use fayalite::{
|
||||
memory::{ReadStruct, ReadWriteStruct, WriteStruct},
|
||||
module::{instance_with_loc, reg_builder_with_loc},
|
||||
module::{instance_with_loc, memory_with_init_and_loc, reg_builder_with_loc},
|
||||
prelude::*,
|
||||
reset::ResetType,
|
||||
sim::vcd::VcdWriterDecls,
|
||||
|
|
@ -1261,6 +1261,310 @@ fn test_memories3() {
|
|||
}
|
||||
}
|
||||
|
||||
#[hdl_module(outline_generated)]
|
||||
pub fn many_memories() {
|
||||
#[hdl]
|
||||
let r: Array<ReadStruct<Bool, ConstUsize<4>>, 8> = m.input();
|
||||
#[hdl]
|
||||
let w: Array<WriteStruct<Bool, ConstUsize<4>>, 8> = m.input();
|
||||
for (mem_index, (r, w)) in r.into_iter().zip(w).enumerate() {
|
||||
let mut mem = memory_with_init_and_loc(
|
||||
&format!("mem_{mem_index}"),
|
||||
(0..16)
|
||||
.map(|bit_index| mem_index.pow(5).to_expr()[bit_index])
|
||||
.collect::<Vec<_>>(),
|
||||
SourceLocation::caller(),
|
||||
);
|
||||
connect_any(mem.new_read_port(), r);
|
||||
connect_any(mem.new_write_port(), w);
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl]
|
||||
#[test]
|
||||
fn test_many_memories() {
|
||||
let _n = SourceLocation::normalize_files_for_tests();
|
||||
let mut sim = Simulation::new(many_memories());
|
||||
let mut writer = RcWriter::default();
|
||||
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
|
||||
for r in sim.io().r {
|
||||
sim.write_clock(r.clk, false);
|
||||
}
|
||||
for w in sim.io().w {
|
||||
sim.write_clock(w.clk, false);
|
||||
}
|
||||
#[hdl(cmp_eq)]
|
||||
struct IO {
|
||||
r_addr: UInt<4>,
|
||||
r_en: Bool,
|
||||
r_data: Array<Bool, 8>,
|
||||
w_addr: UInt<4>,
|
||||
w_en: Bool,
|
||||
w_data: Array<Bool, 8>,
|
||||
w_mask: Array<Bool, 8>,
|
||||
}
|
||||
let io_cycles = [
|
||||
#[hdl(sim)]
|
||||
IO {
|
||||
r_addr: 0_hdl_u4,
|
||||
r_en: false,
|
||||
r_data: [false; 8],
|
||||
w_addr: 0_hdl_u4,
|
||||
w_en: false,
|
||||
w_data: [false; 8],
|
||||
w_mask: [false; 8],
|
||||
},
|
||||
#[hdl(sim)]
|
||||
IO {
|
||||
r_addr: 0_hdl_u4,
|
||||
r_en: true,
|
||||
r_data: [false, true, false, true, false, true, false, true],
|
||||
w_addr: 0_hdl_u4,
|
||||
w_en: true,
|
||||
w_data: [true; 8],
|
||||
w_mask: [true; 8],
|
||||
},
|
||||
#[hdl(sim)]
|
||||
IO {
|
||||
r_addr: 0_hdl_u4,
|
||||
r_en: true,
|
||||
r_data: [true; 8],
|
||||
w_addr: 0_hdl_u4,
|
||||
w_en: true,
|
||||
w_data: [false; 8],
|
||||
w_mask: [true; 8],
|
||||
},
|
||||
#[hdl(sim)]
|
||||
IO {
|
||||
r_addr: 0_hdl_u4,
|
||||
r_en: true,
|
||||
r_data: [false; 8],
|
||||
w_addr: 0_hdl_u4,
|
||||
w_en: false,
|
||||
w_data: [false; 8],
|
||||
w_mask: [true; 8],
|
||||
},
|
||||
#[hdl(sim)]
|
||||
IO {
|
||||
r_addr: 1_hdl_u4,
|
||||
r_en: true,
|
||||
r_data: [false, false, false, true, false, false, false, true],
|
||||
w_addr: 0_hdl_u4,
|
||||
w_en: false,
|
||||
w_data: [false; 8],
|
||||
w_mask: [true; 8],
|
||||
},
|
||||
#[hdl(sim)]
|
||||
IO {
|
||||
r_addr: 2_hdl_u4,
|
||||
r_en: true,
|
||||
r_data: [false, false, false, false, false, true, false, true],
|
||||
w_addr: 0_hdl_u4,
|
||||
w_en: false,
|
||||
w_data: [false; 8],
|
||||
w_mask: [true; 8],
|
||||
},
|
||||
#[hdl(sim)]
|
||||
IO {
|
||||
r_addr: 3_hdl_u4,
|
||||
r_en: true,
|
||||
r_data: [false, false, false, false, false, false, false, false],
|
||||
w_addr: 0_hdl_u4,
|
||||
w_en: false,
|
||||
w_data: [false; 8],
|
||||
w_mask: [true; 8],
|
||||
},
|
||||
#[hdl(sim)]
|
||||
IO {
|
||||
r_addr: 4_hdl_u4,
|
||||
r_en: true,
|
||||
r_data: [false, false, false, true, false, true, false, false],
|
||||
w_addr: 0_hdl_u4,
|
||||
w_en: false,
|
||||
w_data: [false; 8],
|
||||
w_mask: [true; 8],
|
||||
},
|
||||
#[hdl(sim)]
|
||||
IO {
|
||||
r_addr: 5_hdl_u4,
|
||||
r_en: true,
|
||||
r_data: [false, false, true, true, false, true, true, true],
|
||||
w_addr: 0_hdl_u4,
|
||||
w_en: false,
|
||||
w_data: [false; 8],
|
||||
w_mask: [true; 8],
|
||||
},
|
||||
#[hdl(sim)]
|
||||
IO {
|
||||
r_addr: 6_hdl_u4,
|
||||
r_en: true,
|
||||
r_data: [false, false, false, true, false, false, true, false],
|
||||
w_addr: 0_hdl_u4,
|
||||
w_en: false,
|
||||
w_data: [false; 8],
|
||||
w_mask: [true; 8],
|
||||
},
|
||||
#[hdl(sim)]
|
||||
IO {
|
||||
r_addr: 7_hdl_u4,
|
||||
r_en: true,
|
||||
r_data: [false, false, false, true, false, false, false, true],
|
||||
w_addr: 0_hdl_u4,
|
||||
w_en: false,
|
||||
w_data: [false; 8],
|
||||
w_mask: [true; 8],
|
||||
},
|
||||
#[hdl(sim)]
|
||||
IO {
|
||||
r_addr: 8_hdl_u4,
|
||||
r_en: true,
|
||||
r_data: [false, false, false, false, false, false, false, true],
|
||||
w_addr: 0_hdl_u4,
|
||||
w_en: false,
|
||||
w_data: [false; 8],
|
||||
w_mask: [true; 8],
|
||||
},
|
||||
#[hdl(sim)]
|
||||
IO {
|
||||
r_addr: 9_hdl_u4,
|
||||
r_en: true,
|
||||
r_data: [false, false, false, false, false, false, true, false],
|
||||
w_addr: 0_hdl_u4,
|
||||
w_en: false,
|
||||
w_data: [false; 8],
|
||||
w_mask: [true; 8],
|
||||
},
|
||||
#[hdl(sim)]
|
||||
IO {
|
||||
r_addr: 0xA_hdl_u4,
|
||||
r_en: true,
|
||||
r_data: [false, false, false, false, true, true, true, false],
|
||||
w_addr: 0_hdl_u4,
|
||||
w_en: false,
|
||||
w_data: [false; 8],
|
||||
w_mask: [true; 8],
|
||||
},
|
||||
#[hdl(sim)]
|
||||
IO {
|
||||
r_addr: 0xB_hdl_u4,
|
||||
r_en: true,
|
||||
r_data: [false, false, false, false, false, true, true, false],
|
||||
w_addr: 0_hdl_u4,
|
||||
w_en: false,
|
||||
w_data: [false; 8],
|
||||
w_mask: [true; 8],
|
||||
},
|
||||
#[hdl(sim)]
|
||||
IO {
|
||||
r_addr: 0xC_hdl_u4,
|
||||
r_en: true,
|
||||
r_data: [false, false, false, false, false, false, true, false],
|
||||
w_addr: 0_hdl_u4,
|
||||
w_en: false,
|
||||
w_data: [false; 8],
|
||||
w_mask: [true; 8],
|
||||
},
|
||||
#[hdl(sim)]
|
||||
IO {
|
||||
r_addr: 0xD_hdl_u4,
|
||||
r_en: true,
|
||||
r_data: [false, false, false, false, false, false, false, false],
|
||||
w_addr: 0_hdl_u4,
|
||||
w_en: false,
|
||||
w_data: [false; 8],
|
||||
w_mask: [true; 8],
|
||||
},
|
||||
#[hdl(sim)]
|
||||
IO {
|
||||
r_addr: 0xE_hdl_u4,
|
||||
r_en: true,
|
||||
r_data: [false, false, false, false, false, false, false, true],
|
||||
w_addr: 0_hdl_u4,
|
||||
w_en: false,
|
||||
w_data: [false; 8],
|
||||
w_mask: [true; 8],
|
||||
},
|
||||
#[hdl(sim)]
|
||||
IO {
|
||||
r_addr: 0xF_hdl_u4,
|
||||
r_en: true,
|
||||
r_data: [false, false, false, false, false, false, false, false],
|
||||
w_addr: 0_hdl_u4,
|
||||
w_en: false,
|
||||
w_data: [false; 8],
|
||||
w_mask: [true; 8],
|
||||
},
|
||||
];
|
||||
for (cycle, expected) in io_cycles.into_iter().enumerate() {
|
||||
#[hdl(sim)]
|
||||
let IO {
|
||||
r_addr,
|
||||
r_en,
|
||||
r_data: _,
|
||||
w_addr,
|
||||
w_en,
|
||||
w_data,
|
||||
w_mask,
|
||||
} = expected;
|
||||
for (((r, w), w_data), w_mask) in sim
|
||||
.io()
|
||||
.r
|
||||
.into_iter()
|
||||
.zip(sim.io().w)
|
||||
.zip(w_data.iter())
|
||||
.zip(w_mask.iter())
|
||||
{
|
||||
sim.write(r.addr, &r_addr);
|
||||
sim.write(r.en, &r_en);
|
||||
sim.write(w.addr, &w_addr);
|
||||
sim.write(w.en, &w_en);
|
||||
sim.write(w.data, w_data);
|
||||
sim.write(w.mask, w_mask);
|
||||
}
|
||||
let io = #[hdl(sim)]
|
||||
IO {
|
||||
r_addr,
|
||||
r_en,
|
||||
r_data: std::array::from_fn(|i| sim.read(sim.io().r[i].data)),
|
||||
w_addr,
|
||||
w_en,
|
||||
w_data,
|
||||
w_mask,
|
||||
};
|
||||
assert_eq!(
|
||||
expected,
|
||||
io,
|
||||
"vcd:\n{}\ncycle: {cycle}",
|
||||
String::from_utf8(writer.take()).unwrap(),
|
||||
);
|
||||
sim.advance_time(SimDuration::from_micros(1));
|
||||
for r in sim.io().r {
|
||||
sim.write_clock(r.clk, true);
|
||||
}
|
||||
for w in sim.io().w {
|
||||
sim.write_clock(w.clk, true);
|
||||
}
|
||||
sim.advance_time(SimDuration::from_micros(1));
|
||||
for r in sim.io().r {
|
||||
sim.write_clock(r.clk, false);
|
||||
}
|
||||
for w in sim.io().w {
|
||||
sim.write_clock(w.clk, false);
|
||||
}
|
||||
}
|
||||
sim.flush_traces().unwrap();
|
||||
let vcd = String::from_utf8(writer.take()).unwrap();
|
||||
println!("####### VCD:\n{vcd}\n#######");
|
||||
if vcd != include_str!("sim/expected/many_memories.vcd") {
|
||||
panic!();
|
||||
}
|
||||
let sim_debug = format!("{sim:#?}");
|
||||
println!("#######\n{sim_debug}\n#######");
|
||||
if sim_debug != include_str!("sim/expected/many_memories.txt") {
|
||||
panic!();
|
||||
}
|
||||
}
|
||||
|
||||
#[hdl_module(outline_generated)]
|
||||
pub fn duplicate_names() {
|
||||
#[hdl]
|
||||
|
|
|
|||
7782
crates/fayalite/tests/sim/expected/many_memories.txt
Normal file
7782
crates/fayalite/tests/sim/expected/many_memories.txt
Normal file
File diff suppressed because it is too large
Load diff
2596
crates/fayalite/tests/sim/expected/many_memories.vcd
Normal file
2596
crates/fayalite/tests/sim/expected/many_memories.vcd
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue