1use std::{
16 cell::RefCell,
17 collections::{HashMap, hash_map::Entry},
18 ffi::{self, OsString},
19 fmt, fs,
20 hash::{self, Hash, Hasher},
21 io::Write,
22 os::fd::FromRawFd,
23 sync::{LazyLock, Mutex},
24 time::Instant,
25};
26
27use boxcar::Vec as BoxcarVec;
28use build_library::build_library;
29use camino::{Utf8Path, Utf8PathBuf};
30use dashmap::DashMap;
31use dpi::DpiFunction;
32use dynamic::DynamicVerilatedModel;
33use libloading::Library;
34use owo_colors::OwoColorize;
35use snafu::{ResultExt, Whatever, whatever};
36
37mod build_library;
38pub mod dpi;
39pub mod dynamic;
40pub mod vcd;
41
42pub use dynamic::AsDynamicVerilatedModel;
43
44pub mod types {
46 pub type CData = u8;
49
50 pub type SData = u16;
53
54 pub type IData = u32;
57
58 pub type QData = u64;
61
62 pub type EData = u32;
65
66 pub type WData = EData;
69}
70
71#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
73pub enum PortDirection {
74 Input,
75 Output,
76 Inout,
77}
78
79impl fmt::Display for PortDirection {
80 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
81 match self {
82 PortDirection::Input => "input",
83 PortDirection::Output => "output",
84 PortDirection::Inout => "inout",
85 }
86 .fmt(f)
87 }
88}
89
90#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
92pub struct VerilatedModelConfig {
93 pub verilator_optimization: usize,
97
98 pub ignored_warnings: Vec<String>,
101
102 pub enable_tracing: bool,
104}
105
106pub trait AsVerilatedModel<'ctx>: 'ctx {
109 fn name() -> &'static str;
111
112 fn source_path() -> &'static str;
114
115 fn ports() -> &'static [(&'static str, usize, usize, PortDirection)];
117
118 #[doc(hidden)]
119 fn init_from(library: &'ctx Library, tracing_enabled: bool) -> Self;
120
121 #[doc(hidden)]
122 unsafe fn model(&self) -> *mut ffi::c_void;
123}
124
125#[derive(Debug, Clone, PartialEq, Eq, Hash)]
128pub struct VerilatorRuntimeOptions {
129 pub verilator_executable: OsString,
132
133 pub force_verilator_rebuild: bool,
136
137 pub log: bool,
139}
140
141impl Default for VerilatorRuntimeOptions {
142 fn default() -> Self {
143 Self {
144 verilator_executable: "verilator".into(),
145 force_verilator_rebuild: false,
146 log: false,
147 }
148 }
149}
150
151impl VerilatorRuntimeOptions {
152 pub fn default_logging() -> Self {
155 Self {
156 log: true,
157 ..Default::default()
158 }
159 }
160}
161
162#[derive(PartialEq, Eq, Hash, Clone)]
163struct LibraryArenaKey {
164 name: String,
165 source_path: String,
166 hash: u64,
167}
168
169pub struct VerilatorRuntime {
171 artifact_directory: Utf8PathBuf,
172 source_files: Vec<Utf8PathBuf>,
173 include_directories: Vec<Utf8PathBuf>,
174 dpi_functions: Vec<&'static dyn DpiFunction>,
175 options: VerilatorRuntimeOptions,
176 library_map: RefCell<HashMap<LibraryArenaKey, usize>>,
179 library_arena: BoxcarVec<Library>,
181 model_deallocators:
185 RefCell<Vec<(*mut ffi::c_void, extern "C" fn(*mut ffi::c_void))>>,
186}
187
188impl Drop for VerilatorRuntime {
189 fn drop(&mut self) {
190 for (model, deallocator) in
191 self.model_deallocators.borrow_mut().drain(..)
192 {
193 deallocator(model);
194 }
195 }
196}
197
198static STDERR: LazyLock<Mutex<fs::File>> =
202 LazyLock::new(|| Mutex::new(unsafe { fs::File::from_raw_fd(2) }));
203
204macro_rules! eprintln_nocapture {
205 ($($contents:tt)*) => {{
206 use snafu::ResultExt;
207
208 writeln!(
209 &mut STDERR.lock().expect("poisoned"),
210 $($contents)*
211 )
212 .whatever_context("Failed to write to non-captured stderr")
213 }};
214}
215
216#[derive(Default)]
217struct ThreadLocalFileLock;
218
219static THREAD_LOCKS_PER_BUILD_DIR: LazyLock<
223 DashMap<Utf8PathBuf, Mutex<ThreadLocalFileLock>>,
224> = LazyLock::new(DashMap::default);
225
226fn one_time_library_setup(
229 library: &Library,
230 dpi_functions: &[&'static dyn DpiFunction],
231 tracing_enabled: bool,
232 options: &VerilatorRuntimeOptions,
233) -> Result<(), Whatever> {
234 if !dpi_functions.is_empty() {
235 let dpi_init_callback: extern "C" fn(*const *const ffi::c_void) =
236 *unsafe { library.get(b"dpi_init_callback") }
237 .whatever_context("Failed to load DPI initializer")?;
238
239 let function_pointers = dpi_functions
244 .iter()
245 .map(|dpi_function| dpi_function.pointer())
246 .collect::<Vec<_>>();
247
248 (dpi_init_callback)(function_pointers.as_ptr_range().start);
249
250 if options.log {
251 log::info!("Initialized DPI functions");
252 }
253 }
254
255 if tracing_enabled {
256 let trace_ever_on_callback: extern "C" fn(bool) =
257 *unsafe { library.get(b"ffi_Verilated_traceEverOn") }
258 .whatever_context(
259 "Model was not configured with tracing enabled",
260 )?;
261 trace_ever_on_callback(true);
262
263 if options.log {
264 log::info!("Initialized VCD tracing");
265 }
266 }
267
268 Ok(())
269}
270
271impl VerilatorRuntime {
272 pub fn new(
275 artifact_directory: &Utf8Path,
276 source_files: &[&Utf8Path],
277 include_directories: &[&Utf8Path],
278 dpi_functions: impl IntoIterator<Item = &'static dyn DpiFunction>,
279 options: VerilatorRuntimeOptions,
280 ) -> Result<Self, Whatever> {
281 if options.log {
282 log::info!("Validating source files");
283 }
284 for source_file in source_files {
285 if !source_file.is_file() {
286 whatever!(
287 "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",
288 source_file
289 );
290 }
291 }
292
293 Ok(Self {
294 artifact_directory: artifact_directory.to_owned(),
295 source_files: source_files
296 .iter()
297 .map(|path| path.to_path_buf())
298 .collect(),
299 include_directories: include_directories
300 .iter()
301 .map(|path| path.to_path_buf())
302 .collect(),
303 dpi_functions: dpi_functions.into_iter().collect(),
304 options,
305 library_map: RefCell::new(HashMap::new()),
306 library_arena: BoxcarVec::new(),
307 model_deallocators: RefCell::new(vec![]),
308 })
309 }
310
311 pub fn create_model_simple<'ctx, M: AsVerilatedModel<'ctx>>(
316 &'ctx self,
317 ) -> Result<M, Whatever> {
318 self.create_model(&VerilatedModelConfig::default())
319 }
320
321 pub fn create_model<'ctx, M: AsVerilatedModel<'ctx>>(
326 &'ctx self,
327 config: &VerilatedModelConfig,
328 ) -> Result<M, Whatever> {
329 let library = self
330 .build_or_retrieve_library(
331 M::name(),
332 M::source_path(),
333 M::ports(),
334 config,
335 )
336 .whatever_context(
337 "Failed to build or retrieve verilator dynamic library. Try removing the build directory if it is corrupted.",
338 )?;
339
340 let delete_model: extern "C" fn(*mut ffi::c_void) = *unsafe {
341 library.get(format!("ffi_delete_V{}", M::name()).as_bytes())
342 }
343 .expect("failed to get symbol");
344
345 let model = M::init_from(library, config.enable_tracing);
346
347 self.model_deallocators.borrow_mut().push((
348 unsafe { model.model() },
350 delete_model,
351 ));
352
353 Ok(model)
354 }
355
356 pub fn create_dyn_model<'ctx>(
386 &'ctx self,
387 name: &str,
388 source_path: &str,
389 ports: &[(&str, usize, usize, PortDirection)],
390 config: VerilatedModelConfig,
391 ) -> Result<DynamicVerilatedModel<'ctx>, Whatever> {
392 let library = self
393 .build_or_retrieve_library(name, source_path, ports, &config)
394 .whatever_context(
395 "Failed to build or retrieve verilator dynamic library. Try removing the build directory if it is corrupted.",
396 )?;
397
398 let new_main: extern "C" fn() -> *mut ffi::c_void =
399 *unsafe { library.get(format!("ffi_new_V{name}").as_bytes()) }
400 .whatever_context(format!(
401 "Failed to load constructor for module {}",
402 name
403 ))?;
404 let delete_main =
405 *unsafe { library.get(format!("ffi_delete_V{name}").as_bytes()) }
406 .whatever_context(format!(
407 "Failed to load destructor for module {}",
408 name
409 ))?;
410 let eval_main =
411 *unsafe { library.get(format!("ffi_V{name}_eval").as_bytes()) }
412 .whatever_context(format!(
413 "Failed to load evalulator for module {}",
414 name
415 ))?;
416
417 let main = new_main();
418
419 let ports = ports
420 .iter()
421 .copied()
422 .map(|(port, high, low, direction)| {
423 (port.to_string(), (high - low + 1, direction))
424 })
425 .collect();
426
427 self.model_deallocators
428 .borrow_mut()
429 .push((main, delete_main));
430
431 Ok(DynamicVerilatedModel {
432 ports,
433 name: name.to_string(),
434 main,
435 eval_main,
436 library,
437 })
438 }
439
440 fn build_or_retrieve_library(
466 &self,
467 name: &str,
468 source_path: &str,
469 ports: &[(&str, usize, usize, PortDirection)],
470 config: &VerilatedModelConfig,
471 ) -> Result<&Library, Whatever> {
472 if name.chars().any(|c| c == '\\' || c == ' ') {
473 whatever!("Escaped module names are not supported");
474 }
475
476 if self.options.log {
477 log::info!("Validating model source file");
478 }
479 if !self.source_files.iter().any(|source_file| {
480 match (
481 source_file.canonicalize_utf8(),
482 Utf8Path::new(source_path).canonicalize_utf8(),
483 ) {
484 (Ok(lhs), Ok(rhs)) => lhs == rhs,
485 _ => false,
486 }
487 }) {
488 whatever!(
489 "Module `{}` requires source file {}, which was not provided to the runtime",
490 name,
491 source_path
492 );
493 }
494
495 if let Some((port, _, _, _)) =
496 ports.iter().find(|(_, high, low, _)| high < low)
497 {
498 whatever!(
499 "Port {} on module {} was specified with the high bit less than the low bit",
500 port,
501 name
502 );
503 }
504 if let Some((port, _, _, _)) =
505 ports.iter().find(|(_, high, low, _)| high + 1 - low > 64)
506 {
507 whatever!(
508 "Port {} on module {} is greater than 64 bits",
509 port,
510 name
511 );
512 }
513
514 let mut hasher = hash::DefaultHasher::new();
515 ports.hash(&mut hasher);
516 config.hash(&mut hasher);
517 let library_key = LibraryArenaKey {
518 name: name.to_owned(),
519 source_path: source_path.to_owned(),
520 hash: hasher.finish(),
521 };
522
523 let library_idx = match self
524 .library_map
525 .borrow_mut()
526 .entry(library_key.clone())
527 {
528 Entry::Occupied(entry) => *entry.get(),
529 Entry::Vacant(entry) => {
530 let local_directory_name = format!(
531 "{name}_{}_{}",
532 source_path.replace("_", "__").replace("/", "_"),
533 library_key.hash
534 );
535 let local_artifacts_directory =
536 self.artifact_directory.join(&local_directory_name);
537
538 if self.options.log {
539 log::info!(
540 "Creating artifacts directory {}",
541 local_artifacts_directory
542 );
543 }
544 fs::create_dir_all(&local_artifacts_directory)
545 .whatever_context(format!(
546 "Failed to create artifacts directory {}",
547 local_artifacts_directory,
548 ))?;
549
550 if !THREAD_LOCKS_PER_BUILD_DIR
556 .contains_key(&local_artifacts_directory)
557 {
558 THREAD_LOCKS_PER_BUILD_DIR.insert(
559 local_artifacts_directory.clone(),
560 Default::default(),
561 );
562 }
563 let thread_mutex = THREAD_LOCKS_PER_BUILD_DIR
564 .get(&local_artifacts_directory)
565 .expect("We just inserted if it didn't exist");
566
567 let _thread_lock = if let Ok(_thread_lock) =
568 thread_mutex.try_lock()
569 {
570 _thread_lock
575 } else {
576 eprintln_nocapture!(
577 "{} waiting for file lock on build directory",
578 " Blocking".bold().cyan(),
579 )?;
580 let Ok(_thread_lock) = thread_mutex.lock() else {
581 whatever!(
582 "Failed to acquire thread-local lock for artifacts directory"
583 );
584 };
585 _thread_lock
586 };
587
588 if self.options.log {
592 log::info!("Acquiring file lock on artifact directory");
593 }
594 let lockfile = fs::OpenOptions::new()
595 .read(true)
596 .write(true)
597 .create(true)
598 .truncate(true)
599 .open(self.artifact_directory.join(format!("{local_directory_name}.lock")))
600 .whatever_context(
601 "Failed to open lockfile for artifacts directory (this is not the actual lock itself, it is an I/O error)",
602 )?;
603
604 let _file_lock = file_guard::lock(
605 &lockfile,
606 file_guard::Lock::Exclusive,
607 0,
608 1,
609 )
610 .whatever_context(
611 "Failed to acquire file lock for artifacts directory",
612 )?;
613 let start = Instant::now();
619
620 if self.options.log {
621 log::info!("Building the dynamic library with verilator");
622 }
623 let (library_path, was_rebuilt) = build_library(
624 &self.source_files,
625 &self.include_directories,
626 &self.dpi_functions,
627 name,
628 ports,
629 &local_artifacts_directory,
630 &self.options,
631 config,
632 self.options.log,
633 || {
634 eprintln_nocapture!(
635 "{} {}#{} ({})",
636 " Compiling".bold().green(),
637 name,
638 library_key.hash,
639 source_path
640 )
641 },
642 )
643 .whatever_context(
644 "Failed to build verilator dynamic library",
645 )?;
646
647 if self.options.log {
648 log::info!("Opening the dynamic library");
649 }
650 let library = unsafe { Library::new(library_path) }
651 .whatever_context(
652 "Failed to load verilator dynamic library",
653 )?;
654
655 one_time_library_setup(
656 &library,
657 &self.dpi_functions,
658 config.enable_tracing,
659 &self.options,
660 )?;
661
662 let library_idx = self.library_arena.push(library);
663 entry.insert(library_idx);
664
665 let end = Instant::now();
666 let duration = end - start;
667
668 if was_rebuilt {
669 eprintln_nocapture!(
670 "{} `verilator-{}` profile target in {}.{:02}s",
671 " Finished".bold().green(),
672 if config.verilator_optimization == 0 {
673 "unoptimized".into()
674 } else {
675 format!("O{}", config.verilator_optimization)
676 },
677 duration.as_secs(),
678 duration.subsec_millis() / 10
679 )?;
680 }
681
682 library_idx
683 }
684 };
685
686 Ok(self
687 .library_arena
688 .get(library_idx)
689 .expect("bug: We just inserted the library"))
690 }
691}