// SPDX-License-Identifier: LGPL-3.0-or-later // See Notices.txt for copyright information use fayalite::{ int::UIntValue, memory::{ReadStruct, ReadWriteStruct, WriteStruct}, module::{instance_with_loc, reg_builder_with_loc}, prelude::*, reset::ResetType, sim::{time::SimDuration, vcd::VcdWriterDecls, Simulation}, 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(); 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_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 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!(); } }