1use 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
46pub mod types {
48 pub type CData = u8;
51
52 pub type SData = u16;
55
56 pub type IData = u32;
59
60 pub type QData = u64;
63
64 pub type EData = u32;
67
68 pub type WData = EData;
71
72 pub type WDataInP = *const WData;
75
76 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#[doc(hidden)]
88pub const fn compute_approx_width_from_wdata_length(length: usize) -> usize {
89 length * (types::WData::BITS as usize)
90}
91
92#[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 #[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#[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 #[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#[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#[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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
195pub struct VerilatedModelConfig {
196 pub verilator_optimization: usize,
200
201 pub ignored_warnings: Vec<String>,
204
205 pub enable_tracing: bool,
207
208 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
223pub trait AsVerilatedModel<'ctx>: 'ctx {
226 fn name() -> &'static str;
228
229 fn source_path() -> &'static str;
231
232 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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
245pub struct VerilatorRuntimeOptions {
246 pub verilator_executable: OsString,
249
250 pub force_verilator_rebuild: bool,
253
254 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 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
286pub 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 library_map: RefCell<HashMap<LibraryArenaKey, usize>>,
296 library_arena: BoxcarVec<Library>,
298 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#[derive(Default)]
318struct ThreadLocalFileLock;
319
320static THREAD_LOCKS_PER_BUILD_DIR: LazyLock<
324 DashMap<Utf8PathBuf, Mutex<ThreadLocalFileLock>>,
325> = LazyLock::new(DashMap::default);
326
327fn 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 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 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 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 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 unsafe { model.model() },
451 delete_model,
452 ));
453
454 Ok(model)
455 }
456
457 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 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 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 _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 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 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}