Skip to main content

marlin_verilator/
lib.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//! This module implements the Verilator runtime for instantiating hardware
8//! modules. The Marlin Verilator runtime supports DPI, VCDs, and dynamic
9//! models in addition to standard models.
10//!
11//! For an example of how to use this runtime to add support for your own custom
12//! HDL, see `SpadeRuntime` (under the "language-support/spade/" directory),
13//! which just wraps [`VerilatorRuntime`].
14
15use core::convert::Into;
16use std::{
17    cell::RefCell,
18    cmp,
19    collections::{HashMap, hash_map::Entry},
20    ffi::{self, OsStr, OsString},
21    fmt, fs,
22    hash::{self, Hash, Hasher},
23    process::Command,
24    slice,
25    sync::{LazyLock, Mutex},
26    time::Instant,
27};
28
29use boxcar::Vec as BoxcarVec;
30use build_library::build_library;
31use camino::{Utf8Path, Utf8PathBuf};
32use dashmap::DashMap;
33use dpi::DpiFunction;
34use dynamic::DynamicVerilatedModel;
35use libloading::Library;
36use owo_colors::OwoColorize;
37use snafu::{OptionExt, ResultExt, Whatever, whatever};
38
39mod build_library;
40pub mod dpi;
41pub mod dynamic;
42pub mod ffi_names;
43pub mod nocapture;
44pub mod vcd;
45
46pub use dynamic::AsDynamicVerilatedModel;
47
48use crate::{
49    dynamic::DynamicPortInfo,
50    ffi_names::{DPI_INIT_CALLBACK, TRACE_EVER_ON},
51};
52
53/// Verilator-defined types for C FFI.
54pub mod types {
55    /// From the Verilator documentation: "Data representing 'bit' of 1-8 packed
56    /// bits."
57    pub type CData = u8;
58
59    /// From the Verilator documentation: "Data representing 'bit' of 9-16
60    /// packed bits"
61    pub type SData = u16;
62
63    /// From the Verilator documentation: "Data representing 'bit' of 17-32
64    /// packed bits."
65    pub type IData = u32;
66
67    /// From the Verilator documentation: "Data representing 'bit' of 33-64
68    /// packed bits."
69    pub type QData = u64;
70
71    /// From the Verilator documentation: "Data representing one element of
72    /// WData array."
73    pub type EData = u32;
74
75    /// From the Verilator documentation: "Data representing >64 packed bits
76    /// (used as pointer)."
77    pub type WData = EData;
78
79    /// From the Verilator documentation: "'bit' of >64 packed bits as array
80    /// input to a function."
81    pub type WDataInP = *const WData;
82
83    /// From the Verilator documentation: "'bit' of >64 packed bits as array
84    /// output from a function."
85    pub type WDataOutP = *mut WData;
86}
87
88/// Computes the length of the [`types::WData`] array that Verilator generates
89/// for a given wide port of bit width `width`.
90///
91/// See also: [`compute_approx_width_from_wdata_word_count`]
92pub const fn compute_wdata_word_count_from_width_not_msb(
93    width: usize,
94) -> usize {
95    width.div_ceil(types::WData::BITS as usize)
96}
97
98/// Computes the width upper bound for a wide port with the given the given
99/// `word_count` of the [`types::WData`] array Verilator generates.
100///
101/// See also: [`compute_wdata_word_count_from_width_not_msb`]
102pub const fn compute_approx_width_from_wdata_word_count(
103    word_count: usize,
104) -> usize {
105    word_count * (types::WData::BITS as usize)
106}
107
108///  `WORDS` is [`compute_wdata_word_count_from_width_not_msb`]`(HIGH + 1 -
109/// LOW)` where `HIGH` is the  most significant bit index and `LOW` is the
110/// least.
111#[derive(PartialEq, Eq, Hash, Clone, Debug)]
112pub struct WideIn<const WORDS: usize> {
113    inner: [types::WData; WORDS],
114}
115
116impl<const WORDS: usize> WideIn<WORDS> {
117    pub fn new(value: [types::WData; WORDS]) -> Self {
118        Self { inner: value }
119    }
120
121    pub fn value(&self) -> &[types::WData; WORDS] {
122        &self.inner
123    }
124
125    /// # Safety
126    ///
127    /// The returned pointer may not outlive `self`.
128    #[doc(hidden)]
129    pub fn as_ptr(&self) -> types::WDataInP {
130        self.inner.as_ptr()
131    }
132}
133
134impl<const WORDS: usize> Default for WideIn<WORDS> {
135    fn default() -> Self {
136        Self { inner: [0; WORDS] }
137    }
138}
139
140/// See [`WideIn`].
141#[derive(PartialEq, Eq, Hash, Clone, Debug)]
142pub struct WideOut<const WORDS: usize> {
143    inner: [types::WData; WORDS],
144}
145
146impl<const WORDS: usize> WideOut<WORDS> {
147    pub fn value(&self) -> &[types::WData; WORDS] {
148        &self.inner
149    }
150
151    /// # Safety
152    ///
153    /// `slice::from_raw_parts(raw, WORDS)` must be defined.
154    #[allow(clippy::not_unsafe_ptr_arg_deref)]
155    #[doc(hidden)]
156    pub fn from_ptr(raw: types::WDataOutP) -> Self {
157        let mut inner = [0; WORDS];
158        inner.copy_from_slice(unsafe { slice::from_raw_parts(raw, WORDS) });
159        Self { inner }
160    }
161}
162
163impl<const WORDS: usize> From<WideOut<WORDS>> for [types::WData; WORDS] {
164    fn from(val: WideOut<WORDS>) -> Self {
165        val.inner
166    }
167}
168
169impl<const WORDS: usize> Default for WideOut<WORDS> {
170    fn default() -> Self {
171        Self { inner: [0; WORDS] }
172    }
173}
174
175#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
176pub enum PortDirection {
177    Input,
178    Output,
179    Inout,
180}
181
182impl fmt::Display for PortDirection {
183    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
184        match self {
185            PortDirection::Input => "input",
186            PortDirection::Output => "output",
187            PortDirection::Inout => "inout",
188        }
189        .fmt(f)
190    }
191}
192
193/// Based off of the [C++ standards supported by GCC](https://gcc.gnu.org/projects/cxx-status.html) as
194/// of June 6th, 2025.
195#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
196pub enum CxxStandard {
197    Cxx98,
198    Cxx11,
199    Cxx14,
200    Cxx17,
201    Cxx20,
202    Cxx23,
203    Cxx26,
204}
205
206/// Configuration for a particular [`VerilatedModel`].
207#[derive(Debug, Clone, PartialEq, Eq, Hash)]
208pub struct VerilatedModelConfig {
209    /// The flag `-O<verilator_optimization>` will be passed. Enabling (> 0)
210    /// will slow compilation times.
211    pub verilator_optimization: usize,
212
213    /// A list of Verilator warnings to disable on the Verilog source code for
214    /// this model.
215    pub ignored_warnings: Vec<String>,
216
217    /// Whether this model should be compiled with tracing support.
218    pub enable_tracing: bool,
219
220    /// The name of the C++ compiler executable; interpreted by [`Command`] and
221    /// in some way by Verilator.
222    pub cxx_executable: String,
223
224    /// Optionally specify the C++ standard used by Verilator.
225    pub cxx_standard: Option<CxxStandard>,
226}
227
228impl Default for VerilatedModelConfig {
229    fn default() -> Self {
230        Self {
231            verilator_optimization: 0,
232            ignored_warnings: Default::default(),
233            enable_tracing: Default::default(),
234            cxx_executable: "c++".into(),
235            cxx_standard: Some(CxxStandard::Cxx14),
236        }
237    }
238}
239
240impl VerilatedModelConfig {
241    pub fn verilator_optimization(self, level: usize) -> Self {
242        Self {
243            verilator_optimization: level,
244            ..self
245        }
246    }
247
248    pub fn enable_tracing(self, tracing_enabled: bool) -> Self {
249        Self {
250            enable_tracing: tracing_enabled,
251            ..self
252        }
253    }
254
255    pub fn cxx_executable(self, cxx_executable: String) -> Self {
256        Self {
257            cxx_executable,
258            ..self
259        }
260    }
261
262    pub fn cxx_standard(self, cxx_standard: Option<CxxStandard>) -> Self {
263        Self {
264            cxx_standard,
265            ..self
266        }
267    }
268}
269
270/// You should not implement this `trait` manually. Instead, use a procedural
271/// macro like `#[verilog(...)]` to derive it for you.
272pub trait AsVerilatedModel<'ctx>: 'ctx {
273    /// The source-level name of the module.
274    fn name() -> &'static str;
275
276    /// The path of the module's definition.
277    fn source_path() -> &'static str;
278
279    /// The module's interface; each element is `(port_name, port_msb, port_lsb,
280    /// port_direction)`.
281    fn ports() -> &'static [(&'static str, usize, usize, PortDirection)];
282
283    #[doc(hidden)]
284    fn init_from(library: &'ctx Library, tracing_enabled: bool) -> Self;
285
286    #[doc(hidden)]
287    unsafe fn model(&self) -> *mut ffi::c_void;
288}
289
290/// Optional configuration for creating a [`VerilatorRuntime`]. Usually, you can
291/// just use [`VerilatorRuntimeOptions::default()`].
292#[derive(Debug, Clone, PartialEq, Eq, Hash)]
293pub struct VerilatorRuntimeOptions {
294    /// The name of the Verilator executable, interpreted in some way by the
295    /// OS/shell.
296    pub verilator_executable: OsString,
297
298    /// If `Some(version)`, whether unsupported Verilator versions at least
299    /// `version` should be silently ignored instead of erroring.
300    pub allow_unsupported_verilator: Option<VerilatorVersion>,
301
302    /// Whether Verilator should always be invoked instead of only when the
303    /// source files or DPI functions change.
304    pub force_verilator_rebuild: bool,
305
306    /// Whether to use the log crate.
307    pub log: bool,
308}
309
310impl Default for VerilatorRuntimeOptions {
311    fn default() -> Self {
312        Self {
313            verilator_executable: "verilator".into(),
314            allow_unsupported_verilator: None,
315            force_verilator_rebuild: false,
316            log: false,
317        }
318    }
319}
320
321impl VerilatorRuntimeOptions {
322    /// The same as the [`Default`] implementation except that the log crate is
323    /// used.
324    pub fn default_logging() -> Self {
325        Self {
326            log: true,
327            ..Default::default()
328        }
329    }
330
331    pub fn verilator_executable(self, verilator_executable: OsString) -> Self {
332        Self {
333            verilator_executable,
334            ..self
335        }
336    }
337
338    pub fn allow_unsupported_verilator(
339        self,
340        version: Option<VerilatorVersion>,
341    ) -> Self {
342        Self {
343            allow_unsupported_verilator: version,
344            ..self
345        }
346    }
347
348    pub fn force_verilator_rebuild(
349        self,
350        force_verilator_rebuild: bool,
351    ) -> Self {
352        Self {
353            force_verilator_rebuild,
354            ..self
355        }
356    }
357}
358
359#[derive(PartialEq, Eq, Hash, Clone)]
360struct LibraryArenaKey {
361    name: String,
362    source_path: String,
363    hash: u64,
364}
365
366struct ModelDeallocator {
367    model: *mut ffi::c_void,
368    deallocator: extern "C" fn(*mut ffi::c_void),
369}
370
371#[derive(Clone, Copy)]
372enum BuildTarget {
373    Linux,
374    MacOS,
375}
376
377/// Runtime for (System)Verilog code.
378pub struct VerilatorRuntime {
379    artifact_directory: Utf8PathBuf,
380    build_target: BuildTarget,
381    source_files: Vec<Utf8PathBuf>,
382    include_directories: Vec<Utf8PathBuf>,
383    dpi_functions: Vec<&'static dyn DpiFunction>,
384    options: VerilatorRuntimeOptions,
385    verilator_version: VerilatorVersion,
386    /// Mapping between hardware (top, path) and arena index of Verilator
387    /// implementations.
388    library_map: RefCell<HashMap<LibraryArenaKey, usize>>,
389    /// Verilator implementations arena.
390    library_arena: BoxcarVec<Library>,
391    /// SAFETY: These are dropped when the runtime is dropped. They will not be
392    /// "borrowed mutably" because the models created for this runtime must
393    /// not outlive it and thus will be all gone before these are dropped.
394    model_deallocators: RefCell<Vec<ModelDeallocator>>,
395}
396
397impl Drop for VerilatorRuntime {
398    fn drop(&mut self) {
399        for ModelDeallocator { model, deallocator } in
400            self.model_deallocators.borrow_mut().drain(..)
401        {
402            // SAFETY: todo
403            deallocator(model);
404        }
405    }
406}
407
408/* <Forgive me father for I have sinned> */
409
410#[derive(Default)]
411struct ThreadLocalFileLock;
412
413/// The file_guard handles locking across processes, but does not guarantee
414/// locking between threads in one process. Thus, we have this lock to
415/// synchronize threads for a given artifacts directoru.
416static THREAD_LOCKS_PER_BUILD_DIR: LazyLock<
417    DashMap<Utf8PathBuf, Mutex<ThreadLocalFileLock>>,
418> = LazyLock::new(DashMap::default);
419
420/* </Forgive me father for I have sinned> */
421
422fn one_time_library_setup(
423    library: &Library,
424    dpi_functions: &[&'static dyn DpiFunction],
425    tracing_enabled: bool,
426    options: &VerilatorRuntimeOptions,
427) -> Result<(), Whatever> {
428    if !dpi_functions.is_empty() {
429        let dpi_init_callback: extern "C" fn(*const *const ffi::c_void) =
430            *unsafe { library.get(DPI_INIT_CALLBACK.as_bytes()) }
431                .whatever_context("Failed to load DPI initializer")?;
432
433        // order is important here. the function pointers will be
434        // initialized in the same order that they
435        // appear in the DPI array --- this is to match how the C
436        // initialization code was constructed in `build_library`.
437        let function_pointers = dpi_functions
438            .iter()
439            .map(|dpi_function| dpi_function.pointer())
440            .collect::<Vec<_>>();
441
442        (dpi_init_callback)(function_pointers.as_ptr_range().start);
443
444        if options.log {
445            log::info!("Initialized DPI functions");
446        }
447    }
448
449    if tracing_enabled {
450        let trace_ever_on_callback: extern "C" fn(bool) =
451            *unsafe { library.get(TRACE_EVER_ON.as_bytes()) }
452                .whatever_context(
453                    "Model was not configured with tracing enabled",
454                )?;
455        trace_ever_on_callback(true);
456
457        if options.log {
458            log::info!("Initialized VCD tracing");
459        }
460    }
461
462    Ok(())
463}
464
465#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
466pub struct VerilatorVersion {
467    pub major: usize,
468    pub minor: usize,
469}
470
471#[macro_export]
472macro_rules! verilator_version {
473    ($major:literal $minor:literal) => {
474        $crate::VerilatorVersion {
475            major: $major,
476            #[allow(clippy::zero_prefixed_literal)]
477            minor: $minor,
478        }
479    };
480}
481
482impl fmt::Display for VerilatorVersion {
483    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
484        write!(f, "{}.{:03}", self.major, self.minor)
485    }
486}
487
488impl cmp::PartialOrd for VerilatorVersion {
489    fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
490        Some(self.cmp(other))
491    }
492}
493
494impl cmp::Ord for VerilatorVersion {
495    fn cmp(&self, other: &Self) -> cmp::Ordering {
496        self.major
497            .cmp(&other.major)
498            .then(self.minor.cmp(&other.minor))
499    }
500}
501
502pub const MINIMUM_SUPPORTED_VERILATOR: VerilatorVersion =
503    verilator_version!(5 025);
504
505fn retrieve_verilator_version(
506    verilator_executable: &OsStr,
507) -> Result<VerilatorVersion, Whatever> {
508    let output = Command::new(verilator_executable)
509        .arg("--version")
510        .output()
511        .whatever_context("Failed to retrieve Verilator --version")?;
512    let stdout = String::from_utf8(output.stdout).whatever_context(
513        "Failed to decode Verilator --version output as UTF-8",
514    )?;
515    let (major, minor_and_rest) = stdout
516        .strip_prefix("Verilator ")
517        .and_then(|rest| rest.split_once('.'))
518        .whatever_context("Unexpected Verilator --version output format")?;
519    let minor = minor_and_rest
520        .split_once(' ')
521        .map(|(minor, _)| minor)
522        .unwrap_or(minor_and_rest);
523    Ok(VerilatorVersion {
524        major: major.parse().whatever_context(
525            "Failed to parse major version from Verilator --version output",
526        )?,
527        minor: minor.parse().whatever_context(
528            "Failed to parse minor version from Verilator --version output",
529        )?,
530    })
531}
532
533fn check_verilator_version(version: VerilatorVersion) -> Result<(), Whatever> {
534    if MINIMUM_SUPPORTED_VERILATOR.major != version.major
535        || version.minor < MINIMUM_SUPPORTED_VERILATOR.minor
536    {
537        whatever!(
538            "Unsupported Verilator version {version} (see `VerilatorRuntimeOptions::allow_unsupported_verilator`)"
539        );
540    }
541    Ok(())
542}
543
544impl VerilatorRuntime {
545    /// Creates a new runtime for instantiating (System)Verilog modules as Rust
546    /// objects.
547    pub fn new(
548        artifact_directory: &Utf8Path,
549        source_files: &[&Utf8Path],
550        include_directories: &[&Utf8Path],
551        dpi_functions: impl IntoIterator<Item = &'static dyn DpiFunction>,
552        options: VerilatorRuntimeOptions,
553    ) -> Result<Self, Whatever> {
554        if options.log {
555            log::info!("Validating Verilator version");
556        }
557        let verilator_version =
558            retrieve_verilator_version(&options.verilator_executable)?;
559        if let Some(allowed_version) = options.allow_unsupported_verilator {
560            if verilator_version < allowed_version {
561                whatever!(
562                    "Unsupported Verilator version {verilator_version} ({allowed_version} was explicitly allowed)"
563                )
564            }
565        } else {
566            check_verilator_version(verilator_version)?;
567        }
568
569        if options.log {
570            log::info!("Validating source files");
571        }
572        for source_file in source_files {
573            if !source_file.is_file() {
574                whatever!(
575                    "Source file {} does not exist or is not a file. Note that if it's a relative path, you must be in the correct directory",
576                    source_file
577                );
578            }
579        }
580
581        let uname_output = Command::new("uname")
582            .output()
583            .whatever_context("Invocation of uname failed")?;
584
585        if !uname_output.status.success() {
586            whatever!(
587                "Invocation of uname failed with nonzero exit code {}\n\n--- STDOUT ---\n{}\n\n--- STDERR ---\n{}",
588                uname_output.status,
589                String::from_utf8_lossy(&uname_output.stdout),
590                String::from_utf8_lossy(&uname_output.stderr)
591            );
592        }
593
594        let build_target = if String::from_utf8(uname_output.stdout)
595            .map(|s| s.trim() == "Darwin")
596            .unwrap_or(false)
597        {
598            BuildTarget::MacOS
599        } else {
600            BuildTarget::Linux
601        };
602
603        Ok(Self {
604            artifact_directory: artifact_directory.to_owned(),
605            build_target,
606            source_files: source_files
607                .iter()
608                .map(|path| path.to_path_buf())
609                .collect(),
610            include_directories: include_directories
611                .iter()
612                .map(|path| path.to_path_buf())
613                .collect(),
614            dpi_functions: dpi_functions.into_iter().collect(),
615            options,
616            verilator_version,
617            library_map: RefCell::new(HashMap::new()),
618            library_arena: BoxcarVec::new(),
619            model_deallocators: RefCell::new(vec![]),
620        })
621    }
622
623    /// Constructs a new model. Uses lazy and incremental building for
624    /// efficiency.
625    ///
626    /// See also: [`VerilatorRuntime::create_dyn_model`]
627    pub fn create_model_simple<'ctx, M: AsVerilatedModel<'ctx>>(
628        &'ctx self,
629    ) -> Result<M, Whatever> {
630        self.create_model(&VerilatedModelConfig::default())
631    }
632
633    /// Constructs a new model. Uses lazy and incremental building for
634    /// efficiency.
635    ///
636    /// From Verilator's website:
637    /// > The thread used for constructing a model must be the same thread that
638    /// > calls eval() into the model; this is called the “eval thread”.
639    ///
640    /// See also: [`VerilatorRuntime::create_dyn_model`]
641    pub fn create_model<'ctx, M: AsVerilatedModel<'ctx>>(
642        &'ctx self,
643        config: &VerilatedModelConfig,
644    ) -> Result<M, Whatever> {
645        let library = self
646            .build_or_retrieve_library(
647                M::name(),
648                M::source_path(),
649                M::ports(),
650                config,
651            )
652            .whatever_context(
653                "Failed to build or retrieve verilator dynamic library. Try removing the build directory if it is corrupted.",
654            )?;
655
656        let delete_model: extern "C" fn(*mut ffi::c_void) = *unsafe {
657            library.get(format!("ffi_delete_V{}", M::name()).as_bytes())
658        }
659        .expect("failed to get symbol");
660
661        let model = M::init_from(library, config.enable_tracing);
662
663        self.model_deallocators.borrow_mut().push(ModelDeallocator {
664            // SAFETY: The `model` cannot outlive the runtime, and it is the
665            // model's responsibility to deallocate (because models
666            // themselves do not deallocate on `Drop`).
667            model: unsafe { model.model() },
668            deallocator: delete_model,
669        });
670
671        Ok(model)
672    }
673
674    // TODO: should this be unified with the normal create_model by having
675    // DynamicVerilatedModel implement VerilatedModel?
676
677    /// Constructs a new dynamic model. Uses lazy and incremental building for
678    /// efficiency. You must guarantee the correctness of the suppplied
679    /// information, namely, that `name` is precisely the name of the
680    /// Verilog module, `source_path` is, when canonicalized
681    /// using [`fs::canonicalize`], the relative/absolute path to the Verilog
682    /// file defining the module `name`, and `ports` is a correct subset of
683    /// the ports of the Verilog module.
684    ///
685    /// ```no_run
686    /// # use marlin_verilator::*;
687    /// # use marlin_verilator::dynamic::*;
688    /// # let runtime = VerilatorRuntime::new("".as_ref(), &[], &[], [], Default::default()).unwrap();
689    /// # || -> Result<(), snafu::Whatever> {
690    /// let mut main = runtime.create_dyn_model(
691    ///    "main",
692    ///    "src/main.sv",
693    ///    &[
694    ///        ("medium_input", 31, 0, PortDirection::Input),
695    ///        ("medium_output", 31, 0, PortDirection::Output),
696    ///    ],
697    ///    VerilatedModelConfig::default(),
698    /// )?;
699    /// # Ok(()) };
700    /// ````
701    ///
702    /// See also: [`VerilatorRuntime::create_model`]
703    pub fn create_dyn_model<'ctx>(
704        &'ctx self,
705        name: &str,
706        source_path: &str,
707        ports: &[(&str, usize, usize, PortDirection)],
708        config: VerilatedModelConfig,
709    ) -> Result<DynamicVerilatedModel<'ctx>, Whatever> {
710        let library = self
711            .build_or_retrieve_library(name, source_path, ports, &config)
712            .whatever_context(
713                "Failed to build or retrieve verilator dynamic library. Try removing the build directory if it is corrupted.",
714            )?;
715
716        let new_main: extern "C" fn() -> *mut ffi::c_void =
717            *unsafe { library.get(format!("ffi_new_V{name}").as_bytes()) }
718                .whatever_context(format!(
719                    "Failed to load constructor for module {name}"
720                ))?;
721        let delete_main =
722            *unsafe { library.get(format!("ffi_delete_V{name}").as_bytes()) }
723                .whatever_context(format!(
724                "Failed to load destructor for module {name}"
725            ))?;
726        let eval_main =
727            *unsafe { library.get(format!("ffi_V{name}_eval").as_bytes()) }
728                .whatever_context(format!(
729                    "Failed to load evalulator for module {name}"
730                ))?;
731
732        let main = new_main();
733
734        let ports = ports
735            .iter()
736            .copied()
737            .map(|(port, high, low, direction)| {
738                (
739                    port.to_string(),
740                    DynamicPortInfo {
741                        width: high + 1 - low,
742                        direction,
743                    },
744                )
745            })
746            .collect();
747
748        self.model_deallocators.borrow_mut().push(ModelDeallocator {
749            model: main,
750            deallocator: delete_main,
751        });
752
753        Ok(DynamicVerilatedModel {
754            ports,
755            name: name.to_string(),
756            main,
757            eval_main,
758            library,
759        })
760    }
761
762    /// Invokes verilator to build a dynamic library for the Verilog module
763    /// named `name` defined in the file `source_path` and with signature
764    /// `ports`.
765    ///
766    /// If the library is already cached for the given module name/source path
767    /// pair, then it is returned immediately.
768    ///
769    /// It is required that the `ports` signature matches a subset of the ports
770    /// defined on the Verilog module exactly.
771    ///
772    /// If `self.options.force_verilator_rebuild`, then the library will always
773    /// be rebuilt. Otherwise, it is only rebuilt on (a conservative
774    /// definition) of change:
775    ///
776    /// - Edits to Verilog source code
777    /// - Edits to DPI functions
778    ///
779    /// Then, if this is the first time building the library, and there are DPI
780    /// functions, the library will be initialized with the DPI functions.
781    ///
782    /// See [`build_library::build_library`] for more information.
783    ///
784    /// # Safety
785    ///
786    /// This function is thread-safe.
787    fn build_or_retrieve_library(
788        &self,
789        name: &str,
790        source_path: &str,
791        ports: &[(&str, usize, usize, PortDirection)],
792        config: &VerilatedModelConfig,
793    ) -> Result<&Library, Whatever> {
794        if name.chars().any(|c| c == '\\' || c == ' ') {
795            whatever!("Escaped module names are not supported");
796        }
797
798        if self.options.log {
799            log::info!("Validating model source file");
800        }
801        if !self.source_files.iter().any(|source_file| {
802            match (
803                source_file.canonicalize_utf8(),
804                Utf8Path::new(source_path).canonicalize_utf8(),
805            ) {
806                (Ok(lhs), Ok(rhs)) => lhs == rhs,
807                _ => false,
808            }
809        }) {
810            whatever!(
811                "Module `{}` requires source file {}, which was not provided to the runtime",
812                name,
813                source_path
814            );
815        }
816
817        if let Some((port, _, _, _)) =
818            ports.iter().find(|(_, high, low, _)| high < low)
819        {
820            whatever!(
821                "Port {} on module {} was specified with the high bit less than the low bit",
822                port,
823                name
824            );
825        }
826
827        let mut hasher = hash::DefaultHasher::new();
828        ports.hash(&mut hasher);
829        config.hash(&mut hasher);
830        let library_key = LibraryArenaKey {
831            name: name.to_owned(),
832            source_path: source_path.to_owned(),
833            hash: hasher.finish(),
834        };
835
836        let library_idx = match self
837            .library_map
838            .borrow_mut()
839            .entry(library_key.clone())
840        {
841            Entry::Occupied(entry) => *entry.get(),
842            Entry::Vacant(entry) => {
843                let local_directory_name = format!(
844                    "{name}_{}_{}",
845                    source_path.replace("_", "__").replace("/", "_"),
846                    library_key.hash
847                );
848                let local_artifacts_directory =
849                    self.artifact_directory.join(&local_directory_name);
850
851                if self.options.log {
852                    log::info!(
853                        "Creating artifacts directory {}",
854                        local_artifacts_directory
855                    );
856                }
857                fs::create_dir_all(&local_artifacts_directory)
858                    .whatever_context(format!(
859                        "Failed to create artifacts directory {local_artifacts_directory}",
860                    ))?;
861
862                //eprintln_nocapture!(
863                //    "on thread {:?}",
864                //    std::thread::current().id()
865                //)?;
866
867                if !THREAD_LOCKS_PER_BUILD_DIR
868                    .contains_key(&local_artifacts_directory)
869                {
870                    THREAD_LOCKS_PER_BUILD_DIR.insert(
871                        local_artifacts_directory.clone(),
872                        Default::default(),
873                    );
874                }
875                let thread_mutex = THREAD_LOCKS_PER_BUILD_DIR
876                    .get(&local_artifacts_directory)
877                    .expect("We just inserted if it didn't exist");
878
879                let _thread_lock = if let Ok(_thread_lock) =
880                    thread_mutex.try_lock()
881                {
882                    //eprintln_nocapture!(
883                    //    "thread-level try lock for {:?} succeeded",
884                    //    std::thread::current().id()
885                    //)?;
886                    _thread_lock
887                } else {
888                    eprintln_nocapture!(
889                        "{} waiting for file lock on artifact directory",
890                        "    Blocking".bold().green(),
891                    )?;
892                    let Ok(_thread_lock) = thread_mutex.lock() else {
893                        whatever!(
894                            "Failed to acquire thread-local lock for artifacts directory"
895                        );
896                    };
897                    _thread_lock
898                };
899
900                // # Safety
901                // build_library is not thread-safe, so we have to lock the
902                // directory
903                if self.options.log {
904                    log::info!("Acquiring file lock on build directory");
905                }
906                let lockfile = fs::OpenOptions::new()
907                    .read(true)
908                    .write(true)
909                    .create(true)
910                    .truncate(true)
911                    .open(self.artifact_directory.join(format!("{local_directory_name}.lock")))
912                    .whatever_context(
913                        "Failed to open lockfile for artifacts directory (this is not the actual lock itself, it is an I/O error)",
914                    )?;
915
916                let _file_lock = file_guard::lock(
917                    &lockfile,
918                    file_guard::Lock::Exclusive,
919                    0,
920                    1,
921                )
922                .whatever_context(
923                    "Failed to acquire file lock for artifacts directory",
924                )?;
925                //eprintln_nocapture!(
926                //    "lockfile for {:?} succeeded",
927                //    std::thread::current().id()
928                //)?;
929
930                let start = Instant::now();
931
932                if self.options.log {
933                    log::info!("Building the dynamic library with verilator");
934                }
935                let (library_path, was_rebuilt) = build_library(
936                    &self.source_files,
937                    self.build_target,
938                    &self.include_directories,
939                    &self.dpi_functions,
940                    name,
941                    ports,
942                    &local_artifacts_directory,
943                    &self.options,
944                    config,
945                    self.verilator_version,
946                    self.options.log,
947                    || {
948                        eprintln_nocapture!(
949                            "{} {}#{} ({})",
950                            "   Compiling".bold().green(),
951                            name,
952                            library_key.hash,
953                            source_path
954                        )
955                    },
956                )
957                .whatever_context(
958                    "Failed to build verilator dynamic library",
959                )?;
960
961                if self.options.log {
962                    log::info!("Opening the dynamic library");
963                }
964                let library = unsafe { Library::new(library_path) }
965                    .whatever_context(
966                        "Failed to load verilator dynamic library",
967                    )?;
968
969                one_time_library_setup(
970                    &library,
971                    &self.dpi_functions,
972                    config.enable_tracing,
973                    &self.options,
974                )?;
975
976                let library_idx = self.library_arena.push(library);
977                entry.insert(library_idx);
978
979                let end = Instant::now();
980                let duration = end - start;
981
982                if was_rebuilt {
983                    eprintln_nocapture!(
984                        "{} `verilator-{}` profile [{}] target in {}.{:02}s",
985                        "    Finished".bold().green(),
986                        if config.verilator_optimization == 0 {
987                            "O0".into()
988                        } else {
989                            format!("O{}", config.verilator_optimization)
990                        },
991                        if config.verilator_optimization == 0 {
992                            "unoptimized"
993                        } else {
994                            "optimized"
995                        },
996                        duration.as_secs(),
997                        duration.subsec_millis() / 10
998                    )?;
999                }
1000
1001                library_idx
1002            }
1003        };
1004
1005        Ok(self
1006            .library_arena
1007            .get(library_idx)
1008            .expect("bug: We just inserted the library"))
1009    }
1010}