Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

vyges-opendb — the OpenDB substrate

Part of the Vyges Loom suite. Install once with vyges install loom, then run vyges opendb <subcommand>. It’s also a standalone vyges-opendb binary on your PATH (the integration contract for flow authors). Unlike the other engines it takes subcommands rather than a job file — it is the design database the other engines read and edit, not an analysis over one.

vyges-opendb is the in-memory design database layer of Loom: a safe Rust API over OpenROAD’s OpenDB (libodb), built standalone — no Tcl, no SWIG, no OpenROAD engines. Every Loom step that reads or edits a placed/routed design (ECO, audits, extraction feeders) goes through it, and it carries LEF/DEF/GDS/CDL I/O with it.

Two crates (both Apache-2.0, on github.com/vyges-tools):

CrateRepoRole
vyges-opendb-libvyges-tools/opendb-libstandalone libodb build + low-level cxx FFI
vyges-opendbvyges-tools/opendbsafe, ergonomic API (Db, ECO write path)

How the build works — pinned + sparse, no fork

vyges-opendb-lib pins an OpenROAD commit (matching the vyges-opendb distribution) and does a blobless, cone-sparse checkout of only src/odb + src/utl + cmake (~24 MB, not the ~1.8 GB tree), then compiles a standalone libodb.a (C++20). fmt / spdlog / abseil are built from source at pinned versions and static-linked — never the host’s (often older) copies — so the binary is portable across distros with the only runtime floor being glibc (Boost is header-only, zlib dynamic). An on-demand GitHub workflow publishes the per-target bundle with a provenance manifest.json (OpenROAD SHA + dep versions + measured glibc floor). Proven on linux/x86_64, linux/arm64, and macOS/Apple Silicon.

The API

#![allow(unused)]
fn main() {
use vyges_opendb::Db;

let mut db = Db::open("design.odb")?;
println!("{} — {} insts, {} nets", db.block_name(), db.num_insts(), db.num_nets());

// ECO: splice a buffer onto a pin (legalization delegated to the engines separately)
let buf = db.find_master("buf");
db.insert_buffer("inst42", "A", &buf, "eco_buf0", 10_000, 10_000)?;
db.write("design_eco.odb")?;
}
  • &self reads, &mut self edits — the borrow checker enforces no read-while-mutate.
  • Typed Error carrying the OpenDB message.
  • Write primitives: create_net, create_inst, set_inst_location, connect, disconnect, plus the composed insert_buffer.

The vyges-opendb CLI

vyges install opendb puts a single vyges-opendb multi-tool on the path. Subcommands:

# read path — one-line design summary
vyges-opendb info --input design.odb
# design.odb: block=counter insts=229 nets=52 bterms=13

# ECO step — mirrors LibreLane's Odb.InsertECOBuffers (same INSERT_ECO_BUFFERS config)
vyges-opendb insert-eco-buffers --input in.odb --output out.odb --config eco.json
# eco.json: { "INSERT_ECO_BUFFERS": [ { "target": "inst42/A", "buffer": "sky130_fd_sc_hd__buf_2" } ] }

# ECO step — mirrors LibreLane's Odb.InsertECODiodes (antenna diodes)
vyges-opendb insert-eco-diodes --input in.odb --output out.odb --config eco.json
# eco.json: { "INSERT_ECO_DIODES": [ { "target": "inst42/A", "diode": "sky130_fd_sc_hd__diode_2" } ] }

insert-eco-buffers splices a buffer (create buffer + net, rewire the pin’s driver through it); insert-eco-diodes ties an antenna diode onto the target pin’s net as a leaf — no new net, no rewiring. Both place at the target’s location; downstream grt/dpl legalization runs as a separate engine step. Loom steps invoke vyges-opendb <step> ….

16 of LibreLane’s 21 Odb.* steps are implemented as vyges-opendb subcommands (all with a --describe JSON contract). Surgery/placement steps take --input/--output; report steps emit JSON/text to stdout:

SubcommandLibreLane step(s)
insert-eco-buffersOdb.InsertECOBuffers
insert-eco-diodesOdb.InsertECODiodes
diodes-on-portsOdb.DiodesOnPorts (≈ PortDiodePlacement)
set-power-connectionsOdb.SetPowerConnections
add-obstructions / remove-obstructionsOdb.{Add,Remove}{PDN,Routing}Obstructions
custom-io-placementOdb.CustomIOPlacement
manual-global-placementOdb.ManualGlobalPlacement
manual-macro-placementOdb.ManualMacroPlacement
cell-frequency-tablesOdb.CellFrequencyTables (report)
report-disconnected-pinsOdb.ReportDisconnectedPins (report)
write-verilog-headerOdb.WriteVerilogHeader
report-wire-lengthOdb.ReportWireLength (report)
apply-def-templateOdb.ApplyDEFTemplate

