Design Rule Check

A design rule check (DRC) measures every shape of a layout against the limits of a fabrication process, such as minimum widths, minimum gaps, enclosures and the area a design may occupy. The rules are written in a rule deck that the foundry ships with its process design kit (PDK).
drc_check runs a rule deck on a PhotonForge component. It exports the component to GDSII, runs the deck in KLayout and reads the results database back into a DRCResults object, so the whole loop stays in Python. Calibre, which runs an SVRF rule deck, is also supported and returns the same results. See Layout Verification for the full API.
This guide uses SiPhoPDK, a demonstration PDK for PhotonForge: a synthetic 220 nm silicon-on-insulator process for the O-band, with silicon nitride, germanium photodiodes, a heater and two metal layers. It ships a KLayout DRC deck of 43 rules for width, space, enclosure, placement and area, and the deck states where each value comes from. SiPhoPDK is not a foundry PDK and must not be used for a tape-out.
The guide checks a directional coupler whose gap is below the minimum silicon spacing, reads the violations, draws them on the layout, and fixes the layout until the check is clean.
Prerequisites
Section titled “Prerequisites”- Install the full KLayout application, version 0.29.12 or newer, from the KLayout download page. PhotonForge searches for it in
PATHand in the common install locations; setpf.config.klayout_executablewhen it is installed elsewhere. - Install SiPhoPDK with
pip install siphopdk-forge. The rule deck is part of the package, which this guide imports aspdk.
import photonforge as pfimport siphopdk as pdkfrom photonforge.live_viewer import LiveViewer
# Start the LiveViewer to see the layouts in a browserviewer = LiveViewer()LiveViewer started at http://127.0.0.1:35497. Call its 'stop()' method to free the port.Setting up the technology
Section titled “Setting up the technology”pdk.siphopdk() creates the SiPhoPDK technology: its layers, layer stack and port specifications. Setting it as the default makes every new component use it.
We add one annotation layer, DRC_MARKER, to draw the violations on the layout later. It is not part of the process.
# Create the SiPhoPDK technologytech = pdk.siphopdk()
# Add a layer to draw the DRC markers on, outside the layers the deck checkstech.add_layer("DRC_MARKER", pf.LayerSpec((99, 0), "DRC marker", "#ff4500cc", "xx"))
# Use the technology for every new componentpf.config.default_technology = techThe layer specifications of SiPhoPDK also carry the basic width and spacing rules of each layer. For the silicon core layer SI, both are 150 nm:
# Minimum width and spacing stored on the silicon layer (μm)print("SI minimum width:", tech.layers["SI"].min_width)print("SI minimum spacing:", tech.layers["SI"].min_spacing)SI minimum width: 0.15SI minimum spacing: 0.15Creating a layout with violations
Section titled “Creating a layout with violations”The SiPhoPDK directional coupler, pdk.component.directional_coupler, takes coupling_distance, the distance between the centers of its two waveguides in the coupling section. The gap between the silicon edges is that distance minus the core width, 400 nm for the strip waveguide SI_STRIP_O.
We ask for a gap of 120 nm, below the 150 nm minimum spacing, so the check has something to find.
# Read the core width of the strip waveguide from its port specificationcore_width, _ = tech.ports["SI_STRIP_O"].path_profile_for("SI")
# Gap between the silicon edges in the coupling section (μm)gap = 0.12
# Build the coupler with a center-to-center distance of core width plus gapcoupler = pdk.component.directional_coupler(coupling_distance=core_width + gap)viewer(coupler)Running DRC
Section titled “Running DRC”drc_check takes the component and the rule deck. SiPhoPDK gives the path of its deck as pdk.DRC_RULES. The deck reads the layout with source($gdsfile) and writes its report with report(..., $resultsfile); drc_check sets both variables.
The optional results_file argument keeps KLayout’s own results database, here drc_results.lyrdb. It can be opened in KLayout’s marker browser or loaded again later.
# Export the coupler, run the SiPhoPDK deck in KLayout and load the markersresults = pf.drc_check(coupler, pdk.DRC_RULES, results_file="drc_results.lyrdb")KLayout version: 0 s -
KLayout DRC: 0 s -
KLayout DRC: 1 s \
KLayout DRC: 1 s |
KLayout DRC: 1 s /
KLayout DRC: 2 s -
KLayout DRC: 2 s \
KLayout DRC: 2 s |
KLayout DRC: 2 s /
KLayout DRC: 3 s -
Reading results: 0 s -
Reading results: 1 s \
Reading results: 1 s |
Reading results: 1 s /
Reading results: 2 s -
Reading results: 2 s \
Reading results: 2 s |
Reading results: 2 s /
KLayout DRC finished in 6 sSiPhoPDK also provides pdk.drc_check(component), which is the same call with the deck filled in. The rest of this guide uses it.
Inspecting results
Section titled “Inspecting results”The output of drc_check is a DRCResults object holding the markers of every rule in the deck. results.is_clean is the verdict:
# Check whether any rule reported a violationprint("DRC clean:", results.is_clean)DRC clean: Falseprint(results) lists every rule of the deck with its count, which is long for a deck of 43 rules. results.violation_counts holds the same numbers as a dictionary, from which we keep the rules that failed:
# Keep the rules with at least one violationfailed = {rule: count for rule, count in results.violation_counts.items() if count > 0}print(failed){'FP.1': 2, 'SI.S.1': 13}Indexing the results with a rule name returns that rule’s violations, with the description written in the deck, the number of markers and the markers themselves:
# Print the description of each failed rule, as written in the deckfor rule in failed: print(f"{rule}: {results[rule].description}")FP.1: Geometry outside FLOORPLANSI.S.1: SI space < 150 nmWhat these violations mean
Section titled “What these violations mean”- SI.S.1 reports silicon shapes closer than 150 nm. The 120 nm gap of the coupling section is the main marker; the deck also marks the start of each S-bend, where the arms are still closer than the rule.
- FP.1 reports geometry outside the
FLOORPLANlayer. The SiPhoPDK deck requires every drawn shape to lie inside the floor plan of the die, and the coupler has none yet.
Where the markers are
Section titled “Where the markers are”KLayout checks the layout hierarchically, so each marker is reported once, in the cell where the violation occurs and in the coordinates of that cell. results.violated_cells lists the cells with at least one marker. results.in_top_cell(marker) moves a marker into the coordinates of the top cell. Here the coupler is the top cell, so both are the same.
# Cells with at least one violationprint(results.violated_cells)
# First 'SI.S.1' marker, a polygon in the coordinates of its cellprint(results["SI.S.1"].markers[0])('dc_lc15p0_g0p52',)PolygonMarker(cell='dc_lc15p0_g0p52', hull=((14.493, 4.191), (14.558, 4.326), (14.631, 4.325), (14.558, 4.194)), holes=())Drawing the markers on the layout
Section titled “Drawing the markers on the layout”Each SI.S.1 marker is a polygon. Adding the polygons on the DRC_MARKER layer, in a component that also holds the coupler, shows where the rule failed:
# Put the coupler and the markers together in one componentmarked = pf.Component("COUPLER_DRC")marked.add_reference(coupler)
# Draw every 'SI.S.1' marker, moved into the coordinates of the top cellfor marker in results["SI.S.1"].markers: marker = results.in_top_cell(marker) marked.add("DRC_MARKER", pf.Polygon(marker.hull))
viewer(marked)A results database written earlier can be read back with load_drc_results, without running the check again:
# Read the saved results databasesaved = pf.load_drc_results("drc_results.lyrdb", show_progress=False)print({rule: count for rule, count in saved.violation_counts.items() if count > 0}){'FP.1': 2, 'SI.S.1': 13}Fixing the violations
Section titled “Fixing the violations”We fix the two rules in turn:
- SI.S.1: widen the gap to 200 nm. The coupler is a parametric component, so
updaterebuilds it with the new distance. - FP.1: place the coupler on a die that has a floor plan around it.
# Widen the gap to 200 nm, which rebuilds the couplercoupler.update(coupling_distance=core_width + 0.2)
# Check the coupler again with the SiPhoPDK deckresults = pdk.drc_check(coupler, show_progress=False)print({rule: count for rule, count in results.violation_counts.items() if count > 0}){'FP.1': 2}Only FP.1 is left. A die is a cell that holds the design and the FLOORPLAN rectangle that encloses it. envelope gives a box around the coupler, enlarged by a margin:
# Margin between the coupler and the edge of the floor plan (μm)margin = 10.0
# Place the coupler on a die with a floor plan around itdie = pf.Component("COUPLER_DIE")die.add_reference(coupler)die.add("FLOORPLAN", pf.envelope(coupler, offset=margin, use_box=True))viewer(die)# Check the die with the SiPhoPDK deckresults = pdk.drc_check(die, show_progress=False)print("DRC clean:", results.is_clean)DRC clean: TrueThe die passes every rule of the deck.
Other ways to run DRC
Section titled “Other ways to run DRC”- Another rule deck.
pf.drc_check(component, "deck.drc")runs any KLayout deck that reads$gdsfileand writes$resultsfile. A deck that uses other variable names is driven withvariable_names, for examplevariable_names={"gdsfile": "input", "resultsfile": "report"}. Withengine="calibre", the deck is an SVRF rule deck run by Calibre. - Waivers. Geometry inside the SiPhoPDK layer
EXCL_DRC(95, 0) is waived by the deck. Other PDKs have their own waiver layers. - The LiveViewer. The LiveViewer has a DRC section that runs a rule deck on the component on screen, with KLayout or Calibre, and draws the markers on the layout. It also opens an existing report, such as
drc_results.lyrdbabove. - Rules in the technology. The
min_widthandmin_spacingof a LayerSpec are used as defaults by route_electrical, so that metal routes are drawn within the rules in the first place.
For connectivity checks between the layout and the schematic, see the Layout vs. Schematic guide.