KLayout is a high performance layout viewer and editor that supports GDS and OASIS files and more formats
—
Design rule checking (DRC), layout versus schematic (LVS) capabilities, and results database management for storing and analyzing verification reports in IC layout workflows.
class ReportDatabase:
def __init__(self, name: str):
"""
Create a results database for verification reports.
Parameters:
- name: Database name
"""
@property
def name(self) -> str:
"""Get database name."""
def set_name(self, name: str) -> None:
"""Set database name."""
def create_category(self, name: str) -> Category:
"""
Create a new result category.
Parameters:
- name: Category name (e.g., "Width Violations", "Spacing Errors")
Returns:
Category: New category object
"""
def category_by_name(self, name: str) -> Category:
"""Get category by name."""
def each_category(self):
"""Iterate over all categories."""
def num_categories(self) -> int:
"""Get number of categories."""
def save(self, filename: str) -> None:
"""
Save database to file.
Parameters:
- filename: Output file path
"""
def load(self, filename: str) -> None:
"""
Load database from file.
Parameters:
- filename: Input file path
"""
def clear(self) -> None:
"""Clear all categories and items."""
class Category:
def __init__(self, name: str = ""):
"""
Create a result category.
Parameters:
- name: Category name
"""
@property
def name(self) -> str:
"""Get category name."""
def set_name(self, name: str) -> None:
"""Set category name."""
@property
def description(self) -> str:
"""Get category description."""
def set_description(self, description: str) -> None:
"""Set category description."""
def create_item(self, polygon: Polygon = None) -> Item:
"""
Create a new result item in this category.
Parameters:
- polygon: Optional polygon representing the violation area
Returns:
Item: New result item
"""
def each_item(self):
"""Iterate over all items in category."""
def num_items(self) -> int:
"""Get number of items in category."""
def clear(self) -> None:
"""Clear all items from category."""
class Item:
def __init__(self):
"""Create a result item."""
def add_value(self, value: Value) -> None:
"""
Add a measurement value to the item.
Parameters:
- value: Measurement or property value
"""
def each_value(self):
"""Iterate over all values."""
def num_values(self) -> int:
"""Get number of values."""
def add_tag(self, tag: Tags) -> None:
"""Add a tag to categorize the item."""
def each_tag(self):
"""Iterate over all tags."""
def set_visited(self, visited: bool) -> None:
"""Mark item as visited/reviewed."""
def visited(self) -> bool:
"""Check if item has been visited."""
class Value:
def __init__(self, value=None):
"""
Create a measurement value.
Parameters:
- value: Numeric value, string, or geometric object
"""
def is_numeric(self) -> bool:
"""Check if value is numeric."""
def is_string(self) -> bool:
"""Check if value is string."""
def is_geometric(self) -> bool:
"""Check if value represents geometry."""
def to_s(self) -> str:
"""Convert value to string representation."""
class Tags:
def __init__(self, tag: str = ""):
"""
Create a tag for result categorization.
Parameters:
- tag: Tag string
"""
@property
def tag(self) -> str:
"""Get tag string."""
def set_tag(self, tag: str) -> None:
"""Set tag string."""# DRC operations are primarily performed using Region methods
# from the shape operations module
class Region:
def width_check(self, d: int, whole_edges: bool = False,
metrics: str = "euclidean", ignore_angle: float = None) -> EdgePairs:
"""
Check minimum width rule.
Parameters:
- d: Minimum width requirement
- whole_edges: Check whole edges vs. edge segments
- metrics: Distance metrics ("euclidean", "square", "projected")
- ignore_angle: Ignore edges at this angle (degrees)
Returns:
EdgePairs: Width violations as edge pairs
"""
def space_check(self, d: int, whole_edges: bool = False,
metrics: str = "euclidean", ignore_angle: float = None) -> EdgePairs:
"""
Check minimum spacing rule.
Parameters:
- d: Minimum spacing requirement
- whole_edges: Check whole edges vs. edge segments
- metrics: Distance metrics
- ignore_angle: Ignore edges at this angle
Returns:
EdgePairs: Spacing violations as edge pairs
"""
def enclosing_check(self, other: Region, d: int,
whole_edges: bool = False) -> EdgePairs:
"""
Check enclosure rule (this region must enclose other by distance d).
Parameters:
- other: Region to be enclosed
- d: Minimum enclosure distance
- whole_edges: Check whole edges vs. segments
Returns:
EdgePairs: Enclosure violations
"""
def overlap_check(self, other: Region, d: int,
whole_edges: bool = False) -> EdgePairs:
"""
Check overlap rule (regions must overlap by at least distance d).
Parameters:
- other: Other region to check overlap with
- d: Minimum overlap distance
- whole_edges: Check whole edges vs. segments
Returns:
EdgePairs: Overlap violations
"""
def separation_check(self, other: Region, d: int,
whole_edges: bool = False) -> EdgePairs:
"""
Check separation rule (regions must be separated by at least d).
Parameters:
- other: Other region to check separation from
- d: Minimum separation distance
- whole_edges: Check whole edges vs. segments
Returns:
EdgePairs: Separation violations
"""
def notch_check(self, d: int) -> EdgePairs:
"""
Check for notches smaller than minimum width.
Parameters:
- d: Minimum notch width
Returns:
EdgePairs: Notch violations
"""
def isolated_check(self, d: int) -> EdgePairs:
"""
Check for isolated features smaller than minimum size.
Parameters:
- d: Minimum feature size
Returns:
EdgePairs: Isolated feature violations
"""class Netlist:
def __init__(self):
"""Create an empty netlist."""
def create_circuit(self, name: str) -> Circuit:
"""
Create a new circuit in the netlist.
Parameters:
- name: Circuit name
Returns:
Circuit: New circuit object
"""
def circuit_by_name(self, name: str) -> Circuit:
"""Get circuit by name."""
def each_circuit(self):
"""Iterate over all circuits."""
def purge(self) -> None:
"""Remove unused circuits and nets."""
def make_top_level_pins(self) -> None:
"""Create top-level pins for unconnected nets."""
class Circuit:
def __init__(self, name: str = ""):
"""
Create a circuit.
Parameters:
- name: Circuit name
"""
@property
def name(self) -> str:
"""Get circuit name."""
def create_net(self, name: str = "") -> Net:
"""
Create a new net in the circuit.
Parameters:
- name: Net name (optional)
Returns:
Net: New net object
"""
def create_device(self, device_class, name: str = "") -> Device:
"""
Create a device instance.
Parameters:
- device_class: Device class/model
- name: Device instance name
Returns:
Device: New device instance
"""
def create_pin(self, name: str) -> Pin:
"""
Create a circuit pin.
Parameters:
- name: Pin name
Returns:
Pin: New pin object
"""
def each_net(self):
"""Iterate over all nets."""
def each_device(self):
"""Iterate over all devices."""
def each_pin(self):
"""Iterate over all pins."""
class Net:
def __init__(self, name: str = ""):
"""
Create a net.
Parameters:
- name: Net name
"""
@property
def name(self) -> str:
"""Get net name."""
def connect_pin(self, pin: Pin) -> None:
"""
Connect a pin to this net.
Parameters:
- pin: Pin to connect
"""
def each_pin(self):
"""Iterate over connected pins."""
def pin_count(self) -> int:
"""Get number of connected pins."""
class Device:
def __init__(self, name: str = ""):
"""
Create a device.
Parameters:
- name: Device name
"""
@property
def name(self) -> str:
"""Get device name."""
def create_terminal(self, name: str) -> Pin:
"""
Create a device terminal.
Parameters:
- name: Terminal name
Returns:
Pin: Terminal pin
"""
def each_terminal(self):
"""Iterate over device terminals."""
class Pin:
def __init__(self, name: str = ""):
"""
Create a pin.
Parameters:
- name: Pin name
"""
@property
def name(self) -> str:
"""Get pin name."""
def net(self) -> Net:
"""Get connected net."""import klayout.db as db
import klayout.rdb as rdb
# Load layout
layout = db.Layout()
layout.read("design.gds")
# Get shapes from specific layers
metal1_layer = layout.layer(db.LayerInfo(1, 0))
metal1_region = db.Region()
# Collect all Metal1 shapes
top_cell = layout.top_cell()
for shape in top_cell.shapes(metal1_layer).each():
metal1_region.insert(shape.polygon)
# Create results database
report_db = rdb.ReportDatabase("DRC_Report")
# Define DRC rules
min_width = 120 # 120nm minimum width
min_spacing = 140 # 140nm minimum spacing
# Check width violations
width_violations = metal1_region.width_check(min_width)
if width_violations.count() > 0:
width_category = report_db.create_category("Metal1 Width Violations")
width_category.set_description(f"Minimum width {min_width}nm")
for edge_pair in width_violations.each():
item = width_category.create_item()
# Convert edge pair to polygon for visualization
violation_poly = edge_pair.to_polygon(10) # 10nm marker
item.add_value(rdb.Value(violation_poly))
# Add measurement value
distance = edge_pair.distance()
item.add_value(rdb.Value(f"Width: {distance}nm"))
# Check spacing violations
space_violations = metal1_region.space_check(min_spacing)
if space_violations.count() > 0:
space_category = report_db.create_category("Metal1 Spacing Violations")
space_category.set_description(f"Minimum spacing {min_spacing}nm")
for edge_pair in space_violations.each():
item = space_category.create_item()
violation_poly = edge_pair.to_polygon(10)
item.add_value(rdb.Value(violation_poly))
distance = edge_pair.distance()
item.add_value(rdb.Value(f"Spacing: {distance}nm"))
# Save report
report_db.save("metal1_drc_report.lyrdb")
print(f"DRC complete: {width_violations.count()} width, {space_violations.count()} spacing violations")import klayout.db as db
import klayout.rdb as rdb
def run_comprehensive_drc(layout_file: str, output_report: str):
"""Run comprehensive DRC on a layout file."""
layout = db.Layout()
layout.read(layout_file)
# Define layers and rules
layer_rules = {
"NWELL": {"layer": (1, 0), "min_width": 600, "min_spacing": 600},
"ACTIVE": {"layer": (2, 0), "min_width": 150, "min_spacing": 150},
"POLY": {"layer": (3, 0), "min_width": 100, "min_spacing": 140},
"CONTACT": {"layer": (4, 0), "min_width": 120, "min_spacing": 140},
"METAL1": {"layer": (5, 0), "min_width": 120, "min_spacing": 140},
"VIA1": {"layer": (6, 0), "min_width": 120, "min_spacing": 140},
"METAL2": {"layer": (7, 0), "min_width": 140, "min_spacing": 140},
}
# Define enclosure rules
enclosure_rules = [
{"inner": "CONTACT", "outer": "ACTIVE", "min_enc": 60},
{"inner": "CONTACT", "outer": "POLY", "min_enc": 50},
{"inner": "VIA1", "outer": "METAL1", "min_enc": 60},
{"inner": "VIA1", "outer": "METAL2", "min_enc": 60},
]
report_db = rdb.ReportDatabase("Comprehensive_DRC")
# Extract regions for each layer
regions = {}
for layer_name, rules in layer_rules.items():
layer_info = db.LayerInfo(rules["layer"][0], rules["layer"][1])
layer_idx = layout.layer(layer_info)
region = db.Region()
for cell in layout.each_cell():
for shape in cell.shapes(layer_idx).each():
region.insert(shape.polygon)
regions[layer_name] = region.merged() # Merge overlapping shapes
# Run width and spacing checks
for layer_name, rules in layer_rules.items():
region = regions[layer_name]
if region.is_empty():
continue
# Width check
width_violations = region.width_check(rules["min_width"])
if width_violations.count() > 0:
category = report_db.create_category(f"{layer_name} Width Violations")
category.set_description(f"Minimum width {rules['min_width']}nm")
for edge_pair in width_violations.each():
item = category.create_item()
violation_poly = edge_pair.to_polygon(20)
item.add_value(rdb.Value(violation_poly))
item.add_value(rdb.Value(f"Measured: {edge_pair.distance()}nm"))
# Spacing check
space_violations = region.space_check(rules["min_spacing"])
if space_violations.count() > 0:
category = report_db.create_category(f"{layer_name} Spacing Violations")
category.set_description(f"Minimum spacing {rules['min_spacing']}nm")
for edge_pair in space_violations.each():
item = category.create_item()
violation_poly = edge_pair.to_polygon(20)
item.add_value(rdb.Value(violation_poly))
item.add_value(rdb.Value(f"Measured: {edge_pair.distance()}nm"))
# Run enclosure checks
for rule in enclosure_rules:
inner_region = regions.get(rule["inner"])
outer_region = regions.get(rule["outer"])
if inner_region and outer_region and not inner_region.is_empty() and not outer_region.is_empty():
enc_violations = inner_region.enclosing_check(outer_region, rule["min_enc"])
if enc_violations.count() > 0:
category = report_db.create_category(
f"{rule['inner']} in {rule['outer']} Enclosure Violations"
)
category.set_description(f"Minimum enclosure {rule['min_enc']}nm")
for edge_pair in enc_violations.each():
item = category.create_item()
violation_poly = edge_pair.to_polygon(20)
item.add_value(rdb.Value(violation_poly))
item.add_value(rdb.Value(f"Measured: {edge_pair.distance()}nm"))
# Generate summary
total_violations = sum(cat.num_items() for cat in report_db.each_category())
print(f"DRC Summary: {total_violations} total violations in {report_db.num_categories()} categories")
# Save report
report_db.save(output_report)
return report_db
# Run comprehensive DRC
report = run_comprehensive_drc("complex_design.gds", "comprehensive_drc.lyrdb")import klayout.db as db
def extract_netlist(layout: db.Layout) -> db.Netlist:
"""Extract netlist from layout (simplified example)."""
netlist = db.Netlist()
top_circuit = netlist.create_circuit("TOP")
# Get layer regions
active_layer = layout.layer(db.LayerInfo(2, 0))
poly_layer = layout.layer(db.LayerInfo(3, 0))
contact_layer = layout.layer(db.LayerInfo(4, 0))
metal1_layer = layout.layer(db.LayerInfo(5, 0))
active_region = db.Region()
poly_region = db.Region()
contact_region = db.Region()
metal1_region = db.Region()
top_cell = layout.top_cell()
# Collect shapes
for shape in top_cell.shapes(active_layer).each():
active_region.insert(shape.polygon)
for shape in top_cell.shapes(poly_layer).each():
poly_region.insert(shape.polygon)
for shape in top_cell.shapes(contact_layer).each():
contact_region.insert(shape.polygon)
for shape in top_cell.shapes(metal1_layer).each():
metal1_region.insert(shape.polygon)
# Find transistors (simplified: active AND poly intersections)
transistor_regions = active_region & poly_region
device_count = 0
for transistor_poly in transistor_regions.each():
device_count += 1
device = top_circuit.create_device(None, f"M{device_count}")
# Create terminals
gate_pin = device.create_terminal("G")
source_pin = device.create_terminal("S")
drain_pin = device.create_terminal("D")
bulk_pin = device.create_terminal("B")
# This is a simplified example - real extraction would:
# 1. Analyze connectivity through contacts and metal
# 2. Identify source/drain regions
# 3. Trace nets through the layout
# 4. Handle hierarchy and instances
# Extract nets by analyzing metal connectivity
# (This would be much more complex in a real implementation)
metal_connected = metal1_region.merged()
net_count = 0
for metal_poly in metal_connected.each():
net_count += 1
net = top_circuit.create_net(f"net{net_count}")
# Find contacts that connect to this metal
metal_contacts = contact_region & db.Region([metal_poly])
# Connect devices through contacts (simplified)
# Real implementation would trace through layout hierarchy
return netlist
def compare_netlists(layout_netlist: db.Netlist, schematic_netlist: db.Netlist) -> rdb.ReportDatabase:
"""Compare extracted netlist with reference schematic (simplified LVS)."""
report_db = rdb.ReportDatabase("LVS_Report")
# Compare circuit counts
layout_circuits = list(layout_netlist.each_circuit())
schematic_circuits = list(schematic_netlist.each_circuit())
if len(layout_circuits) != len(schematic_circuits):
category = report_db.create_category("Circuit Count Mismatch")
item = category.create_item()
item.add_value(rdb.Value(f"Layout: {len(layout_circuits)}, Schematic: {len(schematic_circuits)}"))
# Compare each circuit
for layout_circuit in layout_circuits:
schematic_circuit = schematic_netlist.circuit_by_name(layout_circuit.name)
if not schematic_circuit:
category = report_db.create_category("Missing Circuits")
item = category.create_item()
item.add_value(rdb.Value(f"Circuit {layout_circuit.name} not found in schematic"))
continue
# Compare device counts
layout_devices = list(layout_circuit.each_device())
schematic_devices = list(schematic_circuit.each_device())
if len(layout_devices) != len(schematic_devices):
category = report_db.create_category("Device Count Mismatch")
item = category.create_item()
item.add_value(rdb.Value(
f"Circuit {layout_circuit.name}: Layout {len(layout_devices)}, "
f"Schematic {len(schematic_devices)}"
))
# Compare net counts
layout_nets = list(layout_circuit.each_net())
schematic_nets = list(schematic_circuit.each_net())
if len(layout_nets) != len(schematic_nets):
category = report_db.create_category("Net Count Mismatch")
item = category.create_item()
item.add_value(rdb.Value(
f"Circuit {layout_circuit.name}: Layout {len(layout_nets)}, "
f"Schematic {len(schematic_nets)}"
))
return report_db
# Example usage
layout = db.Layout()
layout.read("extracted_design.gds")
# Extract netlist from layout
layout_netlist = extract_netlist(layout)
# Load reference schematic netlist (would typically be SPICE format)
schematic_netlist = db.Netlist()
# ... load schematic netlist from file ...
# Run LVS comparison
lvs_report = compare_netlists(layout_netlist, schematic_netlist)
lvs_report.save("lvs_report.lyrdb")
print(f"LVS complete: {lvs_report.num_categories()} categories, "
f"{sum(cat.num_items() for cat in lvs_report.each_category())} total issues")import klayout.db as db
import klayout.rdb as rdb
def create_detailed_drc_report(violations: db.EdgePairs, rule_name: str,
rule_value: int, category: rdb.Category):
"""Create detailed DRC report with comprehensive information."""
violation_count = violations.count()
category.set_description(
f"{rule_name}: {rule_value}nm requirement, {violation_count} violations found"
)
# Statistics
distances = []
areas = []
for edge_pair in violations.each():
item = category.create_item()
# Add geometric representation
violation_poly = edge_pair.to_polygon(25) # 25nm marker
item.add_value(rdb.Value(violation_poly))
# Add measurements
distance = edge_pair.distance()
distances.append(distance)
item.add_value(rdb.Value(f"Measured: {distance}nm"))
item.add_value(rdb.Value(f"Required: {rule_value}nm"))
item.add_value(rdb.Value(f"Violation: {rule_value - distance}nm"))
# Add location information
bbox = violation_poly.bbox()
center_x = (bbox.left + bbox.right) // 2
center_y = (bbox.bottom + bbox.top) // 2
item.add_value(rdb.Value(f"Location: ({center_x}, {center_y})"))
# Add severity tag
severity = "Critical" if distance < rule_value * 0.8 else "Warning"
tag = rdb.Tags(severity)
item.add_tag(tag)
# Calculate affected area (approximate)
area = violation_poly.area()
areas.append(area)
item.add_value(rdb.Value(f"Affected area: {area} sq.nm"))
# Add summary statistics to first item
if violation_count > 0:
summary_item = category.create_item()
summary_item.add_value(rdb.Value(f"Total violations: {violation_count}"))
summary_item.add_value(rdb.Value(f"Min distance: {min(distances)}nm"))
summary_item.add_value(rdb.Value(f"Max distance: {max(distances)}nm"))
summary_item.add_value(rdb.Value(f"Avg distance: {sum(distances)/len(distances):.1f}nm"))
summary_item.add_value(rdb.Value(f"Total affected area: {sum(areas)} sq.nm"))
summary_tag = rdb.Tags("Summary")
summary_item.add_tag(summary_tag)
def generate_verification_dashboard(report_db: rdb.ReportDatabase):
"""Generate verification dashboard with key metrics."""
print("=== VERIFICATION DASHBOARD ===")
print(f"Report: {report_db.name}")
print(f"Categories: {report_db.num_categories()}")
total_items = 0
critical_items = 0
warning_items = 0
for category in report_db.each_category():
cat_items = category.num_items()
total_items += cat_items
print(f"\n{category.name}: {cat_items} items")
print(f" Description: {category.description}")
# Count by severity
cat_critical = 0
cat_warning = 0
for item in category.each_item():
for tag in item.each_tag():
if tag.tag == "Critical":
cat_critical += 1
critical_items += 1
elif tag.tag == "Warning":
cat_warning += 1
warning_items += 1
if cat_critical > 0 or cat_warning > 0:
print(f" Critical: {cat_critical}, Warnings: {cat_warning}")
print(f"\n=== SUMMARY ===")
print(f"Total violations: {total_items}")
print(f"Critical: {critical_items}")
print(f"Warnings: {warning_items}")
print(f"Success rate: {max(0, 100 - (critical_items + warning_items)/max(1, total_items)*100):.1f}%")
# Example: Enhanced DRC with detailed reporting
def run_enhanced_drc(layout_file: str):
"""Run DRC with enhanced reporting and analysis."""
layout = db.Layout()
layout.read(layout_file)
report_db = rdb.ReportDatabase("Enhanced_DRC_Report")
# Extract Metal1 layer
metal1_layer = layout.layer(db.LayerInfo(5, 0))
metal1_region = db.Region()
top_cell = layout.top_cell()
for shape in top_cell.shapes(metal1_layer).each():
metal1_region.insert(shape.polygon)
metal1_region = metal1_region.merged()
# Run checks with detailed reporting
min_width = 120
width_violations = metal1_region.width_check(min_width)
if width_violations.count() > 0:
width_category = report_db.create_category("Metal1 Width Violations")
create_detailed_drc_report(width_violations, "Minimum Width",
min_width, width_category)
min_spacing = 140
space_violations = metal1_region.space_check(min_spacing)
if space_violations.count() > 0:
space_category = report_db.create_category("Metal1 Spacing Violations")
create_detailed_drc_report(space_violations, "Minimum Spacing",
min_spacing, space_category)
# Generate dashboard
generate_verification_dashboard(report_db)
# Save enhanced report
report_db.save("enhanced_drc_report.lyrdb")
return report_db
# Run enhanced DRC
enhanced_report = run_enhanced_drc("test_design.gds")Install with Tessl CLI
npx tessl i tessl/pypi-klayout