DEF I/O landed with libodb v1, so read-def / write-def are available alongside these, and report-connectivity dumps the netlist graph as JSON. The steps still outstanding are the antenna checks and the antenna-driven diode heuristics, which need OpenROAD’s antenna module.

Every step is self-describing — vyges-opendb insert-eco-buffers --describe emits a JSON step contract (identity, CLI args, config schema) so an orchestrator can introspect it without running it. Those contracts are rendered per step in the CLI reference — config keys, types and defaults included.

2.5D / 3D chiplet assemblies

Three subcommands go beyond LibreLane’s step set, because LibreLane has no equivalent: they work on a chiplet assembly — several dies and how they stack — rather than on one design.

vyges-opendb read-3dblox  --input stack.3dbx --output stack.odb   # assembly file -> database
vyges-opendb check-3dblox --input stack.odb                       # -> findings, as JSON
vyges-opendb view-3dblox  --input stack.3dbx --output stack.svg   # -> a drawing

read-3dblox reads 3Dblox, the 2.5D/3D interchange format — .3dbx for the assembly, .3dbv for the chiplet definitions it includes — and builds the chips, bonding regions and die-to-die connections in the database.

Anything the database cannot hold is named rather than silently dropped: virtual bonds (bot: ~) have no bottom die to attach to, a non-rectangular region collapses to its bounding rectangle, and a stack whose dies sit on different processes cannot be fully represented because a database carries one technology. That last one is an upstream limit, and it is why this is honest about being an assembly description rather than a heterogeneous stack.

Where the external files are looked for

A 3Dblox assembly is not one file. The .3dbx include:s one or more .3dbv definitions, and each .3dbv points at collateral of its own — bmap, APR_tech_file, LEF_file. Once bump maps are being read, how those paths resolve stops being a detail: a path that resolves against the wrong directory fails at open time, far from the line that caused it.

The rule is one sentence: every relative path resolves against the file that names it, never against your working directory.

