Why a board HDL?
I’ve designed a lot of boards over the years, a mix of for-fun and semi-professionally. Unfortunately, that involves a lot of boring repetitive work:
- transcribing datasheet circuits into schematic capture
- selecting jellybean parts
- applying standard calculations listed in the datasheet or from well-known equations
- checking the same absolute maximum ratings parameters.
These aren't things that require human creativity, but just things that schematic editors are not good at.
Code, however, is great for re-use and automation. So I built this board HDL to address those issues and provide meaningful design automation.
What can this do?
- High-level block design: build boards at the block-diagram / system-architecture level of abstraction instead of individual parts. Worry less about lower-level details and focus more on the big picture.
- Batteries included: built-in libraries include coverage of common microcontrollers (ESP32, STM32, and more), power converters (buck, boost, LDO), sensors, displays, analog signal chains, and more.
- Automatic JLC parts selection: the system automatically selects jellybean (passives, discrete semiconductors) parts from a parts table and generates an assembly-ready BoM.
- Vendor-neutral abstract parts: you can build your own parts table parser that plugs in to the entire circuits library.
- Basic ERC: automates basic checks like voltage and current compatibility.
- Advanced capabilities: supports multi-pack devices (like dual-pack
opamps) and multi-board systems with managed connector-pairs. - KiCad layout ready: generates KiCad netlists with stable tstamps and
hierarchical data, allowing co-iteration with layout and hierarchical layout replication plugins. - Tooling friendly: exports a JSON representation of the compiled design to support third-party tooling.
- Minimal magic: deterministic compilation and deterministic subcircuit generators.
Example by Mechanical Keyboard Macropad
from edg import *
class Keyboard(SimpleBoardTop):
def contents(self) -> None:
super().contents()
self.usb = self.Block(UsbCReceptacle())
self.reg = self.Block(LinearRegulator(3.3 * Volt(tol=0.05)))
self.connect(self.usb.gnd, self.reg.gnd)
self.connect(self.usb.pwr, self.reg.pwr_in)
with self.implicit_connect(
ImplicitConnect(self.reg.pwr_out, [Power]),
ImplicitConnect(self.reg.gnd, [Common]),
) as imp:
self.mcu = imp.Block(IoController())
self.connect(self.usb.usb, self.mcu.usb.request())
self.sw = self.Block(SwitchMatrix(ncols=3, nrows=4))
self.connect(self.sw.cols, self.mcu.gpio.request_vector("sw_col"))
self.connect(self.sw.rows, self.mcu.gpio.request_vector("sw_row"))
def refinements(self) -> Refinements:
return super().refinements() + Refinements(
class_refinements=[
(IoController, Ch32v203),
(Switch, KailhSocket),
])
compile_board_inplace(Keyboard)
This generates a netlist, which can be imported into the KiCad PCB editor.
The included hierarchical data allows switch cell layout replication and microcontroller layout loading.
This also generates JLC PCBA BoM data for easy factory-assembled boards.
Does this actually produce working hardware?
Since this project started ~2019, over 20 different designs of varying complexity have been produced in this system. A short list of examples is on GitHub.
Here is the bring-up of the latest device, a three-board assembly (two rigid, one FPC) of a BLE joystick / air-mouse device.

Actually, ALL the boards in the photo, including the power supply (a 2-quadrant source-measure unit), SWD probe, were designed in this HDL!
Give it a try!
Check out the getting started tutorial on GitHub!
Setup is just a pip install away (you do need a working Java install).
Where is this going?
This continues as a personal project, in part for me to continue building boards for fun.
While the HDL is pretty battle-tested, there are still parts where things are rough around the edges. The error messages in particular need work.
I would love wider...
Read more »
Richard "Ducky" Lin
Michael Welling
NandFarm Creator
Jon Thomasson
MakerM0