KLayout is a high performance layout viewer and editor that supports GDS and OASIS files and more formats
—
Cell hierarchy management, instance creation and manipulation, library organization, and parametric cell (PCell) support for reusable design components in IC layout design.
class CellInstArray:
def __init__(self, cell_index: int, trans: Trans, na: int = 1, nb: int = 1,
da: Vector = None, db: Vector = None):
"""
Create a cell instance or instance array.
Parameters:
- cell_index: Index of the cell to instantiate
- trans: Transformation for the instance
- na: Number of instances in A direction (default 1)
- nb: Number of instances in B direction (default 1)
- da: Displacement vector for A direction array
- db: Displacement vector for B direction array
"""
@property
def cell_index(self) -> int:
"""Get the instantiated cell index."""
@property
def trans(self) -> Trans:
"""Get the instance transformation."""
@property
def na(self) -> int:
"""Get number of instances in A direction."""
@property
def nb(self) -> int:
"""Get number of instances in B direction."""
@property
def da(self) -> Vector:
"""Get A direction displacement vector."""
@property
def db(self) -> Vector:
"""Get B direction displacement vector."""
def is_regular_array(self) -> bool:
"""Check if this is a regular array (na > 1 or nb > 1)."""
def bbox(self, layout: Layout) -> Box:
"""Get bounding box of the instance array."""
def transform_into(self, trans: Trans) -> CellInstArray:
"""Apply additional transformation to instance."""
class DCellInstArray:
def __init__(self, cell_index: int, trans: DCplxTrans):
"""Create a double precision cell instance."""
@property
def cell_index(self) -> int:
"""Get the instantiated cell index."""
@property
def trans(self) -> DCplxTrans:
"""Get the instance transformation."""class Cell:
def each_inst(self):
"""Iterate over all instances in this cell."""
def each_parent_inst(self):
"""Iterate over all instances of this cell (parent instances)."""
def each_child_cell(self):
"""Iterate over all child cells (cells instantiated by this cell)."""
def each_parent_cell(self):
"""Iterate over all parent cells (cells that instantiate this cell)."""
def child_cells(self) -> int:
"""Get number of child cells."""
def parent_cells(self) -> int:
"""Get number of parent cells."""
def hierarchy_levels(self) -> int:
"""Get maximum hierarchy depth below this cell."""
def is_top(self) -> bool:
"""Check if this is a top-level cell (no parents)."""
def is_leaf(self) -> bool:
"""Check if this is a leaf cell (no children)."""
def is_proxy(self) -> bool:
"""Check if this is a proxy cell (library reference)."""
class Layout:
def top_cells(self):
"""Get all top-level cells in the layout."""
def top_cell(self) -> Cell:
"""Get the single top cell (if there is exactly one)."""
def each_cell(self):
"""Iterate over all cells in the layout."""
def cells(self) -> int:
"""Get total number of cells."""class PCellDeclaration:
def __init__(self):
"""Base class for parametric cell declarations."""
def display_name(self, parameters) -> str:
"""
Get display name for the PCell with given parameters.
Parameters:
- parameters: Dictionary of parameter values
Returns:
str: Display name for this parameter set
"""
def produce(self, layout: Layout, layers, parameters, cell: Cell) -> None:
"""
Produce the PCell geometry.
Parameters:
- layout: Target layout
- layers: Layer mapping
- parameters: Parameter values
- cell: Target cell to populate
"""
def get_parameters(self) -> list:
"""Get list of parameter declarations."""
def get_layers(self) -> list:
"""Get list of layer declarations."""
class PCellDeclarationHelper:
def __init__(self, name: str):
"""
Helper class for creating parametric cells.
Parameters:
- name: Name of the PCell
"""
def param(self, name: str, param_type: type, description: str, default=None) -> None:
"""
Declare a parameter.
Parameters:
- name: Parameter name
- param_type: Parameter type (int, float, str, bool)
- description: Parameter description
- default: Default value
"""
def layer(self, name: str, description: str, default: LayerInfo = None) -> None:
"""
Declare a layer parameter.
Parameters:
- name: Layer parameter name
- description: Layer description
- default: Default layer info
"""
def produce_impl(self) -> None:
"""
Implementation method to be overridden.
This method should create the actual geometry.
"""
class PCellParameterDeclaration:
def __init__(self, name: str, param_type: type, description: str = "",
default=None, choices=None):
"""
Parameter declaration for PCells.
Parameters:
- name: Parameter name
- param_type: Parameter type
- description: Parameter description
- default: Default value
- choices: List of valid choices (for enum parameters)
"""
@property
def name(self) -> str:
"""Get parameter name."""
@property
def type(self) -> type:
"""Get parameter type."""
@property
def description(self) -> str:
"""Get parameter description."""
# Parameter types
class PCellParameterType:
TypeInt = int
TypeDouble = float
TypeString = str
TypeBoolean = bool
TypeLayer = LayerInfo
TypeShape = objectclass Library:
def __init__(self, name: str = ""):
"""
Create a library.
Parameters:
- name: Library name
"""
@property
def name(self) -> str:
"""Get library name."""
def set_name(self, name: str) -> None:
"""Set library name."""
def register_pcell(self, pcell_class, name: str) -> int:
"""
Register a PCell class in the library.
Parameters:
- pcell_class: PCell declaration class
- name: PCell name in library
Returns:
int: PCell ID
"""
def layout(self) -> Layout:
"""Get the library layout."""
def delete(self) -> None:
"""Delete the library."""
class Technology:
def __init__(self, name: str = ""):
"""
Create a technology definition.
Parameters:
- name: Technology name
"""
@property
def name(self) -> str:
"""Get technology name."""
def load(self, tech_file: str) -> None:
"""Load technology from file."""
def save(self, tech_file: str) -> None:
"""Save technology to file."""import klayout.db as db
# Create layout with hierarchy
layout = db.Layout()
# Create leaf cell (basic building block)
nmos_cell = layout.create_cell("NMOS_TRANSISTOR")
layer_active = layout.layer(db.LayerInfo(1, 0))
layer_poly = layout.layer(db.LayerInfo(2, 0))
layer_contact = layout.layer(db.LayerInfo(3, 0))
# Add transistor geometry
nmos_cell.shapes(layer_active).insert(db.Box(0, 0, 200, 100))
nmos_cell.shapes(layer_poly).insert(db.Box(50, -20, 150, 120))
nmos_cell.shapes(layer_contact).insert(db.Box(25, 25, 75, 75))
nmos_cell.shapes(layer_contact).insert(db.Box(125, 25, 175, 75))
# Create intermediate cell (logic gate)
inverter_cell = layout.create_cell("INVERTER")
# Instantiate NMOS in inverter
nmos_instance = db.CellInstArray(nmos_cell.cell_index, db.Trans(db.Point(0, 0)))
inverter_cell.insert(nmos_instance)
# Add PMOS (simplified - just another NMOS for this example)
pmos_instance = db.CellInstArray(nmos_cell.cell_index,
db.Trans(0, True, db.Point(0, 200))) # Mirrored
inverter_cell.insert(pmos_instance)
# Create top-level cell
top_cell = layout.create_cell("LOGIC_BLOCK")
# Create array of inverters
for x in range(0, 1000, 250):
for y in range(0, 500, 300):
inv_instance = db.CellInstArray(inverter_cell.cell_index,
db.Trans(db.Point(x, y)))
top_cell.insert(inv_instance)
layout.write("hierarchy_example.gds")import klayout.db as db
layout = db.Layout()
unit_cell = layout.create_cell("UNIT")
top_cell = layout.create_cell("TOP")
# Add content to unit cell
layer = layout.layer(db.LayerInfo(1, 0))
unit_cell.shapes(layer).insert(db.Box(0, 0, 10, 10))
# Create 1D array
array_1d = db.CellInstArray(
unit_cell.cell_index,
db.Trans(db.Point(0, 0)), # Base transformation
na=10, # 10 instances in A direction
nb=1, # 1 instance in B direction
da=db.Vector(15, 0), # 15 unit spacing in X
db=db.Vector(0, 0) # No B direction spacing
)
top_cell.insert(array_1d)
# Create 2D array
array_2d = db.CellInstArray(
unit_cell.cell_index,
db.Trans(db.Point(0, 50)), # Offset from 1D array
na=8, # 8 instances in A direction
nb=6, # 6 instances in B direction
da=db.Vector(15, 0), # 15 unit spacing in X
db=db.Vector(0, 20) # 20 unit spacing in Y
)
top_cell.insert(array_2d)
print(f"1D Array bbox: {array_1d.bbox(layout)}")
print(f"2D Array bbox: {array_2d.bbox(layout)}")
layout.write("array_example.gds")import klayout.db as db
layout = db.Layout()
layout.read("complex_design.gds")
# Find all top cells
top_cells = list(layout.top_cells())
print(f"Found {len(top_cells)} top-level cells")
if top_cells:
top_cell = top_cells[0]
print(f"Analyzing cell: {top_cell.name}")
# Analyze hierarchy
print(f"Child cells: {top_cell.child_cells()}")
print(f"Parent cells: {top_cell.parent_cells()}")
print(f"Hierarchy levels: {top_cell.hierarchy_levels()}")
# Walk through hierarchy
def analyze_cell(cell, level=0):
indent = " " * level
print(f"{indent}Cell: {cell.name}")
print(f"{indent} Instances: {sum(1 for _ in cell.each_inst())}")
print(f"{indent} Is leaf: {cell.is_leaf()}")
# Analyze instances
for inst in cell.each_inst():
child_cell = layout.cell(inst.cell_index)
print(f"{indent} -> {child_cell.name} at {inst.trans.disp}")
# Recurse for first few levels
if level < 3:
analyze_cell(child_cell, level + 1)
analyze_cell(top_cell)import klayout.db as db
class ResistorPCell(db.PCellDeclarationHelper):
def __init__(self):
super().__init__("Resistor")
# Declare parameters
self.param("width", db.PCellParameterType.TypeDouble, "Width", 1.0)
self.param("length", db.PCellParameterType.TypeDouble, "Length", 10.0)
self.param("num_contacts", db.PCellParameterType.TypeInt, "Number of contacts", 2)
# Declare layers
self.layer("poly", "Poly layer", db.LayerInfo(1, 0))
self.layer("contact", "Contact layer", db.LayerInfo(2, 0))
def produce_impl(self):
# Get parameter values
width = self.width
length = self.length
num_contacts = self.num_contacts
# Get layer indices
poly_layer = self.layout.layer(self.poly)
contact_layer = self.layout.layer(self.contact)
# Create resistor body
resistor_body = db.Box(0, 0, int(length * 1000), int(width * 1000))
self.cell.shapes(poly_layer).insert(resistor_body)
# Add contacts
contact_size = min(int(width * 200), int(length * 200 / num_contacts))
for i in range(num_contacts):
if num_contacts == 1:
x = int(length * 500) # Center
else:
x = int(length * 1000 * i / (num_contacts - 1))
y = int(width * 500 - contact_size / 2)
contact = db.Box(x - contact_size//2, y,
x + contact_size//2, y + contact_size)
self.cell.shapes(contact_layer).insert(contact)
# Register and use the PCell
library = db.Library("MyLibrary")
resistor_id = library.register_pcell(ResistorPCell(), "Resistor")
# Create layout using the PCell
layout = db.Layout()
top_cell = layout.create_cell("TOP")
# Instantiate PCell with different parameters
params1 = {"width": 2.0, "length": 20.0, "num_contacts": 3}
params2 = {"width": 1.5, "length": 15.0, "num_contacts": 2}
# Note: Actual PCell instantiation requires library integration
# This is a simplified exampleimport klayout.db as db
layout = db.Layout()
layout.read("design.gds")
def analyze_hierarchy(layout):
"""Analyze layout hierarchy and provide statistics."""
stats = {
"total_cells": layout.cells(),
"top_cells": len(list(layout.top_cells())),
"leaf_cells": 0,
"max_depth": 0,
"total_instances": 0
}
# Analyze each cell
for cell in layout.each_cell():
if cell.is_leaf():
stats["leaf_cells"] += 1
# Count instances
instance_count = sum(1 for _ in cell.each_inst())
stats["total_instances"] += instance_count
# Track maximum depth
depth = cell.hierarchy_levels()
if depth > stats["max_depth"]:
stats["max_depth"] = depth
return stats
def find_unused_cells(layout):
"""Find cells that are never instantiated."""
used_cells = set()
# Mark all instantiated cells as used
for cell in layout.each_cell():
for inst in cell.each_inst():
used_cells.add(inst.cell_index)
# Find unused cells
unused_cells = []
for cell in layout.each_cell():
if cell.cell_index not in used_cells and not cell.is_top():
unused_cells.append(cell)
return unused_cells
def flatten_hierarchy(cell, target_cell, level=1):
"""Flatten cell hierarchy to specified level."""
if level <= 0:
return
instances_to_remove = []
for inst in cell.each_inst():
child_cell = cell.layout().cell(inst.cell_index)
# Copy child cell content to target cell with transformation
for layer in range(cell.layout().layers()):
for shape in child_cell.shapes(layer).each():
transformed_shape = shape.transformed(inst.trans)
target_cell.shapes(layer).insert(transformed_shape)
# Recursively flatten child cells
flatten_hierarchy(child_cell, target_cell, level - 1)
instances_to_remove.append(inst)
# Remove flattened instances
for inst in instances_to_remove:
cell.erase(inst)
# Use the analysis functions
stats = analyze_hierarchy(layout)
print(f"Hierarchy Statistics: {stats}")
unused = find_unused_cells(layout)
print(f"Unused cells: {[cell.name for cell in unused]}")
# Flatten top cell by 2 levels
if layout.top_cell():
flattened_cell = layout.create_cell("FLATTENED")
flatten_hierarchy(layout.top_cell(), flattened_cell, 2)import klayout.db as db
layout = db.Layout()
base_cell = layout.create_cell("BASE")
pattern_cell = layout.create_cell("PATTERN")
# Add content to base cell
layer = layout.layer(db.LayerInfo(1, 0))
base_cell.shapes(layer).insert(db.Box(0, 0, 100, 50))
# Create various transformed instances
transformations = [
db.Trans(0, False, db.Point(0, 0)), # Original
db.Trans(1, False, db.Point(200, 0)), # 90° rotation
db.Trans(2, False, db.Point(200, 200)), # 180° rotation
db.Trans(3, False, db.Point(0, 200)), # 270° rotation
db.Trans(0, True, db.Point(400, 0)), # X mirror
db.Trans(1, True, db.Point(600, 0)), # X mirror + 90° rotation
]
for i, trans in enumerate(transformations):
instance = db.CellInstArray(base_cell.cell_index, trans)
# Apply additional transformation
if i > 2: # Scale some instances
scaled_trans = db.Trans(db.Vector(50, 25)) # Additional offset
modified_instance = instance.transform_into(scaled_trans)
pattern_cell.insert(modified_instance)
else:
pattern_cell.insert(instance)
# Create complex patterns with arrays and transformations
spiral_cell = layout.create_cell("SPIRAL")
radius = 50
angle_step = 30
for i in range(12): # 12 positions around circle
angle_rad = i * angle_step * 3.14159 / 180
x = int(radius * cos(angle_rad))
y = int(radius * sin(angle_rad))
# Rotation to face outward
rotation = i * angle_step / 90 # Convert to 90-degree increments
trans = db.Trans(int(rotation) % 4, False, db.Point(x, y))
instance = db.CellInstArray(base_cell.cell_index, trans)
spiral_cell.insert(instance)
layout.write("advanced_instances.gds")Install with Tessl CLI
npx tessl i tessl/pypi-klayout