Skip to content

Download notebook (.ipynb)

Grid Layout

When assembling test chips or comparison arrays, you often need to arrange a collection of cells into a regular grid with consistent spacing. kfactory's grid functions handle this in one call.

Function Units Cell slot size
kf.grid µm (float) Uniform — largest bbox in the whole grid
kf.flexgrid µm (float) Flexible — per-column width, per-row height
kf.grid_dbu DBU (int) Uniform — largest bbox in the whole grid
kf.flexgrid_dbu DBU (int) Flexible — per-column width, per-row height

grid vs flexgrid: grid gives every slot the same fixed size (set by the largest component), so columns and rows stay perfectly aligned like a spreadsheet. flexgrid shrinks each column/row to its actual maximum, saving area when components vary significantly in size.

All four functions return an InstanceGroup so you can apply a common transformation to the whole block afterwards.

Setup

import kfactory as kf


class LAYER(kf.LayerInfos):
    WG: kf.kdb.LayerInfo = kf.kdb.LayerInfo(1, 0)


L = LAYER()
kf.kcl.infos = L

1 · Creating Sample Components

We create a set of DKCell components (µm-based) with different sizes. The size variation is intentional — it makes the difference between grid and flexgrid visible.

# (width_µm, height_µm) pairs
sizes_um = [(5, 10), (10, 5), (8, 8), (6, 4), (3, 7), (12, 3)]

components: list[kf.DKCell] = []
for w, h in sizes_um:
    c = kf.DKCell(name=f"box_{w}x{h}")
    c.shapes(c.kcl.layer(L.WG)).insert(kf.kdb.DBox(w, h))
    components.append(c)

print(f"Created {len(components)} components")
Created 6 components

2 · grid — 1D (Single Row)

Pass a flat list of DKCell objects and a spacing in µm. All cells are arranged in a single row by default.

Every slot is sized to fit the largest component's bounding box, so all cells are equally spaced even when they have different sizes.

target_row = kf.KCell(name="grid_row")
ig_row = kf.grid(target_row, components, spacing=2.0)

print(
    f"Row bbox: {target_row.dbbox().width():.1f} µm × {target_row.dbbox().height():.1f} µm"
)
print(f"Placed {len(ig_row.insts)} instances")
target_row
Row bbox: 78.5 µm × 10.0 µm
Placed 6 instances


png

3 · grid — 2D with shape=

Pass shape=(rows, cols) to reflow a flat list into a 2-D grid. The slot size is still uniform (largest component sets the pitch in each axis).

target_2d = kf.KCell(name="grid_2d")
ig_2d = kf.grid(target_2d, components, spacing=2.0, shape=(2, 3))

print(
    f"2D bbox: {target_2d.dbbox().width():.1f} µm × {target_2d.dbbox().height():.1f} µm"
)
target_2d
2D bbox: 37.0 µm × 20.5 µm


png

4 · grid — Explicit 2D Input

You can supply a list-of-lists directly to control exactly which cell goes in each position. This is useful when you want a hand-curated layout rather than a simple reflow.

row0 = [components[0], components[1], components[2]]
row1 = [components[3], components[4], components[5]]

target_2d_explicit = kf.KCell(name="grid_2d_explicit")
ig_2d_explicit = kf.grid(target_2d_explicit, [row0, row1], spacing=2.0)

print(
    f"Explicit 2D bbox: {target_2d_explicit.dbbox().width():.1f} µm × {target_2d_explicit.dbbox().height():.1f} µm"
)
target_2d_explicit
Explicit 2D bbox: 37.0 µm × 20.5 µm


png

5 · flexgrid — Compact Layout

flexgrid gives each column its own width (tallest/widest in that column) and each row its own height, so overall footprint is smaller when components vary in size.

Here the same six components are arranged with shape=(2, 3) using both functions so you can compare the bounding boxes.

target_flex = kf.KCell(name="flexgrid_2d")
ig_flex = kf.flexgrid(target_flex, components, spacing=2.0, shape=(2, 3))

grid_area = target_2d.dbbox().width() * target_2d.dbbox().height()
flex_area = target_flex.dbbox().width() * target_flex.dbbox().height()

