Chiplet Studio ============== .. include:: /common.inc .. include:: /formats/_vendored_state.inc Chiplet Studio is the interactive viewer of the toolchain. It opens a ``.chiplet`` assembly file, renders the assembly in 3D from the layouts the file points at, and lets you drill down into any component's real GDS or OASIS in a 2D KLayout view without leaving the application. It also carries three surfaces that turn it from a viewer into a workbench: a connectivity graph, an embedded Python interpreter, and a subprocess pipeline runner driven by the assembly file's own ``flow:`` block. Repository: |studio-repo|. Licensed GPL-3.0-or-later, because it links KLayout as a library. .. warning:: Chiplet Studio is a |current-status|. Treat what it draws as a design aid. Nothing it displays is a verification verdict: the checks live in the ADK DRC and in the export-time consistency tests, not in the viewer. See :doc:`/flow/06_verification_stages`. What it is for -------------- The problem it solves is that a 2.5D assembly has no single file you can open. The interposer is one layout, each die is another, the placement lives in the assembly file, and the attachment bodies are not in any of them: they are generated. Reviewing a placement therefore used to mean reading coordinates. Chiplet Studio resolves all of those inputs and draws the result. The important consequence is that the 3D view is not a schematic-style diagram of boxes. In either of its detailed modes it extrudes the actual polygons read out of each component's GDS, layer by layer, at the elevations the technology's stackup declares. .. figure:: /_figures/screenshots/chiplet-studio-3d-2d-layers-hierarchy.png :align: center :width: 100% :alt: The Chiplet Studio window with an extruded 3D assembly on the left, a KLayout-backed 2D view in assembly mode with its technology layer table in the centre, the cell hierarchy on the right, and the assembly hierarchy listing per-component technology, interconnect method and render mode along the bottom. Chiplet Studio on a two-die wire-bonding demo, a simplified variant of the reference design rather than the shipped example itself. All three components are in ``DetailedNoSubstrate`` mode, abbreviated ``Detail-NoSi`` in the Mode column, so the layouts' own polygons are extruded with the silicon bulk slab left out. The centre panel is the 2D view in assembly mode, carrying the interposer technology's own layer table. The cell hierarchy is on the right, and the assembly hierarchy along the bottom gives each component its technology (``intm4tm2``, ``sg13g2``) and each die its interconnect method (``cupillar_opt1``, ``vendorx_microbump``). .. note:: Chiplet Studio is not the only open 3D layout viewer: OpenROAD ships one, and GDS3D, whose tessellation code Chiplet Studio links, predates both. What is specific here is that the unit of display is the *assembly*, resolved through the ``.chiplet`` format and the two PDK axes, rather than a single die. Opening an assembly ------------------- The executable opens the GUI. Pass a ``.chiplet`` file to load an assembly on startup: .. code-block:: bash chiplet-studio # empty session chiplet-studio path/to/assembly.chiplet ``File > Open`` does the same from the GUI. Loading runs on a worker thread and is cancellable, so a large assembly does not freeze the window. ``File > Reload`` re-reads the file from disk, which is the fast loop when you are editing the assembly file in a text editor beside the viewer. The parse itself is not implemented in Chiplet Studio. ``src/formats/`` is a thin consumer that delegates ``.chiplet`` reading and validation to the vendored Apache-2.0 ``chiplet_format_io`` reference library, a copy of the specification repository's reader, and maps the resulting document into the studio's own ``Assembly`` model. That vendoring is |vendored-reader-state|. A file the specification rejects is rejected here for the same reason and with the same message, so the viewer cannot quietly accept something the rest of the flow will not. See :doc:`/formats/01_chiplet`. Autosave runs on a timer, and a crash-recovery dialog offers the recovered assembly on the next start. A recovered assembly is deliberately detached from its path and needs ``Save As``. Importing a bare GDS ~~~~~~~~~~~~~~~~~~~~ ``File > Import GDS`` (``Ctrl+I``) opens a single layout that has no assembly file. The importer detects the top cell and the bounding box, then synthesises a minimal ``.chiplet`` around it and loads that through the normal path. This is deliberate: the imported layout is validated by the same ``format_version`` gate as any other assembly, and the synthesised file is a real artefact you can keep and edit. The import dialog asks for the technology. Naming one of the supported ids (``sg13g2``, ``sg13cmos5l``, ``sky130``, ``gf180``, ``intm4tm2``) is enough: the studio resolves that id to a shipped stackup and colour scheme on its own. Leaving it empty produces a ``custom_imported`` technology that carries whatever layer-properties file and stackup YAML you supply. The 3D view ----------- Camera control is an orbit camera: drag to rotate, wheel to zoom, and a rubber band selects. ``Ctrl+B`` toggles the base plane and ``Ctrl+M`` the scene mini-map. ``File > Export PNG`` (``Ctrl+E``) renders the current view offscreen at a chosen resolution, which is how you get a figure out of it. Render modes ~~~~~~~~~~~~ Render mode is per component, not global, so an assembly can show one die in full detail while its neighbours stay as cheap solids. Each mode has two names. The first column below is the name the C++ enumeration and the scripting surface use; the second is the abbreviation the hierarchy panel prints in its Mode column, which is the one you read off a screenshot. The right-click **Render Mode** menu spells the two detailed modes out again as ``Detailed (with Si bulk)`` and ``Detailed (no Si bulk)``. .. list-table:: :header-rows: 1 :widths: 26 16 58 * - Mode - In the panel - What it draws * - ``Hidden`` - ``Hidden`` - Nothing. * - ``Wireframe`` - ``Wire`` - Bounding-box edges only. * - ``Transparent`` - ``Trans`` - Semi-transparent solid block at the declared dimensions. * - ``Solid`` - ``Solid`` - Opaque solid block at the declared dimensions. * - ``Detailed`` - ``Detail`` - Full per-layer GDS tessellation, including the silicon bulk slab. * - ``DetailedNoSubstrate`` - ``Detail-NoSi`` - The same tessellation with the bulk slab omitted. The last two are the modes that read the layout. Polygons are extracted through KLayout's ``db`` library, triangulated by the GDS3D tessellator (with a fallback to an ear-clipping implementation when GDS3D returns a degenerate result for a polygon), and extruded between the ``z_bottom`` and ``z_top`` the stackup gives for that layer and datatype. Two conventions from the ``.chiplet`` coordinate contract are honoured at mesh build time rather than at placement time, and both are visible in the render. A die declared ``flip_chip`` has its layer elevations inverted so the topmost metal sits at the mounting surface, and each component's ``anchor`` field (``gds_origin`` or ``bbox_center``) decides whether its mesh is built around its own GDS origin or centred on its bounding box. Dies produced by ``gds2kicad`` use ``gds_origin``; interposers use ``bbox_center``. See :doc:`/formats/03_coordinate_frames`. The **Filter** slider on the View Mode toolbar hides polygons below a percentage of the selected component's shape-area distribution. On a real die the polygon count is dominated by fill and by tiny features; pushing the filter up leaves the structures that are actually worth looking at and makes the view interactive again. Black-box chiplets ~~~~~~~~~~~~~~~~~~ A chiplet from a closed PDK arrives as a GDS with pads, pad names and an outline and no layer-properties file at all. Rendering that against a real stackup would be wrong, and rendering every unmodelled layer as a full-thickness slab would produce a vertical explosion of meaningless geometry. The studio detects the case (no ``.lyp`` **and** no resolved stackup) and augments the stackup with two canonical roles instead: ``outline`` becomes the die body at the declared thickness, and ``pad`` becomes a 1 um plane flush on the die's active face, moved to the bottom face for a flip-chip die. Colours for those two roles come from a user-editable file, ``configs/stackups/colors/generic/blackbox.yaml``, so the black-box look is a knob rather than a hardcode. The layer numbers involved are the ones the ADK's ``config/chiplet_pads.json`` registry declares for black-box chiplets, which is what keeps the producer and the viewer in agreement. See :doc:`/adk/09_registries`. Layer stackups -------------- A stackup answers one question per GDS layer: where does it start in z, and how thick is it. Chiplet Studio resolves that answer from several sources in order of specificity. * An explicit ``stackup:`` path on the technology in the ``.chiplet`` file. This wins, and it is resolved through the same ``${VAR}`` and relative-path chain as every other path in the file. * A shipped stackup for a supported PDK id. ``configs/stackups/`` carries ``ihp-sg13g2.yaml``, ``ihp-sg13cmos5l.yaml``, ``intm4tm2.yaml``, ``sky130.yaml`` and ``gf180mcu.yaml``. * A GDS3D techfile sitting beside the technology's ``.lyp``, at ``/techfile/.txt``, which is auto-loaded when present. * A default stacking derived from the ``.lyp`` itself, as a last resort. The YAML dialect is the BlenderGDS one: top-level keys are layer names, each carrying ``index`` (GDS layer), ``type`` (datatype), ``z`` and ``height`` in micrometres. .. code-block:: yaml Metal4: index: 50 type: 0 z: 3.66 height: 0.49 .. note:: The shipped stackups render the silicon bulk at 50 um and say so in a comment, although the real SG13G2 wafer is 750 um. A true-scale substrate dwarfs a 14 um back end and a 48 um pillar stack and makes the assembly view unusable. Restore ``height: 750.0`` in the ``Substrate`` entry if you need the physical proportion, for example when preparing geometry for a thermal or mechanical study. Attachment bodies ~~~~~~~~~~~~~~~~~ The Cu pillars and solder balls between a die and the interposer are not in either layout, and they do not belong to the interposer PDK. They come from the interconnect PDK as stackup fragments, one per method id, and are merged into the render at load time. The join is explicit rather than positional. An interposer stackup declares ``attachment_surface_z``, the surface it offers to attachment bodies, which is the exposed pad top rather than the maximum z of the stack (the passivation around the opening rises above the real mounting surface). Each interconnect fragment declares ``z_reference: attachment_surface``, so its own z values are relative and get offset by whatever the interposer declared. That is what lets the same ``cupillar_opt2`` fragment sit correctly on a different interposer. Resolution of which fragments to use is one function, ``resolveInterconnectKeys``. Method ids that resolve to a fragment win, so per-die methods render with their own option's heights. The older adapter-keyed fragments (``ihp_cupillar``, ``ihp_sbump``) are used only when no method id resolves at all, because they carry family-default heights that must not overwrite a resolved per-method value. An id that resolves to no fragment is skipped without complaint, because a ``connection:`` id is not required to be a manifest method id: legacy and custom connection stacks are legitimate, and the adapter fallback covers them. What it is loud about is an elevation conflict. The three Cu-pillar options share layers 500 and 501, so an assembly mixing them gives the merged render two different heights for one layer and datatype. The studio draws the taller body and warns which fragment it came from. Seating is unaffected, because each die is seated from its own resolved fragment rather than from the merged view. Without the interconnect PDK on disk, the bodies simply do not render. See :doc:`/tools/06_interconnect_pdk`. The **Layer Z** spin box on the View Mode toolbar exaggerates the spacing between stackup layers in either detailed mode so you can see into the stack. It is visualisation only and never touches the saved model, and it is enabled only for a single imported die: on a multi-component assembly the exploded fans of neighbouring components would interleave. The 2D drill-down ----------------- ``F4`` toggles the 2D panel. It embeds KLayout's own layout view widget, so what you get is KLayout's rendering, its layer panel and its cell hierarchy, not a reimplementation. The panel has two modes. In **assembly** mode it shows the assembly layout as a whole. It resolves that layout by looking first at the assembly file's explicit ``assembly_gds`` field, then by deriving a merged-layout name from the interposer's own layout path (``*_interposer.gds`` becomes ``*_complete.gds``, otherwise ``*_complete`` is appended). If no merged layout exists on disk it falls back to the interposer's own GDS and labels the view to say so, because in that state the dies are genuinely not in the picture. In **drill-down** mode you are inside one component's layout. Selecting a component in the 3D view or in the hierarchy panel drills into it; the header shows the component name and technology, a combo box selects the cell, and the status line gives live coordinates. The mapping between GDS cells and assembly components is kept by a dedicated ``CellComponentMapper``, which is what makes selection sync work in both directions instead of only from the tree down. Selection is synchronised across the 3D view, the hierarchy panel, the properties panel and the net graph. The properties panel shows the selected component's identity, position, dimensions and technology, with unit conversion. The net graph ------------- ``Ctrl+G`` opens the net graph. It draws the assembly's connectivity as components (nodes) joined by nets (edges), with a hub node inserted for nets that have more than two endpoints so a power net does not turn into a mesh of pairwise edges. Nets are classified, and the classification drives both colour and filtering: ``Signal``, ``Power``, ``Ground``, ``DiffPair``, ``NC`` and ``Interface``, the last being an inter-chiplet interface signal. A net can also be flagged ``external``, meaning it leaves the package boundary, for instance because it terminates on an interposer IO pad for wire bonding. Selecting a component highlights its nodes and its incident edges. The edges carry their two endpoint component ids as item data, so highlighting matches by netlist relationship rather than by pixel proximity. The graph is only as good as the netlist in the assembly file: it reads the ``.chiplet`` netlist and does no extraction of its own. An assembly with no netlist shows an empty-state panel. The ``netlist:`` block is described in :doc:`/formats/02_chiplet_components`. The flow pipeline ----------------- An assembly file can carry a ``flow:`` block, and the studio will run it. The Flow Pipeline dock shows each step with its status, elapsed time, a per-step run button, and the captured output of whichever step you select. .. note:: The dock has no menu entry on purpose. It reveals itself when the loaded assembly defines a ``flow:`` and stays hidden otherwise, so it never presents an empty panel. The project ``README.md`` still lists it under ``View > Flow Pipeline``; the code is the accurate description. A step is one subprocess invocation: .. code-block:: yaml flow: working_directory: /tmp/flow_test environment: PDK_ROOT: /opt/pdk steps: - id: step_hello name: "Hello World" tool: /bin/echo args: ["hello", "${assembly.name}"] output_files: [hello.txt] - id: step_goodbye name: "Goodbye" tool: /bin/echo args: ["goodbye"] depends_on: [step_hello] input_files: [hello.txt] Steps declare ``depends_on``, and the engine topologically sorts them before running, so ``Run All`` executes in dependency order rather than file order. Arguments support variable substitution against the loaded model: ``${assembly.field}``, ``${component.ID.field}`` and ``${technology.ID.field}`` are resolved at parse time, and an unresolved variable is a parse error rather than an empty string passed to a tool. The engine itself contains no domain knowledge. It resolves dependencies, starts processes, captures stdout and stderr, tracks exit codes, and reports status. Everything that makes a pipeline meaningful lives in the assembly file. The shipped ``examples/openroad_3dblox/`` demo uses this to run two real steps against an assembly: exporting it to 3Dblox with the ADK's ``chiplet2dbx``, then loading that export with ``read_3dbx`` and linting it with ``check_3dblox`` in an OpenROAD container, with the step failing on any linter warning rather than only on a crash. See :doc:`/adk/07_chiplet2dbx` and :doc:`/formats/08_3dblox_interop`. Python scripting ---------------- ``View > Python Console`` (``Ctrl`` plus backtick) opens an embedded interpreter. The ``chiplet_studio`` module, built with pybind11, exposes the same model the GUI edits, so a script drives the live assembly rather than a copy of it. .. code-block:: python import chiplet_studio as cs asm = cs.get_current_assembly() die = asm.create_component("my_die", "Die", width=5000.0, height=5000.0, thickness=100.0) die.set_position(1000.0, 2000.0, 50.0) die.set_technology("ihp-sg13g2") for comp in asm.components(): print(f"{comp.id}: {comp.position}") The surface, as shipped: .. list-table:: :header-rows: 1 :widths: 22 78 * - Object - Members * - module - ``get_current_assembly()``, ``load_assembly(path)``, ``create_assembly()``, ``save_assembly(assembly, path)``, ``__version__`` * - ``Assembly`` - ``name``, ``description``, ``author`` (read/write); ``units``, ``component_count`` (read-only); ``component(id)``, ``components()``, ``has_component(id)``, ``create_component(...)``, ``remove_component(id)``, ``interface(id)``, ``interfaces()``, ``technology(id)``, ``technologies()``, ``is_valid()`` * - ``Component`` - ``id``, ``type``, ``technology``, ``layout_path``, ``top_cell``, ``position``, ``rotation``, ``dimensions``, ``is_array`` (read-only); ``name`` (read/write); ``set_position(x, y, z)``, ``move(dx, dy, dz)``, ``set_technology(id)``, ``metadata(key)``, ``set_metadata(key, value)`` * - ``Technology`` - ``id``, ``description``, ``layer_properties_path``, ``dbu`` * - ``Interface`` - ``id``, ``type``, ``from_endpoint``, ``to_endpoint``, ``physical`` * - ``FlowStep``, ``FlowEngine`` - step construction, ``add_step``, ``run_step(id)``, ``run_all()``, ``cancel()``, ``is_running()``, ``topological_sort()``, ``set_working_directory``, ``set_environment`` * - ``ChipletFormat`` - ``load(path)``, ``save(assembly, path)`` * - value types - ``Position3D``, ``Rotation3D``, ``Dimensions3D`` ``get_current_assembly()`` returns a borrowed handle. It stays valid only while it still matches the assembly the GUI has open, and raises once the GUI replaces or closes it, rather than dangling. .. tip:: Component geometry setters go through the same command objects the GUI uses, so a scripted move participates in undo and redo like a mouse drag. Running the application ----------------------- Three ways to get a runnable binary, in increasing order of independence from the host. What each of them has to provide is the bottom row of the application's own structure: .. figure:: /_figures/tikz_studio_architecture.* :align: center :width: 80% :alt: Three stacked tiers. A UI tier of ui/Qt6 Widgets and scripting/pybind11 sits above an engine tier of core/Data, formats/YAML, view3d/OpenGL and view2d/KLayout, which in turn sits above a dependency tier of Qt6, yaml-cpp, OpenGL and KLayout. The three tiers. The UI is Qt6 widgets plus the pybind11 scripting surface; the engine holds the data model, the ``.chiplet`` reader, the OpenGL renderer and the KLayout-backed 2D view. Everything in the bottom row is an external dependency, and the KLayout entry there is both why a build needs a KLayout tree and why the licence is GPL-3.0-or-later. **Docker.** ``./scripts/build-docker.sh`` builds inside the image and ``./scripts/run-docker.sh`` runs it with display forwarding. Note that a Docker-built ``build/chiplet-studio`` will generally not run directly on the host; launch it through the scripts. **Manual build.** Requires Qt6, yaml-cpp, OpenGL and KLayout libraries. KLayout is a submodule and is built first: .. code-block:: bash cd extern/klayout && ./build.sh -j$(nproc) mkdir build && cd build cmake .. -DKLAYOUT_BUILD_DIR=../extern/klayout/bin-release make -j$(nproc) **Portable bundle.** ``scripts/build-portable.sh`` produces a self-contained tree in ``dist/`` for machines with neither Docker nor root: .. code-block:: bash ./scripts/build-portable.sh # tarball, bundled software-GL fallback ./scripts/build-portable.sh --appimage # also emit an AppImage ./scripts/build-portable.sh --lean # smaller, host OpenGL only The bundle runs on any glibc 2.35 or newer Linux. It uses the host GPU when it provides OpenGL 3.3 and otherwise falls back to a bundled llvmpipe software renderer. Force either with ``CHIPLET_GL``: .. code-block:: bash CHIPLET_GL=hardware ./AppRun CHIPLET_GL=software ./AppRun Resolving PDK roots ------------------- Paths inside a ``.chiplet`` file may be written with ``${VAR}`` placeholders, and Chiplet Studio resolves them with the same three-tier discovery the Python side of the toolchain uses: an environment variable that is set *and* valid, then a walk up the parent directories for a sibling checkout, then a loud failure. .. list-table:: Ecosystem roots accepted in ``.chiplet`` paths :header-rows: 1 :widths: 30 40 30 * - Variable - Sibling directory names - Marker subpath * - ``INTERPOSER_PDK_ROOT`` - ``interposer``, ``OpenIntM4TM2`` - ``libs.tech/klayout`` * - ``INTERCONNECT_PDK_ROOT`` - ``interconnect_pdk``, ``IHP-Interconnect-IntM4TM2`` - ``manifest`` * - ``ADK_ROOT`` - ``adk``, ``ADK`` - ``klayout/drc`` * - ``GDS_TO_KICAD_ROOT`` - ``gds_to_kicad``, ``gds-to-kicad`` - ``pdks`` * - ``PDK_ROOT`` - ``IHP-Open-PDK`` - ``ihp-sg13g2/libs.tech/klayout`` The marker subpath is what makes "set and valid" checkable: a variable pointing at a directory that does not contain its marker is treated as unset and the walk continues. This matters in practice, because a stale exported variable is a more common failure than an unset one. .. note:: The Docker images bake these variables at fixed container paths, and the launcher scripts mount host checkouts onto them. Those values exist only inside the image. Running the tools directly on the host means exporting ``INTERPOSER_PDK_ROOT``, ``INTERCONNECT_PDK_ROOT`` and ``PDK_ROOT`` yourself. The visible symptom of a missing interconnect PDK is that Cu-pillar and bump bodies do not appear in the interposer's detailed view. See :doc:`/install/03_environment`. Known limits ------------ * 3D pad-name text is not rendered. Pad names appear in the 2D drill-down only. * The ``Layer Z`` exaggeration is restricted to a single imported die. * The net graph reads the netlist in the assembly file and performs no extraction, so it shows declared connectivity, not verified connectivity. * Assembly-mode 2D falls back to the interposer's own layout when no merged assembly GDS exists, which is a labelled fallback rather than a complete view.