Boundary manifest¶
The boundary manifest is the sidecar file that carries the mechanical boundaries of the placed chiplets for an assembly GDS. It is the single source of truth for the ADK assembly DRC and for the boundary viewer.
The boundary lives in no fabrication-layer namespace. It is not a GDS layer, not a datatype, and not a cell name convention: it is a JSON file written next to the GDS. That is the whole point of the format, and the reason it exists is explained in Why the boundary left the fabrication layer namespace.
The normative JSON Schema is config/schema/boundary_manifest.schema.json in
IHP-GmbH/IHP-Open-ADK. This page explains that schema, and records where the runtime
contract is narrower or looser than the schema text.
File location and auto-discovery¶
The manifest is written next to the GDS it describes and named after its stem:
board_complete.gds
board_complete.boundaries.json
Every reader auto-discovers the sidecar by that rule. run_drc.py derives
<layout-stem>.boundaries.json from --path when --manifest is
omitted, and boundaries_to_rdb.py applies the same rule (in its own
find_sidecar helper, kept independent so the KLayout macro does not import
the runner’s module tree). An explicit path always wins:
# auto-discovery
python klayout/drc/run_drc.py --path board_complete.gds \
--interposer-adapter intm4tm2
# explicit sidecar
python klayout/drc/run_drc.py --path board_complete.gds \
--interposer-adapter intm4tm2 \
--manifest /elsewhere/board_complete.boundaries.json
A missing manifest is a hard error, not a warning. The runner refuses to check
an assembly with no boundary source, because such a run would pass vacuously:
the ASM rules operate on the chiplet_boundary layer, and an empty layer
produces no violations.
Producers and consumers¶
Role |
Component |
Notes |
|---|---|---|
Producer |
|
One entry per placed chiplet. Always writes the file, even with zero
boundaries. Appends |
Producer |
|
One entry: the mechanical outline of a standalone die, in die-local DBU with an identity transform. |
Consumer |
|
Validates schema, version and structure before the deck runs. |
Consumer |
|
Parses the JSON blind and injects it as the Ruby local
|
Consumer |
|
Builds the |
Consumer |
|
Resolves the die instance names of the interconnect methods manifest
against |
Consumer |
|
The viewer half of the contract. Renders the same manifest as a
|
The viewer and the checker read the same file, so what you see in the Marker Browser is exactly what the DRC injected. Nothing is written into the GDS to make that work.
Note
hyp_to_gds.py has an opt-in --annotate-boundaries flag that paints
the boundaries onto a viewer-only annotation layer (default 1000/0, well
outside the IHP SG13G2 fabrication range). No DRC rule reads that layer and
it is not part of the assembly contract. It exists for eyeballing a layout,
not for checking one.
Schema¶
Top level¶
The schema requires only schema, version and boundaries. Everything
else is optional, which means “no consumer fails when it is absent”, not “no
producer writes it”: both producers always emit generator,
assembly_gds, dbu_um and top_cell.
Field |
Required |
Type |
Meaning |
|---|---|---|---|
|
yes |
string |
Literal |
|
yes |
string |
Exact |
|
yes |
array |
One entry per placed chiplet. May be empty. |
|
no |
string |
Producing tool, for provenance. |
|
no |
string |
Basename of the GDS this sidecar describes. |
|
no |
string |
Hash of the GDS at write time. A staleness hint only; no consumer verifies it. |
|
no |
number |
Database unit in microns, typically |
|
no |
string |
Top cell the boundaries are placed in. Viewer default |
Boundary entries¶
Field |
Required |
Type |
Meaning |
|---|---|---|---|
|
yes |
array of at least three |
Placed, transformed boundary contour in integer database units. Authoritative for the DRC. |
|
no |
string |
Placed-instance name, for example |
|
no |
string |
Die or library the instance came from. |
|
no |
string |
|
|
no |
array of at least three |
The same contour in microns, for humans and non-DRC tools. |
|
no |
object |
Placement provenance: |
|
no |
boolean or null |
Declared by the schema, written and read by nobody. See Vestigial fields. |
|
no |
array of four integers |
Declared by the schema, written and read by nobody. See Vestigial fields. |
Vestigial fields¶
The schema declares kgd and bbox_dbu on each boundary entry. No
producer writes them and no consumer reads them. Treat them as reserved: valid
if present, ignored either way. Before relying on either, wire it into a
producer and a reader, or drop it from the schema.
No z coordinate: single tier by design¶
Boundaries are two-dimensional placement polygons. The schema has no z, no tier
and no height field, and transform.origin_um is exactly [x, y]. Every
consumer, the ASM rules included, therefore treats all chiplets as sitting on
one attachment plane.
This is a deliberate scope limit, not an oversight. Adding a z or tier field is a version-bump event and has to land together with tier-aware consumers, since a manifest that carried a z which the deck ignored would report overlaps between chiplets that are stacked rather than colliding.
Tip
Assembly placement rules (ASM) for what the single-plane assumption means for the overlap and spacing rules, and Coordinate frames for the frame the polygons live in.
The schema file¶
config/schema/boundary_manifest.schema.json, validated against the
shipped manifests in CI so the committed schema cannot drift from the data.
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://heterogenic-chip-design-project/adk/config/schema/boundary_manifest.schema.json",
"title": "ADK chiplet boundary manifest",
"description": "Sidecar emitted next to an assembly GDS by the boundary producer (hyp_to_gds / blackbox_chiplet). Carries one mechanical-boundary polygon per placed chiplet, OUTSIDE any fabrication-layer namespace, so the assembly contract is PDK-agnostic. Consumed by the ADK assembly DRC: adk_assembly.drc injects it and layers_def.drc builds the chiplet_boundary layer from polygon_dbu.",
"type": "object",
"required": ["schema", "version", "boundaries"],
"properties": {
"schema": { "const": "adk-boundary-manifest" },
"version": { "type": "string" },
"generator": { "type": "string" },
"assembly_gds": { "type": "string" },
"assembly_gds_sha256": { "type": "string" },
"dbu_um": { "type": "number", "exclusiveMinimum": 0 },
"top_cell": { "type": "string" },
"boundaries": {
"type": "array",
"description": "One entry per placed chiplet. polygon_dbu is authoritative for the DRC; the rest is identity/provenance.",
"items": {
"type": "object",
"required": ["polygon_dbu"],
"properties": {
"instance": { "type": "string" },
"source_die": { "type": "string" },
"class": { "type": "string" },
"kgd": { "type": ["boolean", "null"] },
"transform": {
"type": "object",
"properties": {
"origin_um": { "type": "array", "items": { "type": "number" }, "minItems": 2, "maxItems": 2 },
"rotation_deg": { "type": "number" },
"mirror_x": { "type": "boolean" },
"magnification": { "type": "number" }
}
},
"bbox_dbu": { "type": "array", "items": { "type": "integer" }, "minItems": 4, "maxItems": 4 },
"polygon_dbu": {
"type": "array",
"description": "Placed, transformed boundary contour in integer database units (1 DBU = dbu_um microns).",
"items": {
"type": "array",
"items": { "type": "integer" },
"minItems": 2,
"maxItems": 2
},
"minItems": 3
},
"polygon_um": {
"type": "array",
"description": "Same contour in microns, for humans and non-DRC tools.",
"items": {
"type": "array",
"items": { "type": "number" },
"minItems": 2,
"maxItems": 2
},
"minItems": 3
}
}
}
}
}
}
Version policy¶
Readers pin the version with an exact string match against "1.0.0". Any
other value, a missing field included, is a hard error, never a warning.
The reasoning is that the sidecar is trusted. The DRC does not re-derive boundaries from the layout; it injects whatever the manifest says. A stale manifest silently reinterpreted under new semantics could therefore pass an assembly that should fail, which is the one failure mode a checker must not have.
The runner validates in this order, and exits on the first failure:
the file exists (auto-discovered or explicit),
it parses as JSON and is an object,
schemaequals"adk-boundary-manifest",versionequals the runner’sSUPPORTED_MANIFEST_VERSION,boundariesis a list and every entry carries apolygon_dbuof at least three[x, y]pairs.
The last step exists because the deck reads polygon_dbu blind, so a
structurally malformed sidecar has to be caught by the runner rather than
surfacing as a Ruby backtrace from inside KLayout.
When the schema changes, bump the version in both producers and both reader constants in one change set:
Repository |
File |
Symbol |
|---|---|---|
Chiplets-KiCad-Plugin |
|
|
gds2kicad |
|
|
IHP-Open-ADK |
|
|
IHP-Open-ADK |
|
|
The two reader constants are intentionally separate copies: the viewer macro runs inside KLayout and deliberately does not import the runner’s module tree. A test pins the two equal, and the producer test suites pin their writers to the same string.
Why the boundary left the fabrication layer namespace¶
The boundary used to be a fabrication layer. It was called exchange0 and it
was bound to GDS 190/0.
190/0 is IHP SG13G2’s Exchange0, a layer the process actually uses.
Binding the boundary to it had two consequences that got worse as the ADK
generalised.
The first is an abstraction leak. The ADK sits above all PDKs: an assembly can
combine dies from processes that have never heard of SG13G2, checked against an
interposer selected by an adapter. Pinning that cross-PDK contract to one
foundry’s layer number made the assembly format depend on a detail of one
process. A chiplet from another node has no reason to know what 190 means,
and a second interposer technology would have had no principled way to answer
“which number is the boundary here”.
The second is aliasing. If the boundary is a fabrication layer, then any chiplet whose own geometry happens to use that layer, legitimately or by accident, contributes shapes to the boundary layer of the merged assembly. The DRC cannot tell the difference: it sees polygons on the boundary layer and checks them as boundaries. That failure is silent and it is in the direction that matters, because extra or wrong boundary geometry changes the verdict of the placement rules.
Moving the boundary into a per-assembly manifest removes both problems by construction. A JSON sidecar has no layer number to collide with, so it cannot alias a PDK layer or a chiplet’s internal geometry, and it is PDK-agnostic because it is not expressed in any PDK’s vocabulary at all.
Note
The manifest is not the only place the assembly contract left the fab
namespace. The black-box pad vocabulary in config/chiplet_pads.json
(205/0, 205/25, 206/0) is deliberately placed in a band the
reference interposer PDK leaves free, and the optional boundary annotation
layer sits at 1000/0. Same reasoning, applied where a synthetic layer is
genuinely needed.
The legacy path¶
config/layers.json still records the exchange0 entry, marked legacy in
its own description. It has exactly one reader: layers_def.drc, and only
when the deck runs with legacy_exchange0 set, which only
run_drc.py --legacy-exchange0 sets:
if defined?(legacy_exchange0) && legacy_exchange0
exchange0_layer = adk_layers['exchange0']
chiplet_boundary = polygons(exchange0_layer['gds_layer'],
exchange0_layer['gds_datatype'])
else
chiplet_boundary = polygon_layer
# ... insert polygon_dbu from the injected boundary_manifest
end
Nothing in the ecosystem emits 190/0 any more. The flag exists so that a
GDS produced before the migration can still be checked, and for nothing else.
In that mode --manifest is ignored with a warning, and
--interconnect-methods is rejected outright: per-method scoping resolves
die instance names against the manifest, and legacy mode has no instance names
to resolve.
Example¶
{
"schema": "adk-boundary-manifest",
"version": "1.0.0",
"generator": "hyp_to_gds.py",
"assembly_gds": "board_complete.gds",
"dbu_um": 0.001,
"top_cell": "INTERPOSER",
"boundaries": [
{
"instance": "U1",
"source_die": "ACME_PHY",
"class": "chiplet",
"transform": {
"origin_um": [1200.0, 800.0],
"rotation_deg": 0.0,
"mirror_x": true,
"magnification": 1.0
},
"polygon_dbu": [[1100000, 725000], [1300000, 725000],
[1300000, 875000], [1100000, 875000]],
"polygon_um": [[1100.0, 725.0], [1300.0, 725.0],
[1300.0, 875.0], [1100.0, 875.0]]
}
],
"assembly_gds_sha256": "9f2c..."
}
Tip
Running the assembly DRC for the runner, Assembly placement rules (ASM) for the rules
that consume chiplet_boundary, and Interconnect methods manifest for the
sidecar that resolves die instances against this one.