MyHDL: describe hardware in Python (and try it in your browser now)
Verilog and VHDL are the languages of FPGA design, and they're worth
learning. But if you already think in Python, there's a delightful third
option: MyHDL, a library that turns Python itself into a hardware
description language. You write generators that model concurrent
hardware, simulate them on the Python interpreter with the full
unittest framework, and then convert the design to Verilog or VHDL for
a standard synthesis flow.
We built a MyHDL playground so you can run all of this in your browser, no installs. This post is the on-ramp.
Why Python for hardware?
- One language, whole flow. Your testbench, your models, your build scripts, and your RTL are all Python. No context-switch between an HDL and a scripting language for verification.
- Real verification tooling. MyHDL simulates on top of CPython, so
you test hardware with
unittest,pytest, coverage tools, and everything else the Python ecosystem already gives you. - Parameterization is just Python. Generating a bank of N filters or
a configurable bus is a
forloop and a list comprehension, not agenerateblock you fight with. - It converts to Verilog/VHDL. MyHDL isn't a walled garden. A design converts to clean Verilog or VHDL and drops into Vivado, Quartus, or the open Yosys flow like any other RTL. Real MyHDL designs have shipped in ASICs and FPGAs.
The trade is real too, and worth stating plainly: MyHDL is a smaller community than Verilog/VHDL, the synthesizable subset is a subset (as it is in every HDL), and most job postings still say "Verilog." MyHDL is a wonderful way to learn and to build when Python is your home, not a replacement for knowing the classic HDLs.
How it compares
| MyHDL | Verilog | VHDL | Chisel | Amaranth | |
|---|---|---|---|---|---|
| Host language | Python | (own) | (own) | Scala | Python |
| Simulate with | Python interpreter | iverilog/Verilator | GHDL/others | Scala/Verilator | Python |
| Verification | unittest/pytest |
SystemVerilog/UVM | VHDL testbench | ScalaTest | Python |
| Converts to | Verilog, VHDL | — | — | Verilog | Verilog |
| Parameterize with | plain Python | generate/params |
generics | Scala | plain Python |
| Learning curve | gentle (if you know Python) | moderate | steeper | steep | gentle |
| Best when | you think in Python | industry default | rigor/mil-aero | complex generators | modern Python RTL |
The two Python options (MyHDL and Amaranth, formerly nMigen) share a philosophy; MyHDL is the older, generator-based one with the most direct "this looks like the Verilog I'd write" mapping, which makes it a great teaching bridge.
The basics in five minutes
A MyHDL design is a function decorated with @block that returns
generator functions. Each generator is a concurrent process. Here's a
4-bit counter:
from myhdl import block, always_seq, Signal, modbv, ResetSignal
@block
def counter(clk, rst, en, count):
@always_seq(clk.posedge, reset=rst)
def logic():
if en:
count.next = count + 1 # .next = the "<=" of MyHDL
return logic
Three things to notice:
@always_seq(clk.posedge, reset=rst)is exactly Verilog'salways @(posedge clk)with synchronous reset, expressed as a Python decorator.count.next = ...assigns the next value. Readingcountgives the current value; writing.nextschedules the update, precisely the non-blocking<=semantics you know.modbv(0)[4:]is a 4-bit value that wraps on overflow. (Plainintbvwould raise an exception on overflow, MyHDL's bounds checking catching bugs the way choosing your bit widths carefully would.)
Simulate and test
The testbench is also a @block: it wires signals, generates a clock,
drives stimulus, and calls run_sim(). config_sim(trace=True) dumps a
VCD.
from myhdl import block, always, instance, Signal, modbv, ResetSignal, \
delay, StopSimulation
@block
def tb():
clk = Signal(bool(0))
rst = ResetSignal(0, active=1, isasync=False)
en = Signal(bool(0))
count = Signal(modbv(0)[4:])
dut = counter(clk, rst, en, count)
@always(delay(5))
def clkgen():
clk.next = not clk
@instance
def stim():
rst.next = 1
yield delay(12)
rst.next = 0
en.next = 1
yield delay(160)
raise StopSimulation
return dut, clkgen, stim
inst = tb()
inst.config_sim(trace=True)
inst.run_sim()
The real payoff is self-checking tests. Because it's just Python, a
proper test asserts expected values and integrates with unittest:
@block
def check(clk, rst, en, count):
@instance
def logic():
yield rst.negedge
for expected in range(5):
yield clk.posedge
yield delay(1)
assert count == expected, f"got {count}, want {expected}"
raise StopSimulation
return logic
That is the whole pitch: your hardware and its proof live in the same language, tested with the same tools.
Try it right now
Everything above runs in the MyHDL playground: the counter is the default; press Run and read the waveform. Change the width, add a down-counter, write a shift register. It simulates server-side and draws the trace, exactly like our Verilog playground does for classic HDL.
What's next: libfpga-myhdl
The libfpga library is a growing set of verified building blocks
in Verilog. Its Python companion,
libfpga-myhdl, is now
live: CDC synchronizers, a show-ahead FIFO, Gray codecs, an LFSR and a
MAC, each written in MyHDL with a self-checking pytest suite and a
conversion check that proves the generated Verilog and VHDL compile.
It grows toward parity with the Verilog library, UART, SPI and friends
next. Open an issue
to steer the roadmap.
New to the underlying ideas, clocks, resets, FIFOs, state machines? The free HDL course teaches them from logic gates up, and the glossary defines every term. MyHDL is a lovely way to practice them in a language you already speak.
Sources: the MyHDL project and its overview.