print(
    f"grid   bbox: {target_2d.dbbox().width():.1f} × {target_2d.dbbox().height():.1f} µm  ({grid_area:.0f} µm²)"
)
print(
    f"flexgrid bbox: {target_flex.dbbox().width():.1f} × {target_flex.dbbox().height():.1f} µm  ({flex_area:.0f} µm²)"
)
print(f"Area saving: {100 * (1 - flex_area / grid_area):.0f}%")
target_flex
grid   bbox: 37.0 × 20.5 µm  (758 µm²)
flexgrid bbox: 32.0 × 19.0 µm  (608 µm²)
Area saving: 20%


png

6 · Alignment Options

Both grid and flexgrid accept align_x and align_y to control how components are positioned within their slot:

Value Meaning
"center" (default) Component centred in its slot
"xmin" / "ymin" Left-aligned / bottom-aligned
"xmax" / "ymax" Right-aligned / top-aligned
"origin" Component placed so its origin is at the slot position
target_aligned = kf.KCell(name="grid_aligned")
ig_aligned = kf.grid(
    target_aligned,
    components,
    spacing=2.0,
    shape=(2, 3),
    align_x="xmin",
    align_y="ymin",
)
target_aligned


png

7 · grid_dbu — DBU Variant

If you work with KCell (integer DBU coordinates) instead of DKCell, use grid_dbu and pass the spacing in DBU. The API is otherwise identical.

# Build KCell (DBU) components
comps_dbu: list[kf.KCell] = []
for w_um, h_um in sizes_um:
    c = kf.KCell(name=f"kdbox_{w_um}x{h_um}")
    c.shapes(c.kcl.layer(L.WG)).insert(
        kf.kdb.Box(kf.kcl.to_dbu(w_um), kf.kcl.to_dbu(h_um))
    )
    comps_dbu.append(c)

target_dbu = kf.KCell(name="grid_dbu_example")
ig_dbu = kf.grid_dbu(target_dbu, comps_dbu, spacing=kf.kcl.to_dbu(2))

print(
    f"grid_dbu bbox: {target_dbu.dbbox().width():.1f} µm × {target_dbu.dbbox().height():.1f} µm"
)
target_dbu
grid_dbu bbox: 78.5 µm × 10.0 µm


png

8 · Placing the Grid in a Larger Layout

The returned InstanceGroup can be used to move the whole block at once. Here we pack two sub-grids and place them side-by-side in a top-level cell.

top = kf.KCell(name="top_assembly")

# First sub-grid: first 3 components
sub_a = kf.KCell(name="sub_a")
kf.grid(sub_a, components[:3], spacing=2.0)
inst_a = top << sub_a

# Second sub-grid: last 3 components, placed to the right with a 10 µm gap
sub_b = kf.KCell(name="sub_b")
kf.grid(sub_b, components[3:], spacing=2.0)
inst_b = top << sub_b
gap_dbu = kf.kcl.to_dbu(10)
inst_b.transform(kf.kdb.Trans(sub_a.bbox().width() + gap_dbu, 0))

print(f"Top bbox: {top.dbbox().width():.1f} µm × {top.dbbox().height():.1f} µm")
top
Top bbox: 78.0 µm × 10.0 µm


png

Quick Reference

import kfactory as kf

# µm-based uniform grid (DKCell components)
ig = kf.grid(target, cells, spacing=2.0)
ig = kf.grid(target, cells, spacing=2.0, shape=(rows, cols))
ig = kf.grid(target, [row0, row1], spacing=2.0)        # explicit 2D

# µm-based flexible grid (per-column/row sizing)
ig = kf.flexgrid(target, cells, spacing=2.0, shape=(rows, cols))

# DBU-based variants (KCell components, spacing in dbu)
ig = kf.grid_dbu(target, cells, spacing=kf.kcl.to_dbu(2))
ig = kf.flexgrid_dbu(target, cells, spacing=kf.kcl.to_dbu(2))

# Alignment within each slot
ig = kf.grid(target, cells, spacing=2.0, align_x="xmin", align_y="ymin")

# Move the whole block after placement
inst = top << target
inst.transform(kf.kdb.DCplxTrans(1, 0, False, x_um, y_um))

See Also

Topic Where
Component packing (free placement) Utilities: Packing
Components overview & gallery Components: Overview
Creating a full PDK PDK: Creating a PDK
Instance placement Core Concepts: Instances