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