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