Dynamic Bindings to Verilog
note
This tutorial is aimed at Unix-like systems like macOS, Linux, and WSL.
In this tutorial, we'll setup a SystemVerilog project and test our code with
Marlin. You can find the full source code for this tutorial here (see in particular the dynamic_model_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: 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
├── Cargo.lock
├── .gitignore
├── src
│ ├── lib.rs
│ ├── main.sv
└── tests
└── dynamic_test.rs
Let's use the same SystemVerilog module from the Verilog quickstart.
mkdir src
vi src/main.sv
// file: src/main.sv
module main(
input[31:0] medium_input,
output[31:0] medium_output
);
assign medium_output = medium_input;
endmodule
Part 2: Testing
We'll create a new Rust project:
cargo init --lib
Next, we'll add Marlin and other desired dependencies.
cargo add marlin --dev # no features sneeded
cargo add snafu --dev
caution
Please use snafu! 😂
We will illustrate using dynamic models by implementing the exact same test as we did in the Verilog quickstart.
The code for dynamic models is slightly more verbose. It's not necessarily meant for human usage, though; this API is better suited for using Marlin as a library (e.g., writing an interpreter).
mkdir tests
vi tests/dynamic_test.rs
// file: tests/simple_test.rs use marlin::verilator::{ AsDynamicVerilatedModel, PortDirection, VerilatedModelConfig, VerilatorRuntime, VerilatorRuntimeOptions, }; use snafu::Whatever; //#[snafu::report] fn main() -> Result<(), Whatever> { let runtime = VerilatorRuntime::new( "build2".into(), &["src/main.sv".as_ref()], &[], [], VerilatorRuntimeOptions::default(), )?; let mut main = runtime.create_dyn_model( "main", "src/main.sv", &[ ("medium_input", 31, 0, PortDirection::Input), ("medium_output", 31, 0, PortDirection::Output), ], VerilatedModelConfig::default(), )?; main.pin("medium_input", u32::MAX).whatever_context("pin")?; println!("{}", main.read("medium_output").whatever_context("read")?); assert_eq!( main.read("medium_output").whatever_context("read")?, 0u32.into() ); main.eval(); println!("{}", main.read("medium_output").whatever_context("read")?); assert_eq!( main.read("medium_output").whatever_context("read")?, u32::MAX.into() ); Ok(()) }
caution
Using #[snafu::report] on the function gives error messages that are
actually useful, but sometimes breaks LSP services like code completion.
I recommend to only apply it to your test functions when you actually
encounter an error.
We can cargo test as usual to test.
If you're using git, remember to add build2/ to your .gitignore.
Make sure you pass in the correct filename to create_dyn_model.
You only need to pass in a correct subset of the ports.
You can use create_dyn_model again with different ports. Of course, if you use
the same ports, the model will just be loaded from the cache.
You need to bring the AsDynamicVerilatedModel trait into scope to use any methods on a dynamic model.