Simulating Verilog on Linux
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.