rust_hdl_core/
vcd_probe.rs1use crate::atom::Atom;
2use crate::block::Block;
3use crate::probe::Probe;
4use crate::synth::VCDValue;
5use crate::type_descriptor::TypeDescriptor;
6use crate::type_descriptor::TypeKind;
7use std::collections::HashMap;
8use std::io::Write;
9
10#[derive(Clone, Debug)]
11enum VCDIDCode {
12 Singleton(vcd::IdCode),
13 Composite(Vec<Box<VCDIDCode>>),
14}
15
16pub struct VCDProbe<W: Write> {
17 vcd: vcd::Writer<W>,
18 id_map: HashMap<usize, VCDIDCode>,
19 val_map: HashMap<vcd::IdCode, VCDValue>,
20}
21
22impl<W: Write> VCDProbe<W> {
23 pub fn new(w: W) -> VCDProbe<W> {
24 Self {
25 vcd: vcd::Writer::new(w),
26 id_map: HashMap::default(),
27 val_map: HashMap::default(),
28 }
29 }
30
31 pub fn timestamp(&mut self, ts: u64) -> std::io::Result<()> {
32 self.vcd.timestamp(ts)
33 }
34}
35
36struct VCDHeader<W: Write>(VCDProbe<W>);
37
38fn register_signal<W: Write>(
39 name: &str,
40 descriptor: &TypeDescriptor,
41 vcd: &mut vcd::Writer<W>,
42) -> VCDIDCode {
43 match &descriptor.kind {
44 TypeKind::Bits(width) | TypeKind::Signed(width) => {
45 VCDIDCode::Singleton(vcd.add_wire(*width as u32, name).unwrap())
46 }
47 TypeKind::Enum(_) => VCDIDCode::Singleton(vcd.add_wire(0, name).unwrap()),
48 TypeKind::Composite(k) => {
49 let mut ret = vec![];
50 for field in k {
51 let sub_name = format!("{}${}", name, field.fieldname);
52 let code = register_signal(&sub_name, &field.kind, vcd);
53 ret.push(Box::new(code));
54 }
55 VCDIDCode::Composite(ret)
56 }
57 }
58}
59
60impl<W: Write> Probe for VCDHeader<W> {
61 fn visit_start_scope(&mut self, name: &str, _node: &dyn Block) {
62 self.0.vcd.add_module(name).unwrap();
63 }
64
65 fn visit_start_namespace(&mut self, name: &str, _node: &dyn Block) {
66 self.0.vcd.add_module(name).unwrap();
67 }
68
69 fn visit_atom(&mut self, name: &str, signal: &dyn Atom) {
70 self.0.id_map.insert(
71 signal.id(),
72 register_signal(name, &signal.descriptor(), &mut self.0.vcd),
73 );
74 }
75
76 fn visit_end_namespace(&mut self, _name: &str, _node: &dyn Block) {
77 self.0.vcd.upscope().unwrap();
78 }
79
80 fn visit_end_scope(&mut self, _name: &str, _node: &dyn Block) {
81 self.0.vcd.upscope().unwrap();
82 }
83}
84
85pub fn write_vcd_header<W: Write>(writer: W, uut: &dyn Block) -> VCDProbe<W> {
86 let mut visitor = VCDHeader(VCDProbe::new(writer));
87 visitor.0.vcd.timescale(1, vcd::TimescaleUnit::PS).unwrap();
88 uut.accept("uut", &mut visitor);
89 visitor.0.vcd.enddefinitions().unwrap();
90 visitor.0
91}
92
93struct VCDChange<W: Write>(VCDProbe<W>);
94
95impl<W: Write> Probe for VCDChange<W> {
96 fn visit_atom(&mut self, _name: &str, signal: &dyn Atom) {
97 if let Some(idc) = self.0.id_map.get(&signal.id()) {
98 do_vcd_change(
99 &mut self.0.val_map,
100 &mut self.0.vcd,
101 idc,
102 &signal.vcd(),
103 false,
104 );
105 }
106 }
107}
108
109pub fn write_vcd_change<W: Write>(vcd: VCDProbe<W>, uut: &dyn Block) -> VCDProbe<W> {
110 let mut visitor = VCDChange(vcd);
111 uut.accept("uut", &mut visitor);
112 visitor.0
113}
114
115struct VCDDump<W: Write>(VCDProbe<W>);
116
117impl<W: Write> Probe for VCDDump<W> {
118 fn visit_atom(&mut self, _name: &str, signal: &dyn Atom) {
119 if let Some(idc) = &self.0.id_map.get(&signal.id()) {
120 do_vcd_change(
121 &mut self.0.val_map,
122 &mut self.0.vcd,
123 idc,
124 &signal.vcd(),
125 true,
126 );
127 }
128 }
129}
130
131fn do_vcd_change<W: Write>(
132 val_map: &mut HashMap<vcd::IdCode, VCDValue>,
133 vcd: &mut vcd::Writer<W>,
134 idc: &VCDIDCode,
135 val: &VCDValue,
136 dump: bool,
137) {
138 match idc {
139 VCDIDCode::Singleton(idc) => {
140 if !dump {
141 if let Some(old_val) = val_map.get(idc) {
142 if val.eq(old_val) {
143 return;
144 }
145 }
146 }
147 let _ = val_map.insert(*idc, val.clone());
148 match val {
149 VCDValue::Single(s) => {
150 vcd.change_scalar(*idc, s.clone()).unwrap();
151 }
152 VCDValue::Vector(v) => {
153 if v.len() == 1 {
154 vcd.change_scalar(*idc, v[0]).unwrap();
155 } else {
156 vcd.change_vector(*idc, &v).unwrap();
157 }
158 }
159 VCDValue::String(t) => {
160 vcd.change_string(*idc, &t).unwrap();
161 }
162 VCDValue::Composite(_) => {
163 panic!("Composite data received for singleton type");
164 }
165 }
166 }
167 VCDIDCode::Composite(idcs) => match val {
168 VCDValue::Composite(vals) => {
169 assert_eq!(
170 idcs.len(),
171 vals.len(),
172 "Mismatch in values versus type information"
173 );
174 for n in 0..idcs.len() {
175 do_vcd_change(val_map, vcd, &idcs[n], &vals[n], dump);
176 }
177 }
178 _ => {
179 panic!("Scalar data received for composite type");
180 }
181 },
182 }
183}
184
185pub fn write_vcd_dump<W: Write>(vcd: VCDProbe<W>, uut: &dyn Block) -> VCDProbe<W> {
186 let mut visitor = VCDDump(vcd);
187 visitor
188 .0
189 .vcd
190 .begin(vcd::SimulationCommand::Dumpvars)
191 .unwrap();
192 uut.accept("uut", &mut visitor);
193 visitor.0.vcd.end().unwrap();
194 visitor.0
195}