rust_hdl/docs/vcd2svg/
trace_collection.rs

1use crate::docs::vcd2svg::display_metrics::DisplayMetrics;
2use crate::docs::vcd2svg::symbols;
3use crate::docs::vcd2svg::text_frame::TextFrame;
4use crate::docs::vcd2svg::timed_value::{changes, SignalType, TimedValue};
5use crate::docs::vcd2svg::utils::{time_label, value_to_bigint, value_to_bool};
6use num_bigint::BigInt;
7use std::clone::Clone;
8use std::collections::HashMap;
9use std::fmt::Debug;
10use std::fs::File;
11use std::iter::Iterator;
12use std::string::ToString;
13use svg::Document;
14use vcd::IdCode;
15
16type StringTrace = Vec<TimedValue<String>>;
17type VectorTrace = Vec<TimedValue<BigInt>>;
18type BinaryTrace = Vec<TimedValue<bool>>;
19
20// Lifted from dwfv/src/tui/waveform.rs
21#[derive(Clone, PartialEq, Eq)]
22enum WaveformElement {
23    Low,
24    High,
25    Value(String),
26    Transition,
27    RisingEdge,
28    FallingEdge,
29    Invalid,
30    LowDensity,
31    MediumDensity,
32    HighDensity,
33}
34
35impl WaveformElement {
36    pub fn to_symbols(&self) -> (char, char, char) {
37        match self {
38            WaveformElement::Low => (symbols::BLANK, symbols::BLANK, symbols::HORIZONTAL),
39            WaveformElement::High => (symbols::HORIZONTAL, symbols::BLANK, symbols::BLANK),
40            WaveformElement::Value(_) => (symbols::HORIZONTAL, symbols::BLANK, symbols::HORIZONTAL),
41            WaveformElement::RisingEdge => {
42                (symbols::TOP_LEFT, symbols::VERTICAL, symbols::BOTTOM_RIGHT)
43            }
44            WaveformElement::FallingEdge => {
45                (symbols::TOP_RIGHT, symbols::VERTICAL, symbols::BOTTOM_LEFT)
46            }
47            WaveformElement::Transition => (
48                symbols::HORIZONTAL_DOWN,
49                symbols::VERTICAL,
50                symbols::HORIZONTAL_UP,
51            ),
52            WaveformElement::Invalid => (symbols::FULL_LOWER, symbols::FULL, symbols::FULL_UPPER),
53            WaveformElement::LowDensity => {
54                (symbols::LIGHT_LOWER, symbols::LIGHT, symbols::LIGHT_UPPER)
55            }
56            WaveformElement::MediumDensity => (
57                symbols::MEDIUM_LOWER,
58                symbols::MEDIUM,
59                symbols::MEDIUM_UPPER,
60            ),
61            WaveformElement::HighDensity => {
62                (symbols::FULL_LOWER, symbols::FULL, symbols::FULL_UPPER)
63            }
64        }
65    }
66}
67
68#[derive(Clone, Default, Debug)]
69struct SignalBin<T: SignalType> {
70    start_time: u64,
71    end_time: u64,
72    values: Vec<TimedValue<T>>,
73    before: Option<T>,
74    after: Option<T>,
75    changes: usize,
76}
77
78fn bin_edges(first_time: u64, last_time: u64, num_bins: usize) -> Vec<(u64, u64)> {
79    let delta = (last_time - first_time) as f64 / (num_bins as f64);
80    (0..num_bins)
81        .map(|x| {
82            (
83                (first_time + ((x as f64) * delta) as u64),
84                (first_time + ((x as f64 + 1.0) * delta) as u64),
85            )
86        })
87        .collect()
88}
89
90fn bin_trace<T: SignalType>(trace: &Vec<TimedValue<T>>, timing: &BinTimes) -> Vec<SignalBin<T>> {
91    let num_bins = timing.count();
92    let mut bins: Vec<SignalBin<T>> = vec![Default::default(); num_bins];
93    trace.iter().for_each(|val| {
94        if let Some(bin) = timing.to_bin(val.time) {
95            bins[bin].values.push(val.clone());
96        }
97    });
98    // The before value for bin N is the last value in bin N - 1 - but bins might be empty
99    let mut signal_value = None;
100    for ndx in 0..num_bins {
101        let mut bin = &mut bins[ndx];
102        (bin.start_time, bin.end_time) = timing.bracket(ndx);
103        bin.before = signal_value.clone();
104        signal_value = if let Some(x) = bin.values.last() {
105            Some(x.value.clone())
106        } else {
107            signal_value
108        };
109        bin.after = signal_value.clone();
110        bin.changes = bin.values.len();
111    }
112    bins
113}
114
115pub struct TraceCollection {
116    pub signal_names: Vec<(IdCode, String)>,
117    pub string_valued: HashMap<IdCode, StringTrace>,
118    pub vector_valued: HashMap<IdCode, VectorTrace>,
119    pub scalar_valued: HashMap<IdCode, BinaryTrace>,
120}
121
122impl TraceCollection {
123    pub fn parse(signals: &[&str], mut file: File) -> anyhow::Result<Self> {
124        let mut parser = vcd::Parser::new(&mut file);
125        let header = parser.parse_header()?;
126        let mut string_valued = HashMap::new();
127        let mut vector_valued = HashMap::new();
128        let mut scalar_valued = HashMap::new();
129        let mut signal_names = Vec::new();
130        for signal in signals {
131            let path = signal.split(".").collect::<Vec<_>>();
132            let sig = header
133                .find_var(&path)
134                .ok_or_else(|| anyhow::Error::msg(format!("cannot resolve signal {}", signal)))?;
135            if sig.size == 0 {
136                string_valued.insert(sig.code, StringTrace::new());
137            } else if sig.size == 1 {
138                scalar_valued.insert(sig.code, BinaryTrace::new());
139            } else {
140                vector_valued.insert(sig.code, VectorTrace::new());
141            }
142            signal_names.push((sig.code, signal.to_string()));
143        }
144        let mut timestamp = 0_u64;
145        for command_result in parser {
146            let command = command_result?;
147            match command {
148                vcd::Command::Timestamp(x) => {
149                    timestamp = x;
150                }
151                vcd::Command::ChangeScalar(i, v) => {
152                    if let Some(s) = scalar_valued.get_mut(&i) {
153                        s.push(TimedValue {
154                            time: timestamp,
155                            value: value_to_bool(&v)?,
156                        })
157                    }
158                }
159                vcd::Command::ChangeVector(i, v) => {
160                    if let Some(s) = vector_valued.get_mut(&i) {
161                        s.push(TimedValue {
162                            time: timestamp,
163                            value: value_to_bigint(&v)?,
164                        })
165                    }
166                }
167                vcd::Command::ChangeString(i, v) => {
168                    if let Some(s) = string_valued.get_mut(&i) {
169                        s.push(TimedValue {
170                            time: timestamp,
171                            value: v.clone(),
172                        })
173                    }
174                }
175                _ => {}
176            }
177        }
178        Ok(Self {
179            signal_names,
180            string_valued,
181            vector_valued,
182            scalar_valued,
183        })
184    }
185
186    pub fn as_svg(&self, metrics: &DisplayMetrics) -> anyhow::Result<Document> {
187        let document = Document::new()
188            .set(
189                "viewBox",
190                (0, 0, metrics.canvas_width, metrics.canvas_height),
191            )
192            .add(metrics.background_rect());
193
194        // Paint the timescale rectangle
195        let mut document = document
196            .add(metrics.signal_rect())
197            .add(metrics.timescale_header_rect())
198            .add(metrics.timescale_midline());
199
200        document = metrics.timescale(document);
201
202        for (index, details) in self.signal_names.iter().enumerate() {
203            document = document
204                .add(metrics.signal_label(index, &details.1))
205                .add(metrics.signal_line(index));
206            document = metrics.horiz_grid_line(index, document);
207            if let Some(s) = self.scalar_valued.get(&details.0) {
208                document = document.add(metrics.bit_signal_plot(index, s));
209            } else if let Some(s) = self.vector_valued.get(&details.0) {
210                document = metrics.vector_signal_plot(index, s, document);
211            } else if let Some(s) = self.string_valued.get(&details.0) {
212                document = metrics.vector_signal_plot(index, s, document);
213            } else {
214                anyhow::bail!("Unable to find signal {} in the trace...", details.1)
215            }
216        }
217        Ok(document)
218    }
219    pub fn as_string(
220        &self,
221        first_time: u64,
222        last_time: u64,
223        max_columns: usize,
224    ) -> anyhow::Result<String> {
225        let mut frame = TextFrame::new(max_columns);
226        let sig_columns = self
227            .signal_names
228            .iter()
229            .map(|(_, x)| x.len())
230            .max()
231            .unwrap_or(0)
232            .max(4);
233        let num_bins = max_columns - sig_columns - 1;
234        let timing = BinTimes::new(first_time, last_time, num_bins);
235        draw_symbols(
236            &render_multibit(&clock_trace(&timing)),
237            &mut frame,
238            0,
239            sig_columns + 1,
240        );
241        frame.write(1, sig_columns - 4, "time");
242        for (index, details) in self.signal_names.iter().enumerate() {
243            let index = index + 1;
244            frame.write(index * 3 + 1, sig_columns - details.1.len(), &details.1);
245            if let Some(s) = self.scalar_valued.get(&details.0) {
246                let bins = bin_trace(&changes(s), &timing);
247                draw_symbols(&render_bool(&bins), &mut frame, index * 3, sig_columns + 1);
248            } else if let Some(s) = self.vector_valued.get(&details.0) {
249                let bins = bin_trace(&changes(s), &timing);
250                draw_symbols(
251                    &render_multibit(&bins),
252                    &mut frame,
253                    index * 3,
254                    sig_columns + 1,
255                );
256            } else if let Some(s) = self.string_valued.get(&details.0) {
257                let bins = bin_trace(&changes(s), &timing);
258                draw_symbols(
259                    &render_multibit(&bins),
260                    &mut frame,
261                    index * 3,
262                    sig_columns + 1,
263                );
264            } else {
265                anyhow::bail!("Unable to find signal {} in the trace...", details.1)
266            }
267        }
268        Ok(frame.to_string())
269    }
270}
271
272struct BinTimes {
273    first_time: u64,
274    last_time: u64,
275    num_bins: usize,
276    delta: f64,
277}
278
279impl BinTimes {
280    fn new(first_time: u64, last_time: u64, num_bins: usize) -> Self {
281        Self {
282            first_time,
283            last_time,
284            num_bins,
285            delta: (last_time - first_time) as f64 / (num_bins as f64),
286        }
287    }
288    fn bracket(&self, ndx: usize) -> (u64, u64) {
289        (
290            (self.first_time as f64 + self.delta * (ndx as f64)).floor() as u64,
291            (self.first_time as f64 + self.delta * (ndx as f64 + 1.0)).floor() as u64,
292        )
293    }
294    fn to_bin(&self, time: u64) -> Option<usize> {
295        let bin = ((time as f64 - self.first_time as f64) / self.delta).floor() as i32;
296        if bin >= 0 && bin < self.num_bins as i32 {
297            Some(bin as usize)
298        } else {
299            None
300        }
301    }
302    fn count(&self) -> usize {
303        self.num_bins
304    }
305    fn first(&self) -> u64 {
306        self.first_time
307    }
308    fn last(&self) -> u64 {
309        self.last_time
310    }
311    fn span(&self) -> u64 {
312        self.last_time - self.first_time
313    }
314}
315
316#[test]
317fn test_bin_times() {
318    let times = BinTimes::new(0, 250, 80);
319    (0..=times.count()).for_each(|x| println!("{:?}", times.bracket(x)));
320}
321
322fn clock_trace(timing: &BinTimes) -> Vec<SignalBin<String>> {
323    let major_tick_delta = compute_major_tick_delta_t(timing.span());
324    let first_index = (timing.first() as f64 / major_tick_delta as f64).floor() as u64;
325    let last_index = (timing.last() as f64 / major_tick_delta as f64).ceil() as u64;
326    let clock = (first_index..=last_index)
327        .map(|ndx| ndx * major_tick_delta)
328        .map(|x| TimedValue {
329            time: x,
330            value: time_label(x),
331        })
332        .collect::<Vec<_>>();
333    bin_trace(&changes(&clock), timing)
334}
335
336fn compute_major_tick_delta_t(span: u64) -> u64 {
337    let delta_t = span as f64;
338    let s = delta_t.log10() - 1.0;
339    let x = s.floor();
340    let e = s - x;
341    let d0 = (e - 0.0).abs();
342    let d1 = (e - 2.0_f64.log10()).abs();
343    let d2 = (e - 5.0_f64.log10()).abs();
344    let value = if d0 <= d1 && d0 <= d2 {
345        (10.0_f64.powf(x)) as u64
346    } else if d1 <= d0 && d1 <= d2 {
347        (2.0_f64 * 10.0_f64.powf(x)) as u64
348    } else {
349        (5.0_f64 * 10.0_f64.powf(x)) as u64
350    };
351    value
352}
353
354fn render_multibit<T: SignalType>(bins: &[SignalBin<T>]) -> Vec<WaveformElement> {
355    bins.iter()
356        .map(|bin| {
357            if bin.after.is_none() {
358                WaveformElement::Invalid
359            } else if bin.changes == 0 || (bin.changes == 1 && bin.before.is_none()) {
360                if let Some(b) = bin.before.as_ref() {
361                    WaveformElement::Value(b.render())
362                } else {
363                    WaveformElement::Value("?".to_string())
364                }
365            } else if bin.changes == 1 {
366                WaveformElement::Transition
367            } else if bin.changes <= 3 {
368                WaveformElement::LowDensity
369            } else if bin.changes <= 10 {
370                WaveformElement::MediumDensity
371            } else {
372                WaveformElement::HighDensity
373            }
374        })
375        .collect()
376}
377
378fn render_bool(bins: &[SignalBin<bool>]) -> Vec<WaveformElement> {
379    bins.iter()
380        .map(|bin| {
381            if bin.after.is_none() {
382                WaveformElement::Invalid
383            } else if bin.changes == 0 || (bin.changes == 1 && bin.before.is_none()) {
384                if !bin.after.unwrap() {
385                    WaveformElement::Low
386                } else {
387                    WaveformElement::High
388                }
389            } else if bin.changes == 1 {
390                if let Some(x) = bin.before {
391                    if !x {
392                        WaveformElement::RisingEdge
393                    } else {
394                        WaveformElement::FallingEdge
395                    }
396                } else {
397                    WaveformElement::Invalid
398                }
399            } else if bin.changes <= 3 {
400                WaveformElement::LowDensity
401            } else if bin.changes <= 10 {
402                WaveformElement::MediumDensity
403            } else {
404                WaveformElement::HighDensity
405            }
406        })
407        .collect()
408}
409
410fn draw_symbols(symbols: &[WaveformElement], frame: &mut TextFrame, row: usize, start_col: usize) {
411    symbols.iter().enumerate().for_each(|(ndx, element)| {
412        let symbol = element.to_symbols();
413        frame.put(row, start_col + ndx, symbol.0);
414        frame.put(row + 1, start_col + ndx, symbol.1);
415        frame.put(row + 2, start_col + ndx, symbol.2);
416    });
417    // From dwfv/src/tui/waveform.rs
418    let mut elmts = symbols[1..].iter().enumerate();
419    loop {
420        let mut free_space = 0;
421        let mut value = "";
422        let mut elmt = elmts.next();
423        let offset = if let Some((i, _)) = elmt { i } else { break };
424
425        while let Some((_, WaveformElement::Value(v))) = elmt {
426            free_space += 1;
427            value = v;
428            elmt = elmts.next();
429        }
430
431        for (i, c) in value.chars().enumerate() {
432            if i >= free_space {
433                break;
434            }
435
436            let r = &c.to_string();
437            let symbol = if i >= free_space - 1 && i + 1 < value.len() {
438                "…"
439            } else {
440                r
441            };
442
443            frame.write(row + 1, start_col + (offset + i + 1), symbol);
444        }
445    }
446}