OpenROAD 3Dblox export¶
openroad/chiplet2dbx.py derives a 3Dblox view of a finalised .chiplet
assembly, so the assembly geometry can be loaded into OpenROAD and linted by
check_3dblox. It is a one-way, geometric export: dies become black-box
chiplet definitions, the interposer becomes a substrate, and each die’s
connection stack becomes a connection whose thickness is the stack’s exact
total height.
3Dblox, originated by TSMC and being standardised as IEEE P3537, describes multi-die physical assemblies, and has an open-source implementation in OpenROAD under BSD-3 that this exporter writes for.
The two formats describe the same physical assembly at different abstraction
levels. 3Dblox binds it to the place-and-route abstraction and references no
artwork. .chiplet binds it to the mask level: GDS bodies, layer property
files, per-layer interconnect metallurgy and fab DRC parameters. A design can
usefully hold both, one for exploration and P&R-level linting, one for
mask-level assembly and DRC.
Note
The export is lossy by declaration. Mask artwork, per-layer metallurgy,
interconnect method provenance, DRC parameter linkage, io_pads,
polygonal boundary manifests and parasitics have no 3Dblox target and are
dropped. The .chiplet file stays the authoritative placement source;
a .3dbx is a derived artefact, regenerated rather than edited. The
mapping follows the non-normative interoperability appendix
(docs/3dblox_interop.md) of the IHP-GmbH/chiplet-spec repository; where that
appendix and this tool disagree, the appendix governs. See
3Dblox interoperability.
What it consumes and what it emits¶
The single required input is a finalised .chiplet file: an assembly whose
components carry final positions, dimensions, technologies and connection
stacks. It is loaded through the vendored reference reader, so a file that
fails .chiplet validation fails here first.
Artefact |
Content |
|---|---|
|
The |
|
The placed assembly: the |
|
One minimal technology LEF per distinct |
|
With |
|
With |
--name overrides the output basename, which otherwise comes from
assembly.name. --out-dir defaults to the directory of the input file.
Every written path is printed on stdout.
Tip
A minimal technology LEF is generated rather than required because the
target ODB needs every chip to carry a technology: dbBlock::create
reads the technology’s database units with no null check. The tech-LEF
filename is also the technology identity the loader deduplicates on, so
two dies that share a .chiplet technology share one LEF and one ODB
technology.
Running the exporter¶
python openroad/chiplet2dbx.py \
--chiplet demo.chiplet \
--out-dir build/3dblox
# Bump-aware export: one pin list per die
python openroad/chiplet2dbx.py \
--chiplet demo.chiplet \
--out-dir build/3dblox \
--pins U1=chiplets/die_a.pins.json \
--pins U2=chiplets/die_b.pins.json
usage: chiplet2dbx.py [-h] --chiplet CHIPLET [--out-dir OUT_DIR] [--name NAME]
[--pins REF=PINS_JSON]
Export a finalized .chiplet assembly to 3Dblox (.3dbv + .3dbx) for OpenROAD read_3dbx / check_3dblox.
options:
-h, --help show this help message and exit
--chiplet CHIPLET finalized .chiplet file to export
--out-dir OUT_DIR output directory (default: alongside the input)
--name NAME basename for the outputs (default: assembly.name)
--pins REF=PINS_JSON per-die pin list (gds_to_kicad *.pins.json);
repeatable; enables the bump map for that die
Examples:
python openroad/chiplet2dbx.py --chiplet demo.chiplet
python openroad/chiplet2dbx.py --chiplet demo.chiplet --out-dir build/3dblox \
--pins U1=chiplets/die_a.pins.json --pins U2=chiplets/die_b.pins.json
Exit status is 0 on success and 1 on any failure. There is no second failure
tier by design: a malformed field surfaces as
chiplet2dbx: error: <message> and exit 1, never as a raw traceback.
What the output looks like¶
For a two-die demo assembly on a 1000 by 800 um interposer, the .3dbx
placement and connection blocks read:
Design:
name: "openroad_3dblox_demo"
ChipletInst:
interposer:
reference: DEMO_INTERPOSER
D1:
reference: DIE_A
D2:
reference: DIE_B
Stack:
interposer:
loc: [0, 0]
z: 0
orient: R0
D1:
loc: [100, 200]
z: 54
orient: MZ
D2:
loc: [600, 200]
z: 54
orient: MZ
Connection:
D1_attach:
top: D1.regions.front
bot: interposer.regions.front
thickness: 44
D2_attach:
top: D2.regions.front
bot: interposer.regions.front
thickness: 44
interposer_mount:
top: interposer.regions.back
bot: ~
thickness: 0
Two conversions are visible there. .chiplet positions are geometric
centres and 3Dblox places by corner, so loc is the position minus half the
placed span; a quarter turn swaps the width and height spans before that
subtraction, because the centre stays put while the footprint rotates. And the
substrate carries a grounded <interposer>_mount connection with no bot
so that the linter’s floating-component check has a reference chip to anchor
the stack on.
Validation before export¶
The exporter refuses to emit geometry it cannot represent faithfully. It never rounds and never guesses.
z consistency, exactly¶
The .chiplet z-mounting rule is
z_die = mounting_surface + connection.total_height(). OpenROAD’s linter
asserts that a connection’s declared thickness equals the actual gap between
the connected surfaces, exactly. The equality is guaranteed only when the die’s
z was auto-calculated; a .chiplet may carry an explicit position.z,
which readers honour as written.
So the exporter recomputes it. For every die it takes the connection stack
named by connection:, sums the height of every layer in that stack, and
compares that total against position.z minus the mount surface z. The
tolerance is Z_TOLERANCE = 1e-6 um, well below one database unit at any
realistic precision, so float noise passes and a real mismatch fails:
chiplet2dbx: error: die 'D1': z gap to the mount surface is 55 um but
connection stack 'cupillar_opt1' totals 44 um; a .chiplet with explicit
position.z must satisfy z == mount_surface + stack height exactly
A connection: id that does not resolve to a stack with layers, or a stack
layer without a height, is likewise a hard error: there is no thickness
source, and inventing one would produce a model that loads and lints against
the wrong geometry.
Note
Do not derive the die-to-substrate gap from outline maximum z: passivation can rise above the attachment plane, so a gap taken from the outline disagrees with the stack total and fails the exact-equality check.
Mounting on attachment_surface_z¶
The mount surface is the interposer’s component-level
attachment_surface_z: the BEOL-top die-attachment plane, decoupled from the
physical interposer body. Legacy .chiplet files that predate the field
encoded that surface in dimensions.thickness, and the exporter falls back
to it when attachment_surface_z is absent. A non-positive
attachment_surface_z is an error.
The die mount z is then the interposer’s position.z plus that reference.
The same reference is used as the substrate def’s thickness, because the
3Dblox substrate here is a thin mount plane, not a model of the interposer
body: the physical body is intentionally not modelled.
Rotation restricted to quarter turns¶
rotation.z is normalised modulo 360 and must land on 0, 90, 180 or 270
within 1e-6 degrees. 3Dblox expresses only multiples of 90 degrees, so any
other angle fails loudly rather than being rounded to the nearest quarter turn.
A non-numeric or otherwise malformed rotation.z is reported as an export
error, not as a traceback.
Orientation, and why face_down is rejected¶
The .chiplet orientation and the rotation quarter compose into a single
3Dblox orientation token, never two attributes:
|
|
Emitted token |
|---|---|---|
|
0 / 90 / 180 / 270 |
|
|
0 |
|
|
90 / 180 / 270 |
|
|
any |
rejected |
face_down records a distinct mounting intent whose 2D realisation the
.chiplet format deliberately leaves to the writer. It is not a canonical
orientation token, so mapping it would mean pinning a convention the format
does not fix, and a wrong guess here silently mirrors an entire die. The
exporter rejects it and tells you to use flip_chip. Any orientation value
other than face_up or flip_chip is also rejected. An absent
orientation is read as face_up, which is the format’s own default.
The emitted token also decides which bond region attaches to the substrate:
front for a flipped die, back otherwise.
The anchor requirement¶
Every die must declare anchor: gds_origin explicitly. The exporter emits
die-local bump coordinates offset by the die position, which is the
gds_origin anchor semantics: the die GDS origin is the placement
reference.
anchor: bbox_centeris a hard error. Those bumps would need to be re-centred on the die GDS bounding box first, which this exporter does not do, and skipping that step misplaces every bump by the bbox-corner offset.An absent anchor is also a hard error. The coordinate frame contract defaults an absent anchor to
bbox_center, which is exactly the unsupported case, so guessinggds_originwould risk misplacing a genuine legacy file. Every die written bygds_to_kicadand by the KiCad plugin writer already emits the field. See Coordinate frames.
Other preconditions¶
assembly.unitsmust beum. 3Dblox coordinates are microns.Exactly one component of type
interposer. Onlydieandinterposercomponents are supported; anything else is an error.Every die needs
dimensions.width,heightandthickness, all strictly positive. For dies the physical thickness comes from the board’sDIE_THICKNESS_UMfield, and a zero or missing value says so in the error message rather than exporting a zero-height slab. The interposer needswidthandheighton the same terms; it needsthicknessonly on the legacy path, where it stands in forattachment_surface_z.Every component needs a
technology: the generated technology LEF is named after it.All technologies must declare the same
dbu, and it must be an integer fraction of 1 um. The headerprecisionis the resulting database units per micron; with no declareddbuit falls back to 1000.
Bump-aware export¶
Passing --pins REF=PINS_JSON for a die turns its definition bump-aware.
The pin list is the *.pins.json artefact that gds_to_kicad extracts
from a die’s GDS or footprint (see Pin lists). The die’s def
then gains:
a
.bmapfile listing every bump asbumpInstName bumpCellType x y portName netName, with x and y being the bump centre in the def-local frame;a
LEF_filereference to a bump macro LEF for that connection method, rendered by the interconnect PDK’sbump_lef_generator;a
bmap:key on the def’sfrontregion.
Together these activate the linter’s bump-alignment check.
REF must name a die in the assembly, and that die must declare a
connection method, since the bump map is keyed by method and the macro LEF
is rendered from it. A --pins reference that matches no die, and a die
given a pin list but no method, are both hard errors: either one means the
flags and the .chiplet disagree about which dies are bumped.
Pin coordinates in the artefact are die-local GDS database units
(center_x_dbu, center_y_dbu), y-up, relative to the GDS origin;
load_pinlist converts them to micrometres with the file’s own dbu_um,
which is why a pin list without dbu_um is rejected outright. The def-local
position is pin + dimensions/2, mirrored
in x for flip_chip dies: this ecosystem realises the flip as a layout
mirror, while the target’s flipped orientation leaves bump x and y unchanged.
A pin that lands outside the declared outline is a hard error naming the
offending pins, because it means the pin list and the dimensions describe
different dies.
Port and net columns are emitted only when the .chiplet netlist: block
binds the pad name to a net. Otherwise both columns are -, since the target
warns on a port declared without a net.
Note
A bump map is a property of the ChipletDef, not of the instance. Two
dies that share artwork but differ in connection method or in net binding
therefore split into separate defs. Giving two dies that resolve to the
same def different pin lists is an error rather than a silent last-writer
wins.
The bump LEF generator lives in the interconnect PDK, not in the ADK. It is
discovered through INTERCONNECT_PDK_ROOT first, then by walking upward for
a sibling checkout named interconnect_pdk or
IHP-Interconnect-IntM4TM2. When pin lists are supplied and no generator is
found, the export fails loudly with the variable to set: a bump-aware export
silently degraded to a bumpless one would lint clean and mean nothing. A
connection method that is not in the interconnect PDK manifest is likewise a
hard error. See Interconnect PDK.
Loading the result in OpenROAD¶
read_3dbx on the .3dbx loads the assembly; the .3dbv and the LEFs
are pulled in through the header include and the APR_tech_file and
LEF_file references, so keep the whole output directory together.
read_3dbx openroad_3dblox_demo.3dbx
check_3dblox
Run it from the export directory, since the references are by filename.
check_3dblox is the assembly linter. It covers floating components, 3D
overlap, connection-region validity, bump alignment, internal-extension usage,
logical connectivity against a declared netlist, and alignment markers. It
reports findings as OpenROAD [WARNING ...] and [ERROR ...] lines, which
means a wrapper that only checks the process exit status will not notice a
geometry finding. Grep the log for those prefixes if you want a flow step to
fail on findings.
Tip
Two of the exporter’s guarantees are worth exercising once, so the failure
mode is familiar. Change a die’s position.z by one micron in the
.chiplet and the export fails, before OpenROAD is ever started.
Hand-edit the same z: in the generated .3dbx instead and the same
rule is caught on the other side: check_3dblox reports an invalid
connection.
Importable entry points¶
load_chiplet(path) -> dict
load_pinlist(path) -> [{"name", "x_um", "y_um"}, ...]
render_3dbv(assembly, *, die_pins=None) -> str
render_3dbx(assembly, dbv_filename, *, die_pins=None) -> str
render_bmap(assembly, def_name, *, die_pins=None) -> str
render_tech_lef(precision, route_layer=None) -> str
export_3dblox(assembly, out_dir, name=None, die_pins=None)
-> (dbv_path, dbx_path, [extra_paths])
The three renderers take die_pins keyword-only; export_3dblox takes it
as an ordinary keyword argument.
export_3dblox accepts die_pins values as either a path to a
*.pins.json or an already-parsed pin list. As everywhere in the ADK, new
options are appended as trailing keyword arguments with defaults, so importers
stay source-compatible.
ExportError is the exception type every faithfulness failure raises. It
subclasses ValueError.