CtrlK
BlogDocsLog inGet started
Tessl Logo

tessl/pypi-klayout

KLayout is a high performance layout viewer and editor that supports GDS and OASIS files and more formats

Pending
Overview
Eval results
Files

verification.mddocs/

Verification and Results Management

Design rule checking (DRC), layout versus schematic (LVS) capabilities, and results database management for storing and analyzing verification reports in IC layout workflows.

Capabilities

Results Database Management

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."""

Design Rule Checking Operations

# 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
        """

Netlist and Circuit Analysis

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."""

Usage Examples

Basic DRC Rule Checking

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")

Comprehensive Multi-Layer DRC

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")

Netlist Extraction and LVS

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")

Advanced Verification Reporting

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

docs

database-operations.md

file-io.md

hierarchical-design.md

index.md

layout-viewer.md

shape-operations.md

transformations.md

verification.md

tile.json