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 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
50pub mod types {
52 pub type CData = u8;
55
56 pub type SData = u16;
59
60 pub type IData = u32;
63
64 pub type QData = u64;
67
68 pub type EData = u32;
71
72 pub type WData = EData;
75
76 pub type WDataInP = *const WData;
79
80 pub type WDataOutP = *mut WData;
83}
84
85pub 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
95pub 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#[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 #[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#[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 #[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#[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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
205pub struct VerilatedModelConfig {
206 pub verilator_optimization: usize,
210
211 pub ignored_warnings: Vec<String>,
214
215 pub enable_tracing: bool,
217
218 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
233pub trait AsVerilatedModel<'ctx>: 'ctx {
236 fn name() -> &'static str;
238
239 fn source_path() -> &'static str;
241
242 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#[derive(Debug, Clone, PartialEq, Eq, Hash)]
256pub struct VerilatorRuntimeOptions {
257 pub verilator_executable: OsString,
260
261 pub force_verilator_rebuild: bool,
264
265 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 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
302pub 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 library_map: RefCell<HashMap<LibraryArenaKey, usize>>,
312 library_arena: BoxcarVec<Library>,
314 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 deallocator(model);
327 }
328 }
329}
330
331#[derive(Default)]
334struct ThreadLocalFileLock;
335
336static THREAD_LOCKS_PER_BUILD_DIR: LazyLock<
340 DashMap<Utf8PathBuf, Mutex<ThreadLocalFileLock>>,
341> = LazyLock::new(DashMap::default);
342
343fn 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 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 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 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 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 model: unsafe { model.model() },
469 deallocator: delete_model,
470 });
471
472 Ok(model)
473 }
474
475 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 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 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 _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 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 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}