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