This guide walks you through setting up Verifrog, building a Verilog design, writing tests, and running them. By the end you'll have a working test that reads and writes signals, uses checkpoints, and verifies behavior.
Install these before starting:
| Tool | Install (macOS) | Install (Linux) | Notes |
|---|---|---|---|
| .NET 8+ SDK | brew install dotnet |
dotnet.microsoft.com | Runtime + compiler |
| Verilator 5+ | brew install verilator |
Build from source | Cycle-based simulator |
| clang++ | Included with Xcode | apt install clang |
Or g++ on Linux |
| Icarus Verilog | brew install icarus-verilog |
apt install iverilog |
Optional, for timing-accurate tests |
Verify your installations:
dotnet --version # 8.0.x or higher
verilator --version # Verilator 5.x or highergit clone https://github.com/bryancostanich/verifrog.git
cd verifrog
./install.shThis symlinks the verifrog and verifrog-vcd commands to /usr/local/bin. Alternatively, add bin/ to your PATH: export PATH="/path/to/verifrog/bin:$PATH".
Without install.sh: You can skip the install and use the long form instead:
dotnet run --project /path/to/verifrog/src/Verifrog.Cli -- <command>. The scripts just wrap this and handle library paths automatically.
Before setting up your own project, make sure everything works with the included counter sample.
verifrog build samples/counter
verifrog test samples/counterThe build command runs Verilator on the counter RTL, compiles the generic C++ shim, and links everything into a shared library. You should see output like:
Building samples/counter/verifrog.toml (top=counter)
Verilating counter...
Built: samples/counter/build/libverifrog_sim.dylib
The test command automatically sets the library path and runs the tests:
Running tests: Verifrog.Tests.fsproj
Library: samples/counter/build/libverifrog_sim.dylib
EXPECTO! 30 tests run in 00:00:00.15 — 30 passed, 0 failed. Success!
Note:
verifrog testwill auto-build if the library doesn't exist yet, so you can often just runverifrog testdirectly.
Now set up Verifrog for your own Verilog design.
cd /path/to/your-project
verifrog init .This creates:
your-project/
verifrog.toml # Design configuration (edit this)
tests/
Tests.fs # Sample test file
Tests.fsproj # F# project referencing Verifrog
Edit verifrog.toml to point at your RTL:
[design]
top = "my_counter" # Your top-level Verilog module name
sources = ["rtl/my_counter.v"] # Path(s) to your RTL source files
[verilator]
flags = ["--trace"] # Enable VCD waveform tracing
[test]
output = "build" # Where build artifacts goThe top field must exactly match your Verilog module declaration. The sources field supports glob patterns like "rtl/**/*.v".
verifrog buildThis:
- Reads
verifrog.tomlto find your top module and sources - Runs Verilator with
--cc --public-flat-rw --traceto compile your RTL to C++ - Generates a
verifrog_model.hheader that binds the generic shim to your design - Compiles everything into
build/libverifrog_sim.dylib(macOS) or.so(Linux)
If the build fails, check:
- Your Verilog has no syntax errors (
verilator --lint-only your_file.v) - The
topinverifrog.tomlmatches your module name exactly - All source files exist at the paths listed in
sources
Edit tests/Tests.fs:
module Tests
open Expecto
open Verifrog.Sim
open Verifrog.Runner
[<Tests>]
let tests = testList "my_counter" [
test "starts at zero after reset" {
use sim = SimFixture.create ()
// SimFixture.create() loads the library, resets for 10 cycles,
// and suppresses $display output. sim is IDisposable.
Expect.signal sim "count" 0L "count should be 0 after reset"
}
test "counts when enabled" {
use sim = SimFixture.create ()
sim.Write("enable", 1L) |> ignore
sim.Step(10)
Expect.signal sim "count" 10L "should have counted to 10"
}
test "checkpoint saves and restores state" {
use sim = SimFixture.create ()
sim.Write("enable", 1L) |> ignore
sim.Step(5)
Expect.signal sim "count" 5L "count is 5"
// Save state
let cp = sim.SaveCheckpoint("halfway")
// Continue running
sim.Step(5)
Expect.signal sim "count" 10L "count reaches 10"
// Restore to saved state
sim.RestoreCheckpoint("halfway")
Expect.signal sim "count" 5L "back to 5 after restore"
}
]Make sure tests/Tests.fsproj references Verifrog:
<ItemGroup>
<ProjectReference Include="$(VERIFROG_ROOT)/src/Verifrog.Sim/Verifrog.Sim.fsproj" />
<ProjectReference Include="$(VERIFROG_ROOT)/src/Verifrog.Runner/Verifrog.Runner.fsproj" />
</ItemGroup>verifrog testThis auto-detects your verifrog.toml, sets the library path, and runs the tests. If the library hasn't been built yet, it builds automatically.
Without the script:
DYLD_LIBRARY_PATH=build dotnet run --project tests/(macOS) orLD_LIBRARY_PATH=build dotnet run --project tests/(Linux).
If your design has SRAM, declare it in verifrog.toml:
[memories.data_ram]
path = "u_ram.mem" # Hierarchical path to the Verilog memory array
banks = 1 # Number of banks (use {bank} placeholder if > 1)
depth = 256 # Words per bank
width = 8 # Bits per wordThen use named access in tests:
test "backdoor memory write and read" {
use sim = SimFixture.createFromToml "verifrog.toml"
sim.Memory("data_ram").Write(0, 42, 0xDEL) // bank 0, addr 42, value 0xDE
sim.Step(1)
Expect.memory sim "data_ram" 0 42 0xDEL "backdoor write should stick"
}For register files, declare the register map:
[registers]
path = "u_regfile.regs" # Path to the register array
width = 8 # Bits per register
[registers.map]
CTRL = 0x00
STATUS = 0x01
DATA = 0x02Then use named registers:
test "register write-read" {
use sim = SimFixture.createFromToml "verifrog.toml"
sim.Register("CTRL").Write(0x42L) |> ignore
sim.Step(1)
Expect.register sim "CTRL" 0x42L "CTRL should hold written value"
}For timing-accurate Verilog testbenches alongside Verilator tests:
[iverilog]
testbenches = ["sim/*_tb.v"] # Glob patterns for testbench files
models = ["sim/bfm_*.v"] # Supporting models (BFMs, SRAM models)test "shift register timing" {
let result = Iverilog.runSimple projectRoot config "shift_reg_tb"
Expect.iverilogPassed result "shift register should pass"
}Both Verilator and iverilog tests run under a single dotnet test invocation.
As your test suite grows, use hardware-domain categories to organize and filter tests:
open Verifrog.Runner.Category
let tests = testList "MyDesign" [
smoke [
test "comes out of reset" { ... }
]
unit [
test "counter increments" { ... }
]
integration [
test "DMA end-to-end" { ... }
]
]verifrog test --category Smoke # Fast sanity checks during development
verifrog test --category Unit # Before committing
verifrog test # Everything (CI)Categories: Smoke, Unit, Parametric, Integration, Stress, Golden, Regression. See Core Concepts for details.
- Core Concepts — Understand signals, checkpoints, forces, what-if exploration
- API Reference — Full API with code examples
- VCD Parser Guide — Analyze waveform dumps in your tests
- Cookbook — Recipes for common test patterns
- Configuration Reference — All
verifrog.tomloptions - Samples — Working examples to study and modify