Verilator has traditionally been used for co-simulation, with testbenches written in C++. With recent versions, it can now also handle pure Verilog simulation. Here’s how.
Installing Verilator using Dev Containers
First, we will have to install Verilator. We’ll probably need more tools of the OSS CAD suite later on, so it makes sense to install the complete OSS CAD Suite toolkit.
I prefer installing the tools in a container, as this allows for a reproducible setup that can be used everywhere containers can be used. When using VS Code’s Dev Containers, we can just open the repository in any VS Code installation and it will automatically set up the container and tools. Furthermore, you could use the same container in CI for automated testing with a 100% compatible environment.
We’ll set up the Dev Container with customizable OSS CAD Suite version and based on Ubuntu LTS, so we don’t have to update the configuration so often.
Let’s create the .devcontainer/devcontainer.json
configuration:
// For format details, see https://aka.ms/devcontainer.json. For config options, see the
// README at: https://github.com/devcontainers/templates/tree/main/src/javascript-node
{
"name": "osscad",
"build": {
"dockerfile": "Dockerfile",
"args": {
"OSSCAD_VERSION": "2025-01-15"
}
},
// For podman / SELinux
"runArgs": [
"--userns=keep-id",
"--security-opt=label=disable"
],
// To program FPGA devices from within the container
"mounts": [
"source=/dev,target=/dev,type=bind"
],
"customizations": {
"vscode": {
"extensions": [
"surfer-project.surfer",
"mshr-h.veriloghdl"
]
}
}
}
There is no ready to use docker image for OSS CAD Suite, so we will build one using the Dockerfile
.
The OSS CAD Suite version can be specified in OSSCAD_VERSION
.
Adding a mount point for /dev
will enable using iceprog
in the container, if we later want to use it to program FPGAs.
In addition, we include a workaround for the Podman SELinux issue, the Surfer waveform viewer and Verilog HDL support.
The .devcontainer/Dockerfile
then looks like this:
FROM docker.io/ubuntu:24.04
ARG OSSCAD_VERSION
RUN apt-get update && \
DEBIAN_FRONTEND=noninteractive apt-get -qq -y install curl git sudo build-essential locales \
&& apt-get clean
RUN echo 'ubuntu ALL=(ALL) NOPASSWD:ALL' > /etc/sudoers.d/ubuntu && \
chmod 0440 /etc/sudoers.d/ubuntu && \
sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
dpkg-reconfigure --frontend=noninteractive locales && \
update-locale LANG=en_US.UTF-8
ENV LANG en_US.UTF-8
RUN cd /opt && \
export OSSCAD_VERSION_SHORT=$(echo "${OSSCAD_VERSION}" | sed 's/-//g') && \
curl --progress-bar -L "https://github.com/YosysHQ/oss-cad-suite-build/releases/download/${OSSCAD_VERSION}/oss-cad-suite-linux-x64-${OSSCAD_VERSION_SHORT}.tgz" -o osscad.tgz && \
tar -xf osscad.tgz && \
rm osscad.tgz && \
echo "${OSSCAD_VERSION}" > /opt/oss-cad-suite/VERSION && \
echo "source /opt/oss-cad-suite/environment" >> /home/ubuntu/.bashrc
It installs some basic dependencies and sets up sudo
and the locale.
It then installs OSS CAD Suite into /opt
and creates a VERSION
file in the installation folder.
Finally, it updates .bashrc
so that the tools are directly available in the VS Code terminal, without having to first manually source the environment file.
Using in Distrobox
You can also use the container in distrobox now. Just build an image using podman:
podman build . --build-arg OSSCAD_VERSION="2025-01-15" -t osscad
Then create a distrobox container:
distrobox create osscad -i localhost/osscad:latest
And now you can:
distrobox enter osscad
When running from distrobox, you can use graphical tools such as gtkwave
out of the box.
I couldn’t get this working in the VS Code container so far.
Distrobox doesn’t source the environment file, so you’ll have to manually do this before using any OSS CAD Suite tool:
source /opt/oss-cad-suite/environment
A Simple Testbench
Now let’s see how to set up the simulation.
Create the typical Verilog counter in src/hdl/counter.v
:
`timescale 1ns/1ps
module counter (
input clk,
input rst,
output reg[7:0] dout
);
always @(posedge clk or posedge rst) begin
if (rst == 1'b1)
dout <= 0;
else
dout <= dout + 1;
end
endmodule
Then create the testbench in src/sim/dac_tb.v
:
`timescale 1ns/1ps
module counter_tb();
reg clk, rst;
wire[7:0] dout;
initial begin
$dumpfile("out/sim/counter_tb.vcd");
$dumpvars();
clk = 0;
#10000 $finish;
end
always #5 clk = ~clk;
initial begin
rst = 1'b1;
#50 rst = 1'b0;
end
counter uut (
.clk(clk),
.rst(rst),
.dout(dout)
);
endmodule
In Verilog, configuration of the simulator is handled directly in the Verilog code, not using parameters for the simulator tool.
The example above shows how to generate the clk
and rst
signals.
It also shows how to limit the simulation time using $finish
and how to dump the variables to out/sim/counter_tb.vcd
.
Verilating the Testbench
With the testbench ready, simulating with Verilator is simple, where the most difficult part is finding the correct tool arguments in the documentation.
To avoid having to type the commands all the time, create a simple sim_counter.sh
script file:
#!/bin/bash
set -ex
verilator -cc src/sim/counter_tb.v src/hdl/counter.v --binary --trace
./obj_dir/Vcounter_tb
The first command compiles the testbench and source files.
--binary
instructs Verilator to just generate a binary, as we don’t want to use the code as a library.
This is needed when the testbench is in Verilog and not in C++, as it’s usually done with Verilator.
The --trace
flag tells Verilator that we want to trace signals into wave form files.
The second command then executes the compiled simulation binary.
Viewing the Waveforms
To view the result, just open out/sim/counter_tb.vcd
in VS Code.
This should automatically open the surfer waveform viewer, as it was installed by the devcontainer.json
file: