opentitanlib/test_utils/
gpio_monitor.rs

1// Copyright lowRISC contributors (OpenTitan project).
2// Licensed under the Apache License, Version 2.0, see LICENSE for details.
3// SPDX-License-Identifier: Apache-2.0
4
5use anyhow::{Result, bail, ensure};
6use std::borrow::Borrow;
7use std::rc::Rc;
8
9use crate::app::TransportWrapper;
10use crate::io::gpio::{ClockNature, GpioMonitoring, MonitoringEvent};
11use crate::util::vcd::{dump_vcd, vcd_from_edges};
12
13// This structure makes it easier to monitor GPIOs and supports dumping a trace
14// in the VCD format for further examination. In addition, for easier debugging
15// in tests, this structure can automatically dump the trace on drop.
16pub struct GpioMon<'a> {
17    // Transport wrapper.
18    transport: &'a TransportWrapper,
19    // Monitor object.
20    monitor: Rc<dyn GpioMonitoring>,
21    // Transport pins names.
22    pins: Vec<String>,
23    // Whether to dump the trace on drop.
24    dump_on_drop: bool,
25    // Are we still monitoring?
26    still_monitoring: bool,
27    // Waves.
28    waves: Waves,
29}
30
31pub struct Waves {
32    // Human pin names.
33    pin_names: Vec<String>,
34    // Initial levels.
35    initial_levels: Vec<bool>,
36    // Initial timestamp.
37    initial_timestamp: u64,
38    // Final timestamp.
39    final_timestamp: u64,
40    // Clock resolution.
41    resolution: u64,
42    // Complete trace since the beginning.
43    events: Vec<MonitoringEvent>,
44}
45
46impl<'a> GpioMon<'a> {
47    // Start monitoring pins and return a monitoring object. `pins` is an array of pairs
48    // (transport pin name, human pin name) where the human name can be
49    // the empty string if the tranposrt pin name is enough.
50    pub fn start(
51        transport: &'a TransportWrapper,
52        pins: &[(&str, &str)],
53        dump_on_drop: bool,
54    ) -> Result<Self> {
55        let pin_names = pins
56            .iter()
57            .map(|(name, hname)| {
58                if hname.is_empty() {
59                    name.to_string()
60                } else {
61                    hname.to_string()
62                }
63            })
64            .collect::<Vec<_>>();
65
66        let monitor = transport.gpio_monitoring()?;
67        let clock_nature = monitor.get_clock_nature()?;
68        let ClockNature::Wallclock { resolution, .. } = clock_nature else {
69            bail!("the transport GPIO monitor does not have reliable clock source");
70        };
71        let pins = pins
72            .iter()
73            .map(|(name, _)| name.to_string())
74            .collect::<Vec<_>>();
75        let gpio_pins = transport.gpio_pins(&pins)?;
76        let gpios_pins = &gpio_pins.iter().map(Rc::borrow).collect::<Vec<_>>();
77        let start = monitor.monitoring_start(gpios_pins)?;
78
79        Ok(GpioMon {
80            transport,
81            monitor,
82            pins,
83            dump_on_drop,
84            still_monitoring: true,
85            waves: Waves::new(pin_names, start.initial_levels, start.timestamp, resolution),
86        })
87    }
88
89    pub fn timestamp_to_ns(&self, timestamp: u64) -> u64 {
90        self.waves.timestamp_to_ns(timestamp)
91    }
92
93    pub fn signal_index(&self, name: &str) -> Option<usize> {
94        self.waves.signal_index(name)
95    }
96
97    pub fn initial_levels(&self) -> Vec<bool> {
98        self.waves.initial_levels()
99    }
100
101    pub fn all_events(&self) -> impl Iterator<Item = &MonitoringEvent> {
102        self.waves.all_events()
103    }
104
105    pub fn read(&mut self, continue_monitoring: bool) -> Result<Vec<MonitoringEvent>> {
106        ensure!(
107            self.still_monitoring,
108            "cannot call read() on GpioMon after monitoring has stopped"
109        );
110        let gpio_pins = self.transport.gpio_pins(&self.pins)?;
111        let gpios_pins = &gpio_pins.iter().map(Rc::borrow).collect::<Vec<_>>();
112        let resp = self
113            .monitor
114            .monitoring_read(gpios_pins, continue_monitoring)?;
115        self.still_monitoring = continue_monitoring;
116        self.waves.add_events(&resp.events);
117        self.waves.final_timestamp = resp.timestamp;
118        Ok(resp.events)
119    }
120
121    pub fn dump_on_drop(&mut self, dump_on_drop: bool) {
122        self.dump_on_drop = dump_on_drop
123    }
124
125    pub fn dump_vcd(&self) -> Result<String> {
126        self.waves.dump_vcd()
127    }
128}
129
130/// Consume a GpioMon and return a copy of the waves. If the monitor
131/// is still active, this function will try to stop it (and return an
132/// error if this is not possible).
133impl TryFrom<GpioMon<'_>> for Waves {
134    type Error = anyhow::Error;
135
136    fn try_from(mut gpio_mon: GpioMon<'_>) -> Result<Waves> {
137        if gpio_mon.still_monitoring {
138            let _ = gpio_mon.read(false)?;
139        }
140        gpio_mon.dump_on_drop = false;
141        let waves = std::mem::replace(
142            &mut gpio_mon.waves,
143            Waves::new(Vec::new(), Vec::new(), 0, 1),
144        );
145        Ok(waves)
146    }
147}
148
149impl Waves {
150    pub fn new(
151        pin_names: Vec<String>,
152        initial_levels: Vec<bool>,
153        initial_timestamp: u64,
154        resolution: u64,
155    ) -> Waves {
156        Waves {
157            pin_names,
158            initial_levels,
159            initial_timestamp,
160            final_timestamp: initial_timestamp,
161            resolution,
162            events: vec![],
163        }
164    }
165
166    // Returns the index of the signal created.
167    pub fn add_signal(&mut self, name: String, initial_level: bool) -> usize {
168        self.pin_names.push(name);
169        self.initial_levels.push(initial_level);
170        self.pin_names.len() - 1
171    }
172
173    pub fn add_event(&mut self, event: MonitoringEvent) {
174        self.events.push(event)
175    }
176
177    pub fn add_events(&mut self, event: &[MonitoringEvent]) {
178        self.events.extend_from_slice(event)
179    }
180
181    pub fn signal_index(&self, name: &str) -> Option<usize> {
182        self.pin_names.iter().position(|x| x == name)
183    }
184
185    pub fn initial_levels(&self) -> Vec<bool> {
186        self.initial_levels.clone()
187    }
188
189    pub fn set_final_timestamp(&mut self, ts: u64) {
190        self.final_timestamp = ts;
191    }
192
193    pub fn timestamp_to_ns(&self, timestamp: u64) -> u64 {
194        (timestamp - self.initial_timestamp) * 1000000000u64 / self.resolution
195    }
196
197    pub fn ns_to_timestamp(&self, ns: u64) -> u64 {
198        self.initial_timestamp + self.resolution * ns / 1000000000u64
199    }
200
201    pub fn all_events(&self) -> impl Iterator<Item = &MonitoringEvent> {
202        self.events.iter()
203    }
204
205    pub fn sort_events(&mut self) {
206        self.events.sort_by(|a, b| a.timestamp.cmp(&b.timestamp));
207    }
208
209    // This function assumes that the events are sorted.
210    pub fn dump_vcd(&self) -> Result<String> {
211        let pin_names = self
212            .pin_names
213            .iter()
214            .map(|n| Some(n.to_string()))
215            .collect::<Vec<_>>();
216        dump_vcd(&vcd_from_edges(
217            pin_names,
218            self.resolution,
219            self.initial_timestamp,
220            &self.initial_levels,
221            &self.events,
222            self.final_timestamp,
223        )?)
224    }
225}
226
227impl Drop for GpioMon<'_> {
228    fn drop(&mut self) {
229        // Stop monitoring, ignore error.
230        let error = if self.still_monitoring {
231            self.read(false).is_err()
232        } else {
233            false
234        };
235        if self.dump_on_drop {
236            if error {
237                log::error!(
238                    "an error occured when reading the monitoring events, some events have been lost"
239                );
240            }
241            log::info!(
242                "====[ VCD dump ]====\n{}\n====[ end dump ]====",
243                self.dump_vcd().unwrap()
244            );
245        }
246    }
247}