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