// 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, Simulation, ToSimValue}, ty::StaticType, util::RcWriter, }; use std::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() { #[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(); #[hdl] let b_out: HdlOption<(UInt<1>, Bool)> = m.output(); #[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, 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(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, } 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, 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, 8>, ConstUsize<3>> = m.input(); #[hdl] let w: fayalite::memory::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 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!(); } }