Verilog meets Python

_verilog_python
HARDWARE DESCRIPTION · VERIFICATION · TOOLING

Verilog meets Python: describing hardware, the scripted way

Verilog is how you tell silicon what to become. Python is how you tell Verilog what to do — generate it, drive it, and check that it behaves. Here's how the two actually fit together, with real code.

VERILOG
.v
RTL ↔ SCRIPT
simulator in the middle
PYTHON
.py

01 Where Python actually fits into a Verilog flow

Python never replaces Verilog on silicon. It sits around it.

Verilog is a hardware description language: every line describes wires, registers, and logic that a synthesis tool eventually turns into real gates on a chip or an FPGA. Python is a general-purpose scripting language with no concept of clock edges or bit widths unless you teach it some. So "Verilog with Python" almost always means one of three things:

  • Verification — Python drives a Verilog design under simulation and checks its outputs (this is what cocotb does).
  • Generation — you write the hardware description in Python and a library emits Verilog for you to synthesize (this is what MyHDL and Amaranth do).
  • Automation — Python glues the flow together: running simulators, parsing logs, scripting synthesis, generating register maps or documentation from a spec.

The rest of this post walks through the first two in detail, since they're where most of the interesting code lives.

02 Verifying Verilog with Python — cocotb

Write the design in Verilog. Write the testbench in Python.

cocotb is a coroutine-based verification library that lets you write testbenches in plain Python using async/await, while an underlying simulator (Icarus Verilog, Verilator, Questa, and others) runs your actual Verilog RTL. Python reads and drives signals on the design under test (DUT) exactly like a real testbench would, just without Verilog's clunkier procedural syntax.

The design under test

counter.v VERILOG
module counter #(parameter WIDTH = 8) (
    input  wire             clk,
    input  wire             rst,
    input  wire             enable,
    output reg  [WIDTH-1:0] count
);
    always @(posedge clk or posedge rst) begin
        if (rst)
            count <= 0;
        else if (enable)
            count <= count + 1'b1;
    end
endmodule

The Python testbench

test_counter.py PYTHON
import cocotb
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge

@cocotb.test()
async def counter_counts_when_enabled(dut):
    # Start a 100 MHz clock on dut.clk
    cocotb.start_soon(Clock(dut.clk, 10, units="ns").start())

    # Reset the design
    dut.rst.value = 1
    dut.enable.value = 0
    await RisingEdge(dut.clk)
    dut.rst.value = 0
    await RisingEdge(dut.clk)

    # Enable and check five consecutive counts
    dut.enable.value = 1
    for expected in range(1, 6):
        await RisingEdge(dut.clk)
        actual = int(dut.count.value)
        assert actual == expected, f"expected {expected}, got {actual}"

    dut._log.info("Counter behaved correctly for 5 cycles")

Run it with a small Makefile that points cocotb at the simulator and the source file, and you get a normal Python test report — pass/fail, tracebacks, assertion messages — instead of parsing raw $display output from a Verilog testbench.

Why this matters: Verilog testbenches are Turing-complete but painful for anything beyond simple stimulus — no real data structures, weak string handling, awkward randomization. cocotb gives you the entire Python ecosystem (assertions, logging, numpy, coverage tools, CI integration) around a DUT that's still pure, synthesizable Verilog.

03 Generating Verilog from Python — MyHDL & Amaranth

Write the hardware description itself in Python, then convert it.

A different family of tools lets you skip writing Verilog by hand altogether. You describe registers and combinational logic in Python using a small, disciplined subset of the language, and the library elaborates that description into synthesizable Verilog (or VHDL). Two well-known options:

  • MyHDL — one of the oldest Python-to-HDL projects. You write generator functions decorated as hardware blocks, simulate them directly in Python, then call .convert() to emit Verilog.
  • Amaranth (formerly nMigen) — a newer, more actively maintained toolbox with its own statement/expression model, built-in simulator, and Verilog backend via Yosys.

The same counter, described in MyHDL

counter.py PYTHON → VERILOG
from myhdl import block, always_seq, Signal, intbv, ResetSignal

@block
def counter(clk, rst, enable, count):
    @always_seq(clk.posedge, reset=rst)
    def logic():
        if enable:
            count.next = count + 1

    return logic

# Elaborate signals, then generate synthesizable Verilog
clk    = Signal(bool(0))
rst    = ResetSignal(0, active=1, isasync=False)
enable = Signal(bool(0))
count  = Signal(intbv(0)[8:])

inst = counter(clk, rst, enable, count)
inst.convert(hdl='Verilog')   # writes counter.v

The generated counter.v is ordinary, readable RTL — the same kind of always-block you'd write by hand — but the source of truth lives in Python, where you get real functions, loops, classes, and parametrization instead of Verilog's generate blocks and macros.

Where this shines: highly parametric or repetitive hardware — a variable-width FFT, an N-lane crossbar, a family of FIR filters with different tap counts. Expressing "build 32 of these, wired like this" in a real programming language is far less error-prone than nested Verilog generate statements.

04 Verilog vs. Python-for-hardware, side by side

Aspect Verilog Python (MyHDL / Amaranth / cocotb)
Primary roleHardware description & synthesis inputGeneration, verification, and automation around it
Runs on silicon?Yes — synthesized directlyOnly indirectly, by emitting Verilog first
TestbenchesPossible, but verbose and weakly typedRich assertions, logging, randomization via cocotb
Parametrized designsgenerate blocks, macrosOrdinary functions, loops, classes
Tool maturityDecades old, universally supported by every vendorSmaller ecosystem, dependent on Verilog/VHDL backends
Learning curveNew concurrency & timing modelFamiliar syntax, but hardware semantics still to learn
Best forProduction RTL, timing-critical logicFast iteration, verification, repetitive structures

05 Which one should you reach for?

In practice, most real projects use both, split cleanly by job:

  • Design in Verilog when you need every synthesis and timing tool in the industry to understand your code without translation — which is still most production silicon and FPGA work.
  • Verify with cocotb whenever the testbench logic — stimulus generation, scoreboarding, coverage — is more complex than a handful of directed test vectors.
  • Generate with MyHDL or Amaranth when the design itself is highly parametric, repetitive, or benefits from being described as a program rather than a static netlist-in-waiting.

None of this replaces learning Verilog's own concurrency model — blocking vs. non-blocking assignment, sensitivity lists, race conditions between always blocks. Python only removes the pain around the design, not the design itself.

Post a Comment

0 Comments