Veryl Quickstart

note

This tutorial is aimed at Unix-like systems like macOS, Linux, and WSL.

caution

Veryl support is still experimental.

In this tutorial, we'll setup a Veryl project and test our code with Marlin. You can find the full source code for this tutorial here.

I'll be assuming you've read the tutorial on testing Verilog projects; if not, read that first and come back.

Also, make sure you have a Veryl toolchain installed.

note

If you already have a Veryl project and are looking to integrate Marlin into it, you don't need to read Part 1 too carefully.

Part 1: Making a Veryl Project

Let's call our project "tutorial_project" (you are free to call it however you like):

veryl new tutorial_project
cd tutorial-project
git init # optional, if using git

Here's what our project will look like in the end:

├── Veryl.toml
├── Veryl.lock
├── Cargo.toml
├── Cargo.lock
├── src
│   ├── lib.rs
│   └── main.veryl
└── tests
    └── simple_test.rs

In main.veryl, we'll write some simple Veryl code:

mkdir src
vi src/main.veryl
// file: src/main.veryl
module Wire(
    medium_input: input logic<32>,
    medium_output: output logic<32>
) {
    assign medium_output = medium_input;
}

You can read the Veryl book for an introduction to Veryl; this tutorial will not focus on teaching the language. If you know Verilog, the code should feel very familiar.

Part 2: Setting up Marlin

cargo init --lib
cargo add marlin --features veryl --dev
cargo add snafu --dev

The only required crate is marlin, but I strongly recommend at this stage of development to use snafu, which will display a human-readable error trace upon Result::Err.

caution

Please use snafu! 😂

In the test file, we'll create the binding to our Veryl module:

mkdir tests
vi tests/simple_test.rs
#![allow(unused)]
fn main() {
// file: tests/simple_test.rs
use marlin::veryl::prelude::*;

#[veryl(src = "src/main.veryl", name = "Wire")]
pub struct Wire;
}

This tells Marlin that the struct Wire should be linked to the Wire module in our Veryl file. You can instead put this in your lib.rs file if you prefer.

Finally, we'll want to actually write the code that drives our hardware in simple_test.rs:

#![allow(unused)]
fn main() {
// file: tests/simple_test.rs
use marlin::veryl::prelude::*;
use snafu::Whatever;

#[veryl(src = "src/main.veryl", name = "Wire")]
pub struct Wire;

#[test]
//#[snafu::report]
fn forwards_correctly() -> Result<(), Whatever> {
    let runtime = VerylRuntime::new(VerylRuntimeOptions {
        call_veryl_build: true, /* warning: not thread safe! don't use if you
                                 * have multiple tests */
        ..Default::default()
    })?;

    let mut main = runtime.create_model::<Wire>()?;

    main.medium_input = u32::MAX;
    println!("{}", main.medium_output);
    assert_eq!(main.medium_output, 0);
    main.eval();
    println!("{}", main.medium_output);
    assert_eq!(main.medium_output, u32::MAX);

    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.

Finally, we can simply use cargo test to drive our design! It will take a while before it starts doing Marlin dynamic compilation because it needs to first build the Veryl project by invoking the Veryl compiler.