CoHDL
  1. Use cases
  2. OpenMicroKbd

A macropad whose schematic is
source code

OpenMicroKbd is an open-source recreation of the OpenAI Codex Micro's control surface: 13 keys, a rotary encoder, an analog joystick, a capacitive touch pad and 21 addressable RGB LEDs, wired over USB-C. Nobody drew its schematic. It is 823 lines of CoHDL, and a compiler — not a checklist — stood between the design and the fab.

The finished OpenMicroKbd held in one hand: black PCB in a white printed case, icon keycaps, a knob, a joystick and a yellow accent key
The finished board in its 3D-printed case. About $37 in parts and assembly for a one-off prototype.
Device
13-key macropad, encoder, joystick, touch pad, 21× RGB
MCU
STM32F072CBT6 (Cortex-M0)
Board
95 × 95 mm, 4-layer
Schematic
823 lines of CoHDL in 4 files
Layout
Quilter.ai, from compiler constraints

Why this board

CoHDL needed to be proven on something real with awkward requirements — not a blinky. A key matrix with per-key diodes, a rotary encoder, an analog joystick, a capacitive touch electrode drawn in PCB copper, two independent WS2812 LED chains sharing a USB power budget, ESD protection, an LDO, and a debug socket. Small enough to finish; complicated enough to be a fair test. The full story of the device — firmware, companion app, enclosure — is told in the project's own launch post. This page is about the part CoHDL played.

The schematic is 823 lines of text

The whole electrical design lives in four files under hw/v1/src/: the top-level design, the datasheet-verified part bindings, and the board's footprints and pads. Thirteen switches and thirteen matrix diodes are two declarations, not twenty-six placed symbols:

hw/v1/src/main.cohdl excerpt
inst sw: [SW_KEY; 13]
inst d: [diode::D_1N4148W; 13]

// key matrix: rows drive, columns read through the diodes
net ROW1: mcu.PA10, sw[2..=5].A
net COL1: mcu.PB5, d[0, 3, 7, 10].Cathode

Nets carry types, not just names. The 3.3 V rail is annotated [3.3V], so connecting a part whose supply pin can't take that voltage is a compile error — and design intent rides along as a checked attribute instead of a comment that quietly goes stale:

hw/v1/src/main.cohdl excerpt
#[intent("AP2112K enable tied to VIN — always on when USB is present")]
#[high_current(500mA)]
net V3V3 [3.3V]: ldo.VOUT, mcu.VDD, mcu.VDDA, mcu.VBAT, mcu.VDDIO2,
                 joy.X_END_B, joy.Y_END_B, c_vdda.A, c_vdd_a.A, c_vdd_b.A, c_vdda_hf.A

The compiler's job is to refuse the silent mistakes. Every pin on every device carries a connection obligation — a required pin left floating is an error naming that pin, and deliberately unconnected pins must be declared with nc, with a reason. Units never coerce. And because the language is text, the diff for "swap the CC pulldowns" is two lines in a code review, not a spot-the-difference between two screenshots.

Attributes the layout tools can consume

The design declares physics, not just connectivity. The crystal declares its oscillator region; every bypass capacitor declares which pin it serves and at what value:

hw/v1/src/main.cohdl excerpt
#[crystal_oscillator(mcu, PF0, PF1)]
inst xtal: osc::XTAL_8M

#[bypass(mcu.VDD, 100nF)]
inst c_vdd_a: passive::C_100n_16V_X7R_0402
#[bypass(mcu.VDD, 100nF)]
inst c_vdd_b: passive::C_100n_16V_X7R_0402

Those attributes aren't decoration — they're the interface to the autorouter. The bypass capacitors deliberately carry no hand placement: the #[bypass] attribute is exactly what the auto-placer consumes, so it puts each capacitor where its pin's fanout actually lands rather than where a human guessed before any routing existed. Parts whose positions are mechanical facts — switches on the 19.05 mm key grid, the encoder, the USB connector — are pinned explicitly, down to which side of the board they sit on:

hw/v1/src/main.cohdl excerpt
place mcu at (-40mm, 24mm) rotate 270 side bottom
place enc at (-35.575mm, -31.075mm)
place sw[0] at (-9.525mm, -28.575mm)

From cohdl build to a routed board

One build emits everything downstream of the schematic: the KiCad netlist, the BOM, a .kicad_mod per footprint, the layout-constraint document, an IPC-2581 handoff file, and the physics-hint CSVs the autorouter reads. All of it is checked into the repository under hw/v1/out/, and all of it is deterministic — rebuilding the same source produces the same bytes.

Physical layout is Quilter's work, not CoHDL's — the routing is not written by hand or by the compiler. Quilter took the netlist, the locked placements and the physics hints, and placed and routed 88 components and 337 pins, returning ten candidate boards in about twelve minutes.

The Quilter.ai layout screen showing the routed 95 by 95 millimetre board, its switch and LED footprints, layer list and a design review panel
Quilter's layout of the board: 88 components, 337 pins, routed from the compiler's constraints.
Macro view of the board's underside during hand assembly: a soldering iron at one joint, surrounded by teal reverse-mount LED packages silkscreened LED17 through LED29
Reverse-mount LEDs on the underside fire up through board cutouts. Their footprints — like every footprint on the board — were emitted by the compiler from pad declarations in the source.

One source of truth, firmware included

The firmware's pin map is generated from the hardware source, which removes a classic two-file failure: changing a schematic pin and forgetting the firmware constant that mirrors it. Generated files aren't automatically correct — they preserve bad choices as faithfully as good ones — but electrical intent and firmware assumptions can no longer silently drift apart.

The assembled board in its white printed case, beside the embossed OpenMicroKbd packaging box and two printed trays of alternate keycaps
The whole device is open: schematic source, fabrication package, firmware, companion app, and the printable case, keycaps and packaging.

Version 2 is the same language on a radio

The wireless successor is already written: hardware v2 moves to the SiFli SF32LB52 — BLE, battery-powered — and lives beside v1 in the same repository as CoHDL source with compiler outputs, not yet routed. Same language, same pipeline, different physics. That's the point of the schematic being a program: v2 is a revision you can read as a diff, not a new drawing.

Honest boundaries

  • CoHDL produced the schematic, its checks, and the generated outputs. Placement and routing are Quilter's work; hand review, soldering and bring-up were human work throughout.
  • Source hardware is most useful when anyone can run the whole toolchain — and now anyone can: the compiler is open source with a one-line installer, so hw/v1/src/ rebuilds on your machine. The complete compiler outputs are also checked into the repository, so every generated artifact is inspectable without installing anything.