CoHDL
  1. Blog
  2. CoHDL 0.7.0

CoHDL 0.7.0: Subdesigns
and a Zed Extension

Circuits become reusable, typed building blocks: a subdesign exposes ports, keeps its internals behind them, and carries its own default layout onto the board as one placed unit.

CoHDL 0.7.0 is out — cohdl self-update if you have it, or:

curl -fsSL https://raw.githubusercontent.com/conol-ai/cohdl/main/install.sh | sh

Subdesigns: typed composition for circuits

Every board has regions that are really one thing — a regulator and its passives, a transceiver and its termination, an H-bridge and its sense network. Until now CoHDL could reuse those only as fn expansions: real components, but no boundary. The new subdesign declaration (RFC-032) makes the boundary part of the type system. Ports are the only electrical surface — internals cannot be reached from outside — and each port carries the same connection obligations a device pin does, so an unconnected required port is a compile error at the use site, not a surprise at bring-up.

subdesign MotorStage {
    ports {
        required IN1: Pin
        required IN2: Pin
        required VM: Pin
        required GND: Pin
    }

    inst drv: motor_driver::MOTOR_DRV8231A
    #[bypass(drv.VM, 100nF)]
    inst c_vm_hf: passive::C_100n_10V_X5R_0402
    inst c_vm_bulk_b: passive::C_10u_25V_X5R_0805

    net _: IN1, drv.IN1
    net _: IN2, drv.IN2
    net _: VM, drv.VM, c_vm_hf.A, c_vm_bulk_b.A
    net _: GND, drv.GND, c_vm_hf.B, c_vm_bulk_b.B

    layout {
        place drv at (0mm, 0mm)
        place c_vm_hf at (-4mm, -1mm) rotate 90
    }
}

design JointMC {
    subdesign motor: MotorStage
    net VBAT [7.4V]: conn_pwr.P1, motor.VM

    layout {
        place motor at (12mm, -6mm) rotate 90
        // This board only: pull the bulk capacitor into the
        // motor connector's current loop.
        place motor.c_vm_bulk_b at (16.5mm, -2mm) rotate 90
    }
}

The container itself never becomes a component: no designator, no BOM row, no netlist entry. Ports dissolve into the nets they join, and what reaches manufacturing is exactly the flat design you would have written by hand — same designators, same bytes. Hierarchy survives where it helps: instances keep Design::use::inst paths, generics and arrays work on subdesigns exactly as they do on devices (subdesign vr: Vreg<100nF>, [CanPort; 2]), and subdesigns nest.

Layout composes the same way. A subdesign's internal layout {} is a default: place the use site and the whole cluster lands translated, rotated, and mirrored as one unit — the back-side math is the same pad arithmetic the KiCad emitter is verified against. When one board needs one component somewhere else, place motor.c_vm_bulk_b at … reaches through the boundary for placement only — explicit beats the default, and the electrical surface stays sealed.

A joint motor controller, built from blocks

The new third example is a daisy-chainable CAN 2.0B joint node for a 2S pack: an STM32F072 with three subdesign regions around it — a JW5033S buck running the datasheet application verbatim, an SN65HVD230 CAN port in slope-control mode, and a DRV8231A H-bridge that regulates its own stall current with no firmware in the loop. Both existing examples were refactored onto subdesigns too, byte-identical outputs before and after. New library packages back it: can, motor-driver, and Micro-Fit 3.0 connectors, each with vendored datasheets and audited lands.

Hardened by review

The subdesign implementation went through an independent adversarial audit — thirty CLI and LSP probes against the shipped compiler — and a follow-up recheck. Every finding is closed and pinned by regression tests: a crash on physics attributes reaching a port, two silent component-loss paths, validation that used to wait for instantiation (a library now fails its own check rather than shipping a broken generic to its consumer), go-to-definition across subdesign boundaries, and a placement-precedence edge where an inert outer override could shadow a real default. The audit's own runners now pass 44/44.

CoHDL in Zed

Alongside the VS Code extension, the repository now carries a Zed extension: a tree-sitter grammar (validated against every source file in the repository) plus the same cohdl lsp server — diagnostics as you type, hover, go-to-definition, find-references. It finds cohdl on your PATH or downloads the matching release on its own. Until it lands in the Zed registry, install it as a dev extension: Extensions → Install Dev Extensioneditors/zed in a checkout.

Full source and design record: github.com/conol-ai/cohdl.