Simulating Verilog Logic in PLECS via Verilator

Introduction

If your control logic is written in Verilog, headed for an FPGA or ASIC down the line, you’ll eventually want to validate it in PLECS. PLECS can’t run Verilog directly, so the usual options are to hand-translate the logic into C, or to co-simulate with a dedicated Verilog simulator (Icarus Verilog, ModelSim, and similar tools) running as a separate process alongside PLECS. A hand translation leaves you maintaining two implementations of the same logic that can silently drift out of sync, with any translation mistake being a bug you introduced yourself. Co-simulation avoids that risk, but the overhead typically results in a very slow simulation.

Verilator, a Verilog-to-C++ translator already widely used in the hardware industry, offers a third option: instead of simulating the Verilog live, it compiles it into a C++ class ahead of time. That class links directly into a small wrapper, which translates between Verilator’s generated interface and the interface PLECS’s DLL block expects, letting the compiled result integrate into PLECS as a single DLL. There’s no second simulator process to keep in sync, no hand-translated logic to maintain, and since the C++ model is generated automatically from the same Verilog source used for hardware, no bugs a manual reimplementation could introduce. From PLECS’s perspective, the Verilated model behaves like any other C++ code inside a DLL block, allowing it to participate naturally in mixed-domain simulations alongside electrical circuits, control systems, and other PLECS components.

This article covers what Verilator actually generates, how that generated model plugs into PLECS’s DLL block, and two complete worked examples: a counter driven by a real clock input, and a larger multi-file, multi-signal traffic-light controller. It also points you to the official Verilator installation guide, and closes with a set of nuances and tradeoffs worth knowing before trying this on your own design.

Both examples below are available as downloads in the Downloads section of this post. Each example includes:

  • The PLECS model
  • The wrapper source
  • The Verilog source
  • A README covering the design and how to build the library from source on your own platform

Installing Verilator

Follow the official installation guide for your platform: Verilator User’s Guide — Verilator 5.050 documentation (see “Installation” in the left-hand navigation for your OS).

You’ll also need a C++ compiler already installed, since Verilator only generates C++ source and relies on your system compiler to build it:

  • macOS: Xcode Command Line Tools (clang++)
  • Windows: MSYS2 MinGW64 (g++), or MSVC via Visual Studio

Once installed, verify it with:

verilator --version

What Verilator Does

Verilator is a translator, not a traditional event-driven simulator: it reads Verilog and generates a C++ class containing:

  • A member variable for each design input and output
  • Internal variables representing the design’s state
  • An eval() method that executes your design’s logic

You compile that class yourself and drive it directly, there’s no simulator process in the loop. Take this module, for example:

module counter (
    input  wire       clk,
    input  wire       rst,
    output reg  [7:0] count
);
    always @(posedge clk) begin
        if (rst) count <= 8'd0;
        else count <= count + 8'd1;
    end
endmodule

verilator --cc counter.v produces a Vcounter class with public members clk, rst, count, and eval(). You drive the model by writing directly to clk and rst, then calling eval(): this re-evaluates the design’s logic against the current member values, updating count and any internal state. Verilator remembers each signal’s value from the previous eval() call, so it’s the act of calling eval() after changing clk that lets the model detect a posedge and actually advance the counter. Simply writing to clk doesn’t do anything on its own.

The PLECS DLL Interface

A PLECS DLL block calls four fixed C functions declared in DllHeader.h, plecsOutput being the one called every sample period. Your wrapper is the translation layer between:

  • PLECS signals (double inputs and outputs)
  • The Verilator-generated C++ model

Example 1: A Real Clock Input and an Asynchronous Reset

In this example, a PLECS Pulse Generator produces the 1 kHz clock signal that’s provided as an input to the DLL block. The DLL uses an inherited sample time, letting the solver take large steps between events and only refine its step size around the actual clock and reset transitions.

The example is intentionally small so the wrapper mechanics are easy to understand before introducing the larger, multi-file traffic-light controller in the next section.

plecsOutput reads clk and rst straight from PLECS and calls eval() unconditionally on every call:

