Skip to content

Latest commit

Β 

History

History
150 lines (121 loc) Β· 3.47 KB

verilog_dpi.md

File metadata and controls

150 lines (121 loc) Β· 3.47 KB

Calling Rust from Verilog

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.

Part 1: The Basics

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

Part 2: Testing

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,
    )?;