Spade Quickstart
note
This tutorial is aimed at Unix-like systems like macOS, Linux, and WSL.
In this tutorial, we'll setup a Spade 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 Spade toolchain installed, although we'll only be using the Swim build tool (follow the instructions here to install it).
note
If you already have a Swim project and are looking to integrate Marlin into it, you don't need to read Part 1 too carefully.
Part 1: Making a Swim Project
Let's call our project "tutorial-project" (you are free to call it however you like):
swim init tutorial-project
cd tutorial-project
git init # optional, if using git
Here's what our project will look like in the end:
.
├── swim.toml
├── swim.lock
├── Cargo.toml
├── src
│ ├── lib.rs
│ └── main.spade
└── tests
└── simple_test.rs
In main.spade (which should already exist after running swim init), we'll write some simple Spade code:
// file: src/main.spade
#[no_mangle(all)]
entity main(out: inv &int<8>) {
set out = &42;
}
You can read the Spade book for an
introduction to Spade; this tutorial will not focus on teaching the language.
Nonetheless, the essence of the above code is to expose an inverted wire which
we pin to the value 42 (think of assigning to an output in Verilog).
We'll write a very simple SystemVerilog module: one that forwards its inputs to
its outputs.
Part 2: Setting up Marlin
cargo init --lib
cargo add marlin --features spade --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 Spade module:
mkdir tests
vi tests/simple_test.rs
#![allow(unused)] fn main() { // file: tests/simple_test.rs use marlin::spade::prelude::*; #[spade(src = "src/main.spade", name = "main")] pub struct Main; }
This tells Marlin that the struct Main should be linked to the main entity
in our Spade 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:
// file: tests/simple_test.rs use marlin::spade::prelude::*; use snafu::Whatever; #[test] //#[snafu::report] fn main() -> Result<(), Whatever> { let runtime = SpadeRuntime::new(SpadeRuntimeOptions { call_swim_build: true, /* warning: not thread safe! don't use if you * have multiple tests */ ..Default::default() })?; let mut main = runtime.create_model_simple::<Main>()?; main.eval(); println!("{}", main.out); assert_eq!(main.out, 42); // hardcoded into Spade source 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 Spade project by invoking the Spade compiler.
warning
By default, SpadeRuntime does not swim build because it is not thread-safe. If
you have multiple tests, run swim build beforehand and then run cargo test
if you have disabled the automatic swim build via
SpadeRuntimeOptions::call_swim_build.
Note that, unlike the Verilog project tutorial, you don't need to add another
directory to your .gitignore, if you have one, because the SpadeRuntime
reuses the existing build/ directory managed by Swim. Thus, you should add
that to your .gitignore instead. swim init should do that automatically,
though.