Pillar manifest¶
The pillar manifest is the sidecar file that records where the Cu-pillar and bump producer actually drew each attachment element in an assembly GDS. It is the alignment ground truth for manifest-level checks: a checker can compare die pad positions against the as-drawn pillar centres without re-deriving bump placement from the GDS artwork, and without repeating the producer’s placement logic.
The normative JSON Schema is config/schema/pillar_manifest.schema.json in
IHP-GmbH/IHP-Open-ADK.
File location¶
The manifest is written next to the assembly GDS it describes and named after
its stem, in the same producer pass that writes <stem>.boundaries.json:
board_complete.gds
board_complete.boundaries.json
board_complete.pillars.json
The file is written whenever the Cu-pillar and bump generation path is entered,
which means the producer had pad locations and at least one device resolved a
connection method, from its per-die override or from the assembly-global
connection type. A run that enters that path and ends with zero bumps still
writes the manifest with an empty pillars array. That distinction is
deliberate: “the bump path ran and drew nothing” and “the bump path never ran”
are different states, and only the first one writes a file.
Producers and consumers¶
Role |
Component |
Notes |
|---|---|---|
Producer |
|
One entry per drawn pillar, after collision auto-resolve, rebased into
the canonical frame and sorted by |
Consumer |
|
Validates schema, version, units and entry structure, then checks pad-to-pillar alignment. |
Schema¶
Top level¶
Field |
Required |
Type |
Meaning |
|---|---|---|---|
|
yes |
string |
Literal |
|
yes |
string |
Exact |
|
yes |
string |
Literal |
|
yes |
array |
One entry per drawn pillar. May be empty. |
|
no |
string |
Producing tool, for provenance. The producer writes |
|
no |
string |
Basename of the assembly GDS this sidecar describes. |
Pillar entries¶
Field |
Required |
Type |
Meaning |
|---|---|---|---|
|
yes |
non-empty string |
KiCad reference of the die the pillar belongs to, for example |
|
yes |
string |
Pad or pin name. |
|
yes |
string |
Interconnect method id, for example |
|
yes |
number |
Pillar centre in the canonical frame. See Frame and semantics. |
|
yes |
number greater than zero |
Bump body diameter for the method. |
|
no |
boolean |
|
|
no |
number, zero or greater |
Distance the bump was shifted by auto-resolve. Present only on shifted bumps. |
The schema file¶
config/schema/pillar_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/pillar_manifest.schema.json",
"title": "ADK pillar manifest",
"description": "Sidecar emitted next to an assembly GDS by the Cu-pillar/bump producer (hyp_to_gds). Carries one entry per pillar/bump instantiated in the assembly, with the as-drawn center rebased into the canonical interposer GDS-bbox-corner frame (the same frame .chiplet die positions use; y-up, micrometers, post collision auto-resolve). Consumed by manifest-level checks (checks/pads_vs_pillars.py); x_um/y_um are authoritative for those checks while the GDS remains fab ground truth.",
"type": "object",
"required": ["schema", "version", "units", "pillars"],
"properties": {
"schema": { "const": "adk-pillar-manifest" },
"version": { "type": "string" },
"generator": { "type": "string" },
"assembly_gds": { "type": "string" },
"units": { "const": "um" },
"pillars": {
"type": "array",
"description": "One entry per pillar/bump drawn in the assembly GDS; empty when the bump-generation path ran but produced no bumps.",
"items": {
"type": "object",
"required": ["device_ref", "pin_name", "method", "x_um", "y_um", "diameter_um"],
"properties": {
"device_ref": { "type": "string", "minLength": 1 },
"pin_name": {
"type": "string",
"description": "Pad/pin name; empty string when unknown."
},
"method": {
"type": "string",
"description": "Interconnect method id (e.g. cupillar_opt1)."
},
"x_um": { "type": "number" },
"y_um": { "type": "number" },
"diameter_um": {
"type": "number",
"exclusiveMinimum": 0,
"description": "Bump body diameter for the method."
},
"moved_by_auto_resolve": {
"type": "boolean",
"description": "True when collision auto-resolve shifted this bump; the key is OMITTED when unknown."
},
"auto_resolve_shift_um": {
"type": "number",
"minimum": 0,
"description": "Distance the bump was shifted by collision auto-resolve (frame-invariant, so unaffected by the rebase). Present only on shifted bumps; lets consumers bound the excused pad-to-pillar deviation to shift + tolerance instead of accepting any distance on the boolean flag."
}
}
}
}
}
}
Frame and semantics¶
As drawn, not as requested¶
x_um and y_um are the centres the producer drew, after its collision
auto-resolve pass has run. If a bump was nudged to clear an obstruction, the
manifest carries the nudged position, not the nominal one derived from the pad
map.
The manifest is therefore authoritative for manifest-level checks. A consumer
must never re-derive pillar positions from pin lists or .chiplet placement
and prefer that derivation over the manifest. The assembly GDS remains the
fabrication ground truth; the manifest is a faithful record of what went into
it, at a granularity the GDS itself does not carry (the GDS has circles, not
named bumps attributed to a die and a method).
The canonical frame¶
The producer accumulates pillar records in its raw drawing frame, which is the HYP coordinate space inherited from the KiCad board. The manifest writer rebases every record by the interposer top-cell bounding-box lower-left corner before writing, so the coordinates land in the canonical GDS-bbox-corner frame: y-up, micrometres, origin at the lower-left of the interposer top cell.
That is the same frame .chiplet die positions and io_pads live in, which
is the entire point. The two sidecars compare directly, with no frame
conversion in the consumer:
checks/pads_vs_pillars.py, die-local pad -> canonical frame:
global = position + R(rotation.z) * M * pad
with M = diag(-1, 1) for flip_chip and identity for face_up.
The rebase origin is captured once, at the first manifest write, which is the interposer GDS write before chiplet instances are merged in. The later complete-GDS manifest reuses the same origin, so both sidecars share one frame rather than each picking up the bounding box of whatever happened to be in the layout at the time.
Note
The boundary manifest is not rebased: its polygon_dbu is the placed
contour in the assembly GDS coordinate system, which is what a KLayout deck
needs. The pillar manifest is rebased, because its consumer compares against
.chiplet positions rather than against layout geometry. The two sidecars
sit next to each other and are in different frames on purpose. See
Coordinate frames.
Auto-resolve bookkeeping¶
moved_by_auto_resolve marks a bump the collision auto-resolver shifted.
auto_resolve_shift_um records how far, and it is frame-invariant: it is a
distance, so the writer’s rebase leaves it untouched.
The pair exists because a shifted bump legitimately does not sit on its pad, and the magnitude is what bounds the excuse: with only the boolean, the check would have to excuse any deviation on a flagged bump.
checks/pads_vs_pillars.py therefore treats the two fields differently:
moved_by_auto_resolve: truewith anauto_resolve_shift_ummagnitude: a named pair beyond tolerance demotes to a warning as long as the deviation stays within shift plus tolerance. Beyond that the flag cannot explain the distance and the finding stands asMISALIGNED.A bare
moved_by_auto_resolveboolean with no magnitude does not demote. It is advisory only.
The ADK producer always records the magnitude alongside the flag, so real manifests are unaffected by the second rule. It closes the gap for foreign or hand-edited manifests.
Tip
Because the key is omitted rather than written false when the producer
does not know, absence of moved_by_auto_resolve means “no information”,
not “not moved”. Do not infer a clean placement from a missing key.
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. This
mirrors the boundary manifest policy and for the same reason: a stale manifest
silently reinterpreted under new semantics could pass an assembly whose pillars
no longer sit where the checker believes they do.
load_pillar_manifest validates, in order, that the file exists, parses as a
JSON object, carries the schema string, carries the exact version, declares
units: "um", and that pillars is a list whose entries have string
device_ref (non-empty), pin_name and method, finite numeric
x_um, y_um and diameter_um with diameter_um greater than zero,
a boolean moved_by_auto_resolve when present, and a finite non-negative
auto_resolve_shift_um when present.
Note
The finiteness checks are not pedantry. NaN in a coordinate would make
every distance comparison fail open, because dist > tol is false for
NaN. Rejecting it at read time keeps a corrupted manifest from turning
the check green.
When the schema changes, bump the version in the producer and the reader in the same change set:
Repository |
File |
Symbol |
|---|---|---|
Chiplets-KiCad-Plugin |
|
|
IHP-Open-ADK |
|
|
Example¶
{
"schema": "adk-pillar-manifest",
"version": "1.0.0",
"generator": "hyp_to_gds.py",
"assembly_gds": "board_complete.gds",
"units": "um",
"pillars": [
{
"device_ref": "U1",
"pin_name": "VDD",
"method": "cupillar_opt1",
"x_um": 1210.5,
"y_um": 806.25,
"diameter_um": 44,
"moved_by_auto_resolve": true,
"auto_resolve_shift_um": 12.0
},
{
"device_ref": "U1",
"pin_name": "",
"method": "cupillar_opt1",
"x_um": 1290.5,
"y_um": 806.25,
"diameter_um": 44,
"moved_by_auto_resolve": false
}
]
}
Using it¶
The manifest is consumed by the pad-to-pillar alignment check:
python checks/pads_vs_pillars.py \
--chiplet demo.chiplet \
--pillars build/demo_interposer.pillars.json \
--pins U1=chiplets/die_a.pins.json \
--gds-pads U2
Tip
Point --pillars at the sidecar of the GDS whose bumps you want to check.
The interposer GDS and the complete GDS each get their own manifest, and both
are written in the same frame, so either can be checked against the same
.chiplet.
See Pad to pillar alignment for the check itself: pad sources, the matching algorithm, the finding taxonomy and the exit codes.