DLLEXPORT void plecsOutput(struct SimulationState* aState) {
    Vcounter* top = /* ... */;
    top->clk = (aState->inputs[0] != 0.0) ? 1 : 0;
    top->rst = (aState->inputs[1] != 0.0) ? 1 : 0;
    top->eval();
    aState->outputs[0] = static_cast<double>(top->count);
}

Since Verilator compares each signal against its value during the previous eval() call, the counter’s posedge clk logic only executes after a genuine low-to-high transition.

The counter’s reset is asynchronous and active-high (always @(posedge clk or posedge rst)), so it responds the instant rst goes high, independent of the clock. The scope below shows a 30 microsecond reset pulse asserted while the clock is stably high, mid-cycle, nowhere near a clock edge, and count still drops to 0 immediately:

This example highlights a simple counter with an asynchronous reset being integrated into PLECS, but the design is a single Verilog file. Real-world Verilog designs are typically modular, spanning multiple files that each implement a different piece of functionality; the next example shows how to integrate a design like that.

Example 2: A Multi-Module Design with Multi-Signal I/O

The second example is a traffic-light controller spanning multiple Verilog files, much closer to a real RTL project. It includes:

  • A top-level state machine module
  • A normal-mode operation submodule
  • A flashing-mode operation submodule

In this example, the top module (trafficLight) is explicitly specified via --top-module.

Like Example 1, the clock is provided by a PLECS Pulse Generator, just at the design’s real 10 MHz clock rate rather than 1 kHz. It has:

  • 4 inputs: clk, reset_n, fault, secondaryRoadSensor, provided as a vectorized input to the DLL
  • 6 outputs: primary and secondary road red/yellow/green, provided as a vectorized output by the DLL

If you want to dig deeper into the design logic itself, the source repo’s state machine diagram gives a high-level picture of what’s actually happening under the hood.

Nuances and Tradeoffs

Clock Frequency and Simulation Speed

Matching the real clock frequency has a real performance cost, and it compounds with the cost of inherited sampling: it requires the solver to repeatedly refine its step size around every clock transition. At 10 MHz, that means tens of millions of DLL evaluations over the course of the simulation. On one machine, simulating the traffic light example’s full 16 second TimeSpan at the true clock rate took roughly 184 seconds of wall-clock time. Keeping the clock at its real frequency like this, combined with inherited sampling, makes for a slow simulation experience.

One strategy is clock scaling: for simulation purposes, scale the clock down and scale the design’s internal timers down to match, so the same real-world dwell times are reached with far fewer clock edges, and therefore far fewer steps the solver has to take to simulate the same span of time. This is heavily dependent on the specific design, though, and may not be an option for every module.

Reset Behavior Matters

A design that’s never actually reset can behave unexpectedly too. Verilator initializes every register to 0 when the model is created, but in real hardware a register only gets its correct starting value once an actual reset pulse runs. If a simulation never pulses reset, any register whose real starting value is only set by that reset logic just keeps sitting at 0, whether or not that’s a sensible value for the design to be in.

In the traffic light example, each state uses a dwell-time counter that counts down to zero and saturates there, and a state transition is only allowed once that counter reaches zero. Since Verilator initializes it to zero from the start, that condition is satisfied immediately, before the counter has ever loaded its real starting value via reset, so the state machine bypasses the very first state it should have dwelt in. If a simulation’s timing looks suspiciously fast, check whether reset was ever genuinely pulsed.

Summary

Verilator lets you bring real RTL directly into PLECS, as a compiled C++ class behind a small DLL wrapper, without hand-translating your control logic into C or running a separate co-simulation engine alongside PLECS. The two examples in this article cover the essentials: a simple counter with an asynchronous reset, and a larger multi-file, multi-signal traffic-light controller integrated the same way.

The main things to watch for when adapting this to your own design are the performance cost of matching a design’s real clock frequency, and making sure your simulation actually exercises your design’s reset logic, since Verilator’s default-zero initialization can otherwise mask a real hardware dependency on reset.

The accompanying downloads provide complete, buildable examples that you can use as a starting point for integrating your own Verilog designs into PLECS.


Downloads

File Description
1_counter_example.zip Simple counter example - Verilog source, PLECS wrapper, and PLECS testbench
2_traffic_light_example.zip Traffic light example - Verilog source, PLECS wrapper, and PLECS testbench