From c117238fa75d05fef3b9f03ccaf60ecb329e3bd4 Mon Sep 17 00:00:00 2001 From: mrg Date: Tue, 3 Aug 2021 14:41:09 -0700 Subject: [PATCH] Initial klayout DRC/LVS options --- compiler/verify/__init__.py | 13 +- technology/freepdk45/tech/freepdk45.lydrc | 430 ++++++++++++++++++ technology/freepdk45/tech/freepdk45.lylvs | 270 +++++++++++ .../{tf/FreePDK45.lyp => tech/freepdk45.lyp} | 0 .../{tf/FreePDK45.lyt => tech/freepdk45.lyt} | 0 technology/freepdk45/tech/tech.py | 6 +- technology/freepdk45/tf/glade_freepdk45.py | 7 - 7 files changed, 712 insertions(+), 14 deletions(-) create mode 100644 technology/freepdk45/tech/freepdk45.lydrc create mode 100644 technology/freepdk45/tech/freepdk45.lylvs rename technology/freepdk45/{tf/FreePDK45.lyp => tech/freepdk45.lyp} (100%) rename technology/freepdk45/{tf/FreePDK45.lyt => tech/freepdk45.lyt} (100%) delete mode 100644 technology/freepdk45/tf/glade_freepdk45.py diff --git a/compiler/verify/__init__.py b/compiler/verify/__init__.py index 326771f8..e9769975 100644 --- a/compiler/verify/__init__.py +++ b/compiler/verify/__init__.py @@ -32,14 +32,16 @@ if not OPTS.check_lvsdrc: # OPTS.magic_exe = None else: debug.info(1, "Finding DRC/LVS/PEX tools.") - OPTS.drc_exe = get_tool("DRC", ["calibre", "assura", "magic"], drc_name) - OPTS.lvs_exe = get_tool("LVS", ["calibre", "assura", "netgen"], lvs_name) - OPTS.pex_exe = get_tool("PEX", ["calibre", "magic"], pex_name) + OPTS.drc_exe = get_tool("DRC", ["klayout", "calibre", "assura", "magic"], drc_name) + OPTS.lvs_exe = get_tool("LVS", ["klayout", "calibre", "assura", "netgen"], lvs_name) + OPTS.pex_exe = get_tool("PEX", ["klayout", "calibre", "magic"], pex_name) # if OPTS.tech_name == "sky130": # OPTS.magic_exe = get_tool("GDS", ["magic"]) if not OPTS.drc_exe: from .none import run_drc, print_drc_stats, write_drc_script +elif "klayout"==OPTS.drc_exe[0]: + from .klayout import run_drc, print_drc_stats, write_drc_script elif "calibre"==OPTS.drc_exe[0]: from .calibre import run_drc, print_drc_stats, write_drc_script elif "assura"==OPTS.drc_exe[0]: @@ -52,6 +54,8 @@ else: if not OPTS.lvs_exe: from .none import run_lvs, print_lvs_stats, write_lvs_script +elif "klayout"==OPTS.lvs_exe[0]: + from .klayout import run_lvs, print_lvs_stats, write_lvs_script elif "calibre"==OPTS.lvs_exe[0]: from .calibre import run_lvs, print_lvs_stats, write_lvs_script elif "assura"==OPTS.lvs_exe[0]: @@ -65,6 +69,8 @@ else: if not OPTS.pex_exe: from .none import run_pex, print_pex_stats +elif "klayout"==OPTS.pex_exe[0]: + from .klayout import run_pex, print_pex_stats elif "calibre"==OPTS.pex_exe[0]: from .calibre import run_pex, print_pex_stats elif "magic"==OPTS.pex_exe[0]: @@ -78,4 +84,3 @@ else: # from .magic import filter_gds # else: # debug.warning("Did not find Magic.") - diff --git a/technology/freepdk45/tech/freepdk45.lydrc b/technology/freepdk45/tech/freepdk45.lydrc new file mode 100644 index 00000000..1d719e33 --- /dev/null +++ b/technology/freepdk45/tech/freepdk45.lydrc @@ -0,0 +1,430 @@ + + + + + drc + + + + false + false + + true + drc_scripts + tools_menu.drc.end + dsl + drc-dsl-xml + +# +# DRC for FreePDK45 according to : +# https://www.eda.ncsu.edu/wiki/FreePDK45:RuleDevel +# https://www.eda.ncsu.edu/wiki/FreePDK45:Contents +# +########################################################################################## +tstart = Time.now + +# optionnal for a batch launch : klayout -b r drc_FreePDK45.lydrc -rd input=my_layout.gds -rd topcell=your_topcell -rd output=drc_FreePDK45.lyrdb +if $input + if $topcell + source($input,$topcell) + else + source($input) + end +end + +if $output + report("FreePDK45 DRC runset", $output) +else + report("FreePDK45 DRC runset", "FreePDK45_DRC.lyrdb") +end + +# DRC test to run or not +############### +OFFGRID = true +ANTENNA = true +DRC = true + +# KLAYOUT setup +######################## +# Use a tile size of 1mm +# tiles(100.um) +# Use a tile border of 10 micron: +# tile_borders(1.um) +# no_borders + +# Hierachical +deep + +# Use 4 CPU cores +threads(4) +verbose(true) + +# layers definitions +######################## +active = polygons(1, 0) +pwell = polygons(2, 0) +nwell = polygons(3, 0) +nplus = polygons(4, 0) +pplus = polygons(5, 0) +vtg = polygons(6, 0) +vth = polygons(7, 0) +thkox = polygons(8, 0) +poly = polygons(9, 0) +cont = polygons(10, 0) +metal1 = polygons(11, 0) +via1 = polygons(12, 0) +metal2 = polygons(13, 0) +via2 = polygons(14, 0) +metal3 = polygons(15, 0) +via3 = polygons(16, 0) +metal4 = polygons(17, 0) +via4 = polygons(18, 0) +metal5 = polygons(19, 0) +via5 = polygons(20, 0) +metal6 = polygons(21, 0) +via6 = polygons(22, 0) +metal7 = polygons(23, 0) +via7 = polygons(24, 0) +metal8 = polygons(25, 0) +via8 = polygons(26, 0) +metal9 = polygons(27, 0) +via9 = polygons(28, 0) +metal10 = polygons(29, 0) + +# Computed layers +well = nwell.or(pwell) +gate = poly & active +implant = nplus.or(pplus) + +if DRC + +# DRC section +######################## +info("DRC section") + +# splits a layer classes with increasing min dimensions +def classify_by_width(layer, *dimensions) + dimensions.collect { |d| layer = layer.sized(-0.5 * (d - 1.dbu)).sized(0.5 * (d - 1.dbu)) } +end + +# Wells +nwell.and(pwell).output("WELL.1", "WELL.1 : nwell/pwell must not overlap") +# the rule "WELL.2 : Minimum spacing of well at different potential : 225nm" was not coded : see : https://www.klayout.de/forum/discussion/comment/6021 +nwell.space(135.nm, euclidian).output("WELL.3", "WELL.3 : Minimum spacing of nwell at same potential : 135nm") +pwell.space(135.nm, euclidian).output("WELL.3", "WELL.3 : Minimum spacing of pwell at same potential : 135nm") +well.separation(well, 200.nm, euclidian).output("WELL.4", "WELL.4 : Minimum width of nwell/pwell : 200nm") +vtg.not(well).output("VT.1","VT.1 : Vtg adjust layers must coincide with well") +vth.not(well).output("VT.1","VT.1 : Vth adjust layers must coincide with well") + + # Poly +poly.width(50.nm, euclidian).output("POLY.1", "POLY.1 : Minimum width of poly : 50nm") +poly_sep_active = poly.separation(active, 140.nm, projection) +if poly_sep_active.polygons? + poly_sep_active.polygons.without_area(0).output("POLY.2", "POLY.2 : Minimum spacing of poly AND active: 140nm") +end +poly_sep_active.forget +poly.enclosing(gate, 55.nm, projection).polygons.without_area(0).output("POLY.3", "POLY.3 : Minimum poly extension beyond active : 55nm") +active.enclosing(gate, 70.nm, projection).polygons.without_area(0).output("POLY.4", "POLY.4 : Minimum enclosure of active around gate : 70nm") +poly.not(active).separation(active, 50.nm, projection).polygons.without_area(0).output("POLY.5", "POLY.5 : Minimum spacing of field poly to active: 50nm") +poly.space(75.nm, euclidian).output("POLY.6", "POLY.6 : Minimum spacing of field poly: 75nm") + + # Active +active.width(90.nm, euclidian).output("ACTIVE.1", "ACTIVE.1 : Minimum width of active : 90nm") +active.space(80.nm, euclidian).output("ACTIVE.2", "ACTIVE.2 : Minimum spacing of active : 80nm") +well.enclosing(active, 55.nm, euclidian).output("ACTIVE.3", "ACTIVE.3 : Minimum enclosure/spacing of nwell/pwell to active: 55nm") +active.not(well).output("ACTIVE.4", "ACTIVE.4 : active must be inside nwell or pwell") + +# Implant +implant.separation(gate, 70.nm, projection).polygons.without_area(0).output("IMPLANT.1", "IMPLANT.1 : Minimum spacing of nimplant/ pimplant to channel : 70nm") +implant.separation(cont, 25.nm, projection).polygons.without_area(0).output("IMPLANT.2", "IMPLANT.1 : Minimum spacing of nimplant/ pimplant to contact : 25nm") +implant.width(45.nm, euclidian).output("IMPLANT.3", "IMPLANT.3 : Minimum width of nimplant/ pimplant : 45nm") +implant.space(45.nm, euclidian).output("IMPLANT.4", "IMPLANT.4 : Minimum spacing of nimplant/ pimplant : 45nm") +nplus.and(pplus).output("IMPLANT.5", "IMPLANT.5 : Nimplant and pimplant must not overlap") +implant.forget + +# Contact +cont.edges.without_length(65.nm).output("CONTACT.1", "CONTACT.1 : Minimum/Maximum width of contact : 65nm") +cont.space(75.nm, euclidian).output("CONTACT.2", "CONTACT.2 : Minimum spacing of contact : 75nm") +cont.not(active).not(poly).not(metal1).output("CONTACT.3", "CONTACT.3 : contact must be inside active or poly or metal1") +active.enclosing(cont, 5.nm, euclidian).output("CONTACT.4", "CONTACT.4 : Minimum enclosure of active around contact : 5nm") +poly.enclosing(cont, 5.nm, euclidian).output("CONTACT.5", "CONTACT.5 : Minimum enclosure of poly around contact : 5nm") +cont.separation(poly, 35.nm, euclidian).output("CONTACT.6", "CONTACT.6 : Minimum spacing of contact and poly : 35nm") + +# Metal1 +metal1.width(65.nm, euclidian).output("METAL1.1", "METAL1.1 : Minimum width of metal1 : 65nm") +metal1.space(65.nm, euclidian).output("METAL1.2", "METAL1.2 : Minimum spacing of metal1 : 65nm") +cont_edges_with_less_enclosure = metal1.enclosing(cont, 35.nm, projection).second_edges +error_corners = cont_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) +cont_edges_with_less_enclosure.forget +cont.interacting(error_corners.polygons(1.dbu)).output("METAL1.3", "METAL1.3 : Minimum enclosure around contact on two opposite sides : 35nm") +error_corners.forget +via1_edges_with_less_enclosure = metal1.enclosing(via1, 35.nm, projection).second_edges +error_corners = via1_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) +via1_edges_with_less_enclosure.forget +via1.interacting(error_corners.polygons(1.dbu)).output("METAL1.4", "METAL1.4 : Minimum enclosure around via1 on two opposite sides : 35nm") +error_corners.forget +metal1_gt90, metal1_gt270, metal1_gt500, metal1_gt900, metal1_gt1500 = classify_by_width(metal1, 90.nm, 270.nm, 500.nm, 900.nm, 1500.nm) +metal1_gt90.edges.with_length(300.nm,nil).space(90.nm,euclidian).output("METAL1.5", "METAL1.5 : Minimum spacing of metal1 wider than 90 nm and longer than 300 nm : 90nm") +metal1_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL1.6", "METAL1.6 : Minimum spacing of metal1 wider than 270 nm and longer than 900 nm : 270nm") +metal1_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL1.7", "METAL1.7 : Minimum spacing of metal1 wider than 500 nm and longer than 1.8 um : 500nm") +metal1_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL1.8", "METAL1.8 : Minimum spacing of metal1 wider than 900 nm and longer than 2.7 um : 900nm") +metal1_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL1.9", "METAL1.9 : Minimum spacing of metal1 wider than 1500 nm and longer than 4.0 um : 1500nm") +[ metal1_gt90, metal1_gt270, metal1_gt500, metal1_gt900, metal1_gt1500 ].each { |l| l.forget } + +# Via1 +via1.edges.without_length(65.nm).output("VIA1.1", "VIA1.1 : Minimum/Maximum width of via1 : 65nm") +via1.space(75.nm, euclidian).output("VIA1.2", "VIA1.2 : Minimum spacing of via1 : 75nm") +via1.not(metal1).output("VIA1.3", "VIA1.3 : via1 must be inside metal1") +via1.not(metal2).output("VIA1.4", "VIA1.4 : via1 must be inside metal2") + +# metal2 +metal2.width(70.nm, euclidian).output("METAL2.1", "METAL2.1 : Minimum width of intermediate metal2 : 70nm") +metal2.space(70.nm, euclidian).output("METAL2.2", "METAL2.2 : Minimum spacing of intermediate metal2 : 70nm") +via1_edges_with_less_enclosure = metal2.enclosing(via1, 35.nm, projection).second_edges +error_corners = via1_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) +via1_edges_with_less_enclosure.forget +via1.interacting(error_corners.polygons(1.dbu)).output("METAL2.3", "METAL2.3 : Minimum enclosure around via1 on two opposite sides : 35nm") +error_corners.forget +via2_edges_with_less_enclosure = metal2.enclosing(via2, 35.nm, projection).second_edges +error_corners = via2_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) +via2_edges_with_less_enclosure.forget +via2.interacting(error_corners.polygons(1.dbu)).output("METAL2.4", "METAL2.4 : Minimum enclosure around via2 on two opposite sides : 35nm") +error_corners.forget +metal2_gt90, metal2_gt270, metal2_gt500, metal2_gt900, metal2_gt1500 = classify_by_width(metal2, 90.nm, 270.nm, 500.nm, 900.nm, 1500.nm) +metal2_gt90.edges.with_length(300.nm,nil).space(90.nm,euclidian).output("METAL2.5", "METAL2.5 : Minimum spacing of intermediate metal2 wider than 90 nm and longer than 300 nm : 90nm") +metal2_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL2.6", "METAL2.6 : Minimum spacing of intermediate metal2 wider than 270 nm and longer than 900 nm : 270nm") +metal2_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL2.7", "METAL2.7 : Minimum spacing of intermediate metal2 wider than 500 nm and longer than 1.8 um : 500nm") +metal2_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL2.8", "METAL2.8 : Minimum spacing of intermediate metal2 wider than 900 nm and longer than 2.7 um : 900nm") +metal2_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL2.9", "METAL2.9 : Minimum spacing of intermediate metal2 wider than 1500 nm and longer than 4.0 um : 1500nm") +[ metal2_gt90, metal2_gt270, metal2_gt500, metal2_gt900, metal2_gt1500 ].each { |l| l.forget } + +# via2 +via2.edges.without_length(70.nm).output("VIA2.1", "VIA2.1 : Minimum/Maximum width of via2 : 70nm") +via2.space(85.nm, euclidian).output("VIA2.2", "VIA2.2 : Minimum spacing of via2 : 85nm") +via2.not(metal2).output("VIA2.3", "VIA2.3 : via2 must be inside metal2") +via2.not(metal3).output("VIA2.4", "VIA2.4 : via2 must be inside metal3") + +# metal3 +metal3.width(70.nm, euclidian).output("METAL3.1", "METAL3.1 : Minimum width of intermediate metal3 : 70nm") +metal3.space(70.nm, euclidian).output("METAL3.2", "METAL3.2 : Minimum spacing of intermediate metal3 : 70nm") +via2_edges_with_less_enclosure = metal3.enclosing(via2, 35.nm, projection).second_edges +error_corners = via2_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) +via2_edges_with_less_enclosure.forget +via2.interacting(error_corners.polygons(1.dbu)).output("METAL3.3", "METAL3.3 : Minimum enclosure around via2 on two opposite sides : 35nm") +error_corners.forget +via3_edges_with_less_enclosure = metal3.enclosing(via3, 35.nm, projection).second_edges +error_corners = via3_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) +via3_edges_with_less_enclosure.forget +via3.interacting(error_corners.polygons(1.dbu)).output("METAL3.4", "METAL3.4 : Minimum enclosure around via3 on two opposite sides : 35nm") +error_corners.forget +metal3_gt90, metal3_gt270, metal3_gt500, metal3_gt900, metal3_gt1500 = classify_by_width(metal3, 90.nm, 270.nm, 500.nm, 900.nm, 1500.nm) +metal3_gt90.edges.with_length(300.nm,nil).space(90.nm,euclidian).output("METAL3.5", "METAL3.5 : Minimum spacing of intermediate metal3 wider than 90 nm and longer than 300 nm : 90nm") +metal3_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL3.6", "METAL3.6 : Minimum spacing of intermediate metal3 wider than 270 nm and longer than 900 nm : 270nm") +metal3_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL3.7", "METAL3.7 : Minimum spacing of intermediate metal3 wider than 500 nm and longer than 1.8 um : 500nm") +metal3_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL3.8", "METAL3.8 : Minimum spacing of intermediate metal3 wider than 900 nm and longer than 2.7 um : 900nm") +metal3_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL3.9", "METAL3.9 : Minimum spacing of intermediate metal3 wider than 1500 nm and longer than 4.0 um : 1500nm") +[ metal3_gt90, metal3_gt270, metal3_gt500, metal3_gt900, metal3_gt1500 ].each { |l| l.forget } + +# via3 +via3.edges.without_length(70.nm).output("VIA3.1", "VIA3.1 : Minimum/Maximum width of via3 : 70nm") +via3.space(85.nm, euclidian).output("VIA3.2", "VIA3.2 : Minimum spacing of via3 : 85nm") +via3.not(metal3).output("VIA3.3", "VIA3.3 : via3 must be inside metal3") +via3.not(metal4).output("VIA3.4", "VIA3.4 : via3 must be inside metal4") + +# metal4 +metal4.width(140.nm, euclidian).output("METAL4.1", "METAL4.1 : Minimum width of semi-global metal4 : 140nm") +metal4.space(140.nm, euclidian).output("METAL4.2", "METAL4.2 : Minimum spacing of semi-global metal4 : 140nm") +metal4_gt270, metal4_gt500, metal4_gt900 = classify_by_width(metal4, 270.nm, 500.nm, 900.nm) +metal4_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL4.6", "METAL4.6 : Minimum spacing of semi-global metal4 wider than 270 nm and longer than 900 nm : 270nm") +metal4_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL4.7", "METAL4.7 : Minimum spacing of semi-global metal4 wider than 500 nm and longer than 1.8 um : 500nm") +metal4_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL4.8", "METAL4.8 : Minimum spacing of semi-global meta4l wider than 900 nm and longer than 2.7 um : 900nm") +[ metal4_gt270, metal4_gt500, metal4_gt900 ].each { |l| l.forget } + +# via4 +via4.edges.without_length(140.nm).output("VIA4.1", "VIA4.1 : Minimum/Maximum width of via4 : 140nm") +via4.space(160.nm, euclidian).output("VIA4.2", "VIA4.2 : Minimum spacing of via4 : 160nm") +via4.not(metal4).output("VIA4.3", "VIA4.3 : via4 must be inside metal4") +via4.not(metal5).output("VIA4.4", "VIA4.4 : via4 must be inside metal5") + +# metal5 +metal5.width(140.nm, euclidian).output("METAL5.1", "METAL5.1 : Minimum width of semi-global metal5 : 140nm") +metal5.space(140.nm, euclidian).output("METAL5.2", "METAL5.2 : Minimum spacing of semi-global metal5 : 140nm") +metal5_gt270, metal5_gt500, metal5_gt900 = classify_by_width(metal5, 270.nm, 500.nm, 900.nm) +metal5_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL5.6", "METAL5.6 : Minimum spacing of semi-global metal5 wider than 270 nm and longer than 900 nm : 270nm") +metal5_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL5.7", "METAL5.7 : Minimum spacing of semi-global metal5 wider than 500 nm and longer than 1.8 um : 500nm") +metal5_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL5.8", "METAL5.8 : Minimum spacing of semi-global meta5l wider than 900 nm and longer than 2.7 um : 900nm") +[ metal5_gt270, metal5_gt500, metal5_gt900 ].each { |l| l.forget } + +# via5 +via5.edges.without_length(140.nm).output("VIA5.1", "VIA5.1 : Minimum/Maximum width of via5 : 140nm") +via5.space(160.nm, euclidian).output("VIA5.2", "VIA5.2 : Minimum spacing of via5 : 160nm") +via5.not(metal5).output("VIA5.3", "VIA5.3 : via5 must be inside metal5") +via5.not(metal6).output("VIA5.4", "VIA5.4 : via5 must be inside metal6") + +# metal6 +metal6.width(140.nm, euclidian).output("METAL6.1", "METAL6.1 : Minimum width of semi-global metal6 : 140nm") +metal6.space(140.nm, euclidian).output("METAL6.2", "METAL6.2 : Minimum spacing of semi-global metal6 : 140nm") +metal6_gt270, metal6_gt500, metal6_gt900 = classify_by_width(metal6, 270.nm, 500.nm, 900.nm) +metal6_gt270.edges.with_length(900.nm,nil).space(270.nm,euclidian).output("METAL6.6", "METAL6.6 : Minimum spacing of semi-global metal6 wider than 270 nm and longer than 900 nm : 270nm") +metal6_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL6.7", "METAL6.7 : Minimum spacing of semi-global metal6 wider than 500 nm and longer than 1.8 um : 500nm") +metal6_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL6.8", "METAL6.8 : Minimum spacing of semi-global metal6 wider than 900 nm and longer than 2.7 um : 900nm") +[ metal6_gt270, metal6_gt500, metal6_gt900 ].each { |l| l.forget } + +# via6 +via6.edges.without_length(140.nm).output("VIA6.1", "VIA6.1 : Minimum/Maximum width of via6 : 140nm") +via6.space(160.nm, euclidian).output("VIA6.2", "VIA6.2 : Minimum spacing of via6 : 160nm") +via6.not(metal6).output("VIA6.3", "VIA6.3 : via6 must be inside metal6") +via6.not(metal7).output("VIA6.4", "VIA6.4 : via6 must be inside metal7") + +# metal7 +metal7.width(400.nm, euclidian).output("METAL7.1", "METAL7.1 : Minimum width of thin global metal7 : 400nm") +metal7.space(400.nm, euclidian).output("METAL7.2", "METAL7.2 : Minimum spacing of thin global metal7 : 400nm") +metal7_gt500, metal7_gt900, metal7_gt1500 = classify_by_width(metal7, 500.nm, 900.nm, 1500.nm) +metal7_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL7.7", "METAL7.7 : Minimum spacing of thin global metal7 wider than 500 nm and longer than 1.8 um : 500nm") +metal7_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL7.8", "METAL7.8 : Minimum spacing of thin global metal7 wider than 900 nm and longer than 2.7 um : 900nm") +metal7_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL7.9", "METAL7.9 : Minimum spacing of thin global meta7l wider than 1500 nm and longer than 4.0 um : 1500nm") +[ metal7_gt500, metal7_gt900, metal7_gt1500 ].each { |l| l.forget } + +# via7 +via7.edges.without_length(400.nm).output("VIA6.1", "VIA6.1 : Minimum/Maximum width of via7 : 400nm") +via7.space(440.nm, euclidian).output("VIA6.2", "VIA6.2 : Minimum spacing of via7 : 440nm") +via7.not(metal7).output("VIA7.3", "VIA7.3 : via7 must be inside metal7") +via7.not(metal8).output("VIA7.4", "VIA7.4 : via7 must be inside metal8") + +# metal8 +metal8.width(400.nm, euclidian).output("METAL8.1", "METAL8.1 : Minimum width of thin global metal8 : 400nm") +metal8.space(400.nm, euclidian).output("METAL8.2", "METAL8.2 : Minimum spacing of thin global metal8 : 400nm") +metal8_gt500, metal8_gt900, metal8_gt1500 = classify_by_width(metal8, 500.nm, 900.nm, 1500.nm) +metal8_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL8.7", "METAL8.7 : Minimum spacing of thin global metal8 wider than 500 nm and longer than 1.8 um : 500nm") +metal8_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL8.8", "METAL8.8 : Minimum spacing of thin global metal8 wider than 900 nm and longer than 2.7 um : 900nm") +metal8_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL8.9", "METAL8.9 : Minimum spacing of thin global metal8 wider than 1500 nm and longer than 4.0 um : 1500nm") +[ metal8_gt500, metal8_gt900, metal8_gt1500 ].each { |l| l.forget } + +# via8 +via8.edges.without_length(400.nm).output("VIA8.1", "VIA8.1 : Minimum/Maximum width of via8 : 400nm") +via8.space(440.nm, euclidian).output("VIA8.2", "VIA8.2 : Minimum spacing of via8 : 440nm") +via8.not(metal8).output("VIA8.3", "VIA8.3 : via8 must be inside metal8") +via8.not(metal9).output("VIA8.4", "VIA8.4 : via8 must be inside metal9") + +# metal9 +metal9.width(800.nm, euclidian).output("METAL9.1", "METAL9.1 : Minimum width of global metal9 : 800nm") +metal9.space(800.nm, euclidian).output("METAL9.2", "METAL9.2 : Minimum spacing of global metal9 : 800nm") +metal9_gt500, metal9_gt900, metal9_gt1500 = classify_by_width(metal9, 500.nm, 900.nm, 1500.nm) +metal9_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL9.7", "METAL9.7 : Minimum spacing of global metal9 wider than 500 nm and longer than 1.8 um : 500nm") +metal9_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL9.8", "METAL9.8 : Minimum spacing of global metal9 wider than 900 nm and longer than 2.7 um : 900nm") +metal9_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL9.9", "METAL9.9 : Minimum spacing of global metal9 wider than 1500 nm and longer than 4.0 um : 1500nm") +[ metal9_gt500, metal9_gt900, metal9_gt1500 ].each { |l| l.forget } + +# via9 +via9.edges.without_length(800.nm).output("VIA9.1", "VIA9.1 : Minimum/Maximum width of via9 : 800nm") +via9.space(880.nm, euclidian).output("VIA9.2", "VIA9.2 : Minimum spacing of via9 : 880nm") +via9.not(metal9).output("VIA9.3", "VIA9.3 : via9 must be inside metal9") +via9.not(metal10).output("VIA9.4", "VIA9.4 : via9 must be inside metal10") + +# metal10 +metal10.width(800.nm, euclidian).output("METAL10.1", "METAL10.1 : Minimum width of global metal10 : 800nm") +metal10.space(800.nm, euclidian).output("METAL10.2", "METAL10.2 : Minimum spacing of global metal10 : 800nm") +metal10_gt500, metal10_gt900, metal10_gt1500 = classify_by_width(metal10, 500.nm, 900.nm, 1500.nm) +metal10_gt500.edges.with_length(1.8.um,nil).space(500.nm,euclidian).output("METAL10.7", "METAL10.7 : Minimum spacing of global metal10 wider than 500 nm and longer than 1.8 um : 500nm") +metal10_gt900.edges.with_length(2.7.um,nil).space(900.nm,euclidian).output("METAL10.8", "METAL10.8 : Minimum spacing of global metal10 wider than 900 nm and longer than 2.7 um : 900nm") +metal10_gt1500.edges.with_length(4.um,nil).space(1500.nm,euclidian).output("METAL10.9", "METAL10.9 : Minimum spacing of global metal10 wider than 1500 nm and longer than 4.0 um : 1500nm") +[ metal10_gt500, metal10_gt900, metal10_gt1500 ].each { |l| l.forget } + +end + + +# ONGRID also defined in : +# https://www.eda.ncsu.edu/wiki/FreePDK45:Manufacturing_Grid +########################################### +if OFFGRID +info("GRID section") + +grid = 2.5.nm +all_drawing = [ :well, :active, :vtg, :vth, :pplus, :nplus, :poly, :thkox, :cont, :metal1, :via1, :metal2, :via2, :metal3, :via3, :metal4, :via4, :metal5, :via5, :metal6, :via6, :metal7, :via7, :metal8, :via8, :metal9, :via9, :metal10 ] +all_drawing.each do |dwg| + # a Ruby idiom to get the value of a variable whose name is in "dwg" (as symbol) + layer = binding.local_variable_get(dwg) + r_grid = layer.ongrid(grid).polygons(10.nm) + r_grid.output("GRID: vertexes on layer #{dwg} not on grid of #{'%.12g' % grid}") +end +end + +# ANTENNA checks +################ +if ANTENNA +info("ANTENNA section") + +diode = nplus & active - nwell # diode recognition layer + +# build connction of poly+gate to metal1 +connect(gate, poly) +connect(poly, cont) +connect(diode, cont) +connect(cont, metal1) + +antenna_check(gate, metal1, 300.0, diode).output("METAL1_ANTENNA", "METAL1_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +# build connction of poly+gate to metal2 +connect(metal1, via1) +connect(via1, metal2) + +antenna_check(gate, metal2, 300.0, diode).output("METAL2_ANTENNA", "METAL2_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +# build connction of poly+gate to metal3 +connect(metal2, via2) +connect(via2, metal3) + +antenna_check(gate, metal3, 300.0, diode).output("METAL3_ANTENNA", "METAL3_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +# build connction of poly+gate to metal4 +connect(metal3, via3) +connect(via3, metal4) + +antenna_check(gate, metal4, 300.0, diode).output("METAL4_ANTENNA", "METAL4_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +# build connction of poly+gate to metal5 +connect(metal4, via4) +connect(via4, metal5) + +antenna_check(gate, metal5, 300.0, diode).output("METAL5_ANTENNA", "METAL5_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +# build connction of poly+gate to metal6 +connect(metal5, via5) +connect(via5, metal6) + +antenna_check(gate, metal6, 300.0, diode).output("METAL6_ANTENNA", "METAL6_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +# build connction of poly+gate to metal7 +connect(metal6, via6) +connect(via6, metal7) + +antenna_check(gate, metal7, 300.0, diode).output("METAL7_ANTENNA", "METAL7_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +# build connction of poly+gate to metal8 +connect(metal7, via7) +connect(via7, metal8) + +antenna_check(gate, metal8, 300.0, diode).output("METAL8_ANTENNA", "METAL8_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +# build connction of poly+gate to metal9 +connect(metal8, via8) +connect(via8, metal9) + +antenna_check(gate, metal9, 300.0, diode).output("METAL9_ANTENNA", "METAL9_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +# build connction of poly+gate to metal10 +connect(metal9, via9) +connect(via9, metal10) + +antenna_check(gate, metal10, 300.0, diode).output("METAL10_ANTENNA", "METAL10_ANTENNA : Ratio of Maximum Allowed (Field poly area or Metal Layer Area) to transistor gate area : 300:1") + +end + +# time spent for the DRC +time = Time.now +hours = ((time - tstart)/3600).to_i +minutes = ((time - tstart)/60 - hours * 60).to_i +seconds = ((time - tstart) - (minutes * 60 + hours * 3600)).to_i +$stdout.write "DRC finished at : #{time.hour}:#{time.min}:#{time.sec} - DRC duration = #{hours} hrs. #{minutes} min. #{seconds} sec.\n" + + diff --git a/technology/freepdk45/tech/freepdk45.lylvs b/technology/freepdk45/tech/freepdk45.lylvs new file mode 100644 index 00000000..c9107e45 --- /dev/null +++ b/technology/freepdk45/tech/freepdk45.lylvs @@ -0,0 +1,270 @@ + + + + + lvs + + + + false + false + + true + lvs_scripts + tools_menu.lvs.end + dsl + lvs-dsl-xml + # +# Extraction for freePDK45 +# +############################ +tstart = Time.now + +# optionnal for a batch launch : klayout -b -rd input=my_layout.gds -rd report=my_report.lyrdb -rd schematic=reference_netlist.cir -rd target_netlist=extracted_netlist.cir -r lvs_freepdk45.lvs +if $input + source($input) +end + +if $report + report_lvs($report) +else + report_lvs("lvs_report.lvsdb") +end + +if $schematic +#reference netlist + schematic($schematic) +else +# schematic("sram_8_256_freepdk45.sp") + schematic(RBA::CellView::active.filename.sub(/\.(oas|gds|oas.gz|gds.gz)$/, ".sp")) +end + +# true: use net names instead of numbers +# false: use numbers for nets +spice_with_net_names = true + +# true: put in comments with details +# false: no comments +spice_with_comments = false + +if $target_netlist + target_netlist($target_netlist) +else + # target_netlist("netlist.cir", write_spice(spice_with_net_names, spice_with_comments), "The netlist comment goes here.") + target_netlist(File.join(File.dirname(RBA::CellView::active.filename), source.cell_name+"_extracted.cir"), write_spice(spice_with_net_names, spice_with_comments), "Extracted by KLayout on : #{Time.now.strftime("%d/%m/%Y %H:%M")}") +end + +# Hierarchical mode +deep +# Use 4 CPU cores +threads(4) +# Print details +verbose(true) + + +# layers definitions +######################## +active = input(1, 0) +pwell = input(2, 0) +nwell = input(3, 0) +nplus = input(4, 0) +pplus = input(5, 0) +vtg = input(6, 0) +vth = input(7, 0) +thkox = input(8, 0) +poly = input(9, 0) +cont = input(10, 0) +metal1 = input(11, 0) +metal1_lbl = input(11, 1) +metal1_pin = input(11, 2) +via1 = input(12, 0) +metal2 = input(13, 0) +metal2_lbl = input(13, 1) +metal2_pin = input(13, 2) +via2 = input(14, 0) +metal3 = input(15, 0) +metal3_lbl = input(15, 1) +metal3_pin = input(15, 2) +via3 = input(16, 0) +metal4 = input(17, 0) +metal4_lbl = input(17, 1) +metal4_pin = input(17, 2) +via4 = input(18, 0) +metal5 = input(19, 0) +metal5_lbl = input(19, 1) +metal5_pin = input(19, 2) +via5 = input(20, 0) +metal6 = input(21, 0) +metal6_lbl = input(21, 1) +metal6_pin = input(21, 2) +via6 = input(22, 0) +metal7 = input(23, 0) +metal7_lbl = input(23, 1) +metal7_pin = input(23, 2) +via7 = input(24, 0) +metal8 = input(25, 0) +metal8_lbl = input(25, 1) +metal8_pin = input(25, 2) +via8 = input(26, 0) +metal9 = input(27, 0) +metal9_lbl = input(27, 1) +metal9_pin = input(27, 2) +via9 = input(28, 0) +metal10 = input(29, 0) +metal10_lbl = input(29, 1) +metal10_pin = input(29, 2) + +# Bulk layer for terminal provisioning +bulk = polygon_layer + +# Computed layers +active_in_nwell = active & nwell +pactive = active_in_nwell & pplus +ntie = active_in_nwell & nplus +pgate = pactive & poly +psd = pactive - pgate +lv_pgate = pgate - vtg - thkox +gv_pgate = pgate & vtg - vth - thkox +hv_pgate = pgate - vtg - vth & thkox + +active_in_pwell = active & pwell +nactive = active_in_pwell & nplus +ptie = active_in_pwell & pplus +ngate = nactive & poly +nsd = nactive - ngate +lv_ngate = ngate - vtg - thkox +gv_ngate = ngate & vtg - vth - thkox +hv_ngate = ngate - vtg - vth & thkox + +cheat("cell_6t", "dummy_cell_6t", "cell_1rw", "dummy_cell_1rw", "cell_2rw", "dummy_cell_2rw", "dff","wordline_driver_0") { + +# PMOS transistor device extraction +extract_devices(mos4("PMOS_VTL"), { "SD" => psd, "G" => lv_pgate, "tS" => psd, "tD" => psd, "tG" => poly, "W" => nwell }) +extract_devices(mos4("PMOS_VTG"), { "SD" => psd, "G" => gv_pgate, "tS" => psd, "tD" => psd, "tG" => poly, "W" => nwell }) +extract_devices(mos4("PMOS_VTH"), { "SD" => psd, "G" => hv_pgate, "tS" => psd, "tD" => psd, "tG" => poly, "W" => nwell }) + +# NMOS transistor device extraction +extract_devices(mos4("NMOS_VTL"), { "SD" => nsd, "G" => lv_ngate, "tS" => nsd, "tD" => nsd, "tG" => poly, "W" => pwell }) +extract_devices(mos4("NMOS_VTG"), { "SD" => nsd, "G" => gv_ngate, "tS" => nsd, "tD" => nsd, "tG" => poly, "W" => pwell }) +extract_devices(mos4("NMOS_VTH"), { "SD" => nsd, "G" => hv_ngate, "tS" => nsd, "tD" => nsd, "tG" => poly, "W" => pwell }) + +} + +# Define connectivity for netlist extraction + +# Inter-layer +connect(nwell, ntie) +connect(pwell, ptie) +connect(cont, ntie) +connect(cont, ptie) +connect(psd, cont) +connect(nsd, cont) +connect(poly, cont) +connect(cont, metal1) +connect(cont, metal1) +connect(metal1, via1) +connect(via1, metal2) +connect(metal2, via2) +connect(via2, metal3) +connect(metal3, via3) +connect(via3, metal4) +connect(metal4, via4) +connect(via4, metal5) +connect(metal5, via5) +connect(via5, metal6) +connect(metal6, via6) +connect(via6, metal7) +connect(metal7, via7) +connect(via7, metal8) +connect(metal8, via8) +connect(via8, metal9) +connect(metal9, via9) +connect(via9, metal10) +# attach labels : +connect(metal1, metal1_lbl) +connect(metal1, metal1_pin) +connect(metal2, metal2_lbl) +connect(metal2, metal2_pin) +connect(metal3, metal3_lbl) +connect(metal3, metal3_pin) +connect(metal4, metal4_lbl) +connect(metal4, metal4_pin) +connect(metal5, metal5_lbl) +connect(metal5, metal5_pin) +connect(metal6, metal6_lbl) +connect(metal6, metal6_pin) +connect(metal7, metal7_lbl) +connect(metal7, metal7_pin) +connect(metal8, metal8_lbl) +connect(metal8, metal8_pin) +connect(metal9, metal9_lbl) +connect(metal9, metal9_pin) +connect(metal10, metal10_lbl) +connect(metal10, metal10_pin) + + +# Global +schematic.simplify + +connect_global(pwell, "PWELL") +connect_global(nwell, "NWELL") +connect_global(bulk, "BULK") + +#for pat in %w(pnand*_0 and2_dec_0 port_address* replica_bitcell_array) +# connect_explicit(pat, [ "NWELL", "vdd" ]) +# connect_explicit(pat, [ "BULK", "PWELL", "gnd" ]) +#end + +#for pat in %w(XOR* XNOR* TLAT* TINV* TBUF* SDFF* OR* OAI* NOR* NAND* MUX* LOGIC* INV* HA* FILLCELL* +# FA* DLL* DLH* DFF* DFFS* DFFR* DFFRS* CLKGATE* CLKBUF* BUF* AOI* ANTENNA* AND*) +# connect_explicit(pat, [ "NWELL", "VDD" ]) +# connect_explicit(pat, [ "BULK", "VSS" ]) +#end + +# Actually performs the extraction +netlist # ... not really required + +# Flatten cells which are present in one netlist only +align +# SIMPLIFICATION of the netlist +#netlist.make_top_level_pins +#netlist.combine_devices +#netlist.purge +#netlist.purge_nets +netlist.simplify + +# Tolerances for the devices extracted parameters +# tolerance(device_class_name, parameter_name [, :absolute => absolute_tolerance] [, :relative => relative_tolerance]) +tolerance("PMOS_LVT", "W", :absolute => 1.nm, :relative => 0.001) +tolerance("PMOS_LVT", "L", :absolute => 1.nm, :relative => 0.001) +tolerance("PMOS_GVT", "W", :absolute => 1.nm, :relative => 0.001) +tolerance("PMOS_GVT", "L", :absolute => 1.nm, :relative => 0.001) +tolerance("PMOS_HVT", "W", :absolute => 1.nm, :relative => 0.001) +tolerance("PMOS_HVT", "L", :absolute => 1.nm, :relative => 0.001) +tolerance("NMOS_LVT", "W", :absolute => 1.nm, :relative => 0.001) +tolerance("NMOS_LVT", "L", :absolute => 1.nm, :relative => 0.001) +tolerance("NMOS_GVT", "W", :absolute => 1.nm, :relative => 0.001) +tolerance("NMOS_GVT", "L", :absolute => 1.nm, :relative => 0.001) +tolerance("NMOS_HVT", "W", :absolute => 1.nm, :relative => 0.001) +tolerance("NMOS_HVT", "L", :absolute => 1.nm, :relative => 0.001) + +#max_res(1000000) +#min_caps(1e-15) + +max_branch_complexity(65536) +max_depth(16) + +if ! compare + #raise "ERROR : Netlists don't match" + puts "ERROR : Netlists don't match" +else + puts "CONGRATULATIONS! Netlists match." +end + +# time spent for the LVS +time = Time.now +hours = ((time - tstart)/3600).to_i +minutes = ((time - tstart)/60 - hours * 60).to_i +seconds = ((time - tstart) - (minutes * 60 + hours * 3600)).to_i +$stdout.write "LVS finished at : #{time.hour}:#{time.min}:#{time.sec} - LVS duration = #{hours} hrs. #{minutes} min. #{seconds} sec.\n" + diff --git a/technology/freepdk45/tf/FreePDK45.lyp b/technology/freepdk45/tech/freepdk45.lyp similarity index 100% rename from technology/freepdk45/tf/FreePDK45.lyp rename to technology/freepdk45/tech/freepdk45.lyp diff --git a/technology/freepdk45/tf/FreePDK45.lyt b/technology/freepdk45/tech/freepdk45.lyt similarity index 100% rename from technology/freepdk45/tf/FreePDK45.lyt rename to technology/freepdk45/tech/freepdk45.lyt diff --git a/technology/freepdk45/tech/tech.py b/technology/freepdk45/tech/tech.py index f5decd3c..de3629e3 100644 --- a/technology/freepdk45/tech/tech.py +++ b/technology/freepdk45/tech/tech.py @@ -460,8 +460,8 @@ parameter["bitcell_drain_cap"] = 0.1 # In Femto-Farad, approximation of d # Technology Tool Preferences ################################################### -drc_name = "calibre" -lvs_name = "calibre" -pex_name = "calibre" +drc_name = "klayout" +lvs_name = "klayout" +pex_name = "klayout" blackbox_bitcell = False diff --git a/technology/freepdk45/tf/glade_freepdk45.py b/technology/freepdk45/tf/glade_freepdk45.py deleted file mode 100644 index 09ad83ea..00000000 --- a/technology/freepdk45/tf/glade_freepdk45.py +++ /dev/null @@ -1,7 +0,0 @@ -import os -CWD = os.environ.get("OPENRAM_TECH") + "/freepdk45/tf" -ui().importCds("default", CWD+"/display.drf", CWD+"/FreePDK45.tf", 1000, 1, CWD+"/layers.map") - - - -