// SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information use fayalite::{ memory::{ReadStruct, ReadWriteStruct, WriteStruct}, module::{instance_with_loc, memory_with_init_and_loc, reg_builder_with_loc}, prelude::*, reset::ResetType, sim::vcd::VcdWriterDecls, util::{RcWriter, ready_valid::queue}, }; use std::{collections::BTreeMap, num::NonZeroUsize, rc::Rc}; #[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(child.ty()); 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() { #[hdl] let cd: ClockDomain = 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::()); 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::()); 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 = 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 = 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(); let b_out_ty = HdlOption[(UInt[1], Bool)]; #[hdl] let b_out: HdlOption<(UInt, Bool)> = m.output(HdlOption[(UInt[1], Bool)]); #[hdl] let b2_out: HdlOption<(UInt<1>, Bool)> = m.output(); connect_any(b2_out, b_out); #[hdl] struct MyStruct { a: T, b: SInt<2>, } #[hdl] enum MyEnum { A, B((UInt<1>, Bool)), C(MyStruct, 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, b_out_ty.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::>() << 1)); connect_any(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(sim.io().cd.clk, false); sim.write(sim.io().cd.rst, true); sim.write(sim.io().en, false); sim.write(sim.io().which_in, 0_hdl_u2); sim.write(sim.io().data_in, 0_hdl_u4); sim.advance_time(SimDuration::from_micros(1)); sim.write(sim.io().cd.clk, true); sim.advance_time(SimDuration::from_nanos(100)); sim.write(sim.io().cd.rst, false); sim.advance_time(SimDuration::from_nanos(900)); #[hdl(cmp_eq)] struct IO { en: Bool, which_in: UInt<2>, data_in: UInt<4>, which_out: UInt<2>, data_out: UInt<4>, b_out: HdlOption<(UIntType, Bool)>, b2_out: HdlOption<(UInt<1>, Bool)>, } let io_ty = IO[1]; let io_cycles = [ #[hdl(sim)] IO::<_> { en: false, which_in: 0_hdl_u2, data_in: 0_hdl_u4, which_out: 0_hdl_u2, data_out: 0_hdl_u4, b_out: #[hdl(sim)] (io_ty.b_out).HdlNone(), b2_out: #[hdl(sim)] HdlNone(), }, #[hdl(sim)] IO::<_> { en: true, which_in: 1_hdl_u2, data_in: 0_hdl_u4, which_out: 0_hdl_u2, data_out: 0_hdl_u4, b_out: #[hdl(sim)] (io_ty.b_out).HdlNone(), b2_out: #[hdl(sim)] HdlNone(), }, #[hdl(sim)] IO::<_> { en: false, which_in: 0_hdl_u2, data_in: 0_hdl_u4, which_out: 1_hdl_u2, data_out: 0_hdl_u4, b_out: #[hdl(sim)] (io_ty.b_out).HdlSome((0u8.cast_to(UInt[1]), false)), b2_out: #[hdl(sim)] HdlSome((0_hdl_u1, false)), }, #[hdl(sim)] IO::<_> { en: true, which_in: 1_hdl_u2, data_in: 0xF_hdl_u4, which_out: 1_hdl_u2, data_out: 0_hdl_u4, b_out: #[hdl(sim)] (io_ty.b_out).HdlSome((0u8.cast_to(UInt[1]), false)), b2_out: #[hdl(sim)] HdlSome((0_hdl_u1, false)), }, #[hdl(sim)] IO::<_> { en: true, which_in: 1_hdl_u2, data_in: 0xF_hdl_u4, which_out: 1_hdl_u2, data_out: 0x3_hdl_u4, b_out: #[hdl(sim)] (io_ty.b_out).HdlSome((1u8.cast_to(UInt[1]), true)), b2_out: #[hdl(sim)] HdlSome((1_hdl_u1, true)), }, #[hdl(sim)] IO::<_> { en: true, which_in: 2_hdl_u2, data_in: 0xF_hdl_u4, which_out: 1_hdl_u2, data_out: 0x3_hdl_u4, b_out: #[hdl(sim)] (io_ty.b_out).HdlSome((1u8.cast_to(UInt[1]), true)), b2_out: #[hdl(sim)] HdlSome((1_hdl_u1, true)), }, #[hdl(sim)] IO::<_> { en: true, which_in: 2_hdl_u2, data_in: 0xF_hdl_u4, which_out: 2_hdl_u2, data_out: 0xF_hdl_u4, b_out: #[hdl(sim)] (io_ty.b_out).HdlNone(), b2_out: #[hdl(sim)] HdlNone(), }, ]; for (cycle, expected) in io_cycles.into_iter().enumerate() { #[hdl(sim)] let IO::<_> { en, which_in, data_in, which_out: _, data_out: _, b_out: _, b2_out: _, } = &expected; sim.write(sim.io().en, en); sim.write(sim.io().which_in, which_in); sim.write(sim.io().data_in, data_in); let io = #[hdl(sim)] IO::<_> { en, which_in, data_in, which_out: sim.read(sim.io().which_out), data_out: sim.read(sim.io().data_out), b_out: sim.read(sim.io().b_out), b2_out: sim.read(sim.io().b2_out), }; assert_eq!( expected, io, "vcd:\n{}\ncycle: {cycle}", String::from_utf8(writer.take()).unwrap(), ); // make sure matching on SimValue works #[hdl(sim)] match &io.b_out { HdlNone => println!("io.b_out is HdlNone"), HdlSome(v) => println!("io.b_out is HdlSome(({:?}, {:?}))", *v.0, *v.1), } 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] pub enum EnumWithSimpleBody { A(UInt<8>), B(UInt<8>), C(UInt<8>), } #[hdl_module(outline_generated)] pub fn enum_with_simple_body() { #[hdl] let which_in: UInt<8> = m.input(); #[hdl] let data_in: UInt<8> = m.input(); #[hdl] let which_out: UInt<8> = m.output(); #[hdl] let data_out: UInt<8> = m.output(); #[hdl] let enum_out: EnumWithSimpleBody = m.output(); #[hdl] if which_in.cmp_eq(0u8) { connect(enum_out, EnumWithSimpleBody.A(data_in)); } else if which_in.cmp_eq(1u8) { connect(enum_out, EnumWithSimpleBody.B(data_in)); } else { connect(enum_out, EnumWithSimpleBody.C(data_in)); } #[hdl] match enum_out { EnumWithSimpleBody::A(v) => { connect(which_out, 0u8); connect(data_out, v); } EnumWithSimpleBody::B(v) => { connect(which_out, 1u8); connect(data_out, v); } EnumWithSimpleBody::C(v) => { connect(which_out, 2u8); connect(data_out, v); } } } #[hdl] #[test] fn test_enum_with_simple_body() { let _n = SourceLocation::normalize_files_for_tests(); let mut sim = Simulation::new(enum_with_simple_body()); let mut writer = RcWriter::default(); sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); for which in 0u8..=2 { for data in (0..u8::MAX).step_by(45) { sim.write(sim.io().which_in, which); sim.write(sim.io().data_in, data); sim.advance_time(SimDuration::from_micros(1)); assert_eq!(sim.read(sim.io().which_out).as_int(), which); assert_eq!(sim.read(sim.io().data_out).as_int(), data); } } sim.flush_traces().unwrap(); let vcd = String::from_utf8(writer.take()).unwrap(); println!("####### VCD:\n{vcd}\n#######"); #[derive(Debug)] struct WireState<'a> { name: &'a str, space_then_id: Option<&'a str>, value: Option<&'a str>, } impl<'a> WireState<'a> { fn new(name: &'a str) -> Self { Self { name, space_then_id: None, value: None, } } } let mut variant_wires = [ WireState::new("A"), WireState::new("B"), WireState::new("C"), ]; // check that output .vcd has the proper values for all variants' wires for (is_last, line) in vcd.lines().map(|line| (false, line)).chain([(true, "")]) { if let Some(line) = line.strip_prefix("$var wire 8") && let Some(line) = line.strip_suffix(" $end") && let Some((space_then_id, state)) = variant_wires .iter_mut() .find_map(|state| Some((line.strip_suffix(state.name)?.strip_suffix(" ")?, state))) { assert_eq!(space_then_id.chars().next(), Some(' ')); assert!( space_then_id .chars() .skip(1) .all(|ch| matches!(ch, '!'..='~')) ); assert_eq!(state.space_then_id.replace(space_then_id), None); } else if line.starts_with("#") || is_last { let Some(expected_value) = variant_wires[0].value else { panic!( "variant {} hasn't been initialized before a timestamp or EOF: {variant_wires:#?}\n\ line={line:?}", variant_wires[0].name, ); }; for state in &variant_wires { assert_eq!( state.value, Some(expected_value), "at a timestamp or EOF: variant value for {} doesn't match expected value.\n\ {variant_wires:#?}\nline={line:?}", state.name, ); } } else if line.starts_with("b") { for state in &mut variant_wires { let Some(space_then_id) = state.space_then_id else { let name = state.name; panic!( "variant {name} hasn't had an id assigned yet: {variant_wires:#?}\n\ line={line:?}", ); }; if let Some(value) = line.strip_suffix(space_then_id) { state.value = Some(value); break; } } } } if vcd != include_str!("sim/expected/enum_with_simple_body.vcd") { panic!(); } let sim_debug = format!("{sim:#?}"); println!("#######\n{sim_debug}\n#######"); if sim_debug != include_str!("sim/expected/enum_with_simple_body.txt") { panic!(); } } #[hdl_module(outline_generated)] pub fn memories() { #[hdl] let r: ReadStruct<(UInt<8>, SInt<8>), ConstUsize<4>> = m.input(); #[hdl] let w: 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); #[hdl(cmp_eq)] struct IO { r_addr: UInt<4>, r_en: Bool, r_data: (UInt<8>, SInt<8>), w_addr: UInt<4>, w_en: Bool, w_data: (UInt<8>, SInt<8>), w_mask: (Bool, Bool), } let io_cycles = [ #[hdl(sim)] IO { r_addr: 0_hdl_u4, r_en: false, r_data: (0u8, 0i8), w_addr: 0_hdl_u4, w_en: false, w_data: (0u8, 0i8), w_mask: (false, false), }, #[hdl(sim)] IO { r_addr: 0_hdl_u4, r_en: true, r_data: (0x1u8, 0x23i8), w_addr: 0_hdl_u4, w_en: true, w_data: (0x10u8, 0x20i8), w_mask: (true, true), }, #[hdl(sim)] IO { r_addr: 0_hdl_u4, r_en: true, r_data: (0x10u8, 0x20i8), w_addr: 0_hdl_u4, w_en: true, w_data: (0x30u8, 0x40i8), w_mask: (false, true), }, #[hdl(sim)] IO { r_addr: 0_hdl_u4, r_en: true, r_data: (0x10u8, 0x40i8), w_addr: 0_hdl_u4, w_en: true, w_data: (0x50u8, 0x60i8), w_mask: (true, false), }, #[hdl(sim)] IO { r_addr: 0_hdl_u4, r_en: true, r_data: (0x50u8, 0x40i8), w_addr: 0_hdl_u4, w_en: true, w_data: (0x70u8, -0x80i8), w_mask: (false, false), }, #[hdl(sim)] IO { r_addr: 0_hdl_u4, r_en: true, r_data: (0x50u8, 0x40i8), w_addr: 0_hdl_u4, w_en: false, w_data: (0x90u8, 0xA0u8 as i8), w_mask: (false, false), }, #[hdl(sim)] IO { r_addr: 0_hdl_u4, r_en: true, r_data: (0x50u8, 0x40i8), w_addr: 1_hdl_u4, w_en: true, w_data: (0x90u8, 0xA0u8 as i8), w_mask: (true, true), }, #[hdl(sim)] IO { r_addr: 0_hdl_u4, r_en: true, r_data: (0x50u8, 0x40i8), w_addr: 2_hdl_u4, w_en: true, w_data: (0xB0u8, 0xC0u8 as i8), w_mask: (true, true), }, #[hdl(sim)] IO { r_addr: 0_hdl_u4, r_en: true, r_data: (0x50u8, 0x40i8), w_addr: 2_hdl_u4, w_en: false, w_data: (0xD0u8, 0xE0u8 as i8), w_mask: (true, true), }, #[hdl(sim)] IO { r_addr: 1_hdl_u4, r_en: true, r_data: (0x90u8, 0xA0u8 as i8), w_addr: 2_hdl_u4, w_en: false, w_data: (0xD0u8, 0xE0u8 as i8), w_mask: (true, true), }, #[hdl(sim)] IO { r_addr: 2_hdl_u4, r_en: true, r_data: (0xB0u8, 0xC0u8 as i8), w_addr: 2_hdl_u4, w_en: false, w_data: (0xD0u8, 0xE0u8 as i8), w_mask: (true, true), }, ]; 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; sim.write(sim.io().r.addr, r_addr); sim.write(sim.io().r.en, r_en); sim.write(sim.io().w.addr, w_addr); sim.write(sim.io().w.en, w_en); sim.write(sim.io().w.data, w_data); sim.write(sim.io().w.mask, w_mask); let io = #[hdl(sim)] IO { r_addr, r_en, r_data: sim.read(sim.io().r.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)); sim.write(sim.io().r.clk, true); sim.write(sim.io().w.clk, true); sim.advance_time(SimDuration::from_micros(1)); sim.write(sim.io().r.clk, false); sim.write(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: ReadWriteStruct, 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: ReadStruct, 8>, ConstUsize<3>> = m.input(); #[hdl] let w: WriteStruct, 8>, ConstUsize<3>> = m.input(); #[hdl] let mut mem: MemBuilder, 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 many_memories() { #[hdl] let r: Array>, 8> = m.input(); #[hdl] let w: Array>, 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::>(), 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, w_addr: UInt<4>, w_en: Bool, w_data: Array, w_mask: Array, } 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] 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, 16> = m.input(); #[hdl] let array_out: Array, 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(); m.extern_module_simulation_fn((i, o), |(i, o), mut sim| 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; } }); } #[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!(); } } #[hdl_module(outline_generated, extern)] pub fn extern_module2() { #[hdl] let en: Bool = m.input(); #[hdl] let clk: Clock = m.input(); #[hdl] let o: UInt<8> = m.output(); m.extern_module_simulation_fn((en, clk, o), |(en, clk, o), mut sim| async move { for b in "Hello, World!\n".bytes().cycle() { sim.write(o, b).await; loop { sim.wait_for_clock_edge(clk).await; if sim.read_bool(en).await { break; } } } }); } #[test] fn test_extern_module2() { let _n = SourceLocation::normalize_files_for_tests(); let mut sim = Simulation::new(extern_module2()); let mut writer = RcWriter::default(); sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); for i in 0..30 { sim.write(sim.io().en, i % 10 < 5); sim.write(sim.io().clk, false); sim.advance_time(SimDuration::from_micros(1)); sim.write(sim.io().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/extern_module2.vcd") { panic!(); } let sim_debug = format!("{sim:#?}"); println!("#######\n{sim_debug}\n#######"); if sim_debug != include_str!("sim/expected/extern_module2.txt") { panic!(); } } // use an extern module to simulate a register to test that the // simulator can handle chains of alternating circuits and extern modules. #[hdl_module(outline_generated, extern)] pub fn sw_reg() { #[hdl] let clk: Clock = m.input(); #[hdl] let o: Bool = m.output(); m.extern_module_simulation_fn((clk, o), |(clk, o), mut sim| async move { let mut state = false; loop { sim.write(o, state).await; sim.wait_for_clock_edge(clk).await; state = !state; } }); } #[hdl_module(outline_generated)] pub fn ripple_counter() { #[hdl] let clk: Clock = m.input(); #[hdl] let o: UInt<6> = m.output(); #[hdl] let bits: Array = wire(); connect_any(o, bits.cast_to_bits()); let mut clk_in = clk; for (i, bit) in bits.into_iter().enumerate() { if i % 2 == 0 { let bit_reg = reg_builder_with_loc(&format!("bit_reg_{i}"), SourceLocation::caller()) .clock_domain( #[hdl] ClockDomain { clk: clk_in, rst: false.to_sync_reset(), }, ) .no_reset(Bool) .build(); connect(bit, bit_reg); connect(bit_reg, !bit_reg); } else { let bit_reg = instance_with_loc(&format!("bit_reg_{i}"), sw_reg(), SourceLocation::caller()); connect(bit_reg.clk, clk_in); connect(bit, bit_reg.o); } clk_in = bit.to_clock(); } } #[test] fn test_ripple_counter() { let _n = SourceLocation::normalize_files_for_tests(); let mut sim = Simulation::new(ripple_counter()); let mut writer = RcWriter::default(); sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); for _ in 0..0x80 { sim.write(sim.io().clk, false); sim.advance_time(SimDuration::from_micros(1)); sim.write(sim.io().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/ripple_counter.vcd") { panic!(); } let sim_debug = format!("{sim:#?}"); println!("#######\n{sim_debug}\n#######"); if sim_debug != include_str!("sim/expected/ripple_counter.txt") { panic!(); } } /// use `Rc` to ensure you can use `!Send + !Sync` types type SimOnlyTestMap = BTreeMap>; #[hdl_module(outline_generated, extern)] fn sim_only_connects_helper() { #[hdl] let cd: ClockDomain = m.input(); #[hdl] let inp: SimOnly = m.input(); #[hdl] let out: SimOnly = m.output(); m.extern_module_simulation_fn((cd, inp, out), |(cd, inp, out), mut sim| async move { sim.write(out, SimOnlyValue::default()).await; loop { sim.wait_for_clock_edge(cd.clk).await; let mut map = sim.read(inp).await; let foo = map.get("foo").cloned().unwrap_or_default(); map.insert(String::from("bar"), foo); map.insert(String::from("foo"), Rc::from("baz")); sim.write(out, map).await; } }); } #[hdl_module(outline_generated)] pub fn sim_only_connects() { #[hdl] let cd: ClockDomain = m.input(); #[hdl] let inp: SimOnly = m.input(); #[hdl] let out1: SimOnly = m.output(); #[hdl] let out2: SimOnly = m.output(); #[hdl] let out3: SimOnly = m.output(); #[hdl] let helper1 = instance(sim_only_connects_helper()); #[hdl] let delay1: SimOnly = reg_builder() .clock_domain(cd) .reset(SimOnly::::new().uninit()); #[hdl] let delay1_empty: Bool = reg_builder().clock_domain(cd).reset(true); connect(helper1.cd, cd); connect(helper1.inp, delay1); connect(out1, delay1); #[hdl] if delay1_empty { connect(helper1.inp, inp); connect(out1, inp); } connect(delay1, inp); connect(delay1_empty, false); connect(out2, helper1.out); #[hdl] let helper2 = instance(sim_only_connects_helper()); connect(helper2.cd, cd); connect(helper2.inp, out2); connect(out3, helper2.out); } #[test] fn test_sim_only_connects() { let _n = SourceLocation::normalize_files_for_tests(); let mut sim = Simulation::new(sim_only_connects()); let mut writer = RcWriter::default(); sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); sim.write(sim.io().cd.rst, true); sim.write( sim.io().inp, SimOnlyValue::new(BTreeMap::from_iter([( String::from("extra"), Rc::from("value"), )])), ); for _ in 0..8 { sim.write(sim.io().cd.clk, false); sim.advance_time(SimDuration::from_micros(1)); sim.write(sim.io().cd.clk, true); sim.advance_time(SimDuration::from_micros(1)); sim.write(sim.io().cd.rst, false); } sim.flush_traces().unwrap(); let vcd = String::from_utf8(writer.take()).unwrap(); println!("####### VCD:\n{vcd}\n#######"); if vcd != include_str!("sim/expected/sim_only_connects.vcd") { panic!(); } let sim_debug = format!("{sim:#?}"); println!("#######\n{sim_debug}\n#######"); if sim_debug != include_str!("sim/expected/sim_only_connects.txt") { panic!(); } } #[hdl_module(outline_generated, extern)] pub fn sim_fork_join() where ConstUsize: KnownSize, { #[hdl] let clocks: Array = m.input(); #[hdl] let outputs: Array, N> = m.output(); m.extern_module_simulation_fn((clocks, outputs), |(clocks, outputs), mut sim| async move { sim.write(outputs, [0u8; N]).await; loop { sim.fork_join( clocks .into_iter() .zip(outputs) .map(|(clock, output)| { move |mut sim: ExternModuleSimulationState| async move { sim.wait_for_clock_edge(clock).await; let v = sim .read_bool_or_int(output) .await .to_bigint() .try_into() .expect("known to be in range"); sim.write(output, 1u8.wrapping_add(v)).await; } }) .collect::>(), ) .await; } }); } #[test] fn test_sim_fork_join() { let _n = SourceLocation::normalize_files_for_tests(); const N: usize = 3; let mut sim = Simulation::new(sim_fork_join::()); let mut writer = RcWriter::default(); sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); sim.write(sim.io().clocks, [false; N]); let mut clocks_triggered = [false; N]; let mut expected = [0u8; N]; for i0 in 0..N { for i1 in 0..N { for i2 in 0..N { for i3 in 0..N { let indexes = [i0, i1, i2, i3]; for i in indexes { sim.advance_time(SimDuration::from_micros(1)); sim.write(sim.io().clocks[i], true); sim.advance_time(SimDuration::from_micros(1)); sim.write(sim.io().clocks[i], false); if !clocks_triggered[i] { expected[i] = expected[i].wrapping_add(1); } clocks_triggered[i] = true; if clocks_triggered == [true; N] { clocks_triggered = [false; N]; } let output = sim.read(sim.io().outputs); assert_eq!(output, expected.to_sim_value(), "indexes={indexes:?} i={i}"); } } } } } sim.flush_traces().unwrap(); let vcd = String::from_utf8(writer.take()).unwrap(); println!("####### VCD:\n{vcd}\n#######"); if vcd != include_str!("sim/expected/sim_fork_join.vcd") { panic!(); } let sim_debug = format!("{sim:#?}"); println!("#######\n{sim_debug}\n#######"); if sim_debug != include_str!("sim/expected/sim_fork_join.txt") { panic!(); } } #[hdl_module(outline_generated, extern)] pub fn sim_fork_join_scope() where ConstUsize: KnownSize, { #[hdl] let clocks: Array = m.input(); #[hdl] let outputs: Array, N> = m.output(); m.extern_module_simulation_fn((clocks, outputs), |(clocks, outputs), mut sim| async move { sim.write(outputs, [0u8; N]).await; loop { let written = vec![std::cell::Cell::new(false); N]; // test shared scope let written = &written; // work around move in async move sim.fork_join_scope(|scope, _| async move { let mut spawned = vec![]; for i in 0..N { let join_handle = scope.spawn(move |_, mut sim: ExternModuleSimulationState| async move { sim.wait_for_clock_edge(clocks[i]).await; let v = sim .read_bool_or_int(outputs[i]) .await .to_bigint() .try_into() .expect("known to be in range"); sim.write(outputs[i], 1u8.wrapping_add(v)).await; written[i].set(true); i }); if i % 2 == 0 && i < N - 1 { spawned.push((i, join_handle)); } } for (i, join_handle) in spawned { assert_eq!(i, join_handle.join().await); } }) .await; for written in written { assert!(written.get()); } } }); } #[test] fn test_sim_fork_join_scope() { let _n = SourceLocation::normalize_files_for_tests(); const N: usize = 3; let mut sim = Simulation::new(sim_fork_join_scope::()); let mut writer = RcWriter::default(); sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); sim.write(sim.io().clocks, [false; N]); let mut clocks_triggered = [false; N]; let mut expected = [0u8; N]; for i0 in 0..N { for i1 in 0..N { for i2 in 0..N { for i3 in 0..N { let indexes = [i0, i1, i2, i3]; for i in indexes { sim.advance_time(SimDuration::from_micros(1)); sim.write(sim.io().clocks[i], true); sim.advance_time(SimDuration::from_micros(1)); sim.write(sim.io().clocks[i], false); if !clocks_triggered[i] { expected[i] = expected[i].wrapping_add(1); } clocks_triggered[i] = true; if clocks_triggered == [true; N] { clocks_triggered = [false; N]; } let output = sim.read(sim.io().outputs); assert_eq!(output, expected.to_sim_value(), "indexes={indexes:?} i={i}"); } } } } } sim.flush_traces().unwrap(); let vcd = String::from_utf8(writer.take()).unwrap(); println!("####### VCD:\n{vcd}\n#######"); if vcd != include_str!("sim/expected/sim_fork_join_scope.vcd") { panic!(); } let sim_debug = format!("{sim:#?}"); println!("#######\n{sim_debug}\n#######"); if sim_debug != include_str!("sim/expected/sim_fork_join_scope.txt") { panic!(); } } #[hdl_module(outline_generated, extern)] pub fn sim_resettable_counter() { #[hdl] let cd: ClockDomain = m.input(); #[hdl] let out: UInt<8> = m.output(); m.extern_module_simulation_fn((cd, out), |(cd, out), mut sim| async move { sim.resettable( cd, |mut sim: ExternModuleSimulationState| async move { sim.write(out, 0u8).await; }, |mut sim: ExternModuleSimulationState, ()| async move { loop { sim.wait_for_clock_edge(cd.clk).await; let v: u8 = sim .read(out) .await .to_bigint() .try_into() .expect("known to be in range"); sim.write(out, v.wrapping_add(1)).await; } }, ) .await }); } fn test_sim_resettable_counter_helper( sim: &mut Simulation>, immediate_reset: bool, ) { sim.write_clock(sim.io().cd.clk, false); sim.write_reset(sim.io().cd.rst, immediate_reset); for _ in 0..2 { sim.advance_time(SimDuration::from_micros(1)); sim.write_clock(sim.io().cd.clk, true); sim.advance_time(SimDuration::from_micros(1)); 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.advance_time(SimDuration::from_micros(1)); sim.write_clock(sim.io().cd.clk, false); sim.write_reset(sim.io().cd.rst, false); for expected in 0..3u8 { assert_eq!(sim.read(sim.io().out), expected.to_sim_value()); sim.advance_time(SimDuration::from_micros(1)); sim.write_clock(sim.io().cd.clk, true); sim.advance_time(SimDuration::from_micros(1)); sim.write_clock(sim.io().cd.clk, false); } } } #[test] fn test_sim_resettable_counter_sync() { let _n = SourceLocation::normalize_files_for_tests(); let mut sim = Simulation::new(sim_resettable_counter::()); let mut writer = RcWriter::default(); sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); test_sim_resettable_counter_helper(&mut sim, false); sim.flush_traces().unwrap(); let vcd = String::from_utf8(writer.take()).unwrap(); println!("####### VCD:\n{vcd}\n#######"); if vcd != include_str!("sim/expected/sim_resettable_counter_sync.vcd") { panic!(); } let sim_debug = format!("{sim:#?}"); println!("#######\n{sim_debug}\n#######"); if sim_debug != include_str!("sim/expected/sim_resettable_counter_sync.txt") { panic!(); } } #[test] fn test_sim_resettable_counter_sync_immediate_reset() { let _n = SourceLocation::normalize_files_for_tests(); let mut sim = Simulation::new(sim_resettable_counter::()); let mut writer = RcWriter::default(); sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); test_sim_resettable_counter_helper(&mut sim, true); sim.flush_traces().unwrap(); let vcd = String::from_utf8(writer.take()).unwrap(); println!("####### VCD:\n{vcd}\n#######"); if vcd != include_str!("sim/expected/sim_resettable_counter_sync_immediate_reset.vcd") { panic!(); } let sim_debug = format!("{sim:#?}"); println!("#######\n{sim_debug}\n#######"); if sim_debug != include_str!("sim/expected/sim_resettable_counter_sync_immediate_reset.txt") { panic!(); } } #[test] fn test_sim_resettable_counter_async() { let _n = SourceLocation::normalize_files_for_tests(); let mut sim = Simulation::new(sim_resettable_counter::()); let mut writer = RcWriter::default(); sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); test_sim_resettable_counter_helper(&mut sim, false); sim.flush_traces().unwrap(); let vcd = String::from_utf8(writer.take()).unwrap(); println!("####### VCD:\n{vcd}\n#######"); if vcd != include_str!("sim/expected/sim_resettable_counter_async.vcd") { panic!(); } let sim_debug = format!("{sim:#?}"); println!("#######\n{sim_debug}\n#######"); if sim_debug != include_str!("sim/expected/sim_resettable_counter_async.txt") { panic!(); } } #[test] fn test_sim_resettable_counter_async_immediate_reset() { let _n = SourceLocation::normalize_files_for_tests(); let mut sim = Simulation::new(sim_resettable_counter::()); let mut writer = RcWriter::default(); sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); test_sim_resettable_counter_helper(&mut sim, true); sim.flush_traces().unwrap(); let vcd = String::from_utf8(writer.take()).unwrap(); println!("####### VCD:\n{vcd}\n#######"); if vcd != include_str!("sim/expected/sim_resettable_counter_async_immediate_reset.vcd") { panic!(); } let sim_debug = format!("{sim:#?}"); println!("#######\n{sim_debug}\n#######"); if sim_debug != include_str!("sim/expected/sim_resettable_counter_async_immediate_reset.txt") { panic!(); } } #[hdl_module(outline_generated)] pub fn phantom_const() { #[hdl] let out: Array>, 2> = m.output(Array::new_static(PhantomConst::new_sized(vec![ "a".into(), "b".into(), ]))); let _ = out; #[hdl] let mut mem = memory(PhantomConst::new("mem_element")); mem.depth(1); let port = mem.new_read_port(); connect_any(port.addr, 0u8); connect(port.clk, false.to_clock()); connect(port.en, false); } #[test] fn test_phantom_const() { let _n = SourceLocation::normalize_files_for_tests(); let mut sim = Simulation::new(phantom_const()); 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/phantom_const.vcd") { panic!(); } let sim_debug = format!("{sim:#?}"); println!("#######\n{sim_debug}\n#######"); if sim_debug != include_str!("sim/expected/phantom_const.txt") { panic!(); } } #[hdl_module(outline_generated, extern)] pub fn sim_read_past() where ConstUsize: KnownSize, { #[hdl] let clocks: Array = m.input(); #[hdl] let outputs: Array, N> = m.output(); #[hdl] let past_clocks: Array = m.output(); #[hdl] let past_outputs: Array, N> = m.output(); for clock in clocks { m.register_clock_for_past(clock); } m.extern_module_simulation_fn( (clocks, outputs, past_clocks, past_outputs), |(clocks, outputs, past_clocks, past_outputs), mut sim| async move { sim.write(outputs, [0u8; N]).await; sim.write(past_clocks, [false; N]).await; sim.write(past_outputs, [0u8; N]).await; loop { sim.fork_join_scope(|scope, _| async move { for (clock, output) in clocks.into_iter().zip(outputs) { scope.spawn_detached( move |_, mut sim: ExternModuleSimulationState| async move { sim.wait_for_clock_edge(clock).await; dbg!(clock); let v = sim .read_bool_or_int(output) .await .to_bigint() .try_into() .expect("known to be in range"); sim.write(output, 1u8.wrapping_add(v)).await; let past_outputs_v = sim.read_past(outputs, clock).await; dbg!(&past_outputs_v); sim.write(past_outputs, past_outputs_v).await; let past_clocks_v = sim.read_past(clocks, clock).await; dbg!(&past_clocks_v); sim.write(past_clocks, past_clocks_v).await; }, ); } }) .await; } }, ); } #[test] fn test_sim_read_past() { let _n = SourceLocation::normalize_files_for_tests(); const N: usize = 3; let mut sim = Simulation::new(sim_read_past::()); // sim.set_breakpoints_unstable(Default::default(), true); let mut writer = RcWriter::default(); sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); sim.write(sim.io().clocks, [false; N]); let mut clocks_triggered = [false; N]; let mut expected = [0u8; N]; let mut past_clocks_expected = [false; N]; let mut past_expected = expected; for i0 in 0..N { for i1 in 0..N { for i2 in 0..N { for i3 in 0..N { let indexes = [i0, i1, i2, i3]; for i in indexes { sim.advance_time(SimDuration::from_micros(1)); sim.write(sim.io().clocks[i], true); sim.advance_time(SimDuration::from_micros(1)); sim.write(sim.io().clocks[i], false); if !clocks_triggered[i] { past_expected = expected; expected[i] = expected[i].wrapping_add(1); past_clocks_expected = [false; N]; past_clocks_expected[i] = true; } dbg!(past_expected); clocks_triggered[i] = true; if clocks_triggered == [true; N] { clocks_triggered = [false; N]; } let output = sim.read(sim.io().outputs); assert_eq!(output, expected.to_sim_value(), "indexes={indexes:?} i={i}"); let past_clocks = sim.read(sim.io().past_clocks); assert_eq!( past_clocks, past_clocks_expected .to_sim_value_with_type(Array::::default()), "indexes={indexes:?} i={i}" ); let past_outputs = sim.read(sim.io().past_outputs); dbg!(&past_outputs); assert_eq!( past_outputs, past_expected.to_sim_value(), "indexes={indexes:?} i={i}" ); } } } } } sim.flush_traces().unwrap(); let vcd = String::from_utf8(writer.take()).unwrap(); println!("####### VCD:\n{vcd}\n#######"); if vcd != include_str!("sim/expected/sim_read_past.vcd") { panic!(); } let sim_debug = format!("{sim:#?}"); println!("#######\n{sim_debug}\n#######"); if sim_debug != include_str!("sim/expected/sim_read_past.txt") { panic!(); } } #[hdl_module(outline_generated)] pub fn last_connect() { #[hdl] let inp: HdlOption> = m.input(); #[hdl] let out: HdlOption> = m.output(); connect(out, HdlNone()); #[hdl] if let HdlSome(v) = inp { #[hdl] let w = wire(); connect(out, HdlSome(w)); connect(w, v.len() as u8); for (i, v) in v.into_iter().enumerate() { #[hdl] if v { connect(w, i as u8); } } } } #[hdl] #[test] fn test_last_connect() { let _n = SourceLocation::normalize_files_for_tests(); let mut sim = Simulation::new(last_connect()); let mut writer = RcWriter::default(); sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); let bools = [false, true]; sim.write(sim.io().inp, HdlNone()); sim.advance_time(SimDuration::from_micros(1)); let expected: SimValue>> = #[hdl(sim)] HdlNone(); assert_eq!(sim.read(sim.io().out), expected); for a in bools { for b in bools { for c in bools { for d in bools { let inp = [a, b, c, d]; sim.write(sim.io().inp, HdlSome(inp)); sim.advance_time(SimDuration::from_micros(1)); let mut expected = inp.len() as u8; for (i, v) in inp.into_iter().enumerate() { if v { expected = i as u8; } } let expected: SimValue>> = #[hdl(sim)] HdlSome(expected); let out = sim.read(sim.io().out); println!("expected={expected:?} out={out:?} inp={inp:?}"); assert_eq!(expected, out); } } } } sim.flush_traces().unwrap(); let vcd = String::from_utf8(writer.take()).unwrap(); println!("####### VCD:\n{vcd}\n#######"); if vcd != include_str!("sim/expected/last_connect.vcd") { panic!(); } let sim_debug = format!("{sim:#?}"); println!("#######\n{sim_debug}\n#######"); if sim_debug != include_str!("sim/expected/last_connect.txt") { panic!(); } } #[track_caller] #[hdl] fn test_queue_helper( capacity: usize, inp_ready_is_comb: bool, out_valid_is_comb: bool, expected_vcd: &str, expected_sim_debug: &str, ) { let _n = SourceLocation::normalize_files_for_tests(); let mut sim = Simulation::new(queue( UInt::<8>::new_static(), NonZeroUsize::new(capacity).expect("capacity should be non-zero"), inp_ready_is_comb, out_valid_is_comb, )); let writer = RcWriter::default(); sim.add_trace_writer(VcdWriterDecls::new(writer.clone())); struct DumpVcdOnDrop { writer: Option, } impl Drop for DumpVcdOnDrop { fn drop(&mut self) { if let Some(mut writer) = self.writer.take() { let vcd = String::from_utf8(writer.take()).unwrap(); println!("####### VCD:\n{vcd}\n#######"); } } } let mut writer = DumpVcdOnDrop { writer: Some(writer), }; sim.write_clock(sim.io().cd.clk, false); sim.write_reset(sim.io().cd.rst, true); let mut input_value = 0u8; let mut expected_output_value = 0u8; /// deterministic random numbers fn rand(mut v: u32) -> bool { // random 32-bit primes v = v.wrapping_mul(0xF807B7EF).rotate_left(16); v ^= 0xA1E24BBA; // random 32-bit constant v = v.wrapping_mul(0xE9D30017).rotate_left(16); v = v.wrapping_mul(0x3895AFFB).rotate_left(16); v & 1 != 0 } for cycle in 0..100u32 { println!("cycle: {cycle}"); sim.write( sim.io().inp.data, if rand(cycle) { #[hdl(sim)] HdlSome(input_value) } else { #[hdl(sim)] HdlNone() }, ); sim.write_bool(sim.io().out.ready, rand(u32::MAX / 2 + cycle)); sim.advance_time(SimDuration::from_nanos(500)); if !sim.read_reset(sim.io().cd.rst) { let inp_ready = sim.read_bool(sim.io().inp.ready); if inp_ready { #[hdl(sim)] if let HdlSome(v) = sim.read(sim.io().inp.data) { println!("enqueued {v}, expected {input_value:#x}"); assert_eq!(v.as_int(), input_value); input_value = input_value.wrapping_add(1); } } let out_valid = #[hdl(sim)] if let HdlSome(v) = sim.read(sim.io().out.data) { if sim.read_bool(sim.io().out.ready) { println!("dequeued {v}, expected {expected_output_value:#x}"); assert_eq!(v.as_int(), expected_output_value); expected_output_value = expected_output_value.wrapping_add(1); } true } else { false }; assert!(inp_ready || out_valid, "queue isn't making progress"); } sim.write_clock(sim.io().cd.clk, true); sim.advance_time(SimDuration::from_nanos(500)); sim.write_clock(sim.io().cd.clk, false); sim.write_reset(sim.io().cd.rst, false); } sim.flush_traces().unwrap(); let vcd = String::from_utf8(writer.writer.take().unwrap().take()).unwrap(); println!("####### VCD:\n{vcd}\n#######"); if vcd != expected_vcd { panic!(); } let sim_debug = format!("{sim:#?}"); println!("#######\n{sim_debug}\n#######"); if sim_debug != expected_sim_debug { panic!(); } } #[test] fn test_queue_1_false_false() { test_queue_helper( 1, false, false, include_str!("sim/expected/queue_1_false_false.vcd"), include_str!("sim/expected/queue_1_false_false.txt"), ); } #[test] fn test_queue_1_false_true() { test_queue_helper( 1, false, true, include_str!("sim/expected/queue_1_false_true.vcd"), include_str!("sim/expected/queue_1_false_true.txt"), ); } #[test] fn test_queue_1_true_false() { test_queue_helper( 1, true, false, include_str!("sim/expected/queue_1_true_false.vcd"), include_str!("sim/expected/queue_1_true_false.txt"), ); } #[test] fn test_queue_1_true_true() { test_queue_helper( 1, true, true, include_str!("sim/expected/queue_1_true_true.vcd"), include_str!("sim/expected/queue_1_true_true.txt"), ); } #[test] fn test_queue_2_false_false() { test_queue_helper( 2, false, false, include_str!("sim/expected/queue_2_false_false.vcd"), include_str!("sim/expected/queue_2_false_false.txt"), ); } #[test] fn test_queue_2_false_true() { test_queue_helper( 2, false, true, include_str!("sim/expected/queue_2_false_true.vcd"), include_str!("sim/expected/queue_2_false_true.txt"), ); } #[test] fn test_queue_2_true_false() { test_queue_helper( 2, true, false, include_str!("sim/expected/queue_2_true_false.vcd"), include_str!("sim/expected/queue_2_true_false.txt"), ); } #[test] fn test_queue_2_true_true() { test_queue_helper( 2, true, true, include_str!("sim/expected/queue_2_true_true.vcd"), include_str!("sim/expected/queue_2_true_true.txt"), ); } #[test] fn test_queue_3_false_false() { test_queue_helper( 3, false, false, include_str!("sim/expected/queue_3_false_false.vcd"), include_str!("sim/expected/queue_3_false_false.txt"), ); } #[test] fn test_queue_3_false_true() { test_queue_helper( 3, false, true, include_str!("sim/expected/queue_3_false_true.vcd"), include_str!("sim/expected/queue_3_false_true.txt"), ); } #[test] fn test_queue_3_true_false() { test_queue_helper( 3, true, false, include_str!("sim/expected/queue_3_true_false.vcd"), include_str!("sim/expected/queue_3_true_false.txt"), ); } #[test] fn test_queue_3_true_true() { test_queue_helper( 3, true, true, include_str!("sim/expected/queue_3_true_true.vcd"), include_str!("sim/expected/queue_3_true_true.txt"), ); } #[test] fn test_queue_4_false_false() { test_queue_helper( 4, false, false, include_str!("sim/expected/queue_4_false_false.vcd"), include_str!("sim/expected/queue_4_false_false.txt"), ); } #[test] fn test_queue_4_false_true() { test_queue_helper( 4, false, true, include_str!("sim/expected/queue_4_false_true.vcd"), include_str!("sim/expected/queue_4_false_true.txt"), ); } #[test] fn test_queue_4_true_false() { test_queue_helper( 4, true, false, include_str!("sim/expected/queue_4_true_false.vcd"), include_str!("sim/expected/queue_4_true_false.txt"), ); } #[test] fn test_queue_4_true_true() { test_queue_helper( 4, true, true, include_str!("sim/expected/queue_4_true_true.vcd"), include_str!("sim/expected/queue_4_true_true.txt"), ); }