Skip to content

Checks

checks

Standalone connectivity / overlap checks producing klayout ReportDatabases.

Each check answers a single conceptual question and writes its findings to an rdb.ReportDatabase. They are composed by ProtoTKCell.connectivity_check for the all-in-one pass-or-fail verification, but can be called individually for narrower checks.

dangling_ports_check

dangling_ports_check(
    cell: ProtoTKCell[Any],
    *,
    port_types: list[str] | None = None,
    layers: list[int] | None = None,
    db: ReportDatabase | None = None,
    recursive: bool = True,
    equivalent_ports: dict[str, list[list[str]]]
    | None = None,
) -> rdb.ReportDatabase

Report dangling instance ports — ports with no matching counterpart.

A dangling port is an instance port at a coord where no other instance port and no cell port appears. Emitted under the DanglingPort category.

Parameters:

Name Type Description Default
cell ProtoTKCell[Any]

Cell to verify.

required
port_types list[str] | None

If given, only ports whose port_type is in this list are considered.

None
layers list[int] | None

If given, only ports on these layers are considered.

None
db ReportDatabase | None

Reuse an existing report database. A new one is created otherwise.

None
recursive bool

Run the same check on every called child cell as well.

True
equivalent_ports dict[str, list[list[str]]] | None

Per-cell groups of electrically-equivalent port names (same shape as Netlist.lvs_equivalent's argument). When provided, an instance port is not reported as dangling if any other port in its group on the same instance is connected. Typical use is multi-contact pads where e1, e2, e3, e4 and pad are the same electrical node.

None
Source code in kfactory/checks.py
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
def dangling_ports_check(
    cell: ProtoTKCell[Any],
    *,
    port_types: list[str] | None = None,
    layers: list[int] | None = None,
    db: rdb.ReportDatabase | None = None,
    recursive: bool = True,
    equivalent_ports: dict[str, list[list[str]]] | None = None,
) -> rdb.ReportDatabase:
    """Report dangling instance ports — ports with no matching counterpart.

    A dangling port is an instance port at a coord where no other instance
    port and no cell port appears. Emitted under the `DanglingPort` category.

    Args:
        cell: Cell to verify.
        port_types: If given, only ports whose `port_type` is in this list are
            considered.
        layers: If given, only ports on these layers are considered.
        db: Reuse an existing report database. A new one is created otherwise.
        recursive: Run the same check on every called child cell as well.
        equivalent_ports: Per-cell groups of electrically-equivalent port
            names (same shape as `Netlist.lvs_equivalent`'s argument).
            When provided, an instance port is **not** reported as dangling if
            any other port in its group on the same instance is connected.
            Typical use is multi-contact pads where ``e1``, ``e2``, ``e3``,
            ``e4`` and ``pad`` are the same electrical node.
    """
    port_types = port_types or []
    layers = layers or []
    db_ = _ensure_db(cell, db, "Dangling Ports Check")
    if recursive:
        _recurse(
            cell,
            db_,
            dangling_ports_check,
            port_types=port_types,
            layers=layers,
            equivalent_ports=equivalent_ports,
        )

    db_cell = db_.create_cell(cell.name)
    layer_cat = _layer_cat_factory(db_, cell)
    cell_ports = _collect_cell_ports(cell, port_types, layers)
    inst_ports = _collect_inst_ports(cell, port_types, layers)

    for layer, coord_map in inst_ports.items():
        lc = layer_cat(layer)
        for coord, ports in coord_map.items():
            if len(ports) != 1:
                continue
            if layer in cell_ports and coord in cell_ports[layer]:
                continue
            port, port_cell, inst_name, inst_obj = ports[0]
            if equivalent_ports:
                group = _resolve_equivalent_group(
                    equivalent_ports, port_cell, port.name or ""
                )
                if (
                    group
                    and len(group) > 1
                    and _siblings_connected(
                        inst_obj,
                        cell.kcl,
                        port.name or "",
                        coord,
                        group,
                        cell_ports,
                        inst_ports,
                    )
                ):
                    continue
            subc = _get_or_create_subcategory(db_, lc, "DanglingPort")
            it = db_.create_item(db_cell, subc)
            port_name = port.name or str(port)
            if inst_name:
                it.add_value(
                    f"Port Name: {inst_name}.{port_name} (cell: {port_cell.name})"
                )
            else:
                it.add_value(f"Port Name: {port_cell.name}.{port_name}")
            if port._base.trans:
                it.add_value(
                    cell.kcl.to_um(
                        port_polygon(port.width).transformed(port._base.trans)
                    )
                )
            else:
                it.add_value(
                    cell.kcl.to_um(port_polygon(port.width)).transformed(
                        port.dcplx_trans
                    )
                )

    return db_

instance_overlap_check

instance_overlap_check(
    cell: ProtoTKCell[Any],
    *,
    layers: list[int] | None = None,
    db: ReportDatabase | None = None,
    recursive: bool = True,
) -> rdb.ReportDatabase

Report instance shapes overlapping shapes of other instances.

For each candidate layer, polygons of one instance that overlap polygons of another instance are reported under InstanceOverlap.

Parameters:

Name Type Description Default
cell ProtoTKCell[Any]

Cell to verify.

required
layers list[int] | None

If given, only check these layers.

None
db ReportDatabase | None

Reuse an existing report database. A new one is created otherwise.

None
recursive bool

Run the same check on every called child cell as well.

True
Source code in kfactory/checks.py
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
def instance_overlap_check(
    cell: ProtoTKCell[Any],
    *,
    layers: list[int] | None = None,
    db: rdb.ReportDatabase | None = None,
    recursive: bool = True,
) -> rdb.ReportDatabase:
    """Report instance shapes overlapping shapes of other instances.

    For each candidate layer, polygons of one instance that overlap polygons
    of another instance are reported under `InstanceOverlap`.

    Args:
        cell: Cell to verify.
        layers: If given, only check these layers.
        db: Reuse an existing report database. A new one is created otherwise.
        recursive: Run the same check on every called child cell as well.
    """
    layers = layers or []
    db_ = _ensure_db(cell, db, "Instance Overlap Check")
    if recursive:
        _recurse(cell, db_, instance_overlap_check, layers=layers)

    db_cell = db_.create_cell(cell.name)
    layer_cat = _layer_cat_factory(db_, cell)

    for layer in _iter_check_layers(cell, layers):
        error_region = kdb.Region()
        inst_regions: dict[int, kdb.Region] = {}
        inst_region = kdb.Region()
        for i, inst in enumerate(cell.insts):
            inst_region_ = kdb.Region(inst.ibbox(layer))
            inst_shapes: kdb.Region | None = None
            if not (inst_region & inst_region_).is_empty():
                if inst_shapes is None:
                    inst_shapes = kdb.Region()
                    shape_it = cell.begin_shapes_rec_overlapping(
                        layer, inst.bbox(layer)
                    )
                    shape_it.select_cells([inst.cell.cell_index()])
                    shape_it.min_depth = 1
                    shape_it.shape_flags = kdb.Shapes.SRegions
                    for _it in shape_it.each():
                        if _it.path()[0].inst() == inst.instance:
                            inst_shapes.insert(
                                _it.shape().polygon.transformed(_it.trans())
                            )
                for j, _reg in inst_regions.items():
                    if _reg & inst_region_:
                        reg_ = kdb.Region()
                        shape_it = cell.begin_shapes_rec_touching(
                            layer, (_reg & inst_region_).bbox()
                        )
                        shape_it.select_cells([cell.insts[j].cell.cell_index()])
                        shape_it.min_depth = 1
                        shape_it.shape_flags = kdb.Shapes.SRegions
                        for _it in shape_it.each():
                            if _it.path()[0].inst() == cell.insts[j].instance:
                                reg_.insert(
                                    _it.shape().polygon.transformed(_it.trans())
                                )
                        error_region.insert(reg_ & inst_shapes)
            inst_region += inst_region_
            inst_regions[i] = inst_region_

        if not error_region.is_empty():
            sc = _get_or_create_subcategory(db_, layer_cat(layer), "InstanceOverlap")
            for poly in error_region.merge().each():
                it = db_.create_item(db_cell, sc)
                it.add_value(
                    "Instance shapes overlapping with shapes of other instances"
                )
                it.add_value(cell.kcl.to_um(poly.downcast()))

    return db_

port_mismatch_check

port_mismatch_check(
    cell: ProtoTKCell[Any],
    *,
    port_types: list[str] | None = None,
    layers: list[int] | None = None,
    db: ReportDatabase | None = None,
    recursive: bool = True,
    add_cell_ports: bool = False,
    check_width: bool = True,
    check_angle: bool = True,
    check_type: bool = True,
    check_port_overlap: bool = True,
    check_missing_physical_shape: bool = True,
    check_partial_physical_shape: bool = True,
    width_mismatch_ignore_layers: list[
        int | LayerInfo | str
    ]
    | None = None,
) -> rdb.ReportDatabase

Report port-pair / port-shape mismatches as one logical check.

Aggregates width / angle / port-type mismatches between coincident ports,

2-port overlaps, and missing/partial physical layer shapes under the port region. Individual sub-rules can be toggled, but this is one connectivity check producing one pass/fail outcome for the cell.

Parameters:

Name Type Description Default
cell ProtoTKCell[Any]

Cell to verify.

required
port_types list[str] | None

If given, only ports whose port_type is in this list are considered.

None
layers list[int] | None

If given, only ports on these layers are considered.

None
db ReportDatabase | None

Reuse an existing report database. A new one is created otherwise.

None
recursive bool

Run the same check on every called child cell as well.

True
add_cell_ports bool

Add a CellPorts category listing the cell's own (filtered) ports for visual inspection in the report.

False
check_width bool

Emit WidthMismatch items.

True
check_angle bool

Emit AngleMismatch items.

True
check_type bool

Emit TypeMismatch items.

True
check_port_overlap bool

Emit PortOverlap items when 2+ instance ports share a coord (and either differ from a cell port or pile up >2).

True
check_missing_physical_shape bool

Emit MissingPhysicalShape items.

True
check_partial_physical_shape bool

Emit PartialPhysicalShape items.

True
width_mismatch_ignore_layers list[int | LayerInfo | str] | None

Layers (specified by int index, kdb.LayerInfo or name) on which WidthMismatch items should be suppressed. Useful for metal stacks where mismatched widths at a via stack are intentional.

None
Source code in kfactory/checks.py
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
def port_mismatch_check(
    cell: ProtoTKCell[Any],
    *,
    port_types: list[str] | None = None,
    layers: list[int] | None = None,
    db: rdb.ReportDatabase | None = None,
    recursive: bool = True,
    add_cell_ports: bool = False,
    check_width: bool = True,
    check_angle: bool = True,
    check_type: bool = True,
    check_port_overlap: bool = True,
    check_missing_physical_shape: bool = True,
    check_partial_physical_shape: bool = True,
    width_mismatch_ignore_layers: list[int | kdb.LayerInfo | str] | None = None,
) -> rdb.ReportDatabase:
    """Report port-pair / port-shape mismatches as one logical check.

    Aggregates width / angle / port-type mismatches between coincident ports,
    >2-port overlaps, and missing/partial physical layer shapes under the port
    region. Individual sub-rules can be toggled, but this is one connectivity
    check producing one pass/fail outcome for the cell.

    Args:
        cell: Cell to verify.
        port_types: If given, only ports whose `port_type` is in this list are
            considered.
        layers: If given, only ports on these layers are considered.
        db: Reuse an existing report database. A new one is created otherwise.
        recursive: Run the same check on every called child cell as well.
        add_cell_ports: Add a `CellPorts` category listing the cell's own
            (filtered) ports for visual inspection in the report.
        check_width: Emit `WidthMismatch` items.
        check_angle: Emit `AngleMismatch` items.
        check_type: Emit `TypeMismatch` items.
        check_port_overlap: Emit `PortOverlap` items when 2+ instance ports
            share a coord (and either differ from a cell port or pile up >2).
        check_missing_physical_shape: Emit `MissingPhysicalShape` items.
        check_partial_physical_shape: Emit `PartialPhysicalShape` items.
        width_mismatch_ignore_layers: Layers (specified by int index,
            ``kdb.LayerInfo`` or name) on which ``WidthMismatch`` items should
            be suppressed. Useful for metal stacks where mismatched widths at
            a via stack are intentional.
    """
    port_types = port_types or []
    layers = layers or []
    db_ = _ensure_db(cell, db, "Port Mismatch Check")
    ignore_width_layers = _resolve_layer_indexes(cell.kcl, width_mismatch_ignore_layers)
    if recursive:
        _recurse(
            cell,
            db_,
            port_mismatch_check,
            port_types=port_types,
            layers=layers,
            add_cell_ports=add_cell_ports,
            check_width=check_width,
            check_angle=check_angle,
            check_type=check_type,
            check_port_overlap=check_port_overlap,
            check_missing_physical_shape=check_missing_physical_shape,
            check_partial_physical_shape=check_partial_physical_shape,
            width_mismatch_ignore_layers=width_mismatch_ignore_layers,
        )

    db_cell = db_.create_cell(cell.name)
    layer_cat = _layer_cat_factory(db_, cell)
    cell_ports = _collect_cell_ports(cell, port_types, layers)

    # Cell-port physical-shape pass + optional CellPorts annotation.
    for by_coord in cell_ports.values():
        for cell_port_list in by_coord.values():
            for port in cell_port_list:
                if add_cell_ports:
                    _emit_cell_port(cell, db_, db_cell, layer_cat, port)
                if not (check_missing_physical_shape or check_partial_physical_shape):
                    continue
                ok, partial = _check_cell_port_physical_shape(cell, port)
                if ok:
                    continue
                if partial is not None and check_partial_physical_shape:
                    _emit_physical_shape_issue(
                        cell, db_, db_cell, layer_cat, port, partial=partial
                    )
                elif partial is None and check_missing_physical_shape:
                    _emit_physical_shape_issue(
                        cell, db_, db_cell, layer_cat, port, partial=None
                    )

    inst_ports = _collect_inst_ports(cell, port_types, layers)

    def emit_mismatch(
        result: int,
        lc: rdb.RdbCategory,
        p_a: Port,
        p_b: ProtoPort[Any],
        c_a: ProtoTKCell[Any],
        c_b: ProtoTKCell[Any],
        *,
        expect_opposite: bool,
        inst_name1: str | None,
        inst_name2: str | None = None,
    ) -> None:
        angle_ok = bool(
            result & (PortCheck.opposite if expect_opposite else PortCheck.same)
        )
        if (
            check_width
            and not result & PortCheck.width
            and layer not in ignore_width_layers
        ):
            subc = _get_or_create_subcategory(db_, lc, "WidthMismatch")
            create_port_error(
                p_a,
                p_b,
                c_a,
                c_b,
                db_,
                db_cell,
                subc,
                cell.kcl.dbu,
                inst_name1=inst_name1,
                inst_name2=inst_name2,
            )
        if check_angle and not angle_ok:
            subc = _get_or_create_subcategory(db_, lc, "AngleMismatch")
            create_port_error(
                p_a,
                p_b,
                c_a,
                c_b,
                db_,
                db_cell,
                subc,
                cell.kcl.dbu,
                inst_name1=inst_name1,
                inst_name2=inst_name2,
            )
        if check_type and not result & PortCheck.port_type:
            subc = _get_or_create_subcategory(db_, lc, "TypeMismatch")
            create_port_error(
                p_a,
                p_b,
                c_a,
                c_b,
                db_,
                db_cell,
                subc,
                cell.kcl.dbu,
                inst_name1=inst_name1,
                inst_name2=inst_name2,
            )

    for layer, coord_map in inst_ports.items():
        lc = layer_cat(layer)
        for coord, ports in coord_map.items():
            n = len(ports)
            if n == 1:
                if layer in cell_ports and coord in cell_ports[layer]:
                    cell_port = cell_ports[layer][coord][0]
                    result = check_connection(cell_port, ports[0][0])
                    emit_mismatch(
                        result,
                        lc,
                        ports[0][0],
                        cell_port,
                        ports[0][1],
                        cell,
                        expect_opposite=False,
                        inst_name1=ports[0][2],
                    )
                # Dangling case is handled by dangling_ports_check.
            elif n == 2:
                result = check_connection(ports[0][0], ports[1][0])
                emit_mismatch(
                    result,
                    lc,
                    ports[0][0],
                    ports[1][0],
                    ports[0][1],
                    ports[1][1],
                    expect_opposite=True,
                    inst_name1=ports[0][2],
                    inst_name2=ports[1][2],
                )
                if (
                    check_port_overlap
                    and layer in cell_ports
                    and coord in cell_ports[layer]
                ):
                    _emit_port_overlap(
                        cell,
                        db_,
                        db_cell,
                        lc,
                        ports,
                        cell_port_at_coord=cell_ports[layer][coord][0],
                    )
            elif n > 2:
                if check_port_overlap:
                    _emit_port_overlap(
                        cell, db_, db_cell, lc, ports, cell_port_at_coord=None
                    )
            else:
                raise ValueError(f"Unexpected number of ports: {n}")

    return db_

shape_instance_overlap_check

shape_instance_overlap_check(
    cell: ProtoTKCell[Any],
    *,
    layers: list[int] | None = None,
    db: ReportDatabase | None = None,
    recursive: bool = True,
) -> rdb.ReportDatabase

Report top-level cell shapes overlapping with shapes of instances.

Polygons drawn directly into the cell that touch polygons from any of its instances are reported under CellShapeInstanceOverlap.

Parameters:

Name Type Description Default
cell ProtoTKCell[Any]

Cell to verify.

required
layers list[int] | None

If given, only check these layers.

None
db ReportDatabase | None

Reuse an existing report database. A new one is created otherwise.

None
recursive bool

Run the same check on every called child cell as well.

True
Source code in kfactory/checks.py
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
def shape_instance_overlap_check(
    cell: ProtoTKCell[Any],
    *,
    layers: list[int] | None = None,
    db: rdb.ReportDatabase | None = None,
    recursive: bool = True,
) -> rdb.ReportDatabase:
    """Report top-level cell shapes overlapping with shapes of instances.

    Polygons drawn directly into the cell that touch polygons from any of its
    instances are reported under `CellShapeInstanceOverlap`.

    Args:
        cell: Cell to verify.
        layers: If given, only check these layers.
        db: Reuse an existing report database. A new one is created otherwise.
        recursive: Run the same check on every called child cell as well.
    """
    layers = layers or []
    db_ = _ensure_db(cell, db, "Shape/Instance Overlap Check")
    if recursive:
        _recurse(cell, db_, shape_instance_overlap_check, layers=layers)

    db_cell = db_.create_cell(cell.name)
    layer_cat = _layer_cat_factory(db_, cell)

    for layer in _iter_check_layers(cell, layers):
        error_region = kdb.Region()
        reg = kdb.Region(cell.shapes(layer))
        for inst in cell.insts:
            inst_region_ = kdb.Region(inst.ibbox(layer))
            if (inst_region_ & reg).is_empty():
                continue
            rec_it = cell.begin_shapes_rec_touching(layer, (inst_region_ & reg).bbox())
            rec_it.min_depth = 1
            error_region += kdb.Region(rec_it) & reg

        if not error_region.is_empty():
            sc = _get_or_create_subcategory(
                db_, layer_cat(layer), "CellShapeInstanceOverlap"
            )
            for poly in error_region.merge().each():
                it = db_.create_item(db_cell, sc)
                it.add_value("Shapes overlapping with shapes of instances")
                it.add_value(cell.kcl.to_um(poly.downcast()))

    return db_