Simulating Verilog, particularly once a design grows beyond anything simple
can be both tricky and frustrating. I’m going to describe the simulation
process that I’ve used during development of the Oldland CPU
project in the hope that it might be useful to others.
Don’t develop in FPGA tools
It is entirely possible to develop a design in the FPGA tools, but this
doesn’t scale - you’re confined to the lacklustre editor, long compile times
and cryptic error messages in addition to being tied to a vendor flow.
Instead, I’d recommend a flow where development happens outside of the FPGA
tools and then the FPGA is a validation target instead of the main flow.
FPGA tools are generally less friendly than some of the free tools, notably:
- It takes a suprisingly long time to even complain about syntax errors.
- Some of the errors/warnings are detected post-synthesis. This can be
particularly confusing for things like combinational loops once optimizations
have been applied - the loop can include generated nodes that have no clear
origin and aren’t easily mapped to source lines. Verilator is so much
better here!
Use multiple simulators
For any moderately complex design, it’s important to use multiple simulations,
typically using one as a golden reference for behaviour and another to develop
the RTL. In the Oldland CPU case, I have a behavioural model of the CPU,
written in C that I use as the golden reference that allows me to quickly
prototype new features and be sure that the design is sensible. When I
implement a new feature, I first make sure that it works in the software
simulation, then I can develop the Verilog, making sure that all simulations
match. The software model can run at high speeds making it practical to run
real software under there, using it to test higher level system tests.
For the Verilog itself, there are two main free options on Linux, Icarus
Verilog and Verilator.
Icarus is a compliant simulator that is event driven and so provides an
accurate model of the circuit and supports the full language including
non-synthesizable constructs, making it useful for certain models. For
example, SDRAM vendor models such as the Micron model I use in the Oldland
project use delays to model the real-world delays and so this simulator can
model board-level delays and give confidence that the design will work on real
hardware. The main downsides to Icarus are that it only supports Verilog and
not SystemVerilog, and that the accuracy of the simulation means that it is
not the fastest.
Verilator takes a different approach, converting Verilog into optimized C++
models that work on a cycle basis, dropping the multiple drive levels and
event triggering. This means that Verilator models can be many times faster
than Icarus models, also capable of running real-world applications.
Verilator also supports elements of SystemVerilog which can be beneficial to a
design.
Where possible it’s best to support every simulation possible and verify them
all against each other. In some cases it can even be possible to run each
simulation in lockstep and verify that they all maintain the same state.
Integrate debugging
In general, debugging Verilog is harder work than debugging software, but
there are some advantages. Most simulators will support writing trace files
such as vcd or lxt2 which allows you to view the state of any signal in the
design at any time, which is fantastic for post-mortem analysis, but doesn’t
allow for interactivity. Additionally, dumping traces of the entire system
can really slow down the simulation, so consider adding a mechanism to start
tracing on a given command, event or state.
For interactive debugging, build debugging into the design and present it to
the outside world in a consistent way across all simulations. In the Oldland
CPU, this interface is the JTAG debugger, and this gets exported as a TCP
socket. In the software simulation there is a thread that receives events
from the socket and interacts with the model. For the verilog there is a
common debug controller that is present in the Icarus and Verilator
simulations, but also in the FPGA design too. The Icarus simulator uses a
small VPI stub to connect the debug controller to a C library to talk to the
socket, the Verilator model uses direct C calls, and the FPGA uses the Altera
virtual JTAG.
The end result is that the debugger can connect to the C model, either Verilog
simulator or the FPGA itself and load programs, read/write the registers,
memory and any control registers to get the system into a specific state and
trigger tracing.
Conclusion
Maintaining multiple simulators is the only way to develop quickly and be sure
that you don’t introduce bugs. The most important things that I have learnt
are:
- Never develop in the FPGA tools, treat them as a validation target for
your design.
- You must be able to build all simluations in a single command.
- All of your tests should be run with a single command on all simulators
and should take as short a time as possible.
- The framework for running your tests should not be dependent on the
simulation type.
- Use Verilator for linting even if you don’t use it for simulationg, the
errors and warnings are far superior to Icarus and FPGA tools.