Note
This tutorial is aimed at Unix-like systems like macOS, Linux, and WSL.
In this tutorial, we'll explore how to use Marlin to call Rust functions from
Verilog. Learn more about DPI in general here.
You can find the full source code for this tutorial here (in the dpi_tutorial.rs
file).
I'll be assuming you've read the tutorial on testing Verilog projects; if not, read that first and come back. In particular, I won't be reexplaining things I discussed in that tutorial, although I will still walk through the entire setup.
Let's call our project "tutorial-project" (you are free to call it however you like):
mkdir tutorial-project
cd tutorial-project
git init # optional, if using git
Here's what our project will look like in the end:
.
βββ Cargo.toml
βββ src
β βββ dpi.sv
βββ test
βββ dpi_test.rs
We'll have a simple SystemVerilog module that writes the result of three
, a
DPI function with a single integer output.
mkdir src
vi src/dpi.sv
// file: src/dpi.sv
import "DPI-C" function void three(output int out);
module main(output logic[31:0] out);
int a = 0;
initial begin
three(a);
$display("%d", a);
out = a;
end
endmodule
We'll create a new Rust project:
mkdir test
vi Cargo.toml
vi test/dpi_test.rs
Next, we'll add Marlin and other desired dependencies.
# file: Cargo.toml
[package]
name = "tutorial-project"
[[bin]]
name = "dpi_test"
path = "test/dpi_test.rs"
[dependencies]
# other dependencies...
marlin = { version = "0.1.0", features = ["verilog"] }
snafu = "0.8.5" # optional, whatever version
colog = "1.3.0" # optional, whatever version
Finally, we need the Rust file where we define the DPI function and drive the model.
// file: test/dpi_test.rs
use snafu::Whatever;
use marlin::{
verilator::{VerilatorRuntime, VerilatorRuntimeOptions},
verilog::prelude::*,
};
#[verilog::dpi]
pub extern "C" fn three(out: &mut u32) {
*out = 3;
}
#[verilog(src = "src/dpi.sv", name = "main")]
struct Main;
#[snafu::report]
fn main() -> Result<(), Whatever> {
colog::init();
let mut runtime = VerilatorRuntime::new(
"artifacts".into(),
&["src/dpi.sv".as_ref()],
[three],
VerilatorRuntimeOptions::default(),
true,
)?;
let mut main = runtime.create_model::<Main>()?;
main.eval();
assert_eq!(main.out, 3);
Ok(())
}
We can cargo run
as usual to test.
The magic happens here:
#[verilog::dpi]
#[no_mangle]
extern "C" fn three(#[output] out: &mut u32) {
*out = 3;
}
By applying #[verilog::dpi]
, we turn our normal Rust function into a DPI one.
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. Beside that, there are no restrictions on the content --- write whatever Rust code you want!
Then, we told the runtime about this function:
let mut runtime = VerilatorRuntime::new(
"artifacts".into(),
&["src/dpi.sv".as_ref()],
- [],
+ [three],
VerilatorRuntimeOptions::default(),
true,
)?;