From 8a39a197b99f798e1ddbea27e9dff04a11e73c8c Mon Sep 17 00:00:00 2001 From: Ethan Uppal <113849268+ethanuppal@users.noreply.github.com> Date: Sat, 8 Feb 2025 17:49:30 -0500 Subject: [PATCH] feat(verilator): Revamp Rust DPI to be more usable and powerful (#48) * feat(verilator): Start improving DPI setup * feat: Finish implementing DPI callback system * docs: Update DPI docs --- docs/verilog_dpi.md | 9 +- examples/verilog-project/test/dpi_tutorial.rs | 3 +- language-support/verilog-macro/src/lib.rs | 216 ++++++++---------- language-support/verilog/src/lib.rs | 6 +- verilator/src/build_library.rs | 193 ++++++---------- verilator/src/dpi.rs | 20 ++ verilator/src/lib.rs | 51 +++-- 7 files changed, 224 insertions(+), 274 deletions(-) create mode 100644 verilator/src/dpi.rs diff --git a/docs/verilog_dpi.md b/docs/verilog_dpi.md index bf4effa..52c4738 100644 --- a/docs/verilog_dpi.md +++ b/docs/verilog_dpi.md @@ -87,8 +87,7 @@ use snafu::Whatever; use verilog::{verilog, VerilatorRuntime, VerilatorRuntimeOptions}; #[verilog::dpi] -#[no_mangle] -extern "C" fn three(#[output] out: &mut u32) { +pub extern "C" fn three(out: &mut u32) { *out = 3; } @@ -127,12 +126,12 @@ extern "C" fn three(#[output] out: &mut u32) { } ``` By applying `#[verilog::dpi]`, we turn our normal Rust function into a DPI one. -We need to apply `#[no_mangle]` and `extern` (or `extern "C"`) so that Rust +We need to apply `pub` and `extern` (or `extern "C"`) so that Rust exposes the function correctly to C. DPI functions cannot have a return value and only take primitive integers or mutable references to primitive integers as -arguments. Each parameter must be annotated with `#[input]`, `#[output]`, or -`#[inout]`. Moreover, their bodies can only access the standard library. +arguments. Beside that, there are no restrictions on the content --- write +whatever Rust code you want! Then, we told the runtime about this function: ```diff diff --git a/examples/verilog-project/test/dpi_tutorial.rs b/examples/verilog-project/test/dpi_tutorial.rs index 867f1f5..5be47fd 100644 --- a/examples/verilog-project/test/dpi_tutorial.rs +++ b/examples/verilog-project/test/dpi_tutorial.rs @@ -16,8 +16,7 @@ use snafu::Whatever; use verilog::{verilog, VerilatorRuntime, VerilatorRuntimeOptions}; #[verilog::dpi] -#[no_mangle] -extern "C" fn three(#[output] out: &mut u32) { +pub extern "C" fn three(out: &mut u32) { *out = 3; } diff --git a/language-support/verilog-macro/src/lib.rs b/language-support/verilog-macro/src/lib.rs index 160c3de..23616c8 100644 --- a/language-support/verilog-macro/src/lib.rs +++ b/language-support/verilog-macro/src/lib.rs @@ -273,19 +273,13 @@ fn parse_dpi_primitive_type( enum DPIType { Input(DPIPrimitiveType), - Output(DPIPrimitiveType), + /// Veriltor handles output and inout types the same Inout(DPIPrimitiveType), } -fn parse_dpi_type( - direction: (PortDirection, &syn::Attribute), - ty: &syn::Type, -) -> Result { +fn parse_dpi_type(ty: &syn::Type) -> Result { match ty { syn::Type::Path(type_path) => { - if matches!(direction.0, PortDirection::Output | PortDirection::Inout) { - return Err(syn::Error::new_spanned(direction.1, "DPI output or inout type must use a lifetime-free mutable reference")); - } Ok(DPIType::Input(parse_dpi_primitive_type(type_path)?)) }, syn::Type::Reference(syn::TypeReference { @@ -294,9 +288,6 @@ fn parse_dpi_type( mutability, elem, }) => { - if matches!(direction.0, PortDirection::Input) { - return Err(syn::Error::new_spanned(direction.1, "DPI input type must use a bare primitive integer")); - } if mutability.is_none() { return Err(syn::Error::new_spanned(and_token, "DPI output or inout type must be represented with a mutable reference")); } @@ -307,13 +298,8 @@ fn parse_dpi_type( let syn::Type::Path(type_path) = elem.as_ref() else { return Err(syn::Error::new_spanned(elem, "DPI output or inout type must be a mutable reference to a primitive integer type")); -}; - let inner = parse_dpi_primitive_type(type_path)?; -match direction.0 { - PortDirection::Output => Ok(DPIType::Output(inner)), - PortDirection::Inout => Ok(DPIType::Inout(inner)), - _ => unreachable!() - } + }; + Ok(DPIType::Inout(parse_dpi_primitive_type(type_path)?)) }, other => Err(syn::Error::new_spanned(other, "This type is not supported in DPI. Please use primitive integers or mutable references to them")), } @@ -323,6 +309,15 @@ match direction.0 { pub fn dpi(_args: TokenStream, item: TokenStream) -> TokenStream { let item_fn = parse_macro_input!(item as syn::ItemFn); + if !matches!(item_fn.vis, syn::Visibility::Public(_)) { + return syn::Error::new_spanned( + item_fn.vis, + "Marking the function `pub` is required to expose this Rust function to C", + ) + .into_compile_error() + .into(); + } + let Some(abi) = &item_fn.sig.abi else { return syn::Error::new_spanned( item_fn, @@ -346,22 +341,6 @@ pub fn dpi(_args: TokenStream, item: TokenStream) -> TokenStream { .into(); } - if !item_fn.attrs.iter().any(|attribute| { - attribute - .path() - .segments - .first() - .map(|segment| segment.ident.to_string().as_str() == "no_mangle") - .unwrap_or(false) - }) { - return syn::Error::new_spanned( - item_fn, - "`#[no_mangle]` is required to expose this Rust function to C", - ) - .into_compile_error() - .into(); - } - if item_fn.sig.generics.lt_token.is_some() { return syn::Error::new_spanned( item_fn.sig.generics, @@ -389,136 +368,121 @@ pub fn dpi(_args: TokenStream, item: TokenStream) -> TokenStream { .into(); } - let ports = match item_fn.sig.inputs.iter().try_fold(vec![], |mut ports, input| { - let syn::FnArg::Typed(parameter) = input else { - return Err(syn::Error::new_spanned(input, "Invalid parameter on DPI function")); - }; + let ports = + match item_fn + .sig + .inputs + .iter() + .try_fold(vec![], |mut ports, input| { + let syn::FnArg::Typed(parameter) = input else { + return Err(syn::Error::new_spanned( + input, + "Invalid parameter on DPI function", + )); + }; - let syn::Pat::Ident(name) = &*parameter.pat else { - return Err(syn::Error::new_spanned(parameter, "Function argument must be an identifier")); - }; + let syn::Pat::Ident(name) = &*parameter.pat else { + return Err(syn::Error::new_spanned( + parameter, + "Function argument must be an identifier", + )); + }; - let Some(direction) = parameter.attrs.iter().find_map(|attr| { - match attr.path().require_ident().ok()?.to_string().as_str() { - "input" => Some((PortDirection::Input, attr)), - "output" => Some((PortDirection::Output, attr)), - "inout" => Some((PortDirection::Inout, attr)), - _ => None + let attrs = parameter.attrs.clone(); + ports.push((name, attrs, parse_dpi_type(¶meter.ty)?)); + Ok(ports) + }) { + Ok(ports) => ports, + Err(error) => { + return error.into_compile_error().into(); } - }) else { - return Err(syn::Error::new_spanned(parameter, "Specify `#[input]`, `#[output]`, or `#[inout]` on the parameter")); }; - let attrs = parameter.attrs.iter().filter(|&attribute| { - attribute.path().require_ident().ok().map(|ident| - !matches!(ident.to_string().as_str(), "input" | "output" | "inout")).unwrap_or(false) - }).cloned().collect::>(); - - ports.push((name, attrs, parse_dpi_type(direction, ¶meter.ty)?)); - Ok(ports) - }) { - Ok(ports) => ports, - Err(error) => { - return error.into_compile_error().into(); - } - }; - - let mut cloned_item_fn = item_fn.clone(); - for input in &mut cloned_item_fn.sig.inputs { - if let syn::FnArg::Typed(parameter) = input { - parameter.attrs.retain(|attribute| { - !attribute - .path() - .require_ident() - .ok() - .map(|ident| { - matches!( - ident.to_string().as_str(), - "input" | "output" | "inout" - ) - }) - .unwrap_or(false) - }); - } - } - let attributes = item_fn.attrs; - let name = item_fn.sig.ident; + let function_name = item_fn.sig.ident; let body = item_fn.block; - let parameters = ports.iter().map(|(name, attributes, dpi_type)| { + let struct_name = format_ident!("__DPI_{}", function_name); + + let mut parameter_types = vec![]; + let mut parameters = vec![]; + + for (name, attributes, dpi_type) in &ports { let parameter_type = match dpi_type { DPIType::Input(inner) => { let type_ident = format_ident!("{}", inner.to_string()); quote! { #type_ident } } - DPIType::Output(inner) | DPIType::Inout(inner) => { + DPIType::Inout(inner) => { let type_ident = format_ident!("{}", inner.to_string()); quote! { *mut #type_ident } } }; - quote! { + parameter_types.push(parameter_type.clone()); + parameters.push(quote! { #(#attributes)* #name: #parameter_type - } - }); + }); + } let preamble = ports .iter() .filter_map(|(name, _, dpi_type)| match dpi_type { - DPIType::Output(_) | DPIType::Inout(_) => Some(quote! { + DPIType::Inout(_) => Some(quote! { let #name = unsafe { &mut *#name }; }), _ => None, }); - let function_name = format_ident!("rust_{}", name); - let function_to_compile = quote! { - #(#attributes)* - pub extern "C" fn #function_name(#(#parameters),*) { - #(#preamble)* - #body - } - } - .to_string(); - - let name_literal = syn::LitStr::new(name.to_string().as_str(), name.span()); - let function_to_compile_literal = - syn::LitStr::new(&function_to_compile, cloned_item_fn.span()); + let function_name_literal = syn::LitStr::new( + function_name.to_string().as_str(), + function_name.span(), + ); let c_signature = ports .iter() .map(|(name, _, dpi_type)| { let c_type = match dpi_type { DPIType::Input(inner) => inner.as_c().to_string(), - DPIType::Output(inner) | DPIType::Inout(inner) => { - format!("{}*", inner.as_c()) - } + DPIType::Inout(inner) => format!("{}*", inner.as_c()), }; - format!("{} {}", c_type, name.ident) + let name_literal = + syn::LitStr::new(name.ident.to_string().as_str(), name.span()); + let type_literal = syn::LitStr::new(&c_type, name.span()); + quote! { + (#name_literal, #type_literal) + } }) - .collect::>() - .join(", "); - let c_arguments = ports - .iter() - .map(|(name, _, _)| name.ident.to_string()) - .collect::>() - .join(", "); - let c_function = format!( - "extern \"C\" void rust_{name}({c_signature});\nextern \"C\" void {name}({c_signature}) {{ rust_{name}({c_arguments}); }}", - ); - let c_function_literal = - syn::LitStr::new(&c_function, cloned_item_fn.span()); + .collect::>(); + quote! { + #[allow(non_camel_case_types)] + struct #struct_name; + + impl #struct_name { + #(#attributes)* + pub extern "C" fn call(#(#parameters),*) { + #(#preamble)* + #body + } + } + + impl verilog::__reexports::verilator::dpi::DpiFunction for #struct_name { + fn name(&self) -> &'static str { + #function_name_literal + } + + fn signature(&self) -> &'static [(&'static str, &'static str)] { + &[#(#c_signature),*] + } + + fn pointer(&self) -> *const verilog::__reexports::libc::c_void { + #struct_name::call as extern "C" fn(#(#parameter_types),*) as *const verilog::__reexports::libc::c_void + } + } + #[allow(non_upper_case_globals)] - const #name: verilog::__reexports::verilator::DpiFunction = { - #cloned_item_fn - verilog::__reexports::verilator::DpiFunction( - #name_literal, - #c_function_literal, - #function_to_compile_literal - ) - }; + pub static #function_name: &'static dyn verilog::__reexports::verilator::dpi::DpiFunction = &#struct_name; } .into() } diff --git a/language-support/verilog/src/lib.rs b/language-support/verilog/src/lib.rs index 9b5e598..46c3bc3 100644 --- a/language-support/verilog/src/lib.rs +++ b/language-support/verilog/src/lib.rs @@ -11,8 +11,8 @@ pub mod __reexports { } pub use verilator::{ - dynamic::DynamicVerilatedModel, dynamic::DynamicVerilatedModelError, - dynamic::VerilatorValue, DpiFunction, PortDirection, VerilatorRuntime, - VerilatorRuntimeOptions, + dpi::DpiFunction, dynamic::DynamicVerilatedModel, + dynamic::DynamicVerilatedModelError, dynamic::VerilatorValue, + PortDirection, VerilatorRuntime, VerilatorRuntimeOptions, }; pub use verilog_macro::{dpi, verilog}; diff --git a/verilator/src/build_library.rs b/verilator/src/build_library.rs index 973294e..a1c6b2c 100644 --- a/verilator/src/build_library.rs +++ b/verilator/src/build_library.rs @@ -11,12 +11,12 @@ // - location of verilated.h // - verilator library is obj_dir/libverilated.a -use std::{ffi::OsStr, fmt::Write, fs, process::Command}; +use std::{fmt::Write, fs, process::Command}; use camino::{Utf8Path, Utf8PathBuf}; use snafu::{prelude::*, Whatever}; -use crate::{DpiFunction, PortDirection, VerilatorRuntimeOptions}; +use crate::{dpi::DpiFunction, PortDirection, VerilatorRuntimeOptions}; /// Writes `extern "C"` C++ bindings for a Verilator model with the given name /// (`top_module`) and signature (`ports`) to the given artifact directory @@ -132,103 +132,105 @@ extern "C" {{ Ok(ffi_wrappers) } -/// Sets up the DPI artifacts directory and builds DPI function bindings if +/// Sets up the DPI artifacts directory and generates DPI function bindings if /// needed, returning: -/// 1. `Some` tuple of the DPI artifact files to link in (or `None` if there are -/// no DPI functions in the first place) -/// 2. Whether there was a rebuild of any kind +/// 1. `Some` DPI bindings file to compile in (or `None` if there are no DPI +/// functions in the first place) +/// 2. Whether there was a regeneration of any kind /// /// This function is a nop if `dpi_functions.is_empty()`. -fn build_dpi_if_needed( +fn bind_dpi_if_needed( top_module: &str, - rustc: &OsStr, - rustc_optimize: bool, - dpi_functions: &[DpiFunction], + dpi_functions: &[&'static dyn DpiFunction], dpi_artifact_directory: &Utf8Path, verbose: bool, -) -> Result<(Option<(Utf8PathBuf, Utf8PathBuf)>, bool), Whatever> { +) -> Result<(Option, bool), Whatever> { if dpi_functions.is_empty() { return Ok((None, false)); } - let dpi_file = dpi_artifact_directory.join("dpi.rs"); - // TODO: hard-coded knowledge - let dpi_object_file = Utf8PathBuf::from("../dpi/dpi.o"); // dpi_file.with_extension("o"); - let dpi_c_wrappers = Utf8PathBuf::from("../dpi/wrappers.cpp"); // dpi_artifact_directory.join("wrappers.cpp"); - - let current_file_code = dpi_functions - .iter() - .map(|DpiFunction(_, _, rust_code)| rust_code) - .cloned() - .collect::>() - .join("\n"); - let c_file_code = format!( - "#include \"svdpi.h\"\n#include \"V{}__Dpi.h\"\n#include \n{}", + let _dpi_file = dpi_artifact_directory.join("dpi.cpp"); + // TODO: hard-coded knowledge, same verilator bug + let dpi_file = Utf8PathBuf::from("../dpi/dpi.cpp"); + + let file_code = format!( + "#include \"svdpi.h\" +#include \"V{}__Dpi.h\" +#include +{} +extern \"C\" void dpi_init_callback(void** callbacks) {{ +{} +}}", top_module, dpi_functions .iter() - .map(|DpiFunction(_, c_code, _)| c_code) - .cloned() + .map(|dpi_function| { + let name = dpi_function.name(); + let signature = dpi_function + .signature() + .iter() + .map(|(name, ty)| format!("{} {}", ty, name)) + .collect::>() + .join(", "); + let arguments = dpi_function + .signature() + .iter() + .map(|(name, _)| name.to_owned()) + .collect::>() + .join(","); + format!( + "static void (*rust_{})({}); +extern \"C\" void {}({}) {{ + rust_{}({}); +}}", + name, signature, name, signature, name, arguments + ) + }) .collect::>() - .join("\n") + .join("\n"), + dpi_functions + .iter() + .enumerate() + .map(|(i, dpi_function)| { + let signature = dpi_function + .signature() + .iter() + .map(|(name, ty)| format!("{} {}", ty, name)) + .collect::>() + .join(", "); + format!( + " rust_{} = ( void(*)({}) ) callbacks[{}];", + dpi_function.name(), + signature, + i + ) + }) + .collect::>() + .join("\n"), ); // only rebuild if there's been a change if fs::read_to_string(&dpi_file) - .map(|file_code| file_code == current_file_code) + .map(|current_file_code| current_file_code == file_code) .unwrap_or(false) { if verbose { - log::info!("| Skipping rebuild of DPI due to no changes"); + log::info!("| Skipping regeneration of DPI due to no changes"); } - return Ok((Some((dpi_object_file, dpi_c_wrappers)), false)); + return Ok((Some(dpi_file), false)); } if verbose { - log::info!("| Building DPI"); + log::info!("| Generating DPI bindings"); } - fs::write(dpi_artifact_directory.join("wrappers.cpp"), c_file_code) + fs::write(dpi_artifact_directory.join("dpi.cpp"), file_code) .whatever_context(format!( "Failed to write DPI function wrapper code to {}", - dpi_c_wrappers - ))?; - fs::write(&dpi_file, current_file_code).whatever_context(format!( - "Failed to write DPI function code to {}", - dpi_file - ))?; - - let mut rustc_command = Command::new(rustc); - rustc_command - .args(["--emit=obj", "--crate-type=cdylib"]) - .args(["--edition", "2021"]) - .arg( dpi_file - .components() - .last() - .expect("We just added dpi.rs to the end..."), - ) - .current_dir(dpi_artifact_directory); - if rustc_optimize { - rustc_command.arg("-O"); - } - if verbose { - log::info!(" | rustc invocation: {:?}", rustc_command); - } - let rustc_output = rustc_command - .output() - .whatever_context("Invocation of verilator failed")?; - - if !rustc_output.status.success() { - whatever!( - "Invocation of rustc failed with nonzero exit code {}\n\n--- STDOUT ---\n{}\n\n--- STDERR ---\n{}", - rustc_output.status, - String::from_utf8(rustc_output.stdout).unwrap_or_default(), - String::from_utf8(rustc_output.stderr).unwrap_or_default() - ); - } + ))?; - Ok((Some((dpi_object_file, dpi_c_wrappers)), true)) + Ok((Some(dpi_file), true)) } /// Returns `Ok(true)` when the library doesn't exist or if any Verilog source @@ -299,7 +301,7 @@ fn needs_verilator_rebuild( /// Finally, we invoke `verilator` and return the library path. pub fn build_library( source_files: &[&str], - dpi_functions: &[DpiFunction], + dpi_functions: &[&'static dyn DpiFunction], top_module: &str, ports: &[(&str, usize, usize, PortDirection)], artifact_directory: &Utf8Path, @@ -323,10 +325,8 @@ pub fn build_library( let library_path = verilator_artifact_directory.join(format!("lib{}.so", library_name)); - let (dpi_artifacts, dpi_rebuilt) = build_dpi_if_needed( + let (dpi_file, dpi_rebuilt) = bind_dpi_if_needed( top_module, - &options.rustc_executable, - options.rustc_optimization, dpi_functions, &dpi_artifact_directory, verbose, @@ -353,17 +353,15 @@ pub fn build_library( let mut verilator_command = Command::new(&options.verilator_executable); verilator_command - .args(["--cc", "-sv", "-j", "0"]) + .args(["--cc", "-sv", "-j", "0", "--build"]) .args(["-CFLAGS", "-shared -fpic"]) .args(["--lib-create", &library_name]) .args(["--Mdir", verilator_artifact_directory.as_str()]) .args(["--top-module", top_module]) .args(source_files) .arg(ffi_wrappers); - if let Some((dpi_object_file, dpi_c_wrapper)) = dpi_artifacts { - verilator_command - .args(["-CFLAGS", dpi_object_file.as_str()]) - .arg(dpi_c_wrapper); + if let Some(dpi_file) = dpi_file { + verilator_command.arg(dpi_file); } if let Some(level) = options.verilator_optimization { if (0..=3).contains(&level) { @@ -388,46 +386,5 @@ pub fn build_library( ); } - let verilator_makefile_filename = - Utf8PathBuf::from(format!("V{}.mk", top_module)); - let verilator_makefile_path = - verilator_artifact_directory.join(&verilator_makefile_filename); - let verilator_makefile_contents = fs::read_to_string( - &verilator_makefile_path, - ) - .whatever_context(format!( - "Failed to read Verilator-generated Makefile {}", - verilator_makefile_path - ))?; - let verilator_makefile_contents = format!( - "VK_USER_OBJS += ../dpi/dpi.o\n\n{}", - verilator_makefile_contents - ); - fs::write(&verilator_makefile_path, verilator_makefile_contents) - .whatever_context(format!( - "Failed to update Verilator-generated Makefile {}", - verilator_makefile_path - ))?; - - let mut make_command = Command::new(&options.make_executable); - make_command - .args(["-f", verilator_makefile_filename.as_str()]) - .current_dir(verilator_artifact_directory); - if verbose { - log::info!("| Make invocation: {:?}", make_command); - } - let make_output = make_command - .output() - .whatever_context("Invocation of Make failed")?; - - if !make_output.status.success() { - whatever!( - "Invocation of make failed with nonzero exit code {}\n\n--- STDOUT ---\n{}\n\n--- STDERR ---\n{}", - make_output.status, - String::from_utf8(make_output.stdout).unwrap_or_default(), - String::from_utf8(make_output.stderr).unwrap_or_default() - ); - } - Ok(library_path) } diff --git a/verilator/src/dpi.rs b/verilator/src/dpi.rs new file mode 100644 index 0000000..3248bf6 --- /dev/null +++ b/verilator/src/dpi.rs @@ -0,0 +1,20 @@ +// Copyright (C) 2024 Ethan Uppal. +// +// This Source Code Form is subject to the terms of the Mozilla Public License, +// v. 2.0. If a copy of the MPL was not distributed with this file, You can +// obtain one at https://mozilla.org/MPL/2.0/. + +pub trait DpiFunction: Sync { + /// The Rust-declared name of the DPI function. This should be taken to be + /// equivalent to the name given for the DPI C function in Verilog + /// source code. + fn name(&self) -> &'static str; + + /// A list of `(name, c_type)` pairs serving as the parameters of the + /// generated C function and the generated function pointer type for the + /// Rust function. + fn signature(&self) -> &'static [(&'static str, &'static str)]; + + /// The Rust function as a function pointer. + fn pointer(&self) -> *const libc::c_void; +} diff --git a/verilator/src/lib.rs b/verilator/src/lib.rs index 969c0c6..f58848e 100644 --- a/verilator/src/lib.rs +++ b/verilator/src/lib.rs @@ -19,11 +19,13 @@ use std::{ use build_library::build_library; use camino::{Utf8Path, Utf8PathBuf}; +use dpi::DpiFunction; use dynamic::DynamicVerilatedModel; use libloading::Library; use snafu::{prelude::*, Whatever}; mod build_library; +pub mod dpi; pub mod dynamic; /// Verilator-defined types for C FFI. @@ -89,9 +91,6 @@ pub trait VerilatedModel { fn init_from(library: &Library) -> Self; } -/// `DpiFunction(c_name, c_function_code, rust_function_code)`. -pub struct DpiFunction(pub &'static str, pub &'static str, pub &'static str); - /// Optional configuration for creating a [`VerilatorRuntime`]. Usually, you can /// just use [`VerilatorRuntimeOptions::default()`]. pub struct VerilatorRuntimeOptions { @@ -107,18 +106,6 @@ pub struct VerilatorRuntimeOptions { /// Whether verilator should always be invoked instead of only when the /// source files or DPI functions change. pub force_verilator_rebuild: bool, - - /// The name of the `rustc` executable, interpreted in some way by the - /// OS/shell. - pub rustc_executable: OsString, - - /// Whether to enable optimization when calling `rustc`. Enabling will slow - /// compilation times. - pub rustc_optimization: bool, - - /// The name of the `make` executable, interpreted in some way by the - /// OS/shell. - pub make_executable: OsString, } impl Default for VerilatorRuntimeOptions { @@ -127,9 +114,6 @@ impl Default for VerilatorRuntimeOptions { verilator_executable: "verilator".into(), verilator_optimization: None, force_verilator_rebuild: false, - rustc_executable: "rustc".into(), - rustc_optimization: false, - make_executable: "make".into(), } } } @@ -138,7 +122,7 @@ impl Default for VerilatorRuntimeOptions { pub struct VerilatorRuntime { artifact_directory: Utf8PathBuf, source_files: Vec, - dpi_functions: Vec, + dpi_functions: Vec<&'static dyn DpiFunction>, options: VerilatorRuntimeOptions, /// Mapping between hardware (top, path) and Verilator implementations libraries: HashMap<(String, String), Library>, @@ -148,7 +132,7 @@ pub struct VerilatorRuntime { impl VerilatorRuntime { /// Creates a new runtime for instantiating (System)Verilog modules as Rust /// objects. - pub fn new>( + pub fn new>( artifact_directory: &Utf8Path, source_files: &[&Utf8Path], dpi_functions: I, @@ -274,6 +258,9 @@ impl VerilatorRuntime { /// - Edits to Verilog source code /// - Edits to DPI functions /// + /// Then, if this is the first time building the library, and there are DPI + /// functions, the library will be initialized with the DPI functions. + /// /// See [`build_library::build_library`] for more information. fn build_or_retrieve_library( &mut self, @@ -362,6 +349,30 @@ impl VerilatorRuntime { } let library = unsafe { Library::new(library_path) } .whatever_context("Failed to load verilator dynamic library")?; + + if !self.dpi_functions.is_empty() { + let dpi_init_callback: extern "C" fn( + *const *const libc::c_void, + ) = *unsafe { library.get(b"dpi_init_callback") } + .whatever_context("Failed to load DPI initializer")?; + + // order is important here. the function pointers will be + // initialized in the same order that they + // appear in the DPI array --- this is to match how the C + // initialization code was constructed in `build_library`. + let function_pointers = self + .dpi_functions + .iter() + .map(|dpi_function| dpi_function.pointer()) + .collect::>(); + + (dpi_init_callback)(function_pointers.as_ptr_range().start); + + if self.verbose { + log::info!("Initialized DPI functions"); + } + } + entry.insert(library); }