fayalite/crates/fayalite/tests/sim.rs
Jacob Lifshay d1bd176b28
All checks were successful
/ deps (pull_request) Successful in 11m39s
/ test (pull_request) Successful in 3m55s
implement simulation of extern modules
2025-03-21 01:47:14 -07:00

1507 lines
42 KiB
Rust

// SPDX-License-Identifier: LGPL-3.0-or-later
// See Notices.txt for copyright information
use fayalite::{
int::UIntValue,
prelude::*,
reset::ResetType,
sim::{
time::SimDuration, vcd::VcdWriterDecls, ExternModuleSimGenerator,
ExternModuleSimulationState, Simulation, ToSimValue,
},
ty::StaticType,
util::RcWriter,
};
use std::{future::IntoFuture, num::NonZeroUsize};
#[hdl_module(outline_generated)]
pub fn connect_const() {
#[hdl]
let o: UInt<8> = m.output();
connect(o, 5u8);
}
#[test]
fn test_connect_const() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(connect_const());
sim.settle();
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/connect_const.txt") {
panic!();
}
assert_eq!(sim.read_bool_or_int(sim.io().o), UIntValue::from(5u8));
}
#[hdl_module(outline_generated)]
pub fn connect_const_reset() {
#[hdl]
let reset_out: Reset = m.output();
#[hdl]
let bit_out: Bool = m.output();
connect(reset_out, true.to_async_reset().to_reset());
connect(bit_out, reset_out.cast_to_static());
}
#[test]
fn test_connect_const_reset() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(connect_const_reset());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
sim.settle();
sim.advance_time(SimDuration::from_micros(1));
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/connect_const_reset.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/connect_const_reset.txt") {
panic!();
}
assert_eq!(sim.read_bool_or_int(sim.io().bit_out), true);
}
#[hdl_module(outline_generated)]
pub fn mod1_child() {
#[hdl]
let i: UInt<4> = m.input();
#[hdl]
let o: SInt<2> = m.output();
#[hdl]
let i2: SInt<2> = m.input();
#[hdl]
let o2: UInt<4> = m.output();
connect(o, i.cast_to_static());
connect(o2, i2.cast_to_static());
#[hdl]
if i.cmp_gt(5_hdl_u4) {
connect(o2, 0xF_hdl_u4);
}
}
#[hdl_module(outline_generated)]
pub fn mod1() {
#[hdl]
let child = instance(mod1_child());
#[hdl]
let o: mod1_child = m.output(Expr::ty(child));
connect(o, child);
}
#[hdl]
#[test]
fn test_mod1() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(mod1());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
sim.write_bool_or_int(sim.io().o.i, 0x3_hdl_u4);
sim.write_bool_or_int(sim.io().o.i2, -2_hdl_i2);
sim.advance_time(SimDuration::from_micros(1));
sim.write_bool_or_int(sim.io().o.i, 0xA_hdl_u4);
sim.advance_time(SimDuration::from_micros(1));
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/mod1.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/mod1.txt") {
panic!();
}
let expected = -2_hdl_i2;
assert_eq!(sim.read_bool_or_int(sim.io().o.o).to_expr(), expected);
let expected = 0xF_hdl_u4;
assert_eq!(sim.read_bool_or_int(sim.io().o.o2).to_expr(), expected);
}
#[hdl_module(outline_generated)]
pub fn counter<R: ResetType>() {
#[hdl]
let cd: ClockDomain<R> = m.input();
#[hdl]
let count_reg: UInt<4> = reg_builder().clock_domain(cd).reset(3_hdl_u4);
connect_any(count_reg, count_reg + 1_hdl_u1);
#[hdl]
let count: UInt<4> = m.output();
connect(count, count_reg);
}
#[hdl]
#[test]
fn test_counter_sync() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(counter::<SyncReset>());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
sim.advance_time(SimDuration::from_micros(1));
sim.write_clock(sim.io().cd.clk, true);
sim.settle();
let reset_value = 3_hdl_u4;
assert_eq!(
reset_value,
sim.read_bool_or_int(sim.io().count).to_expr(),
"vcd:\n{}",
String::from_utf8(writer.take()).unwrap(),
);
sim.write_reset(sim.io().cd.rst, false);
sim.advance_time(SimDuration::from_micros(1));
for i in 0..32u32 {
assert_eq!(
UInt::<4>::new_static().from_int_wrapping(i + 3),
sim.read_bool_or_int(sim.io().count),
"vcd:\n{}",
String::from_utf8(writer.take()).unwrap(),
);
sim.write_clock(sim.io().cd.clk, false);
sim.advance_time(SimDuration::from_micros(1));
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(SimDuration::from_micros(1));
}
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/counter_sync.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/counter_sync.txt") {
panic!();
}
}
#[hdl]
#[test]
fn test_counter_async() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(counter::<AsyncReset>());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, false);
sim.advance_time(SimDuration::from_nanos(500));
sim.write_reset(sim.io().cd.rst, true);
let reset_value = 3_hdl_u4;
assert_eq!(
reset_value,
sim.read_bool_or_int(sim.io().count).to_expr(),
"vcd:\n{}",
String::from_utf8(writer.take()).unwrap(),
);
sim.advance_time(SimDuration::from_nanos(500));
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(SimDuration::from_nanos(500));
sim.write_reset(sim.io().cd.rst, false);
sim.advance_time(SimDuration::from_nanos(500));
for i in 0..32u32 {
assert_eq!(
UInt::<4>::new_static().from_int_wrapping(i + 3),
sim.read_bool_or_int(sim.io().count),
"vcd:\n{}",
String::from_utf8(writer.take()).unwrap(),
);
sim.write_clock(sim.io().cd.clk, false);
sim.advance_time(SimDuration::from_micros(1));
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(SimDuration::from_micros(1));
}
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/counter_async.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/counter_async.txt") {
panic!();
}
}
#[hdl_module(outline_generated)]
pub fn shift_register() {
#[hdl]
let cd: ClockDomain<SyncReset> = m.input();
#[hdl]
let d: Bool = m.input();
#[hdl]
let q: Bool = m.output();
#[hdl]
let reg0: Bool = reg_builder().clock_domain(cd).reset(false);
connect(reg0, d);
#[hdl]
let reg1: Bool = reg_builder().clock_domain(cd).reset(false);
connect(reg1, reg0);
#[hdl]
let reg2: Bool = reg_builder().clock_domain(cd).reset(false);
connect(reg2, reg1);
#[hdl]
let reg3: Bool = reg_builder().clock_domain(cd).reset(false);
connect(reg3, reg2);
connect(q, reg3);
}
#[hdl]
#[test]
fn test_shift_register() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(shift_register());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
sim.write(
sim.io().cd,
#[hdl]
ClockDomain {
clk: false.to_clock(),
rst: true.to_sync_reset(),
},
);
sim.write_bool(sim.io().d, false);
sim.advance_time(SimDuration::from_micros(1));
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(SimDuration::from_nanos(100));
sim.write_reset(sim.io().cd.rst, false);
sim.advance_time(SimDuration::from_nanos(900));
let data = [
false, true, true, false, false, true, false, true, true, true, true,
];
for cycle in 0..32usize {
if let Some(out_cycle) = cycle.checked_sub(4) {
if let Some(expected) = data.get(out_cycle) {
assert_eq!(
*expected,
sim.read_bool(sim.io().q),
"vcd:\n{}\ncycle: {cycle}",
String::from_utf8(writer.take()).unwrap(),
);
}
}
sim.write_bool(sim.io().d, data.get(cycle).copied().unwrap_or(false));
sim.write_clock(sim.io().cd.clk, false);
sim.advance_time(SimDuration::from_micros(1));
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(SimDuration::from_micros(1));
}
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/shift_register.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/shift_register.txt") {
panic!();
}
}
#[hdl_module(outline_generated)]
pub fn enums() {
#[hdl]
let cd: ClockDomain<SyncReset> = m.input();
#[hdl]
let en: Bool = m.input();
#[hdl]
let which_in: UInt<2> = m.input();
#[hdl]
let data_in: UInt<4> = m.input();
#[hdl]
let which_out: UInt<2> = m.output();
#[hdl]
let data_out: UInt<4> = m.output();
#[hdl]
let b_out: HdlOption<(UInt<1>, Bool)> = m.output();
#[hdl]
struct MyStruct<T> {
a: T,
b: SInt<2>,
}
#[hdl]
enum MyEnum {
A,
B((UInt<1>, Bool)),
C(MyStruct<Array<UInt<1>, 2>>),
}
#[hdl]
let the_reg = reg_builder().clock_domain(cd).reset(MyEnum.A());
#[hdl]
if en {
#[hdl]
if which_in.cmp_eq(0_hdl_u2) {
connect(the_reg, MyEnum.A());
} else if which_in.cmp_eq(1_hdl_u2) {
connect(the_reg, MyEnum.B((data_in[0].cast_to_static(), data_in[1])));
} else {
connect(
the_reg,
MyEnum.C(
#[hdl]
MyStruct {
a: #[hdl]
[data_in[0].cast_to_static(), data_in[1].cast_to_static()],
b: data_in[2..4].cast_to_static(),
},
),
);
}
}
connect(b_out, HdlNone());
#[hdl]
match the_reg {
MyEnum::A => {
connect(which_out, 0_hdl_u2);
connect(data_out, 0_hdl_u4);
}
MyEnum::B(v) => {
connect(which_out, 1_hdl_u2);
connect_any(data_out, v.0 | (v.1.cast_to_static::<UInt<1>>() << 1));
connect(b_out, HdlSome(v));
}
MyEnum::C(v) => {
connect(which_out, 2_hdl_u2);
connect_any(data_out, v.cast_to_bits());
}
}
}
#[hdl]
#[test]
fn test_enums() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(enums());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
sim.write_clock(sim.io().cd.clk, false);
sim.write_reset(sim.io().cd.rst, true);
sim.write_bool(sim.io().en, false);
sim.write_bool_or_int(sim.io().which_in, 0_hdl_u2);
sim.write_bool_or_int(sim.io().data_in, 0_hdl_u4);
sim.advance_time(SimDuration::from_micros(1));
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(SimDuration::from_nanos(100));
sim.write_reset(sim.io().cd.rst, false);
sim.advance_time(SimDuration::from_nanos(900));
type BOutTy = HdlOption<(UInt<1>, Bool)>;
#[derive(Debug)]
struct IO {
en: bool,
which_in: u8,
data_in: u8,
which_out: u8,
data_out: u8,
b_out: Expr<BOutTy>,
}
impl PartialEq for IO {
fn eq(&self, other: &Self) -> bool {
let Self {
en,
which_in,
data_in,
which_out,
data_out,
b_out,
} = *self;
en == other.en
&& which_in == other.which_in
&& data_in == other.data_in
&& which_out == other.which_out
&& data_out == other.data_out
&& b_out.to_sim_value(BOutTy::TYPE) == other.b_out.to_sim_value(BOutTy::TYPE)
}
}
let io_cycles = [
IO {
en: false,
which_in: 0,
data_in: 0,
which_out: 0,
data_out: 0,
b_out: HdlNone(),
},
IO {
en: true,
which_in: 1,
data_in: 0,
which_out: 0,
data_out: 0,
b_out: HdlNone(),
},
IO {
en: false,
which_in: 0,
data_in: 0,
which_out: 1,
data_out: 0,
b_out: HdlSome((0_hdl_u1, false)),
},
IO {
en: true,
which_in: 1,
data_in: 0xF,
which_out: 1,
data_out: 0,
b_out: HdlSome((0_hdl_u1, false)),
},
IO {
en: true,
which_in: 1,
data_in: 0xF,
which_out: 1,
data_out: 0x3,
b_out: HdlSome((1_hdl_u1, true)),
},
IO {
en: true,
which_in: 2,
data_in: 0xF,
which_out: 1,
data_out: 0x3,
b_out: HdlSome((1_hdl_u1, true)),
},
IO {
en: true,
which_in: 2,
data_in: 0xF,
which_out: 2,
data_out: 0xF,
b_out: HdlNone(),
},
];
for (
cycle,
expected @ IO {
en,
which_in,
data_in,
which_out: _,
data_out: _,
b_out: _,
},
) in io_cycles.into_iter().enumerate()
{
sim.write_bool(sim.io().en, en);
sim.write_bool_or_int(sim.io().which_in, which_in.cast_to_static());
sim.write_bool_or_int(sim.io().data_in, data_in.cast_to_static());
let io = IO {
en,
which_in,
data_in,
which_out: sim
.read_bool_or_int(sim.io().which_out)
.to_bigint()
.try_into()
.expect("known to be in range"),
data_out: sim
.read_bool_or_int(sim.io().data_out)
.to_bigint()
.try_into()
.expect("known to be in range"),
b_out: sim.read(sim.io().b_out).to_expr(),
};
assert_eq!(
expected,
io,
"vcd:\n{}\ncycle: {cycle}",
String::from_utf8(writer.take()).unwrap(),
);
sim.write_clock(sim.io().cd.clk, false);
sim.advance_time(SimDuration::from_micros(1));
sim.write_clock(sim.io().cd.clk, true);
sim.advance_time(SimDuration::from_micros(1));
}
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/enums.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/enums.txt") {
panic!();
}
}
#[hdl_module(outline_generated)]
pub fn memories() {
#[hdl]
let r: fayalite::memory::ReadStruct<(UInt<8>, SInt<8>), ConstUsize<4>> = m.input();
#[hdl]
let w: fayalite::memory::WriteStruct<(UInt<8>, SInt<8>), ConstUsize<4>> = m.input();
#[hdl]
let mut mem = memory_with_init([(0x01u8, 0x23i8); 16]);
mem.read_latency(0);
mem.write_latency(NonZeroUsize::new(1).unwrap());
mem.read_under_write(ReadUnderWrite::Old);
connect_any(mem.new_read_port(), r);
connect_any(mem.new_write_port(), w);
}
#[hdl]
#[test]
fn test_memories() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(memories());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
sim.write_clock(sim.io().r.clk, false);
sim.write_clock(sim.io().w.clk, false);
#[derive(Debug, PartialEq, Eq)]
struct IO {
r_addr: u8,
r_en: bool,
r_data: (u8, i8),
w_addr: u8,
w_en: bool,
w_data: (u8, i8),
w_mask: (bool, bool),
}
let io_cycles = [
IO {
r_addr: 0,
r_en: false,
r_data: (0, 0),
w_addr: 0,
w_en: false,
w_data: (0, 0),
w_mask: (false, false),
},
IO {
r_addr: 0,
r_en: true,
r_data: (0x1, 0x23),
w_addr: 0,
w_en: true,
w_data: (0x10, 0x20),
w_mask: (true, true),
},
IO {
r_addr: 0,
r_en: true,
r_data: (0x10, 0x20),
w_addr: 0,
w_en: true,
w_data: (0x30, 0x40),
w_mask: (false, true),
},
IO {
r_addr: 0,
r_en: true,
r_data: (0x10, 0x40),
w_addr: 0,
w_en: true,
w_data: (0x50, 0x60),
w_mask: (true, false),
},
IO {
r_addr: 0,
r_en: true,
r_data: (0x50, 0x40),
w_addr: 0,
w_en: true,
w_data: (0x70, -0x80),
w_mask: (false, false),
},
IO {
r_addr: 0,
r_en: true,
r_data: (0x50, 0x40),
w_addr: 0,
w_en: false,
w_data: (0x90, 0xA0u8 as i8),
w_mask: (false, false),
},
IO {
r_addr: 0,
r_en: true,
r_data: (0x50, 0x40),
w_addr: 1,
w_en: true,
w_data: (0x90, 0xA0u8 as i8),
w_mask: (true, true),
},
IO {
r_addr: 0,
r_en: true,
r_data: (0x50, 0x40),
w_addr: 2,
w_en: true,
w_data: (0xB0, 0xC0u8 as i8),
w_mask: (true, true),
},
IO {
r_addr: 0,
r_en: true,
r_data: (0x50, 0x40),
w_addr: 2,
w_en: false,
w_data: (0xD0, 0xE0u8 as i8),
w_mask: (true, true),
},
IO {
r_addr: 1,
r_en: true,
r_data: (0x90, 0xA0u8 as i8),
w_addr: 2,
w_en: false,
w_data: (0xD0, 0xE0u8 as i8),
w_mask: (true, true),
},
IO {
r_addr: 2,
r_en: true,
r_data: (0xB0, 0xC0u8 as i8),
w_addr: 2,
w_en: false,
w_data: (0xD0, 0xE0u8 as i8),
w_mask: (true, true),
},
];
for (
cycle,
expected @ IO {
r_addr,
r_en,
r_data: _,
w_addr,
w_en,
w_data,
w_mask,
},
) in io_cycles.into_iter().enumerate()
{
sim.write_bool_or_int(sim.io().r.addr, r_addr.cast_to_static());
sim.write_bool(sim.io().r.en, r_en);
sim.write_bool_or_int(sim.io().w.addr, w_addr.cast_to_static());
sim.write_bool(sim.io().w.en, w_en);
sim.write_bool_or_int(sim.io().w.data.0, w_data.0);
sim.write_bool_or_int(sim.io().w.data.1, w_data.1);
sim.write_bool(sim.io().w.mask.0, w_mask.0);
sim.write_bool(sim.io().w.mask.1, w_mask.1);
let io = IO {
r_addr,
r_en,
r_data: (
sim.read_bool_or_int(sim.io().r.data.0)
.to_bigint()
.try_into()
.expect("known to be in range"),
sim.read_bool_or_int(sim.io().r.data.1)
.to_bigint()
.try_into()
.expect("known to be in range"),
),
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));
sim.write_clock(sim.io().r.clk, true);
sim.write_clock(sim.io().w.clk, true);
sim.advance_time(SimDuration::from_micros(1));
sim.write_clock(sim.io().r.clk, false);
sim.write_clock(sim.io().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/memories.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/memories.txt") {
panic!();
}
}
#[hdl_module(outline_generated)]
pub fn memories2() {
#[hdl]
let rw: fayalite::memory::ReadWriteStruct<UInt<2>, ConstUsize<3>> = m.input();
#[hdl]
let mut mem = memory_with_init([HdlSome(true); 5]);
mem.read_latency(1);
mem.write_latency(NonZeroUsize::new(1).unwrap());
mem.read_under_write(ReadUnderWrite::New);
let rw_port = mem.new_rw_port();
connect_any(rw_port.addr, rw.addr);
connect(rw_port.en, rw.en);
connect(rw_port.clk, rw.clk);
connect_any(rw.rdata, rw_port.rdata.cast_to_bits());
connect(rw_port.wmode, rw.wmode);
connect(rw_port.wdata, HdlNone());
#[hdl]
if rw.wdata[0] {
connect(rw_port.wdata, HdlSome(rw.wdata[1]));
}
connect(rw_port.wmask, rw.wmask);
}
#[hdl]
#[test]
fn test_memories2() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(memories2());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
sim.write_clock(sim.io().rw.clk, false);
#[derive(Debug, PartialEq, Eq)]
struct IO {
addr: u8,
en: bool,
rdata: u8,
wmode: bool,
wdata: u8,
wmask: bool,
}
let io_cycles = [
IO {
addr: 0,
en: false,
rdata: 0,
wmode: false,
wdata: 0,
wmask: false,
},
IO {
addr: 0,
en: true,
rdata: 0x3,
wmode: false,
wdata: 0,
wmask: false,
},
IO {
addr: 0,
en: false,
rdata: 0,
wmode: false,
wdata: 0,
wmask: false,
},
IO {
addr: 0,
en: true,
rdata: 0,
wmode: true,
wdata: 0,
wmask: true,
},
IO {
addr: 0,
en: true,
rdata: 0,
wmode: false,
wdata: 0,
wmask: false,
},
IO {
addr: 0,
en: true,
rdata: 0,
wmode: true,
wdata: 3,
wmask: false,
},
IO {
addr: 1,
en: true,
rdata: 0,
wmode: true,
wdata: 1,
wmask: true,
},
IO {
addr: 2,
en: true,
rdata: 0,
wmode: true,
wdata: 2,
wmask: true,
},
IO {
addr: 3,
en: true,
rdata: 0,
wmode: true,
wdata: 3,
wmask: true,
},
IO {
addr: 4,
en: true,
rdata: 0,
wmode: true,
wdata: 2,
wmask: true,
},
IO {
addr: 5,
en: true,
rdata: 0,
wmode: true,
wdata: 1,
wmask: true,
},
IO {
addr: 6,
en: true,
rdata: 0,
wmode: true,
wdata: 1,
wmask: true,
},
IO {
addr: 7,
en: true,
rdata: 0,
wmode: true,
wdata: 1,
wmask: true,
},
IO {
addr: 7,
en: true,
rdata: 0,
wmode: false,
wdata: 0,
wmask: false,
},
IO {
addr: 6,
en: true,
rdata: 0,
wmode: false,
wdata: 0,
wmask: false,
},
IO {
addr: 5,
en: true,
rdata: 0,
wmode: false,
wdata: 0,
wmask: false,
},
IO {
addr: 4,
en: true,
rdata: 0,
wmode: false,
wdata: 0,
wmask: false,
},
IO {
addr: 3,
en: true,
rdata: 3,
wmode: false,
wdata: 0,
wmask: false,
},
IO {
addr: 2,
en: true,
rdata: 0,
wmode: false,
wdata: 0,
wmask: false,
},
IO {
addr: 0,
en: true,
rdata: 0,
wmode: false,
wdata: 0,
wmask: false,
},
IO {
addr: 1,
en: true,
rdata: 1,
wmode: false,
wdata: 0,
wmask: false,
},
IO {
addr: 0,
en: false,
rdata: 0,
wmode: false,
wdata: 0,
wmask: false,
},
];
for (
cycle,
expected @ IO {
addr,
en,
rdata: _,
wmode,
wdata,
wmask,
},
) in io_cycles.into_iter().enumerate()
{
sim.write_bool_or_int(sim.io().rw.addr, addr.cast_to_static());
sim.write_bool(sim.io().rw.en, en);
sim.write_bool(sim.io().rw.wmode, wmode);
sim.write_bool_or_int(sim.io().rw.wdata, wdata.cast_to_static());
sim.write_bool(sim.io().rw.wmask, wmask);
sim.advance_time(SimDuration::from_nanos(250));
sim.write_clock(sim.io().rw.clk, true);
sim.advance_time(SimDuration::from_nanos(250));
let io = IO {
addr,
en,
rdata: sim
.read_bool_or_int(sim.io().rw.rdata)
.to_bigint()
.try_into()
.expect("known to be in range"),
wmode,
wdata,
wmask,
};
assert_eq!(
expected,
io,
"vcd:\n{}\ncycle: {cycle}",
String::from_utf8(writer.take()).unwrap(),
);
sim.advance_time(SimDuration::from_nanos(250));
sim.write_clock(sim.io().rw.clk, false);
sim.advance_time(SimDuration::from_nanos(250));
}
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/memories2.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/memories2.txt") {
panic!();
}
}
#[hdl_module(outline_generated)]
pub fn memories3() {
#[hdl]
let r: fayalite::memory::ReadStruct<Array<UInt<8>, 8>, ConstUsize<3>> = m.input();
#[hdl]
let w: fayalite::memory::WriteStruct<Array<UInt<8>, 8>, ConstUsize<3>> = m.input();
#[hdl]
let mut mem: MemBuilder<Array<UInt<8>, 8>> = memory();
mem.depth(8);
mem.read_latency(2);
mem.write_latency(NonZeroUsize::new(2).unwrap());
mem.read_under_write(ReadUnderWrite::Old);
connect_any(mem.new_read_port(), r);
connect_any(mem.new_write_port(), w);
}
#[hdl]
#[test]
fn test_memories3() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(memories3());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
sim.write_clock(sim.io().r.clk, false);
sim.write_clock(sim.io().w.clk, false);
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
struct IO {
r_addr: u8,
r_en: bool,
r_data: [u8; 8],
w_addr: u8,
w_en: bool,
w_data: [u8; 8],
w_mask: [bool; 8],
}
let io_cycles = [
IO {
r_addr: 0,
r_en: false,
r_data: [0; 8],
w_addr: 0,
w_en: true,
w_data: [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF0],
w_mask: [false, true, false, true, true, false, false, true],
},
IO {
r_addr: 0,
r_en: true,
r_data: [0; 8],
w_addr: 1,
w_en: false,
w_data: [0; 8],
w_mask: [false; 8],
},
IO {
r_addr: 0,
r_en: true,
r_data: [0, 0x34, 0, 0x78, 0x9A, 0, 0, 0xF0],
w_addr: 1,
w_en: false,
w_data: [0; 8],
w_mask: [false; 8],
},
IO {
r_addr: 0,
r_en: true,
r_data: [0, 0x34, 0, 0x78, 0x9A, 0, 0, 0xF0],
w_addr: 0,
w_en: true,
w_data: [0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10],
w_mask: [true; 8],
},
IO {
r_addr: 0,
r_en: true,
r_data: [0, 0x34, 0, 0x78, 0x9A, 0, 0, 0xF0],
w_addr: 0,
w_en: true,
w_data: [0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10],
w_mask: [true; 8],
},
IO {
r_addr: 0,
r_en: true,
r_data: [0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10],
w_addr: 0,
w_en: true,
w_data: [0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10],
w_mask: [true; 8],
},
IO {
r_addr: 0,
r_en: false,
r_data: [0; 8],
w_addr: 1,
w_en: true,
w_data: [0x13, 0x57, 0x9B, 0xDF, 0x02, 0x46, 0x8A, 0xCE],
w_mask: [true; 8],
},
IO {
r_addr: 0,
r_en: false,
r_data: [0; 8],
w_addr: 2,
w_en: true,
w_data: *b"testing!",
w_mask: [true; 8],
},
IO {
r_addr: 0,
r_en: false,
r_data: [0; 8],
w_addr: 3,
w_en: true,
w_data: *b"more tst",
w_mask: [true; 8],
},
IO {
r_addr: 0,
r_en: true,
r_data: [0xFE, 0xDC, 0xBA, 0x98, 0x76, 0x54, 0x32, 0x10],
w_addr: 0,
w_en: false,
w_data: [0; 8],
w_mask: [false; 8],
},
IO {
r_addr: 1,
r_en: true,
r_data: [0x13, 0x57, 0x9B, 0xDF, 0x02, 0x46, 0x8A, 0xCE],
w_addr: 0,
w_en: false,
w_data: [0; 8],
w_mask: [false; 8],
},
IO {
r_addr: 2,
r_en: true,
r_data: *b"testing!",
w_addr: 0,
w_en: false,
w_data: [0; 8],
w_mask: [false; 8],
},
IO {
r_addr: 3,
r_en: true,
r_data: *b"more tst",
w_addr: 0,
w_en: false,
w_data: [0; 8],
w_mask: [false; 8],
},
];
for cycle in 0..io_cycles.len() + 2 {
{
let IO {
r_addr,
r_en,
r_data: _,
w_addr,
w_en,
w_data,
w_mask,
} = io_cycles.get(cycle).copied().unwrap_or(IO {
r_addr: 0,
r_en: false,
r_data: [0; 8],
w_addr: 0,
w_en: false,
w_data: [0; 8],
w_mask: [false; 8],
});
sim.write_bool_or_int(sim.io().r.addr, r_addr.cast_to_static());
sim.write_bool(sim.io().r.en, r_en);
sim.write_bool_or_int(sim.io().w.addr, w_addr.cast_to_static());
sim.write_bool(sim.io().w.en, w_en);
for (i, v) in w_data.into_iter().enumerate() {
sim.write_bool_or_int(sim.io().w.data[i], v);
}
for (i, v) in w_mask.into_iter().enumerate() {
sim.write_bool_or_int(sim.io().w.mask[i], v);
}
}
sim.advance_time(SimDuration::from_nanos(250));
sim.write_clock(sim.io().r.clk, true);
sim.write_clock(sim.io().w.clk, true);
sim.advance_time(SimDuration::from_nanos(250));
if let Some(
expected @ IO {
r_addr,
r_en,
r_data: _,
w_addr,
w_en,
w_data,
w_mask,
},
) = cycle.checked_sub(1).and_then(|i| io_cycles.get(i).copied())
{
let io = IO {
r_addr,
r_en,
r_data: std::array::from_fn(|i| {
sim.read_bool_or_int(sim.io().r.data[i])
.to_bigint()
.try_into()
.expect("known to be in range")
}),
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_nanos(250));
sim.write_clock(sim.io().r.clk, false);
sim.write_clock(sim.io().w.clk, false);
sim.advance_time(SimDuration::from_nanos(250));
}
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/memories3.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/memories3.txt") {
panic!();
}
}
#[hdl_module(outline_generated)]
pub fn duplicate_names() {
#[hdl]
let w = wire();
connect(w, 5u8);
#[hdl]
let w = wire();
connect(w, 6u8);
}
#[test]
fn test_duplicate_names() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(duplicate_names());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
sim.advance_time(SimDuration::from_micros(1));
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/duplicate_names.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/duplicate_names.txt") {
panic!();
}
}
#[hdl_module(outline_generated)]
pub fn array_rw() {
#[hdl]
let array_in: Array<UInt<8>, 16> = m.input();
#[hdl]
let array_out: Array<UInt<8>, 16> = m.output();
#[hdl]
let read_index: UInt<8> = m.input();
#[hdl]
let read_data: UInt<8> = m.output();
#[hdl]
let write_index: UInt<8> = m.input();
#[hdl]
let write_data: UInt<8> = m.input();
#[hdl]
let write_en: Bool = m.input();
#[hdl]
let array_wire = wire();
connect(array_wire, array_in);
connect(array_out, array_wire);
#[hdl]
if write_en {
connect(array_wire[write_index], write_data);
}
connect(read_data, array_wire[read_index]);
}
#[test]
fn test_array_rw() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(array_rw());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
#[derive(Debug, PartialEq)]
struct State {
array_in: [u8; 16],
array_out: [u8; 16],
read_index: u8,
read_data: u8,
write_index: u8,
write_data: u8,
write_en: bool,
}
let mut states = Vec::new();
let array_in = [
0xFFu8, 0x7F, 0x3F, 0x1F, 0x0F, 0x07, 0x03, 0x01, //
0x00u8, 0x80, 0xC0, 0xE0, 0xF0, 0xF8, 0xFC, 0xFE,
];
for i in 0..=16 {
states.push(State {
array_in,
array_out: array_in,
read_index: i,
read_data: array_in.get(i as usize).copied().unwrap_or(0),
write_index: 0,
write_data: 0,
write_en: false,
});
}
for i in 0..=16u8 {
let mut array_out = array_in;
let write_data = i.wrapping_mul(i);
if let Some(v) = array_out.get_mut(i as usize) {
*v = write_data;
}
states.push(State {
array_in,
array_out,
read_index: 0,
read_data: array_out[0],
write_index: i,
write_data,
write_en: true,
});
}
for (cycle, expected) in states.into_iter().enumerate() {
let State {
array_in,
array_out: _,
read_index,
read_data: _,
write_index,
write_data,
write_en,
} = expected;
sim.write(sim.io().array_in, array_in);
sim.write(sim.io().read_index, read_index);
sim.write(sim.io().write_index, write_index);
sim.write(sim.io().write_data, write_data);
sim.write(sim.io().write_en, write_en);
sim.advance_time(SimDuration::from_micros(1));
let array_out = std::array::from_fn(|index| {
sim.read_bool_or_int(sim.io().array_out[index])
.to_bigint()
.try_into()
.expect("known to be in range")
});
let read_data = sim
.read_bool_or_int(sim.io().read_data)
.to_bigint()
.try_into()
.expect("known to be in range");
let state = State {
array_in,
array_out,
read_index,
read_data,
write_index,
write_data,
write_en,
};
assert_eq!(
state,
expected,
"vcd:\n{}\ncycle: {cycle}",
String::from_utf8(writer.take()).unwrap(),
);
}
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/array_rw.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/array_rw.txt") {
panic!();
}
}
#[hdl_module(outline_generated)]
pub fn conditional_assignment_last() {
#[hdl]
let i: Bool = m.input();
#[hdl]
let w = wire();
connect(w, true);
#[hdl]
if i {
connect(w, false);
}
}
#[test]
fn test_conditional_assignment_last() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(conditional_assignment_last());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
sim.write(sim.io().i, false);
sim.advance_time(SimDuration::from_micros(1));
sim.write(sim.io().i, true);
sim.advance_time(SimDuration::from_micros(1));
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/conditional_assignment_last.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/conditional_assignment_last.txt") {
panic!();
}
}
#[hdl_module(outline_generated, extern)]
pub fn extern_module() {
#[hdl]
let i: Bool = m.input();
#[hdl]
let o: Bool = m.output();
#[derive(Clone, Eq, PartialEq, Hash, Debug)]
struct Sim {
i: Expr<Bool>,
o: Expr<Bool>,
}
impl ExternModuleSimGenerator for Sim {
type IOType = extern_module;
fn run<'a>(
&'a self,
mut sim: ExternModuleSimulationState<Self::IOType>,
) -> impl IntoFuture<Output = ()> + 'a {
let Self { i, o } = *self;
async move {
sim.write(o, true).await;
sim.advance_time(SimDuration::from_nanos(500)).await;
let mut invert = false;
loop {
sim.advance_time(SimDuration::from_micros(1)).await;
let v = sim.read_bool(i).await;
sim.write(o, v ^ invert).await;
invert = !invert;
}
}
}
}
m.extern_module_simulation(Sim { i, o });
}
#[test]
fn test_extern_module() {
let _n = SourceLocation::normalize_files_for_tests();
let mut sim = Simulation::new(extern_module());
let mut writer = RcWriter::default();
sim.add_trace_writer(VcdWriterDecls::new(writer.clone()));
sim.write(sim.io().i, false);
sim.advance_time(SimDuration::from_micros(10));
sim.write(sim.io().i, true);
sim.advance_time(SimDuration::from_micros(10));
sim.flush_traces().unwrap();
let vcd = String::from_utf8(writer.take()).unwrap();
println!("####### VCD:\n{vcd}\n#######");
if vcd != include_str!("sim/expected/extern_module.vcd") {
panic!();
}
let sim_debug = format!("{sim:#?}");
println!("#######\n{sim_debug}\n#######");
if sim_debug != include_str!("sim/expected/extern_module.txt") {
panic!();
}
}