1use std::collections::BTreeMap;
4use std::fs::File;
5use std::io::{self, Write};
6use std::path::{Path, PathBuf};
7
8use bitloom_hir::{AssignExpr, AssignTarget, FrozenHir, GroundType, PortValues, ProcessKind, Stmt};
9
10pub use bitloom_hir::PortValues as Values;
11
12mod coverage;
13pub use coverage::{
14 Coverage, parse_branch_report, parse_report, parse_state_report, write_coverage_artifacts,
15};
16mod engine;
17pub use engine::TickEngine;
18mod equiv;
19pub use equiv::{
20 EquivStatus, check_functional_equiv, check_functional_equiv_generated, reset_then_run,
21};
22
23mod shared_stimulus;
24pub use shared_stimulus::SharedStimulusScoreboard;
25
26mod formal_equiv;
27pub use formal_equiv::FormalEquivProduct;
28
29mod ip_dual;
30pub use ip_dual::{
31 Axi4LiteSlaveFunctional, GpioFunctional, I2cMasterFunctional, IpDualModelMatrix,
32 SpiMasterFunctional, SyncFifoFunctional, UartRxFunctional, UartTxFunctional,
33 axi4_lite_slave_dual_stimulus, gpio_dual_stimulus, i2c_master_dual_stimulus,
34 spi_master_dual_stimulus, sync_fifo_dual_stimulus, uart_rx_dual_stimulus,
35 uart_tx_dual_stimulus,
36};
37
38mod generate;
39pub use generate::{
40 GeneratedFunctional, emit_functional_crate, generate_functional_sim,
41 generate_functional_sim_with_bin,
42};
43
44mod cycle;
45pub use cycle::{
46 CycleAccurateSim, check_generated_bridge, check_generated_bridge_with,
47 emit_cycle_accurate_crate, generate_cycle_accurate_sim,
48};
49
50mod fst;
51pub use fst::{FstError, resolve_vcd2fst};
52
53mod systemc_tlm;
54pub use systemc_tlm::{
55 SYSTEMC_PIN_VERSION, SystemcToolchain, build_and_run_tlm_lt_smoke, emit_systemc_tlm_lt,
56 generate_systemc_tlm_lt, resolve_systemc,
57};
58mod systemc_tlm_at;
59pub use systemc_tlm_at::{
60 build_and_run_tlm_at_smoke, emit_systemc_tlm_at, generate_systemc_tlm_at,
61};
62
63pub struct Sim {
65 hir: FrozenHir,
66 regs: BTreeMap<String, u64>,
67 mems: BTreeMap<String, Vec<u64>>,
68 pending_mem_reads: BTreeMap<String, u64>,
70 ports: PortValues,
71 time: u64,
72 vcd: Option<VcdWriter>,
73 fst: Option<FstPlan>,
74 engine: TickEngine,
75 kernel: engine::CompiledKernel,
76 coverage: Coverage,
77}
78
79struct FstPlan {
80 converter: PathBuf,
81 vcd: PathBuf,
82 fst: PathBuf,
83}
84
85struct VcdWriter {
86 file: File,
87 vars: Vec<String>,
88}
89
90impl Sim {
91 pub fn new(hir: FrozenHir) -> Self {
92 Self::with_engine(hir, TickEngine::Interpreter)
93 }
94
95 pub fn with_engine(hir: FrozenHir, engine: TickEngine) -> Self {
96 let mut regs = BTreeMap::new();
97 let mut mems = BTreeMap::new();
98 for m in &hir.circuit().modules {
99 for stmt in &m.body {
100 match stmt {
101 Stmt::RegDecl { name, .. } => {
102 regs.insert(name.clone(), 0);
103 }
104 Stmt::MemDecl {
105 name,
106 depth,
107 width,
108 init,
109 ..
110 } => {
111 let _ = width;
112 let words = match init {
113 Some(v) => v.clone(),
114 None => vec![0; *depth as usize],
115 };
116 mems.insert(name.clone(), words);
117 }
118 _ => {}
119 }
120 }
121 }
122 let kernel = engine::compile(&hir);
123 Self {
124 hir,
125 regs,
126 mems,
127 pending_mem_reads: BTreeMap::new(),
128 ports: PortValues::default(),
129 time: 0,
130 vcd: None,
131 fst: None,
132 engine,
133 kernel,
134 coverage: Coverage::default(),
135 }
136 }
137
138 pub fn engine(&self) -> TickEngine {
139 self.engine
140 }
141
142 pub fn enable_vcd(&mut self, path: impl AsRef<Path>) -> io::Result<()> {
143 let mut file = File::create(path)?;
144 writeln!(file, "$timescale 1ns $end")?;
145 writeln!(file, "$scope module {} $end", self.hir.abi_name)?;
146 let mut vars = Vec::new();
147 if let Some(m) = self.hir.circuit().modules.first() {
148 for p in &m.ports {
149 let w = match &p.ty {
150 GroundType::UInt { width } | GroundType::SInt { width } => *width,
151 _ => 1,
152 };
153 writeln!(file, "$var wire {w} {} {} $end", p.name, p.name)?;
154 vars.push(p.name.clone());
155 }
156 for stmt in &m.body {
157 if let Stmt::RegDecl { name, ty, .. } = stmt {
158 let w = match ty {
159 GroundType::UInt { width } | GroundType::SInt { width } => *width,
160 _ => 1,
161 };
162 writeln!(file, "$var reg {w} {name} {name} $end")?;
163 vars.push(name.clone());
164 }
165 }
166 }
167 writeln!(file, "$upscope $end")?;
168 writeln!(file, "$enddefinitions $end")?;
169 self.vcd = Some(VcdWriter { file, vars });
170 self.dump_vcd()?;
171 Ok(())
172 }
173
174 pub fn enable_fst(&mut self, path: impl AsRef<Path>) -> Result<(), FstError> {
176 let converter = resolve_vcd2fst()?;
177 self.enable_fst_with(path, converter)
178 }
179
180 pub fn enable_fst_with(
182 &mut self,
183 path: impl AsRef<Path>,
184 converter: impl AsRef<Path>,
185 ) -> Result<(), FstError> {
186 let fst = path.as_ref().to_path_buf();
187 let vcd = fst.with_extension("vcd");
188 self.enable_vcd(&vcd)?;
189 self.fst = Some(FstPlan {
190 converter: converter.as_ref().to_path_buf(),
191 vcd,
192 fst,
193 });
194 Ok(())
195 }
196
197 pub fn finish_waves(&mut self) -> Result<(), FstError> {
199 if let Some(mut w) = self.vcd.take() {
200 w.file.flush()?;
201 }
202 if let Some(plan) = self.fst.take() {
203 fst::convert_vcd_to_fst(&plan.converter, &plan.vcd, &plan.fst)?;
204 }
205 Ok(())
206 }
207
208 fn dump_vcd(&mut self) -> io::Result<()> {
209 let Some(vcd) = self.vcd.as_mut() else {
210 return Ok(());
211 };
212 writeln!(vcd.file, "#{}", self.time)?;
213 for name in &vcd.vars {
214 let val = self
215 .ports
216 .get(name)
217 .or_else(|| self.regs.get(name).copied())
218 .unwrap_or(0);
219 writeln!(vcd.file, "b{val:b} {name}")?;
220 }
221 Ok(())
222 }
223
224 pub fn set_inputs(&mut self, inputs: PortValues) {
225 for (k, v) in inputs.values {
226 self.ports.set(k, v);
227 }
228 }
229
230 pub fn ports(&self) -> &PortValues {
231 &self.ports
232 }
233
234 fn lookup(&self, name: &str) -> u64 {
235 self.ports
236 .get(name)
237 .or_else(|| self.regs.get(name).copied())
238 .unwrap_or(0)
239 }
240
241 fn eval(&mut self, expr: &AssignExpr) -> u64 {
242 match expr {
243 AssignExpr::Ref(n) => self.lookup(n),
244 AssignExpr::Lit(v) => *v,
245 AssignExpr::Inc(n) => self.lookup(n).wrapping_add(1),
246 AssignExpr::Add(a, b) => self.lookup(a).wrapping_add(self.lookup(b)),
247 AssignExpr::Sub(a, b) => self.lookup(a).wrapping_sub(self.lookup(b)),
248 AssignExpr::And(a, b) => self.lookup(a) & self.lookup(b),
249 AssignExpr::Or(a, b) => self.lookup(a) | self.lookup(b),
250 AssignExpr::Xor(a, b) => self.lookup(a) ^ self.lookup(b),
251 AssignExpr::Shl(a, b) => self.lookup(a) << (self.lookup(b) & 63),
252 AssignExpr::Shr(a, b) => self.lookup(a) >> (self.lookup(b) & 63),
253 AssignExpr::Eq(a, b) => u64::from(self.lookup(a) == self.lookup(b)),
254 AssignExpr::Mux { sel, t, f } => {
255 let took_true = self.lookup(sel) != 0;
256 self.coverage.sample_mux_branch(sel, took_true);
257 if took_true {
258 self.lookup(t)
259 } else {
260 self.lookup(f)
261 }
262 }
263 AssignExpr::MemRead { mem, addr } => {
264 let a = self.lookup(addr) as usize;
265 self.mems
266 .get(mem)
267 .and_then(|m| m.get(a).copied())
268 .unwrap_or(0)
269 }
270 }
271 }
272
273 fn reg_meta(&self, name: &str) -> (bool, bool) {
274 for m in &self.hir.circuit().modules {
275 for stmt in &m.body {
276 if let Stmt::RegDecl {
277 name: n,
278 async_reset,
279 has_enable,
280 ..
281 } = stmt
282 {
283 if n == name {
284 return (*async_reset, *has_enable);
285 }
286 }
287 }
288 }
289 (false, false)
290 }
291
292 fn mem_is_sync(&self, name: &str) -> bool {
293 for m in &self.hir.circuit().modules {
294 for stmt in &m.body {
295 if let Stmt::MemDecl {
296 name: n, sync_read, ..
297 } = stmt
298 {
299 if n == name {
300 return *sync_read;
301 }
302 }
303 }
304 }
305 false
306 }
307
308 fn enable_active(&self) -> bool {
309 let has_en_port = self
310 .hir
311 .circuit()
312 .modules
313 .iter()
314 .any(|m| m.ports.iter().any(|p| p.name == "en"));
315 if has_en_port {
316 self.lookup("en") != 0
317 } else {
318 true
319 }
320 }
321
322 fn reset_active(&self, m: &bitloom_hir::Module) -> bool {
323 let rst = m
324 .ports
325 .iter()
326 .find(|p| matches!(p.ty, GroundType::Reset))
327 .map(|p| p.name.as_str())
328 .unwrap_or("rst");
329 self.lookup(rst) != 0
330 }
331
332 pub fn tick(&mut self) {
334 match self.engine {
335 TickEngine::Interpreter => self.tick_interpreter(),
336 TickEngine::Compiled => self.tick_compiled(),
337 }
338 self.sample_coverage();
339 self.time += 1;
340 let _ = self.dump_vcd();
341 }
342
343 fn sample_coverage(&mut self) {
344 let names: Vec<String> = {
345 let mut n = Vec::new();
346 if let Some(m) = self.hir.circuit().modules.first() {
347 for p in &m.ports {
348 n.push(p.name.clone());
349 }
350 for stmt in &m.body {
351 if let Stmt::RegDecl { name, .. } = stmt {
352 n.push(name.clone());
353 }
354 }
355 }
356 n
357 };
358 for name in names {
359 let val = self.lookup(&name);
360 self.coverage.sample(name, val);
361 }
362 }
363
364 pub fn coverage_report(&self) -> String {
365 self.coverage.report()
366 }
367
368 pub fn coverage(&self) -> &Coverage {
370 &self.coverage
371 }
372
373 pub fn write_coverage_artifacts(&self, out_dir: &Path) -> io::Result<(PathBuf, PathBuf)> {
375 write_coverage_artifacts(&self.coverage, out_dir)
376 }
377
378 pub fn register_fsm_states<I, S>(&mut self, fsm: &str, states: I)
380 where
381 I: IntoIterator<Item = S>,
382 S: Into<String>,
383 {
384 self.coverage.register_fsm_states(fsm, states);
385 }
386
387 pub fn sample_state_visit(&mut self, fsm: &str, state: &str) {
389 self.coverage.sample_state_visit(fsm, state);
390 }
391
392 fn tick_interpreter(&mut self) {
393 self.tick_sequential();
394 self.tick_combinational();
395 }
396
397 pub fn settle(&mut self) {
402 self.tick_combinational();
403 }
404
405 fn tick_compiled(&mut self) {
406 if self.mems.is_empty()
408 && !self.hir.circuit().modules.iter().any(|m| {
409 m.body.iter().any(|s| {
410 matches!(
411 s,
412 Stmt::RegDecl {
413 has_enable: true,
414 ..
415 }
416 )
417 })
418 })
419 {
420 let Some(m) = self.hir.circuit().modules.first().cloned() else {
421 return;
422 };
423 let reset = self.reset_active(&m);
424 let seq = self.kernel.seq.clone();
425 let comb = self.kernel.comb.clone();
426 let mut next_regs: BTreeMap<String, u64> = BTreeMap::new();
428 for (name, expr) in seq {
429 let next = if reset { 0 } else { self.eval(&expr) };
430 next_regs.insert(name, next);
431 }
432 for (name, val) in next_regs {
433 self.regs.insert(name, val);
434 }
435 for (name, expr) in comb {
436 let val = self.eval(&expr);
437 self.ports.set(name, val);
438 }
439 return;
440 }
441 self.tick_interpreter();
442 }
443
444 fn tick_sequential(&mut self) {
445 let Some(m) = self.hir.circuit().modules.first().cloned() else {
446 return;
447 };
448 let reset = self.reset_active(&m);
449 let enable = self.enable_active();
450
451 let pending = std::mem::take(&mut self.pending_mem_reads);
453 for (name, val) in pending {
454 if reset {
455 self.regs.insert(name, 0);
456 } else {
457 self.regs.insert(name, val);
458 }
459 }
460
461 let mut next_pending = BTreeMap::new();
462 let mut next_regs: BTreeMap<String, u64> = BTreeMap::new();
465 for stmt in &m.body {
466 if let Stmt::Process(p) = stmt {
467 if p.kind != ProcessKind::Sequential {
468 continue;
469 }
470 for a in &p.assigns {
471 match &a.target {
472 AssignTarget::RegD(name) => {
473 let (_async_rst, has_en) = self.reg_meta(name);
474 if reset {
475 next_regs.insert(name.clone(), 0);
476 continue;
477 }
478 if has_en && !enable {
479 continue;
480 }
481 match &a.expr {
482 AssignExpr::MemRead { mem, addr } if self.mem_is_sync(mem) => {
483 let val = self.eval(&AssignExpr::MemRead {
484 mem: mem.clone(),
485 addr: addr.clone(),
486 });
487 next_pending.insert(name.clone(), val);
488 }
489 _ => {
490 let next = self.eval(&a.expr);
491 next_regs.insert(name.clone(), next);
492 }
493 }
494 }
495 AssignTarget::MemWrite { mem, addr, we } => {
496 if reset {
497 continue;
498 }
499 if let Some(en) = we {
500 if self.lookup(en) == 0 {
501 continue;
502 }
503 }
504 let a_idx = self.lookup(addr) as usize;
505 let data = self.eval(&a.expr);
506 if let Some(bank) = self.mems.get_mut(mem) {
507 if a_idx < bank.len() {
508 bank[a_idx] = data;
509 }
510 }
511 }
512 _ => {}
513 }
514 }
515 }
516 }
517 for (name, val) in next_regs {
518 self.regs.insert(name, val);
519 }
520 self.pending_mem_reads = next_pending;
521 }
522
523 fn tick_combinational(&mut self) {
524 let Some(m) = self.hir.circuit().modules.first().cloned() else {
525 return;
526 };
527 for stmt in &m.body {
528 if let Stmt::Process(p) = stmt {
529 if p.kind == ProcessKind::Combinational {
530 for a in &p.assigns {
531 if let AssignTarget::Net(name) = &a.target {
532 let val = self.eval(&a.expr);
533 self.ports.set(name.clone(), val);
534 }
535 }
536 }
537 }
538 }
539 }
540}
541
542pub trait AbstractionView {
544 fn cycle(&mut self, inputs: &PortValues) -> PortValues;
545}
546
547#[derive(Debug, Clone, PartialEq, Eq)]
549pub struct PortMismatch {
550 pub name: String,
551 pub left: Option<u64>,
552 pub right: Option<u64>,
553}
554
555pub fn compare_port_values(left: &PortValues, right: &PortValues) -> Result<(), Vec<PortMismatch>> {
557 let mut mismatches = Vec::new();
558 for name in left.values.keys() {
559 if !right.values.contains_key(name) {
560 continue;
561 }
562 let l = left.get(name);
563 let r = right.get(name);
564 if l != r {
565 mismatches.push(PortMismatch {
566 name: name.clone(),
567 left: l,
568 right: r,
569 });
570 }
571 }
572 if mismatches.is_empty() {
573 Ok(())
574 } else {
575 Err(mismatches)
576 }
577}
578
579pub fn check_mixed_both<A: AbstractionView>(
581 sim: &mut Sim,
582 abs: &mut A,
583 inputs: PortValues,
584) -> Result<(), Vec<PortMismatch>> {
585 sim.set_inputs(inputs.clone());
586 sim.tick();
587 let abs_out = abs.cycle(&inputs);
588 compare_port_values(sim.ports(), &abs_out)
589}
590
591#[cfg(test)]
592mod tests {
593 use bitloom_builder::{ElaborateSession, GroundType, Span};
594
595 use super::*;
596
597 fn counter_hir() -> FrozenHir {
598 let mut s = ElaborateSession::new("t");
599 s.begin_module("Counter", Span::default());
600 s.add_input("clk", GroundType::Clock, Span::default());
601 s.add_input("rst", GroundType::Reset, Span::default());
602 s.add_input("data_in", GroundType::UInt { width: 8 }, Span::default());
603 s.add_output("data_out", GroundType::UInt { width: 8 }, Span::default());
604 s.declare_reg("count", GroundType::UInt { width: 8 }, Span::default());
605 s.begin_combinational(Span::default());
606 s.assign_net("data_out", "count", Span::default());
607 s.end_process();
608 s.begin_sequential(Span::default());
609 s.assign_reg_d_inc("count", Span::default());
610 s.end_process();
611 s.end_module();
612 s.finish().unwrap()
613 }
614
615 fn passthrough_hir() -> FrozenHir {
616 let mut s = ElaborateSession::new("t");
617 s.begin_module("Pass", Span::default());
618 s.add_input("clk", GroundType::Clock, Span::default());
619 s.add_input("rst", GroundType::Reset, Span::default());
620 s.add_input("data_in", GroundType::UInt { width: 8 }, Span::default());
621 s.add_output("data_out", GroundType::UInt { width: 8 }, Span::default());
622 s.begin_combinational(Span::default());
623 s.assign_net("data_out", "data_in", Span::default());
624 s.end_process();
625 s.end_module();
626 s.finish().unwrap()
627 }
628
629 #[test]
630 fn tick_counts_after_reset() {
631 let mut sim = Sim::new(counter_hir());
632 let mut pv = PortValues::default();
633 pv.set("rst", 1);
634 sim.set_inputs(pv.clone());
635 sim.tick();
636 pv.set("rst", 0);
637 sim.set_inputs(pv);
638 sim.tick();
639 sim.tick();
640 sim.tick();
641 assert_eq!(sim.ports().get("data_out"), Some(3));
642 }
643
644 #[test]
645 fn tick_passthrough_non_counter() {
646 let mut sim = Sim::new(passthrough_hir());
647 let mut pv = PortValues::default();
648 pv.set("rst", 0);
649 pv.set("data_in", 0xA5);
650 sim.set_inputs(pv);
651 sim.tick();
652 assert_eq!(sim.ports().get("data_out"), Some(0xA5));
653 let mut pv2 = PortValues::default();
654 pv2.set("data_in", 0x3C);
655 sim.set_inputs(pv2);
656 sim.tick();
657 assert_eq!(sim.ports().get("data_out"), Some(0x3C));
658 }
659
660 fn adder_hir() -> FrozenHir {
661 let mut s = ElaborateSession::new("t");
662 s.begin_module("Add8", Span::default());
663 s.add_input("clk", GroundType::Clock, Span::default());
664 s.add_input("rst", GroundType::Reset, Span::default());
665 s.add_input("a", GroundType::UInt { width: 8 }, Span::default());
666 s.add_input("b", GroundType::UInt { width: 8 }, Span::default());
667 s.add_output("y", GroundType::UInt { width: 8 }, Span::default());
668 s.begin_combinational(Span::default());
669 s.assign_add("y", "a", "b", Span::default());
670 s.end_process();
671 s.end_module();
672 s.finish().unwrap()
673 }
674
675 #[test]
676 fn tick_same_width_add() {
677 let mut sim = Sim::new(adder_hir());
678 let mut pv = PortValues::default();
679 pv.set("rst", 0);
680 pv.set("a", 3);
681 pv.set("b", 5);
682 sim.set_inputs(pv);
683 sim.tick();
684 assert_eq!(sim.ports().get("y"), Some(8));
685 }
686
687 #[test]
688 fn vcd_written() {
689 let dir = std::env::temp_dir().join("rhdl_sim_vcd_test.vcd");
690 let mut sim = Sim::new(counter_hir());
691 sim.enable_vcd(&dir).unwrap();
692 let mut pv = PortValues::default();
693 pv.set("rst", 0);
694 sim.set_inputs(pv);
695 sim.tick();
696 let text = std::fs::read_to_string(&dir).unwrap();
697 assert!(text.contains("$var"));
698 assert!(text.contains("count"));
699 let _ = std::fs::remove_file(dir);
700 }
701
702 #[test]
703 fn fst_off_still_writes_vcd() {
704 let dir = std::env::temp_dir().join("rhdl_sim_fst_off.vcd");
705 let mut sim = Sim::new(counter_hir());
706 sim.enable_vcd(&dir).unwrap();
707 sim.tick();
708 let text = std::fs::read_to_string(&dir).unwrap();
709 assert!(text.contains("$var"));
710 assert!(!dir.with_extension("fst").is_file());
711 let _ = std::fs::remove_file(&dir);
712 }
713
714 #[test]
715 fn fst_missing_converter_errors() {
716 let err =
717 crate::fst::resolve_vcd2fst_from(None, Some("/no-such-rhdl-path".into())).unwrap_err();
718 let msg = err.to_string();
719 assert!(msg.contains("vcd2fst"));
720 }
721
722 #[test]
723 fn fst_via_documented_converter() {
724 let tmp = std::env::temp_dir().join("rhdl_fst_stub");
725 let _ = std::fs::create_dir_all(&tmp);
726 let stub = tmp.join("vcd2fst");
727 std::fs::write(&stub, "#!/bin/sh\ncp \"$1\" \"$2\"\n").unwrap();
728 #[cfg(unix)]
729 {
730 use std::os::unix::fs::PermissionsExt;
731 let mut p = std::fs::metadata(&stub).unwrap().permissions();
732 p.set_mode(0o755);
733 std::fs::set_permissions(&stub, p).unwrap();
734 }
735 let fst = tmp.join("wave.fst");
736 let mut sim = Sim::new(counter_hir());
737 sim.enable_fst_with(&fst, &stub).unwrap();
738 let mut pv = PortValues::default();
739 pv.set("rst", 0);
740 sim.set_inputs(pv);
741 sim.tick();
742 sim.finish_waves().unwrap();
743 assert!(fst.is_file());
744 assert!(fst.with_extension("vcd").is_file());
745 let _ = std::fs::remove_file(&fst);
746 let _ = std::fs::remove_file(fst.with_extension("vcd"));
747 }
748
749 #[test]
750 fn functional_model_matches_tick_portvalues() {
751 struct CounterFm {
752 count: u64,
753 }
754 impl CounterFm {
755 fn cycle(&mut self, inputs: &PortValues) -> PortValues {
756 if inputs.get("rst").unwrap_or(0) != 0 {
757 self.count = 0;
758 } else {
759 self.count = self.count.wrapping_add(1);
760 }
761 let mut out = inputs.clone();
762 out.set("data_out", self.count);
763 out
764 }
765 }
766
767 let mut sim = Sim::new(counter_hir());
768 let mut fm = CounterFm { count: 0 };
769 let mut pv = PortValues::default();
770 pv.set("rst", 1);
771 sim.set_inputs(pv.clone());
772 sim.tick();
773 let _ = fm.cycle(&pv);
774 pv.set("rst", 0);
775 for _ in 0..5 {
776 sim.set_inputs(pv.clone());
777 sim.tick();
778 let fm_out = fm.cycle(&pv);
779 assert_eq!(sim.ports().get("data_out"), fm_out.get("data_out"));
780 }
781 }
782
783 struct CounterAbs {
784 count: u64,
785 }
786 impl AbstractionView for CounterAbs {
787 fn cycle(&mut self, inputs: &PortValues) -> PortValues {
788 if inputs.get("rst").unwrap_or(0) != 0 {
789 self.count = 0;
790 } else {
791 self.count = self.count.wrapping_add(1);
792 }
793 let mut out = inputs.clone();
794 out.set("data_out", self.count);
795 out
796 }
797 }
798
799 #[test]
800 fn mixed_both_portvalues_match() {
801 let mut sim = Sim::new(counter_hir());
802 let mut abs = CounterAbs { count: 0 };
803 let mut pv = PortValues::default();
804 pv.set("rst", 1);
805 check_mixed_both(&mut sim, &mut abs, pv.clone()).unwrap();
806 pv.set("rst", 0);
807 for _ in 0..4 {
808 check_mixed_both(&mut sim, &mut abs, pv.clone()).unwrap();
809 }
810 assert_eq!(sim.ports().get("data_out"), Some(4));
811 }
812
813 #[test]
814 fn mixed_both_mismatch_fails() {
815 struct WrongAbs;
816 impl AbstractionView for WrongAbs {
817 fn cycle(&mut self, inputs: &PortValues) -> PortValues {
818 let mut out = inputs.clone();
819 out.set("data_out", 99);
820 out
821 }
822 }
823 let mut sim = Sim::new(counter_hir());
824 let mut abs = WrongAbs;
825 let mut pv = PortValues::default();
826 pv.set("rst", 0);
827 let err = check_mixed_both(&mut sim, &mut abs, pv).unwrap_err();
828 assert!(err.iter().any(|m| m.name == "data_out"));
829 }
830
831 #[test]
832 fn no_hir_to_tlm_api() {
833 let src = include_str!("lib.rs");
837 let prod = src.split("#[cfg(test)]").next().unwrap_or(src);
838 assert!(!prod.contains("fn to_tlm") && !prod.contains("pub fn to_tlm"));
839 assert!(!prod.contains("pub fn emit_tlm(") && !prod.contains("fn emit_tlm("));
840 if prod.contains("mod systemc_tlm") || prod.contains("emit_systemc_tlm") {
841 assert!(
842 prod.contains("FR101") || include_str!("systemc_tlm.rs").contains("FR101"),
843 "SystemC TLM surface must be labeled FR101"
844 );
845 }
846 }
847
848 #[test]
849 fn dual_view_equiv_pass() {
850 let mut abs = CounterAbs { count: 0 };
851 let st = check_functional_equiv(counter_hir(), &mut abs, reset_then_run(5));
852 assert!(st.is_pass());
853 assert_eq!(st, EquivStatus::Pass { cycles: 6 });
854 }
855
856 #[test]
857 fn dual_view_equiv_fail_on_deliberate_mismatch() {
858 struct WrongAbs;
859 impl AbstractionView for WrongAbs {
860 fn cycle(&mut self, inputs: &PortValues) -> PortValues {
861 let mut out = inputs.clone();
862 out.set("data_out", 42);
863 out
864 }
865 }
866 let st = check_functional_equiv(counter_hir(), &mut WrongAbs, reset_then_run(2));
867 assert!(!st.is_pass());
868 match st {
869 EquivStatus::Fail { mismatches, .. } => {
870 assert!(mismatches.iter().any(|m| m.name == "data_out"));
871 }
872 EquivStatus::Pass { .. } => panic!("expected fail"),
873 }
874 }
875
876 fn collect_trace(engine: TickEngine, n: usize) -> Vec<Option<u64>> {
877 let mut sim = Sim::with_engine(counter_hir(), engine);
878 let mut pv = PortValues::default();
879 pv.set("rst", 1);
880 sim.set_inputs(pv.clone());
881 sim.tick();
882 pv.set("rst", 0);
883 let mut out = vec![sim.ports().get("data_out")];
884 for _ in 0..n {
885 sim.set_inputs(pv.clone());
886 sim.tick();
887 out.push(sim.ports().get("data_out"));
888 }
889 out
890 }
891
892 #[test]
893 fn interpreter_and_compiled_portvalues_match() {
894 assert_eq!(
895 collect_trace(TickEngine::Interpreter, 6),
896 collect_trace(TickEngine::Compiled, 6)
897 );
898 assert_eq!(
899 TickEngine::from_name("compiled"),
900 Some(TickEngine::Compiled)
901 );
902 let mut a = Sim::with_engine(passthrough_hir(), TickEngine::Interpreter);
903 let mut b = Sim::with_engine(passthrough_hir(), TickEngine::Compiled);
904 let mut pv = PortValues::default();
905 pv.set("data_in", 0x5A);
906 a.set_inputs(pv.clone());
907 b.set_inputs(pv);
908 a.tick();
909 b.tick();
910 assert_eq!(a.ports().get("data_out"), b.ports().get("data_out"));
911 assert_eq!(a.engine().as_str(), "interpreter");
912 }
913
914 #[test]
915 fn coverage_has_hit_and_miss() {
916 let mut sim = Sim::new(counter_hir());
917 let mut pv = PortValues::default();
918 pv.set("rst", 0);
919 sim.set_inputs(pv);
920 sim.tick();
921 sim.tick();
922 let report = sim.coverage_report();
923 assert!(
924 report.starts_with("# bitloom-sim coverage v2")
925 || report.starts_with("# bitloom-sim coverage v3"),
926 "FR105/FR109 coverage header v2 (Mux) or v3 (with FSM)"
927 );
928 let (hits, misses) = parse_report(&report);
929 assert!(
930 hits.iter().any(|h| h == "data_out" || h == "count"),
931 "hits={hits:?}"
932 );
933 assert!(
934 misses.iter().any(|m| m == "data_in" || m == "clk"),
935 "misses={misses:?}"
936 );
937 }
938
939 fn sync_read_mem_hir() -> FrozenHir {
940 let mut s = ElaborateSession::new("t");
941 s.begin_module("Srm", Span::default());
942 s.add_input("clk", GroundType::Clock, Span::default());
943 s.add_input("rst", GroundType::Reset, Span::default());
944 s.add_input("addr", GroundType::UInt { width: 4 }, Span::default());
945 s.add_input("wdata", GroundType::UInt { width: 8 }, Span::default());
946 s.add_input("we", GroundType::Bool, Span::default());
947 s.add_output("rdata", GroundType::UInt { width: 8 }, Span::default());
948 s.declare_sync_read_mem("ram", 16, 8, Span::default());
949 s.declare_reg("q", GroundType::UInt { width: 8 }, Span::default());
950 s.begin_combinational(Span::default());
951 s.assign_net("rdata", "q", Span::default());
952 s.end_process();
953 s.begin_sequential(Span::default());
954 s.assign_mem_write("ram", "addr", "wdata", Span::default());
956 s.assign_reg_d_mem_read("q", "ram", "addr", Span::default());
957 s.end_process();
958 s.end_module();
959 s.finish().unwrap()
960 }
961
962 #[test]
963 fn sync_read_mem_read_latency_one() {
964 let mut sim = Sim::new(sync_read_mem_hir());
965 let mut pv = PortValues::default();
966 pv.set("rst", 0);
967 pv.set("addr", 3);
968 pv.set("wdata", 0xAB);
969 pv.set("we", 1);
970 sim.set_inputs(pv.clone());
971 sim.tick(); assert_eq!(sim.ports().get("rdata"), Some(0));
973 sim.tick(); assert_eq!(sim.ports().get("rdata"), Some(0xAB));
975 }
976
977 fn async_reset_hir() -> FrozenHir {
978 let mut s = ElaborateSession::new("t");
979 s.begin_module("AsyncCnt", Span::default());
980 s.add_input("clk", GroundType::Clock, Span::default());
981 s.add_input("rst", GroundType::Reset, Span::default());
982 s.add_output("data_out", GroundType::UInt { width: 8 }, Span::default());
983 s.declare_reg_ex(
984 "count",
985 GroundType::UInt { width: 8 },
986 true,
987 false,
988 Span::default(),
989 );
990 s.begin_combinational(Span::default());
991 s.assign_net("data_out", "count", Span::default());
992 s.end_process();
993 s.begin_sequential(Span::default());
994 s.assign_reg_d_inc("count", Span::default());
995 s.end_process();
996 s.end_module();
997 s.finish().unwrap()
998 }
999
1000 #[test]
1001 fn async_reset_assert_and_release_tick_golden() {
1002 let mut sim = Sim::new(async_reset_hir());
1003 let mut pv = PortValues::default();
1004 pv.set("rst", 0);
1005 sim.set_inputs(pv.clone());
1006 sim.tick();
1007 sim.tick();
1008 assert_eq!(sim.ports().get("data_out"), Some(2));
1009 pv.set("rst", 1);
1010 sim.set_inputs(pv.clone());
1011 sim.tick();
1012 assert_eq!(sim.ports().get("data_out"), Some(0));
1013 pv.set("rst", 0);
1014 sim.set_inputs(pv);
1015 sim.tick();
1016 sim.tick();
1017 assert_eq!(sim.ports().get("data_out"), Some(2));
1018 }
1019
1020 fn enable_hir(has_enable: bool) -> FrozenHir {
1021 let mut s = ElaborateSession::new("t");
1022 s.begin_module("EnCnt", Span::default());
1023 s.add_input("clk", GroundType::Clock, Span::default());
1024 s.add_input("rst", GroundType::Reset, Span::default());
1025 s.add_input("en", GroundType::Bool, Span::default());
1026 s.add_output("data_out", GroundType::UInt { width: 8 }, Span::default());
1027 s.declare_reg_ex(
1028 "count",
1029 GroundType::UInt { width: 8 },
1030 false,
1031 has_enable,
1032 Span::default(),
1033 );
1034 s.begin_combinational(Span::default());
1035 s.assign_net("data_out", "count", Span::default());
1036 s.end_process();
1037 s.begin_sequential(Span::default());
1038 s.assign_reg_d_inc("count", Span::default());
1039 s.end_process();
1040 s.end_module();
1041 s.finish().unwrap()
1042 }
1043
1044 #[test]
1045 fn enable_high_matches_ungated_counter() {
1046 let mut gated = Sim::new(enable_hir(true));
1047 let mut plain = Sim::new(enable_hir(false));
1048 let mut pv = PortValues::default();
1049 pv.set("rst", 0);
1050 pv.set("en", 1);
1051 for _ in 0..4 {
1052 gated.set_inputs(pv.clone());
1053 plain.set_inputs(pv.clone());
1054 gated.tick();
1055 plain.tick();
1056 assert_eq!(gated.ports().get("data_out"), plain.ports().get("data_out"));
1057 }
1058 }
1059
1060 #[test]
1061 fn enable_low_holds_value() {
1062 let mut sim = Sim::new(enable_hir(true));
1063 let mut pv = PortValues::default();
1064 pv.set("rst", 0);
1065 pv.set("en", 1);
1066 sim.set_inputs(pv.clone());
1067 sim.tick();
1068 sim.tick();
1069 assert_eq!(sim.ports().get("data_out"), Some(2));
1070 pv.set("en", 0);
1071 sim.set_inputs(pv);
1072 sim.tick();
1073 sim.tick();
1074 assert_eq!(sim.ports().get("data_out"), Some(2));
1075 }
1076}