designs/stack.3dbx          include: dies.3dbv        -> designs/dies.3dbv
designs/dies.3dbv           bmap: maps/mem.bmap       -> designs/maps/mem.bmap
designs/dies.3dbv           APR_tech_file: [../ng45/*_tech.lef]
                                                      -> designs/../ng45/*_tech.lef

So you can run vyges opendb check-d2d --input designs/stack.3dbx from anywhere and get the same answer. Absolute paths are taken as-is.

Two things happen before resolution:

  • #!define macros are expanded. A .3dbv may open with #!define NG45 ../nangate45 and then write APR_tech_file: [NG45/*_tech.lef]. The substitution happens first, so the macro never reaches the filesystem.
  • * and ? in APR_tech_file are real globs, expanded in the directory the pattern names. A pattern matching nothing is reported, not silently treated as “no technology”.

A bmap that cannot be opened is reported and the assembly still loads — the geometry is worth having, and the report says which map was missed. That distinction matters: a bump map that failed to load would otherwise leave every bump check with nothing to look at and a clean verdict to show for it.

check-3dblox runs seven structural checks over the assembly — logical connectivity, floating chips, overlapping dies, unused internal_ext regions, connection-region overlap and mating-surface gap versus connection thickness, bump alignment, and alignment markers — and emits the findings themselves, not just counts:

{ "violations": 1,
  "categories": [
    { "category": "Floating chips", "count": 1,
      "markers": [ { "name": "u_base", "comment": "Isolated chip set starting with u_base" } ] } ] }

It is a checker, not a repairer: it annotates the in-memory database and never modifies the design. OpenDB’s own [WARNING ODB-nnnn] lines go to stderr, leaving stdout parseable.

view-3dblox draws the assembly as a single self-contained SVG — no server, no GUI toolkit, no X. Two views, because one is not enough: a plan view shows footprints and overhang, but it cannot show stacking order, die thickness, bond gaps, or which face is bonded, which is the whole subject. So the primary view is a cross-section, with the plan below it and the linter’s findings listed underneath — the engines say what, the drawing says where.

The Z axis is scaled to fit the page and prints its own factor, because a die is millimetres across and microns thick and every package cross-section is drawn with a non-uniform Z scale. The difference is saying so on the drawing.

Both commands that construct an assembly need a build with --features gen-write; released binaries have it.

Die-to-die interface checking

check-d2d compares the bump maps of two mating faces — the .bmap files a 3Dblox .3dbv points at — and reports what does not agree:

vyges-opendb check-d2d --input stack.3dbx     # every bonded pair; frame from the assembly
vyges-opendb check-d2d --top logic.bmap --bottom mem.bmap --offset-x -120.5 --flip-x

With --input the bump maps and the die placements both come from the 3Dblox assembly, so nothing about how the dies sit has to be stated on the command line. A bonded pair whose regions declare no bmap is listed as skipped rather than counted clean.

It reports unmated bumps (a signal that leaves one die and arrives nowhere), misaligned pairs with the distance between them, net mismatch (mated bumps carrying different signals), and cell mismatch (a microbump against a C4). Both sides are walked, so an unmated bump on the lower die is reported too.

This is not covered elsewhere. check-3dblox’s Logical Connectivity check compares only bumps that already land on precisely the same point and skips anything without a counterpart, and its sibling checkNetConnectivity is an empty function body. Measured on assemblies built for the purpose:

interfacecheck-3dbloxcheck-d2d
a top bump with no mating bump at all01
everything mated and exactly aligned00
a mating pair off by 1 nm01
a mating pair off by 5 µm02

Two deliberate limits. In the two-file form the relative placement is not inferred — pass --offset-x / --offset-y in microns and --flip-x for a face-to-face bond. Either way the frame is echoed in every report, because “no violations” means nothing without knowing what frame produced it. And the tolerance is derived, not invented: half the smaller bump pitch by default, so a match cannot be ambiguous, with --tolerance to override and the report saying which applied.

--input is the better entry point precisely because the orientation is easy to get wrong: MZ does not mirror X. It flips the die’s face and leaves the bump field’s handedness alone, so a face-to-face die is usually MZ_MY. That was measured against odb’s own unfolded bump positions, not read off the names, and writing MZ where MZ_MY was meant turns a correct interface into four net mismatches. An orientation the mapping has not been verified against is refused rather than processed, since odb silently treats an unrecognised one as R0.

Install

vyges install loom now fetches opendb along with the rest of the suite, since the CLI is unix-only too and their platform support is identical. It stays individually installable, like every other engine:

vyges install loom       # the whole suite, opendb included
vyges install opendb     # or just this one: libodb + the odb/ECO/3D steps
cargo install --git https://github.com/vyges-tools/opendb   # or use the crate directly (unix)

It is still not compiled into the core vyges binary. That distinction survives the platform change: libodb is native C++/cmake and version-sensitive, so it stays a separately-versioned installable rather than making every vyges build carry a C++ toolchain. Being installed with the suite and being linked into the CLI are different things, and only the first changed.

Building from source

The first two forms above download a prebuilt binary and need nothing else. cargo install compiles libodb from source, so it needs a C++20 toolchain and the headers it includes:

# Debian / Ubuntu
sudo apt-get install -y cmake g++ bison libboost-dev libboost-iostreams-dev libbz2-dev zlib1g-dev

# macOS
brew install cmake boost bison
export PATH="$(brew --prefix bison)/bin:$PATH"   # see below — this line is not optional

The bison PATH line matters. macOS ships bison 2.3 from 2006, and the LEF/DEF grammars need 3.x. Homebrew’s bison is keg-only, so it is not on PATH by default — without that export the build fails deep inside the parser generator with an error that says nothing about bison’s version. The release workflow exports it for exactly this reason.

Boost is header-only here, so only its include path is needed, not a library to link. zlib is the one dynamic link. Everything else — fmt, spdlog, abseil — is built from source at pinned versions and statically linked, deliberately, so the result does not pick up whatever older copies the host happens to have.

vyges itself is unix-only — see Installation. On Windows, use WSL2.

Status

Read + ECO write path over the db core, DEF I/O, and a 2.5D/3D chiplet path (read an assembly, lint it, check its die-to-die interfaces, draw it). GDS I/O and richer traversal follow; OpenROAD’s own odb C++ GTests are the planned CI conformance gate. OpenROAD is BSD-3-Clause; these crates are Apache-2.0.