Skip to main content

marlin_verilator/
dynamic.rs

1// Copyright (C) 2024 Ethan Uppal.
2//
3// This Source Code Form is subject to the terms of the Mozilla Public License,
4// v. 2.0. If a copy of the MPL was not distributed with this file, You can
5// obtain one at https://mozilla.org/MPL/2.0/.
6
7//! Support for dynamic models.
8
9use std::{collections::HashMap, ffi, fmt, slice};
10
11use libloading::Library;
12use snafu::Snafu;
13
14use crate::{
15    PortDirection, WideOut, compute_approx_width_from_wdata_word_count, types,
16};
17
18/// See [`types`].
19#[derive(PartialEq, Eq, Hash, Clone, Debug)]
20pub enum VerilatorValue<'a> {
21    CData(types::CData),
22    SData(types::SData),
23    IData(types::IData),
24    QData(types::QData),
25    WDataInP(&'a [types::WData]),
26    WDataOutP(Box<[types::WData]>),
27}
28
29impl VerilatorValue<'_> {
30    /// The maximum number of bits this value takes up.
31    pub fn width(&self) -> usize {
32        match self {
33            Self::CData(_) => 8,
34            Self::SData(_) => 16,
35            Self::IData(_) => 32,
36            Self::QData(_) => 64,
37            Self::WDataInP(values) => {
38                compute_approx_width_from_wdata_word_count(values.len())
39            }
40            Self::WDataOutP(values) => {
41                compute_approx_width_from_wdata_word_count(values.len())
42            }
43        }
44    }
45}
46
47impl fmt::Display for VerilatorValue<'_> {
48    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
49        match self {
50            VerilatorValue::CData(cdata) => cdata.fmt(f),
51            VerilatorValue::SData(sdata) => sdata.fmt(f),
52            VerilatorValue::IData(idata) => idata.fmt(f),
53            VerilatorValue::QData(qdata) => qdata.fmt(f),
54            Self::WDataInP(_values) => "wide (fmt is todo)".fmt(f),
55            Self::WDataOutP(_values) => "wide (fmt is todo)".fmt(f),
56        }
57    }
58}
59
60impl From<types::CData> for VerilatorValue<'_> {
61    fn from(value: types::CData) -> Self {
62        Self::CData(value)
63    }
64}
65
66impl From<types::SData> for VerilatorValue<'_> {
67    fn from(value: types::SData) -> Self {
68        Self::SData(value)
69    }
70}
71impl From<types::IData> for VerilatorValue<'_> {
72    fn from(value: types::IData) -> Self {
73        Self::IData(value)
74    }
75}
76
77impl From<types::QData> for VerilatorValue<'_> {
78    fn from(value: types::QData) -> Self {
79        Self::QData(value)
80    }
81}
82
83impl<'a, const WORDS: usize> From<&'a [types::WData; WORDS]>
84    for VerilatorValue<'a>
85{
86    fn from(value: &'a [types::WData; WORDS]) -> Self {
87        Self::WDataInP(value)
88    }
89}
90
91impl<const WORDS: usize> From<[types::WData; WORDS]> for VerilatorValue<'_> {
92    fn from(value: [types::WData; WORDS]) -> Self {
93        Self::WDataOutP(value.into())
94    }
95}
96
97impl<const WORDS: usize> From<WideOut<WORDS>> for VerilatorValue<'_> {
98    fn from(value: WideOut<WORDS>) -> Self {
99        Self::WDataOutP(value.inner.into())
100    }
101}
102
103/// Access model ports at runtime.
104pub trait AsDynamicVerilatedModel<'ctx>: 'ctx {
105    /// Equivalent to the Verilator `eval` method.
106    fn eval(&mut self);
107
108    /// If `port` is a valid port name for this model, returns the current value
109    /// of the port.
110    fn read(
111        &self,
112        port: impl Into<String>,
113    ) -> Result<VerilatorValue<'_>, DynamicVerilatedModelError>;
114
115    /// If `port` is a valid port name for this model, and the port's width is
116    /// `<=` `value.into().width()`, sets the port to `value`.
117    fn pin(
118        &mut self,
119        port: impl Into<String>,
120        value: impl Into<VerilatorValue<'ctx>>,
121    ) -> Result<(), DynamicVerilatedModelError>;
122}
123
124#[derive(Clone, Copy)]
125pub(crate) struct DynamicPortInfo {
126    pub(crate) width: usize,
127    pub(crate) direction: PortDirection,
128}
129
130/// A hardware model constructed at runtime. See
131/// [`super::VerilatorRuntime::create_dyn_model`].
132pub struct DynamicVerilatedModel<'ctx> {
133    // TODO: add the dlsyms here and remove the library field
134    pub(crate) ports: HashMap<String, DynamicPortInfo>,
135    pub(crate) name: String,
136    pub(crate) main: *mut ffi::c_void,
137    pub(crate) eval_main: extern "C" fn(*mut ffi::c_void),
138    pub(crate) library: &'ctx Library,
139}
140
141/// Runtime port read/write error.
142#[derive(Debug, Snafu)]
143pub enum DynamicVerilatedModelError {
144    #[snafu(display(
145        "Port {port} not found on verilated module {top_module}: did you forget to specify it in the runtime `create_dyn_model` constructor?: {source:?}"
146    ))]
147    NoSuchPort {
148        top_module: String,
149        port: String,
150        #[snafu(source(false))]
151        source: Option<libloading::Error>,
152    },
153    #[snafu(display(
154        "Port {port} on verilated module {top_module} has width {width}, but used as if it was in the {attempted_lower} to {attempted_higher} width range"
155    ))]
156    InvalidPortWidth {
157        top_module: String,
158        port: String,
159        width: usize,
160        attempted_lower: usize,
161        attempted_higher: usize,
162    },
163    #[snafu(display(
164        "Port {port} on verilated module {top_module} is an {direction} port, but was used as an {attempted_direction} port"
165    ))]
166    InvalidPortDirection {
167        top_module: String,
168        port: String,
169        direction: PortDirection,
170        attempted_direction: PortDirection,
171    },
172}
173
174impl<'ctx> AsDynamicVerilatedModel<'ctx> for DynamicVerilatedModel<'ctx> {
175    fn eval(&mut self) {
176        (self.eval_main)(self.main);
177    }
178
179    fn read(
180        &self,
181        port: impl Into<String>,
182    ) -> Result<VerilatorValue<'_>, DynamicVerilatedModelError> {
183        let port: String = port.into();
184        let DynamicPortInfo { width, direction } = *self
185            .ports
186            .get(&port)
187            .ok_or(DynamicVerilatedModelError::NoSuchPort {
188                top_module: self.name.clone(),
189                port: port.clone(),
190                source: None,
191            })?;
192
193        if !matches!(direction, PortDirection::Output | PortDirection::Inout,) {
194            return Err(DynamicVerilatedModelError::InvalidPortDirection {
195                top_module: self.name.clone(),
196                port,
197                direction,
198                attempted_direction: PortDirection::Output,
199            });
200        }
201
202        macro_rules! read_value {
203            ($self:ident, $port:expr, $value_type:ty) => {{
204                let symbol: libloading::Symbol<
205                    extern "C" fn(*mut ffi::c_void) -> $value_type,
206                > = unsafe {
207                    self.library.get(
208                        format!("ffi_V{}_read_{}", self.name, $port).as_bytes(),
209                    )
210                }
211                .map_err(|source| {
212                    DynamicVerilatedModelError::NoSuchPort {
213                        top_module: $self.name.to_string(),
214                        port: $port.clone(),
215                        source: Some(source),
216                    }
217                })?;
218
219                Ok((*symbol)($self.main).into())
220            }};
221        }
222
223        if width <= 8 {
224            read_value!(self, port, types::CData)
225        } else if width <= 16 {
226            read_value!(self, port, types::SData)
227        } else if width <= 32 {
228            read_value!(self, port, types::IData)
229        } else if width <= 64 {
230            read_value!(self, port, types::QData)
231        } else {
232            let value: types::WDataOutP =
233                read_value!(self, port, types::WDataOutP)?;
234            assert!(
235                !value.is_null() && value.is_aligned(),
236                "The pointer should be a valid reference to an array in the design class"
237            );
238            let length = width.div_ceil(types::WData::BITS as usize);
239            let (total_bytes, did_overflow) =
240                length.overflowing_mul(size_of::<types::WData>());
241            assert!(!did_overflow && total_bytes < isize::MAX as usize);
242            Ok(VerilatorValue::WDataOutP(
243                // SAFETY:
244                // - `value` is non-null and aligned.
245                // - valid for reads for `length * size_of::<types::WData>()`
246                //   many bytes because via the FFI generated by
247                //   `build_library::build_ffi` uses the `data()` method on a
248                //   Verilator `VlWide`. This method returns a reference to the
249                //   array of words in the model [1]; thus, this reference is
250                //   valid as long as the C++ model class is.
251                // - `value` points to `length` consecutive properly
252                //   initialized values of type `types::WData` because `VlWide`
253                //   uses an array of `EData` [2], which is identical to
254                //   `WData` [3].
255                // - The memory referenced by the returned slice is not mutated
256                //   for the duration its lifetime because it is immediately
257                //   copied onto the heap and the slice is discarded.
258                // - The total size `length * size_of::<types::WData>()` does
259                //   not exceed `isize::MAX`.
260                //
261                // [1]: https://github.com/verilator/verilator/blob/af65a85a1e11fb0e5332bb91024dd020de49dd79/include/verilated_types.h#L413
262                // [2]: https://github.com/verilator/verilator/blob/af65a85a1e11fb0e5332bb91024dd020de49dd79/include/verilated_types.h#L390
263                // [3]: https://github.com/verilator/verilator/blob/af65a85a1e11fb0e5332bb91024dd020de49dd79/include/verilated.h#L124
264                unsafe { slice::from_raw_parts(value, length) }.into(),
265            ))
266        }
267    }
268
269    fn pin(
270        &mut self,
271        port: impl Into<String>,
272        value: impl Into<VerilatorValue<'ctx>>,
273    ) -> Result<(), DynamicVerilatedModelError> {
274        macro_rules! pin_value {
275            ($self:ident, $port:expr, $value:expr, $value_type:ty, $low:literal, $high:expr) => {{
276                let symbol: libloading::Symbol<
277                    extern "C" fn(*mut ffi::c_void, $value_type),
278                > = unsafe {
279                    self.library.get(
280                        format!("ffi_V{}_pin_{}", self.name, $port).as_bytes(),
281                    )
282                }
283                .map_err(|source| {
284                    DynamicVerilatedModelError::NoSuchPort {
285                        top_module: $self.name.to_string(),
286                        port: $port.clone(),
287                        source: Some(source),
288                    }
289                })?;
290
291                let DynamicPortInfo { width, direction } = $self
292                    .ports
293                    .get(&$port)
294                    .ok_or(DynamicVerilatedModelError::NoSuchPort {
295                        top_module: $self.name.clone(),
296                        port: $port.clone(),
297                        source: None,
298                    })?
299                    .clone();
300
301                if width > $high {
302                    return Err(DynamicVerilatedModelError::InvalidPortWidth {
303                        top_module: $self.name.clone(),
304                        port: $port.clone(),
305                        width,
306                        attempted_lower: $low,
307                        attempted_higher: $high,
308                    });
309                }
310
311                if !matches!(
312                    direction,
313                    PortDirection::Input | PortDirection::Inout,
314                ) {
315                    return Err(
316                        DynamicVerilatedModelError::InvalidPortDirection {
317                            top_module: $self.name.clone(),
318                            port: $port,
319                            direction,
320                            attempted_direction: PortDirection::Input,
321                        },
322                    );
323                }
324
325                (*symbol)($self.main, $value);
326                Ok(())
327            }};
328        }
329
330        let port: String = port.into();
331        match value.into() {
332            VerilatorValue::CData(cdata) => {
333                pin_value!(self, port, cdata, types::CData, 0, 8)
334            }
335            VerilatorValue::SData(sdata) => {
336                pin_value!(self, port, sdata, types::SData, 9, 16)
337            }
338            VerilatorValue::IData(idata) => {
339                pin_value!(self, port, idata, types::IData, 17, 32)
340            }
341            VerilatorValue::QData(qdata) => {
342                pin_value!(self, port, qdata, types::QData, 33, 64)
343            }
344            VerilatorValue::WDataInP(values) => {
345                let values_ptr = values.as_ptr();
346                pin_value!(
347                    self,
348                    port,
349                    values_ptr,
350                    types::WDataInP,
351                    65,
352                    usize::MAX
353                )
354            }
355            VerilatorValue::WDataOutP(_) => {
356                panic!(
357                    "Cannot pin with WDataOutP: did you accidently use the From<vec![]> instead of From<&[]>?"
358                )
359            }
360        }
361    }
362}