opentitanlib/transport/verilator/
transport.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 std::any::Any;
6use std::cell::RefCell;
7use std::rc::Rc;
8use std::sync::LazyLock;
9use std::time::{Duration, Instant};
10
11use anyhow::{Context, Result, ensure};
12use regex::Regex;
13use serde_annotate::Annotate;
14
15use crate::io::gpio::{GpioError, GpioPin};
16use crate::io::uart::Uart;
17use crate::transport::common::uart::{SerialPortUart, SoftwareFlowControl};
18use crate::transport::verilator::gpio::{GpioInner, VerilatorGpioPin};
19use crate::transport::verilator::subprocess::{Options, Subprocess};
20use crate::transport::{
21    Capabilities, Capability, Transport, TransportError, TransportInterfaceType,
22};
23use crate::util::parse_int::ParseInt;
24
25const UART_BAUD: u32 = 40;
26
27pub(crate) struct Inner {
28    uart: Option<Rc<dyn Uart>>,
29    pub gpio: GpioInner,
30}
31
32/// Represents the verilator transport object.
33pub struct Verilator {
34    subprocess: Option<Subprocess>,
35    pub uart_tty: String,
36    pub spi_file: String,
37    pub gpio_read_file: String,
38    pub gpio_write_file: String,
39
40    inner: Rc<RefCell<Inner>>,
41}
42
43impl Verilator {
44    /// Creates a verilator subprocess-hosting transport from `options`.
45    pub fn from_options(options: Options) -> Result<Self> {
46        static UART: LazyLock<Regex> =
47            LazyLock::new(|| Regex::new("UART: Created ([^ ]+) for uart0").unwrap());
48        static SPI: LazyLock<Regex> =
49            LazyLock::new(|| Regex::new("SPI: Created ([^ ]+) for spi0").unwrap());
50        static GPIO_RD: LazyLock<Regex> = LazyLock::new(|| {
51            Regex::new(r"GPIO: FIFO pipes created at ([^ ]+) \(read\) and [^ ]+ \(write\) for 32-bit wide GPIO.").unwrap()
52        });
53        static GPIO_WR: LazyLock<Regex> = LazyLock::new(|| {
54            Regex::new(r"GPIO: FIFO pipes created at [^ ]+ \(read\) and ([^ ]+) \(write\) for 32-bit wide GPIO.").unwrap()
55        });
56
57        let deadline = Instant::now() + options.timeout;
58        let subprocess = Subprocess::from_options(options)?;
59        let gpio_rd = subprocess.find(&GPIO_RD, deadline)?;
60        let gpio_wr = subprocess.find(&GPIO_WR, deadline)?;
61        let uart = subprocess.find(&UART, deadline)?;
62        let spi = subprocess.find(&SPI, deadline)?;
63
64        log::info!("Verilator started with the following interfaces:");
65        log::info!("gpio_read = {}", gpio_rd);
66        log::info!("gpio_write = {}", gpio_wr);
67        let gpio = GpioInner::new(&gpio_rd, &gpio_wr)?;
68        log::info!("uart = {}", uart);
69        log::info!("spi = {}", spi);
70
71        Ok(Verilator {
72            subprocess: Some(subprocess),
73            uart_tty: uart,
74            spi_file: spi,
75            gpio_read_file: gpio_rd,
76            gpio_write_file: gpio_wr,
77            inner: Rc::new(RefCell::new(Inner { uart: None, gpio })),
78        })
79    }
80
81    /// Shuts down the verilator subprocess.
82    pub fn shutdown(&mut self) -> Result<()> {
83        if let Some(mut subprocess) = self.subprocess.take() {
84            subprocess.kill()
85        } else {
86            Ok(())
87        }
88    }
89}
90
91impl Drop for Verilator {
92    fn drop(&mut self) {
93        self.shutdown().expect("Kill verilator subprocess");
94    }
95}
96
97impl Transport for Verilator {
98    fn capabilities(&self) -> Result<Capabilities> {
99        Ok(Capabilities::new(Capability::UART | Capability::GPIO))
100    }
101
102    fn uart(&self, instance: &str) -> Result<Rc<dyn Uart>> {
103        ensure!(
104            instance == "0",
105            TransportError::InvalidInstance(TransportInterfaceType::Uart, instance.to_string())
106        );
107        let mut inner = self.inner.borrow_mut();
108        if inner.uart.is_none() {
109            inner.uart = Some(Rc::new(SoftwareFlowControl::new(
110                SerialPortUart::open_pseudo(&self.uart_tty, UART_BAUD)?,
111            )));
112        }
113        Ok(Rc::clone(inner.uart.as_ref().unwrap()))
114    }
115
116    fn gpio_pin(&self, instance: &str) -> Result<Rc<dyn GpioPin>> {
117        let pin = u8::from_str(instance).with_context(|| format!("can't convert {instance:?}"))?;
118        ensure!(pin < 32 || pin == 255, GpioError::InvalidPinNumber(pin));
119        let mut inner = self.inner.borrow_mut();
120        Ok(Rc::clone(inner.gpio.pins.entry(pin).or_insert_with(|| {
121            VerilatorGpioPin::new(Rc::clone(&self.inner), pin)
122        })))
123    }
124
125    fn dispatch(&self, action: &dyn Any) -> Result<Option<Box<dyn Annotate>>> {
126        if let Some(watch) = action.downcast_ref::<Watch>() {
127            let subprocess = self.subprocess.as_ref().unwrap();
128            let deadline = Instant::now() + watch.timeout.unwrap_or(Watch::FOREVER);
129            let result = subprocess.find(&watch.regex, deadline)?;
130            Ok(Some(Box::new(WatchResponse { result })))
131        } else {
132            Err(TransportError::UnsupportedOperation.into())
133        }
134    }
135}
136
137/// Watch verilator's stdout for a expression or timeout.
138pub struct Watch {
139    pub regex: Regex,
140    pub timeout: Option<Duration>,
141}
142
143impl Watch {
144    // Using Duration::MAX causes an overflow when calculating a deadline.
145    // We use 100 years -- although it isn't "forever", it is longer than
146    // any invocation of opentitantool.
147    pub const FOREVER: Duration = Duration::from_secs(100 * 365 * 86400);
148}
149
150#[derive(serde::Serialize, Debug)]
151pub struct WatchResponse {
152    pub result: String,
153}