CoHDL
  1. Docs
  2. Packages & the registry

Packages & the registry

A CoHDL package is a directory the compiler can hash: a manifest and a source tree. Dependencies are pinned to exact versions, a lock file records a content hash for each one, and the registry's namespace makes trust legible in the package name itself. Registry search discovers both packages and public parts without opening a project. This page covers those layers and the libraries that ship today.

A package is a manifest and a source tree

Every package — a board project, a component library, even the standard library — has the same shape: a cohdl.toml manifest and its sources under src/. The manifest's [package] section declares the package's identity: name and version, plus optional license, description and repository. A package that is a buildable board also carries a [design] section naming its top design; a pure library has none. This is the manifest of the Raspberry Pi Pico 2 example that ships with the compiler:

rpi-pico2/cohdl.toml excerpt
[package]
name = "rpi-pico2"
version = "0.1.0"
license = "MIT"

[design]
top = "Pico2"

[dependencies]
"@raspberrypi/mcu" = "0.1.1"
"@richtek/dcdc" = "0.1.1"
flash = "0.1.1"
osc = "0.1.1"
passive = "0.2.1"
qfn = "0.1.1"
soic = "0.1.0"
std = "0.3.0"
usb = "0.1.1"

Module paths follow the file tree — there is no mod declaration. A file's path under src/ becomes its module path, rooted at the package name: in the @richtek/dcdc package, the file src/buck_boost/rt6150b.cohdl is the module richtek_dcdc::buck_boost::rt6150b. Scoped names are quoted in the manifest and normalize to underscore namespaces in source. The Pico 2 design references its parts by exactly these paths:

rpi-pico2/src/main.cohdl excerpt
inst mcu: raspberrypi_mcu::RP2350A_QFN60
inst reg: richtek_dcdc::buck_boost::rt6150b::BUCKBOOST_RT6150B
inst flash: flash::FLASH_W25Q32

A fully-qualified path is always valid. Or import once — use osc::XTAL_12MHZ; — and write inst xtal: XTAL_12MHZ unqualified thereafter. Each use imports exactly one name; there are no glob imports. Within a single package, every top-level name in every file is visible to every other file. Across package boundaries, pub is enforced: referencing a non-pub item from another package is a compile error naming the item and its actual visibility.

Exact versions only

Every [dependencies] entry is name = "X.Y.Z" — an exact version triple. Range operators (^, ~, >=, <, *) are not a feature CoHDL lacks yet; they are rejected at manifest parse, permanently, as a language rule.

The reason is what the language is for. Software lives with semver ranges because a bad patch can be patched again; a fabricated board cannot. A component library's "safe patch" can move real copper — a pad grows, a courtyard shifts — and CoHDL guarantees that the same source and the same dependency set produce the same netlist bytes. That guarantee belongs to the language, not to a resolver's configuration, so there is no syntax for a floating version at all. Every bump is a deliberate, visible act:

