This repository provides a script that converts a standard-cell GDS into a LEF file, built on KLayout, with no commercial EDA tool in the loop. We provide ready-to-run config files for five academic PDKs: NanGate45, ASAP7, IHP SG13G2, SO3, and GT2N, each with that PDK's details. To add a new PDK, refer to the example config files and the guide below to write a new config.
Left: the GDS file. Right: the LEF file. The images below show a representative input and output pair. The script tracks the connectivity of metal (and active) segments and classifies each segment into PIN and OBS.
KLayout with the pya module (tested on 0.30.5) is required.
klayout -b -r run.py -rd config=examples/ng45/input/ng45.json -rd gds=examples/ng45/input/ng45.gds -rd out=examples/ng45/output/ng45.lef-rd key |
meaning |
|---|---|
config |
required, the PDK json |
gds |
one library GDS |
gds_dir |
a folder of *.gds instead of one gds file |
out |
single output LEF (one named cell) instead of out_dir |
out_dir |
one LEF file per cell into this dir, instead of a merged library |
cell |
only this cell (default: every top cell) |
site |
override the SITE name |
A config is one examples/<pdk>/input/<pdk>.json with three sub blocks: lef, layers, and pins; antenna and cell_class
are optional. To add a new PDK, fill in values from the
PDK's layer map (.layermap, .lyp, or the techfile/LVS deck).
Top level:
boundary_layer:[layer, datatype]of the cell-outline shape, whose bounding box sets the cell origin and SIZE.power_nets: the power and ground pins, each{"name": ..., "use": "POWER"|"GROUND"}.antenna(optional): writes per-pin antenna areas:ANTENNAGATEAREA,ANTENNADIFFAREA,ANTENNAPARTIALMETALAREA. Required:gate_layer,active_layer,diff_layer. Optional:metal_thickness({layer: thickness_um}) addsANTENNAPARTIALMETALSIDEAREA;modeladdsANTENNAMODEL;partial_metal(falseskips the partial-metal and partial-cut lines);car({"metal_stack": [...], "cut_stack": [...]}, bottom-to-top) addsANTENNAMAXAREACAR,ANTENNAMAXCUTCAR,ANTENNAPARTIALCUTAREA. These areas may be inaccurate and should be checked against the PDK's antenna rules.cell_class(optional):[{"match": regex, "class": lef_class_string}]. The first rule whosematchhits the cell name sets the cell'sCLASS; no match staysCORE. Give the full LEF class string, e.g."CORE WELLTAP"(not"WELLTAP"),CORE SPACER,CORE TIEHIGH,CORE TIELOW, orCORE ANTENNACELL.cell_classmay instead be a single string, used as theCLASSof every cell.
lef:
header_site: the LEF library header site,{"name", "size": [w, h], "class", "symmetry"}. Theheader_sitename is written on each MACRO.
layers:
layer_table:name: [layer, datatype]per GDS layer. A routing layer's name is the LEF layer name (e.g.metal1orM1).derived_layers: new layers computed by boolean geometry operations onlayer_tableshapes.&= intersection (area where both layers overlap),-= difference (the part of the left layer not covered by the right layer),|= union,^= symmetric difference. e.g.{"name":"sd","expr":"active - gate"}buildssd(the source/drain diffusion) as theactivearea outside thegate.connect_rules:[a, b]pairs corresponding to each cross-layer connect rule in the LVS deck. Layersaandbare connected where the two layers overlap; the script uses the pairs to track metal-segment connectivity. e.g.["contact","M1"],["M1","V1"].routing_layers: the layers that appear in the LEF as PIN or OBS, top metal first.
pins:
direction: every pin must be classified by two regexes (output_regex,input_regex). a name matching neither is an unclassified-pin error naming the cell and pin. A pin whoseDIRECTIONdiffers by cell (e.g., NanGate45Sis a sum output onFA_X1but a mux select input onMUX2) is set per cell inoverridesinstead (see Per-cell overrides).pin_use: the LEFUSEof each signal pin. Defaults toSIGNAL; list exceptions as{USE: [names]}, e.g.{"CLOCK": ["CLK"]}. Note that power/groundUSEis set inpower_nets, not here.text_layers: the metal layers that can hold a pin's name label. A pin appears only if the pin's net has a label on one of these layers.
"overrides": {
"FA_X1": {"pins": {"pin_directions": {"S": "OUTPUT"}}},
"MUX2_X1": {"pins": {"pin_directions": {"S": "INPUT"}}}
}Here, each entry can only override the keys that entry lists for that cell. (The
example above uses only pin_directions.)
examples/ng45/input/ng45.json:
Each PDK's input and output live under the PDK's own examples/<pdk>/ directory:
| path | description |
|---|---|
run.py |
the script that converts a GDS into a LEF |
examples/<pdk>/input/<pdk>.json |
PDK config + inline overrides |
examples/<pdk>/input/<pdk>.gds |
library GDS (input) |
examples/<pdk>/output/<pdk>.lef |
generated LEF (output) |
Tool: KLayout (layout engine via the pya API).
PDKs:
- NanGate45 (FreePDK45): baichen318/FreePDK45
- ASAP7: The-OpenROAD-Project/asap7
- IHP SG13G2: IHP-GmbH/IHP-Open-PDK
- SO3: ckchengucsd/SO3-Cell
- GT2N: azadnaeemi/GT2N
BSD 3-Clause. See LICENSE.





{ "boundary_layer": [235, 0], "power_nets": [ {"name": "VDD", "use": "POWER"}, {"name": "VSS", "use": "GROUND"} ], "lef": { "header_site": {"name": "FreePDK45_38x28_10R_NP_162NW_34O", "size": [0.1900, 1.4000], "class": "CORE", "symmetry": "Y"} }, "layers": { "layer_table": { "active": [1, 0], "gate": [9, 0], "contact": [10, 0], "metal1": [11, 0] }, "derived_layers": [ {"name": "sd", "expr": "active - gate"} ], "connect_rules": [ ["contact", "gate"], ["contact", "sd"], ["contact", "metal1"] ], "routing_layers": ["metal1"] }, "pins": { "direction": {"output_regex": "CO|GCK|Q|QN|Z|ZN", "input_regex": "[ABCDEGI][0-9]*|CI|CK|EN|GN|OE|RN|SE|SI|SN"}, "pin_use": {"CLOCK": ["CK", "GCK"]}, "text_layers": {"metal1": [11, 0]} } }