$ cohdl check .
error[E1101]: dependency `passive`: `^0.2` is a version range — CoHDL requires exact versions (a hardware library's "patch" can move real copper; every bump is an explicit `cohdl update`)
  --> cohdl.toml:19
  = help: did you mean `passive = "0.2.0"`?

The lock file

cohdl.lock is generated beside the manifest: one [[package]] entry per dependency, recording the exact version that resolved and a sha256 hash over the dependency's full package content — its sources, its reference documents, its footprints. The version is a human label; the hash is the identity.

rpi-pico2/cohdl.lock excerpt
# cohdl.lock — generated by cohdl. Do not hand-edit; run `cohdl update` to change a pin.

[[package]]
name = "@raspberrypi/mcu"
version = "0.1.1"
hash = "sha256:afe3112afd773be46a8bb66007f936f0c0c01b103c1abb6d5f787e01f9fd874a"

[[package]]
name = "passive"
version = "0.2.1"
hash = "sha256:75d19bb814aee93091c2f49d7be59f85b55d9ccb2c2654924814d44737d6023e"

On every cohdl check and cohdl build, the compiler re-hashes each resolved dependency and compares it against the locked hash — before a single .cohdl file is parsed. A mismatch is a hard error naming the package and both hashes, never a warning and never a silent proceed:

error[E1103]: locked package `passive 0.2.1` has changed on disk: locked sha256:0000000000000000000000000000000000000000000000000000000000000000, found sha256:75d19bb814aee93091c2f49d7be59f85b55d9ccb2c2654924814d44737d6023e
  --> cohdl.lock
  = help: the content of a locked version must never change; if this bump is intentional, publish it as a new version and run `cohdl update`

An ordinary build never rewrites the lock entry for an unchanged pin — it only verifies it. The lock file changes only when the manifest does: a row is recorded when a dependency first resolves or when its manifest pin changes, and rows are dropped for dependencies removed from the manifest. Re-pinning to newer content remains cohdl update's job. Commit cohdl.lock to version control, the same convention design.lock already establishes for designators. It is the durable, diffable record of exactly which library content a board was built against — answerable months or years after fabrication, without any registry lookup.

The registry

registry.cohdl.org is where published packages live. Its namespace has three closed tiers, and the tier is structural in the name itself — no badge, no metadata flag, no side channel to check:

Tier Name shape Who publishes
Official std, passive Bare names are reserved for the CoHDL project's own account — never first-come-first-served.
Manufacturer @raspberrypi/mcu @brand must be a human-verified manufacturer account; verification is a gated registry process, not self-service.
Community @contrib/imu Any authenticated account, first-come-first-served — within this one shared prefix only.

Because the tier lives in the name, @raspberrypi/mcu and a hypothetical @contrib/mcu are entirely distinct, non-colliding, non-confusable names. The namespace grammar is validated twice: client-side before any network call, so a bad name fails fast with the same message the server would give, and server-side, which is authoritative. Fetched content is cached under ~/.cohdl/registry, so a design that has resolved once keeps building offline.

cohdl search QUERY [--json] is the read-only discovery path. It needs no project or login and searches package metadata plus package-local public parts projected from cohdl docs API sidecars. Part search includes the owning package, fully-qualified and short names, device, intent, arguments, structural variant, and primary and alternate AVL fields within fixed resource-safety projection budgets, including ordinary manufacturer and MPN values. The query is trimmed, must contain at least three Unicode scalar values, may occupy no more than 128 UTF-8 bytes and may contain no control character.

Search uses the most-recently-published version and reports its exact version on every result; that is distinct from the greatest-semantic-version rule used by an unversioned add or update. Package and part families are bounded independently and expose has_more when truncated, never a total count. Each existing package's most-recent sidecar needs one idempotent cohdl docs --publish re-upload after the search-index rollout.

The verbs

Discovery and the package lifecycle:

Command What it does
cohdl search TPS59650 Searches packages and the most-recently-published API sidecars for public parts. It is project-free, login-free and read-only; --json returns the same bounded rows.
cohdl add @raspberrypi/mcu Resolves the greatest published semantic version (or the one given as name@X.Y.Z), writes the [dependencies] entry and the lock row in one step.
cohdl install Resolves every dependency against cohdl.lock, fetching anything missing. A hash mismatch is a hard error.
cohdl update passive Re-resolves one dependency (or all of them, with no name) to its greatest published semantic version, rewriting manifest and lock together. This is the only sanctioned way a pin changes — never a side effect of install or build.
cohdl remove passive Deletes the [dependencies] entry and its lock row — the symmetric inverse of add.
cohdl login Opens the registry's account page and stores a token locally, for publishing.
cohdl publish Validates the package name and license locally, packs the project into a deterministic archive, and publishes it.
cohdl docs --publish Re-generates and uploads API documentation for an already-published version; this also idempotently refreshes its public-part search rows when that version is the most recently published.

One detail closes the trust loop: on publish, the server independently recomputes the archive's content hash, and that server-computed hash — not the publisher's local one — is what cohdl.lock verifies on every later install. A version number alone says nothing about content; the hash chain from the registry's own computation to your lock file does.

Publishing

A license is required to publish. The CLI refuses in pre-flight, before packing, and the server refuses independently — a version that declares no [package] license is never accepted. The value itself is not checked against any license list: proprietary terms are fine; only silence is refused. description and repository are optional and render on the package's registry page. None of these keys affect a verdict or an emitted byte.

passive/cohdl.toml excerpt
[package]
name = "passive"
version = "0.2.1"
license = "MIT"
description = "Chip resistors, MLCC capacitors, and inductors with verified land patterns."

[dependencies]
std = "0.3.0"

A package can also carry reference documents. One or more #[doc("relative/path")] attributes on a declaration name files relative to the package root; they ship inside the published archive and render on the package's registry page through a Markdown subset that allows no raw HTML. The compiler never opens them — like #[intent(...)], they have zero effect on compilation:

misc/src/misc.cohdl excerpt
#[doc("docs/README.md")]
pub device TestPoint {
    pins {
        required SIGNAL: 1 [passive]
    }
}

Separately, cohdl docs derives a structured API document from the compiler's checked view of a package. cohdl publish uploads it best-effort as a replaceable sidecar after the immutable package archive succeeds; cohdl docs --publish regenerates or backfills that sidecar later without changing the package tar or content hash. The registry API explorer reads the sidecar, and its package-local public part items feed the most-recently-published search index. Malformed, private, non-part and foreign dependency entries are skipped by indexing.

What ships today

The standard library is deliberately small: std owns the core traits — TwoTerminal, Capacitor, Resistor, Polarized, Diode, IC, Connector — and nothing else. Because std is every package's implicit prelude, anything it accumulates becomes global vocabulary forever, so devices, purchasable parts, pads and footprints all live in focused packages instead. Nothing about std is privileged: it is an ordinary versioned package that resolves by exactly the rule passive does.

The official families, all resolvable like any other dependency:

Package Owns
std Universal component contracts only — the core traits.
passive Generated chip resistors, MLCCs and chip inductors with their land patterns, plus passive helper circuits.
qfn, soic Audited QFN/DFN/SON and SOIC/TSSOP land patterns.
connectors General-purpose board connectors and headers.
usb USB connectors — Type-C, Micro-B — and controllers.
esd ESD protection devices.
diode Discrete diodes.
flash Nonvolatile NOR flash memories.
ldo Low-dropout regulators.
led Discrete and addressable LEDs and their traits.
mic Microphones and the Microphone trait.
mosfet Discrete MOSFETs.
osc Crystals and oscillators.
dcdc DC/DC step-down converters.
logic Logic gates and buffers.
audio-amp Audio power amplifiers.
antenna RF antennas with layout guidance.
cellular, esim Cellular modules and eUICC (eSIM) devices.
load-switch Power-distribution load switches.
misc Fabrication primitives with explicit electrical semantics — test points, mounting holes.
@espressif/esp32, @raspberrypi/mcu, @st/stm32 Espressif SoCs and modules, Raspberry Pi microcontrollers, and STMicroelectronics STM32 MCUs.
@richtek/dcdc, @ti/dcdc, @ti/logic, @ti/power-switch Richtek DC/DC converters; TI DC/DC controllers, level translators, and protected power paths.

The passive family sets the evidentiary bar: which resistors and capacitors exist is datasheet data, not a guess — Yageo primary throughout — and every emitted Yageo part number is checked against Yageo's own specsheet endpoint. A part that does not resolve is omitted, never asserted. Beyond the official and manufacturer tiers, a growing set of @contrib community packages covers IMUs, chargers, environmental sensors, radios and more, each stating plainly whether its land pattern is the manufacturer's own recommendation or an independent derivation.

Dependency loading is intentionally direct-only: if a part you instantiate resolves its footprint from qfn, your design lists qfn in its own manifest — which is why the Pico 2 manifest above pins the land-pattern packages alongside the component families. What your board depends on is exactly what its manifest says, nothing implied.