From ecc06078b9d61c484b3cf961c519aa8f25f924e8 Mon Sep 17 00:00:00 2001 From: Marwan Abbas Date: Sun, 9 Oct 2022 19:36:50 +0200 Subject: [PATCH] added signoff automation script + supporting scripts --- scripts/run_pt_sta.py | 125 ++--- scripts/signoff_automation.py | 354 +++++++++++++ scripts/tech-files/build.tcl | 21 + scripts/tech-files/sky130A_mr.drc | 794 ++++++++++++++++++++++++++++++ 4 files changed, 1232 insertions(+), 62 deletions(-) create mode 100755 scripts/signoff_automation.py create mode 100644 scripts/tech-files/build.tcl create mode 100644 scripts/tech-files/sky130A_mr.drc diff --git a/scripts/run_pt_sta.py b/scripts/run_pt_sta.py index e34891b8..428d1a40 100644 --- a/scripts/run_pt_sta.py +++ b/scripts/run_pt_sta.py @@ -84,72 +84,73 @@ def search_viol( else: return "pass" -parser = argparse.ArgumentParser( - description="Run STA using PrimeTime" -) -parser.add_argument( - "-d", - "--design", - help="design name", - required=True -) -parser.add_argument( - "-o", - "--output_dir", - help="output directory", - required=True -) -parser.add_argument( - "-rc", - "--rc_corner", - help="Specify the RC corner for the parasitics (Values are nom, max, or min) ", - nargs="?", - default="nom" -) -parser.add_argument( - "-proc", - "--proc_corner", - help="Specify the process corner (Values are t, f, or s) ", - nargs="?", - default="t" -) -parser.add_argument( - "-a", - "--all", - help="Specify to run all the process corners and rc corners combinations for the design", - action='store_true' -) +if __name__ == "__main__": + parser = argparse.ArgumentParser( + description="Run STA using PrimeTime" + ) + parser.add_argument( + "-d", + "--design", + help="design name", + required=True + ) + parser.add_argument( + "-o", + "--output_dir", + help="output directory", + required=True + ) + parser.add_argument( + "-rc", + "--rc_corner", + help="Specify the RC corner for the parasitics (Values are nom, max, or min) ", + nargs="?", + default="nom" + ) + parser.add_argument( + "-proc", + "--proc_corner", + help="Specify the process corner (Values are t, f, or s) ", + nargs="?", + default="t" + ) + parser.add_argument( + "-a", + "--all", + help="Specify to run all the process corners and rc corners combinations for the design", + action='store_true' + ) -args = parser.parse_args() + args = parser.parse_args() -output = os.path.abspath(args.output_dir) -log = os.path.abspath(os.path.join(output,"pt_logs")) + output = os.path.abspath(args.output_dir) + log = os.path.abspath(os.path.join(output,"pt_logs")) -try: - os.makedirs(output) -except FileExistsError: - # directory already exists - pass + try: + os.makedirs(output) + except FileExistsError: + # directory already exists + pass -try: - os.makedirs(log) -except FileExistsError: - # directory already exists - pass + try: + os.makedirs(log) + except FileExistsError: + # directory already exists + pass -try: - os.makedirs(os.path.join(output,"pt_reports")) -except FileExistsError: - # directory already exists - pass + try: + os.makedirs(os.path.join(output,"pt_reports")) + except FileExistsError: + # directory already exists + pass -try: - os.makedirs(os.path.join(output,"pt_sdf")) -except FileExistsError: - # directory already exists - pass + try: + os.makedirs(os.path.join(output,"pt_sdf")) + except FileExistsError: + # directory already exists + pass -if args.all: - run_sta_all (args.design, output, log) -else: - run_sta (args.design, args.proc_corner, args.rc_corner, output, log) + if args.all: + run_sta_all (args.design, output, log) + else: + run_sta (args.design, args.proc_corner, args.rc_corner, output, log) diff --git a/scripts/signoff_automation.py b/scripts/signoff_automation.py new file mode 100755 index 00000000..bca80e08 --- /dev/null +++ b/scripts/signoff_automation.py @@ -0,0 +1,354 @@ +#!/usr/local/bin/python + +import argparse +from cmath import log +import logging +import os +import subprocess +import count_lvs +import glob +import run_pt_sta + + +def build_caravel(caravel_root, mcw_root, pdk_root, log_dir, pdk_env): + os.environ["CARAVEL_ROOT"] = caravel_root + os.environ["MCW_ROOT"] = mcw_root + os.environ["PDK_ROOT"] = pdk_root + os.environ["PDK"] = pdk_env + + if glob.glob(f"{caravel_root}/gds/*.gz"): + logging.error("Compressed gds files. Please uncompress first.") + exit(1) + + gpio_defaults_cmd = ["python3", f"scripts/gen_gpio_defaults.py"] + build_cmd = [ + "magic", + "-noconsole", + "-dnull", + "-rcfile", + f"{pdk_root}/{pdk_env}/libs.tech/magic/{pdk_env}.magicrc", + "tech-files/build.tcl", + ] + log_file_path = f"{log_dir}/build_caravel.log" + with open(log_file_path, "w") as build_log: + subprocess.run( + gpio_defaults_cmd, cwd=caravel_root, stderr=build_log, stdout=build_log + ) + sp_build = subprocess.run(build_cmd, stderr=subprocess.PIPE, stdout=build_log) + if sp_build.stderr: + logging.error(sp_build.stderr.decode()) + exit(1) + + +def run_drc(caravel_root, log_dir, signoff_dir, pdk_root): + klayout_drc_cmd = [ + "python3", + "klayout_drc.py", + "-g", + f"{caravel_root}/gds/caravel.gds", + "-l", + f"{log_dir}", + "-s", + f"{signoff_dir}", + "-d", + "caravel", + ] + p1 = subprocess.Popen(klayout_drc_cmd) + return p1 + + +def run_lvs(caravel_root, mcw_root, log_dir, signoff_dir, pdk_root, lvs_root, pdk_env): + os.environ["PDK_ROOT"] = pdk_root + os.environ["PDK"] = pdk_env + os.environ["LVS_ROOT"] = lvs_root + os.environ["LOG_ROOT"] = log_dir + os.environ["CARAVEL_ROOT"] = caravel_root + os.environ["MCW_ROOT"] = mcw_root + os.environ["SIGNOFF_ROOT"] = os.path.join(signoff_dir, "caravel") + + if not os.path.exists(f"{lvs_root}"): + subprocess.run( + [ + "git", + "clone", + "https://github.com/d-m-bailey/extra_be_checks.git", + "-b", + "caravel", + ], + cwd=f"{caravel_root}/scripts", + stdout=subprocess.PIPE, + ) + + lvs_cmd = [ + "bash", + "./extra_be_checks/run_full_lvs", + "caravel", + f"{caravel_root}/verilog/gl/caravel.v", + "caravel", + f"{caravel_root}/gds/caravel.gds", + ] + p1 = subprocess.Popen(lvs_cmd) + return p1 + + +def run_verification(caravel_root, pdk_root, pdk_env, sim, simulator="vcs"): + os.environ["PDK_ROOT"] = pdk_root + os.environ["PDK"] = pdk_env + if simulator == "vcs": + lvs_cmd = [ + "python3", + "verify_cocotb.py", + "-tag", + f"CI_{sim}", + "-r", + f"r_{sim}", + "-v", + ] + else: + lvs_cmd = [ + "python3", + "verify_cocotb.py", + "-tag", + f"CI_{sim}", + "-r", + f"r_{sim}", + ] + p1 = subprocess.Popen( + lvs_cmd, + cwd=f"{caravel_root}/verilog/dv/cocotb", + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + return p1 + + +def run_sta(caravel_root, mcw_root, pt_lib_root, log_dir, signoff_dir): + os.environ["CARAVEL_ROOT"] = caravel_root + os.environ["MCW_ROOT"] = mcw_root + os.environ["PT_LIB_ROOT"] = pt_lib_root + if not os.path.exists(f"{pt_lib_root}"): + subprocess.run( + [ + "git", + "clone", + "git@github.com:efabless/mpw-2-sta-debug.git", + ], + cwd=f"{caravel_root}/scripts", + stdout=subprocess.PIPE, + ) + run_pt_sta.run_sta_all("caravel", signoff_dir, log_dir) + +def check_errors(caravel_root, log_dir, signoff_dir, drc, lvs, verification): + drc_count_klayout = os.path.join(log_dir, "caravel_klayout_drc.total") + lvs_report = os.path.join(signoff_dir, "caravel/caravel.lvs.rpt") + lvs_summary_report = open(os.path.join(signoff_dir, "caravel/lvs_summary.rpt")) + f = open(os.path.join(signoff_dir, "caravel/signoff.rpt")) + count = 0 + if drc: + with open(drc_count_klayout) as rep: + if rep.readline() != 0: + logging.error(f"klayout DRC failed") + f.write("Klayout MR DRC: Failed") + count = count + 1 + else: + logging.info("Klayout MR DRC: Passed") + f.write("Klayout MR DRC: Passed") + if lvs: + failures = count_lvs.count_LVS_failures(args.file) + if failures[0] > 0: + lvs_summary_report.write("LVS reports:") + lvs_summary_report.write(" net count difference = " + str(failures[5])) + lvs_summary_report.write( + " device count difference = " + str(failures[6]) + ) + lvs_summary_report.write(" unmatched nets = " + str(failures[1])) + lvs_summary_report.write(" unmatched devices = " + str(failures[2])) + lvs_summary_report.write(" unmatched pins = " + str(failures[3])) + lvs_summary_report.write(" property failures = " + str(failures[4])) + logging.error(f"LVS on caravel failed") + logging.info(f"Find full report at {lvs_report}") + logging.info(f"Find summary report at {lvs_summary_report}") + f.write("Layout Vs Schematic: Failed") + else: + logging.info("Layout Vs Schematic: Passed") + f.write("Layout Vs Schematic: Passed") + + if verification: + for sim in ["rtl", "gl", "sdf"]: + verification_report = os.path.join( + caravel_root, f"/verilog/dv/cocotb/sim/CI_{sim}/runs.log" + ) + with open(verification_report) as rep: + if "(0)failed" in rep.read(): + logging.info(f"{sim} simulations: Passed") + f.write(f"{sim} simulations: Passed") + else: + logging.error( + f"{sim} simulations failed, find report at {verification_report}" + ) + f.write(f"{sim} simulations: Failed") + count = count + 1 + + if count > 0: + return False + return True + + +if __name__ == "__main__": + print("here") + logging.basicConfig( + level=logging.DEBUG, + format=f"%(asctime)s | %(levelname)-7s | %(message)s", + datefmt="%d-%b-%Y %H:%M:%S", + ) + parser = argparse.ArgumentParser(description="CI wrapper") + parser.add_argument( + "-d", + "--drc_check", + help="run drc check", + action="store_true", + ) + parser.add_argument( + "-l", + "--lvs_check", + help="run lvs check", + action="store_true", + ) + parser.add_argument( + "-v", + "--verification", + help="run verification", + action="store_true", + ) + parser.add_argument( + "-rtl", + "--rtl", + help="run rtl verification", + action="store_true", + ) + parser.add_argument( + "-gl", + "--gl", + help="run gl verification", + action="store_true", + ) + parser.add_argument( + "-sdf", + "--sdf", + help="run sdf verification", + action="store_true", + ) + parser.add_argument( + "-iv", + "--iverilog", + help="run verification using iverilog", + action="store_true", + ) + parser.add_argument( + "-sta", + "--primetime_sta", + help="run verification using iverilog", + action="store_true", + ) + parser.add_argument( + "-a", + "--all", + help="run all checks", + action="store_true", + ) + args = parser.parse_args() + + if not os.getenv("PDK_ROOT"): + logging.error("Please export PDK_ROOT") + exit(1) + if not os.getenv("PDK"): + logging.error("Please export PDK") + exit(1) + caravel_redesign_root = os.path.dirname( + os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + ) + caravel_root = os.path.join(caravel_redesign_root, "caravel") + mcw_root = os.path.join(caravel_redesign_root, "caravel_mgmt_soc_litex") + pdk_root = os.getenv("PDK_ROOT") + pdk_env = os.getenv("PDK") + log_dir = os.path.join(caravel_root, "scripts/logs") + signoff_dir = os.path.join(caravel_root, "signoff") + lvs_root = os.path.join(caravel_root, "scripts/extra_be_checks") + drc = args.drc_check + lvs = args.lvs_check + rtl = args.rtl + gl = args.gl + sdf = args.sdf + iverilog = args.iverilog + verification = args.verification + sta = args.primetime_sta + + if not os.path.exists(f"{log_dir}"): + os.makedirs(f"{log_dir}") + if not os.path.exists(f"{signoff_dir}/caravel"): + os.makedirs(f"{signoff_dir}/caravel") + print("here 2") + logging.info("Building caravel...") + + build_caravel(caravel_root, mcw_root, pdk_root, log_dir, pdk_env) + + if args.all: + drc = True + lvs = True + verification = True + + if drc: + drc_p1 = run_drc(caravel_root, log_dir, signoff_dir, pdk_root) + logging.info("Running klayout DRC on caravel") + if lvs: + lvs_p1 = run_lvs( + caravel_root, + mcw_root, + log_dir, + signoff_dir, + pdk_root, + lvs_root, + # work_root, + pdk_env, + ) + logging.info("Running LVS on caravel") + if verification or iverilog: + verify_p = [] + sim = [] + if rtl: + sim.append("rtl") + if gl: + sim.append("gl") + if sdf: + sim.append("sdf") + if not rtl and not gl and not sdf: + sim = ["rtl", "gl", "sdf"] + if verification: + simulator = "vcs" + elif iverilog: + simulator = "iverilog" + for sim in sim: + logging.info(f"Running all {sim} verification on caravel") + verify_p.append( + run_verification(caravel_root, pdk_root, pdk_env, sim, simulator) + ) + for i in range(len(verify_p)): + out, err = verify_p[i].communicate() + if err: + logging.error(err.decode()) + + if sta: + logging.info(f"Running PrimeTime STA all corners on caravel") + run_sta(caravel_root, mcw_root, "mpw-2-sta-debug", log_dir, signoff_dir) + + + if lvs and drc: + drc_p1.wait() + lvs_p1.wait() + if lvs: + lvs_p1.wait() + if drc: + drc_p1.wait() + + if not check_errors(caravel_root, log_dir, signoff_dir, drc, lvs, verification): + exit(1) diff --git a/scripts/tech-files/build.tcl b/scripts/tech-files/build.tcl new file mode 100644 index 00000000..60d4dba4 --- /dev/null +++ b/scripts/tech-files/build.tcl @@ -0,0 +1,21 @@ +cd $::env(CARAVEL_ROOT)/mag +random seed `$::env(CARAVEL_ROOT)/scripts/set_user_id.py -report`; +drc off; +crashbackups stop; +addpath hexdigits; +addpath $::env(CARAVEL_ROOT)/mag; +load mgmt_core_wrapper; +property LEFview true; +property GDS_FILE $::env(MCW_ROOT)/gds/mgmt_core_wrapper.gds; +property GDS_START 0; +load user_project_wrapper; +load user_id_programming; +load user_id_textblock; +load $::env(CARAVEL_ROOT)/maglef/simple_por; +load caravel -dereference; +select top cell; +expand; +cif *hier write disable; +cif *array write disable; +gds write $::env(CARAVEL_ROOT)/gds/caravel.gds; +quit -noprompt; \ No newline at end of file diff --git a/scripts/tech-files/sky130A_mr.drc b/scripts/tech-files/sky130A_mr.drc new file mode 100644 index 00000000..fdd2962d --- /dev/null +++ b/scripts/tech-files/sky130A_mr.drc @@ -0,0 +1,794 @@ +# DRC for SKY130 according to : +# https://skywater-pdk.readthedocs.io/en/latest/rules/periphery.html +# https://skywater-pdk.readthedocs.io/en/latest/rules/layers.html +# +# Distributed under GNU GPLv3: https://www.gnu.org/licenses/ +# +# History : +# 2022-6-22 : 2022.6.30_01.07 release +# +########################################################################################## +release = "2022.6.30_01.07" + +require 'time' +require "logger" + +exec_start_time = Time.now + +logger = Logger.new(STDOUT) + +logger.formatter = proc do |severity, datetime, progname, msg| + "#{msg} +" +end +# optionnal for a batch launch : klayout -b -rd input=my_layout.gds -rd report=sky130_drc.txt -r drc_sky130.drc +if $input + source($input, $top_cell) +end + +if $report + report("SKY130 DRC runset", $report) +else + report("SKY130 DRC runset", File.join(File.dirname(RBA::CellView::active.filename), "sky130_drc.txt")) +end + +AL = true # do not change +CU = false # do not change +# choose betwen only one of AL or CU back-end flow here : +backend_flow = AL + +FEOL = false +BEOL = false +OFFGRID = false +SEAL = false +FLOATING_MET = false + +# enable / disable rule groups +if $feol == "1" || $feol == "true" + FEOL = true # front-end-of-line checks +else + FEOL = false +end + +if $beol == "1" || $beol == "true" + BEOL = true # back-end-of-line checks +else + BEOL = false +end + +if $offgrid == "1" || $offgrid == "true" + OFFGRID = true # manufacturing grid/angle checks +else + OFFGRID = false +end + +if $seal == "1" || $seal == "true" + SEAL = true # SEAL RING checks +else + SEAL = false +end + +if $floating_met == "1" || $floating_met == "true" + FLOATING_MET = true # back-end-of-line checks +else + FLOATING_MET = false +end + +# klayout setup +######################## +# use a tile size of 1mm - not used in deep mode- +# tiles(1000.um) +# use a tile border of 10 micron: +# tile_borders(1.um) +#no_borders + +# hierachical +deep + +if $thr + threads($thr) +else + threads(4) +end + +# if more inof is needed, set true +# verbose(true) +verbose(true) + +# layers definitions +######################## + +# all except purpose (datatype) 5 -- label and 44 -- via +li_wildcard = "67/20" +mcon_wildcard = "67/44" + +m1_wildcard = "68/20" +via_wildcard = "68/44" + +m2_wildcard = "69/20" +via2_wildcard = "69/44" + +m3_wildcard = "70/20" +via3_wildcard = "70/44" + +m4_wildcard = "71/20" +via4_wildcard = "71/44" + +m5_wildcard = "72/20" + +nsdm_wildcard = "93/44" + +psdm_wildcard = "94/20" +nwell_wildcard = "64/20" + +diff = input(65, 20) +tap = polygons(65, 44) +nwell = polygons(nwell_wildcard) +dnwell = polygons(64, 18) +pwbm = polygons(19, 44) +pwde = polygons(124, 20) +natfet = polygons(124, 21) +hvtr = polygons(18, 20) +hvtp = polygons(78, 44) +ldntm = polygons(11, 44) +hvi = polygons(75, 20) +tunm = polygons(80, 20) +lvtn = polygons(125, 44) +poly = polygons(66, 20) +hvntm = polygons(125, 20) +nsdm = polygons(nsdm_wildcard) +psdm = polygons(psdm_wildcard) +rpm = polygons(86, 20) +urpm = polygons(79, 20) +npc = polygons(95, 20) +licon = polygons(66, 44) + +li = polygons(li_wildcard) +mcon = polygons(mcon_wildcard) + +m1 = polygons(m1_wildcard) +via = polygons(via_wildcard) + +m2 = polygons(m2_wildcard) +via2 = polygons(via2_wildcard) + +m3 = polygons(m3_wildcard) +via3 = polygons(via3_wildcard) + +m4 = polygons(m4_wildcard) +via4 = polygons(via4_wildcard) + +m5 = polygons(m5_wildcard) + +pad = polygons(76, 20) +nsm = polygons(61, 20) +capm = polygons(89, 44) +cap2m = polygons(97, 44) +vhvi = polygons(74, 21) +uhvi = polygons(74, 22) +npn = polygons(82, 20) +inductor = polygons(82, 24) +vpp = polygons(82, 64) +pnp = polygons(82, 44) +lvs_prune = polygons(84, 44) +ncm = polygons(92, 44) +padcenter = polygons(81, 20) +mf = polygons(76, 44) +areaid_sl = polygons(81, 1) +areaid_ce = polygons(81, 2) +areaid_fe = polygons(81, 3) +areaid_sc = polygons(81, 4) +areaid_sf = polygons(81, 6) +areaid_sw = polygons(81, 7) +areaid_sr = polygons(81, 8) +areaid_mt = polygons(81, 10) +areaid_dt = polygons(81, 11) +areaid_ft = polygons(81, 12) +areaid_ww = polygons(81, 13) +areaid_ld = polygons(81, 14) +areaid_ns = polygons(81, 15) +areaid_ij = polygons(81, 17) +areaid_zr = polygons(81, 18) +areaid_ed = polygons(81, 19) +areaid_de = polygons(81, 23) +areaid_rd = polygons(81, 24) +areaid_dn = polygons(81, 50) +areaid_cr = polygons(81, 51) +areaid_cd = polygons(81, 52) +areaid_st = polygons(81, 53) +areaid_op = polygons(81, 54) +areaid_en = polygons(81, 57) +areaid_en20 = polygons(81, 58) +areaid_le = polygons(81, 60) +areaid_hl = polygons(81, 63) +areaid_sd = polygons(81, 70) +areaid_po = polygons(81, 81) +areaid_it = polygons(81, 84) +areaid_et = polygons(81, 101) +areaid_lvt = polygons(81, 108) +areaid_re = polygons(81, 125) +areaid_ag = polygons(81, 79) +poly_rs = polygons(66, 13) +diff_rs = polygons(65, 13) +pwell_rs = polygons(64, 13) +li_rs = polygons(67, 13) +cfom = polygons(22, 20) + + +# Define a new custom function that selects polygons by their number of holes: +# It will return a new layer containing those polygons with min to max holes. +# max can be nil to omit the upper limit. +class DRC::DRCLayer + def with_holes(min, max) + new_data = RBA::Region::new + self.data.each do |p| + if p.holes >= (min || 0) && (!max || p.holes <= max) + new_data.insert(p) + end + end + DRC::DRCLayer::new(@engine, new_data) + end +end + +# DRC section +######################## +log("DRC section") + +if FEOL +log("FEOL section") +# dnwell +log("START: 64/18 (dnwell)") +dnwell.width(3.0, euclidian).output("dnwell.2", "dnwell.2 : min. dnwell width : 3.0um") +log("END: 64/18 (dnwell)") + +not_sram = layout(source.cell_obj).select("-*sky130_sram_*kbyte_*") +not_sram_nsdm = not_sram.input(nsdm_wildcard) +not_sram_psdm = not_sram.input(psdm_wildcard) +not_sram_nwell = not_sram.input(nwell_wildcard) + +# This is a hack, should be reverted + +not_io = layout(source.cell_obj).select("-*sky130_fd_io__gpiov2_amux", "-*sky130_fd_io__simple_pad_and_busses") +not_io_nwell = not_io.input(nwell_wildcard) + +# nwell +log("START: 64/20 (nwell)") +nwell.width(0.84, euclidian).output("nwell.1", "nwell.1 : min. nwell width : 0.84um") +nwell.space(1.27, euclidian).output("nwell.2a", "nwell.2a : min. nwell spacing (merged if less) : 1.27um") +nwell_interact = not_sram_nwell.and(not_io_nwell).merge +dnwell.enclosing(nwell_interact.holes, 1.03, euclidian).output("nwell.6", "nwell.6 : min enclosure of nwellHole by dnwell : 1.03um") +log("END: 64/20 (nwell)") + +# hvtp +log("START: 78/44 (hvtp)") +hvtp.width(0.38, euclidian).output("hvtp.1", "hvtp.1 : min. hvtp width : 0.38um") +hvtp.space(0.38, euclidian).output("hvtp.2", "hvtp.2 : min. hvtp spacing : 0.38um") +log("END: 78/44 (hvtp)") + +# hvtr +log("START: 18/20 (htvr)") +hvtr.width(0.38, euclidian).output("hvtr.1", "hvtr.1 : min. hvtr width : 0.38um") +hvtr.separation(hvtp, 0.38, euclidian).output("hvtr.2", "hvtr.2 : min. hvtr spacing : 0.38um") +hvtr.and(hvtp).output("hvtr.2_a", "hvtr.2_a : hvtr must not overlap hvtp") +log("END: 18/20 (htvr)") + +# lvtn +log("START: 25/44 (lvtn)") +lvtn.width(0.38, euclidian).output("lvtn.1a", "lvtn.1a : min. lvtn width : 0.38um") +lvtn.space(0.38, euclidian).output("lvtn.2", "lvtn.2 : min. lvtn spacing : 0.38um") +log("END: 25/44 (lvtn)") + +# ncm +log("START: 92/44 (ncm)") +ncm.width(0.38, euclidian).output("ncm.1", "ncm.1 : min. ncm width : 0.38um") +ncm.space(0.38, euclidian).output("ncm.2a", "ncm.2a : min. ncm spacing : 0.38um") +log("END: 92/44 (ncm)") + +# diff-tap +log("START: 65/20 (diff)") +difftap = diff.or(tap) +diff_width = diff.rectangles.width(0.15, euclidian).polygons +diff_cross_areaid_ce = diff_width.edges.outside_part(areaid_ce).not(diff_width.outside(areaid_ce).edges) +diff_cross_areaid_ce.output("difftap.1", "difftap.1 : min. diff width across areaid:ce : 0.15um") +diff.outside(areaid_ce).width(0.15, euclidian).output("difftap.1_a", "difftap.1_a : min. diff width in periphery : 0.15um") +log("END: 65/20 (diff)") + +log("START: 65/44 (tap)") +tap_width = tap.rectangles.width(0.15, euclidian).polygons +tap_cross_areaid_ce = tap_width.edges.outside_part(areaid_ce).not(tap_width.outside(areaid_ce).edges) +tap_cross_areaid_ce.output("difftap.1_b", "difftap.1_b : min. tap width across areaid:ce : 0.15um") +tap.not(areaid_ce).width(0.15, euclidian).output("difftap.1_c", "difftap.1_c : min. tap width in periphery : 0.15um") +log("END: 65/44 (tap)") + +difftap.space(0.27, euclidian).output("difftap.3", "difftap.3 : min. difftap spacing : 0.27um") + +# tunm +log("START: 80/20 (tunm)") +tunm.width(0.41, euclidian).output("tunm.1", "tunm.1 : min. tunm width : 0.41um") +tunm.space(0.5, euclidian).output("tunm.2", "tunm.2 : min. tunm spacing : 0.5um") +log("END: 80/20 (tunm)") + +# poly +log("START: 66/20 (poly)") +poly.width(0.15, euclidian).output("poly.1a", "poly.1a : min. poly width : 0.15um") +poly.not(areaid_ce).space(0.21, euclidian).output("poly.2", "poly.2 : min. poly spacing : 0.21um") + + +# rpm +log("START: 86/20 (rpm)") +rpm.width(1.27, euclidian).output("rpm.1a", "rpm.1a : min. rpm width : 1.27um") +rpm.space(0.84, euclidian).output("rpm.2", "rpm.2 : min. rpm spacing : 0.84um") +log("END: 86/20 (rpm)") + +# urpm +log("START: 79/20 (urpm)") +urpm.width(1.27, euclidian).output("urpm.1a", "urpm.1a : min. rpm width : 1.27um") +urpm.space(0.84, euclidian).output("urpm.2", "urpm.2 : min. rpm spacing : 0.84um") +log("END: 79/20 (urpm)") + +# npc +log("START: 95/20 (npc)") +npc.width(0.27, euclidian).output("npc.1", "npc.1 : min. npc width : 0.27um") +npc.space(0.27, euclidian).output("npc.2", "npc.2 : min. npc spacing, should be manually merged if less than : 0.27um") +log("END: 95/20 (npc)") + +# nsdm +log("START: 93/44 (nsdm)") +not_sram_nsdm.outside(areaid_ce).width(0.38, euclidian).output("nsd.1", "nsd.1 : min. nsdm width : 0.38um") +not_sram_nsdm.not(areaid_ce).space(0.38, euclidian).output("nsd.2", "nsd.2 : min. nsdm spacing, should be manually merged if less than : 0.38um") +log("END: 93/44 (nsdm)") + +# psdm +log("START: 94/20 (psdm)") +not_sram_psdm.outside(areaid_ce).width(0.38, euclidian).output("psd.1", "psd.1 : min. psdm width : 0.38um") +not_sram_psdm.not(areaid_ce).space(0.38, euclidian).output("psd.2", "psd.2 : min. psdm spacing, should be manually merged if less than : 0.38um") +log("END: 94/20 (psdm)") + +# licon +log("START: 66/44 (licon)") +if SEAL + ringLICON = licon.drc(with_holes > 0) + rectLICON = licon.not(ringLICON) +else + rectLICON = licon +end +xfom = difftap.not(poly) +licon1ToXfom = licon.interacting(licon.and(xfom)) +licon1ToXfom_PERI = licon1ToXfom.not(areaid_ce) +rectLICON.non_rectangles.output("licon.1", "licon.1 : licon should be rectangle") +rectLICON.not(rpm.or(urpm)).edges.without_length(0.17).output("licon.1_a/b", "licon.1_a/b : minimum/maximum width of licon : 0.17um") +licon1ToXfom_PERI.separation(npc, 0.09, euclidian).output("licon.13", "licon.13 : min. difftap licon spacing to npc : 0.09um") +licon1ToXfom_PERI.and(npc).output("licon.13_a", "licon.13_a : licon of diffTap in periphery must not overlap npc") +licon.interacting(poly).and(licon.interacting(difftap)).output("licon.17", "licon.17 : Licons may not overlap both poly and (diff or tap)") +log("END: 66/44 (licon)") + +# CAPM +log("START: 89/44 (capm)") +capm.width(1.0, euclidian).output("capm.1", "capm.1 : min. capm width : 1.0um") +capm.space(0.84, euclidian).output("capm.2a", "capm.2a : min. capm spacing : 0.84um") +m3.interacting(capm).isolated(1.2, euclidian).output("capm.2b", "capm.2b : min. capm spacing : 1.2um") +(m3.interacting(capm)).isolated(1.2, euclidian).output("capm.2b_a", "capm.2b_a : min. spacing of m3_bot_plate : 1.2um") +capm.and(m3).enclosing(m3, 0.14, euclidian).output("capm.3", "capm.3 : min. capm and m3 enclosure of m3 : 0.14um") +m3.enclosing(capm, 0.14, euclidian).output("capm.3_a", "capm.3_a : min. m3 enclosure of capm : 0.14um") +capm.enclosing(via3, 0.14, euclidian).output("capm.4", "capm.4 : min. capm enclosure of via3 : 0.14um") +capm.separation(via3, 0.14, euclidian).output("capm.5", "capm.5 : min. capm spacing to via3 : 0.14um") +(m3.not_interacting(capm)).separation(capm, 0.5, euclidian).output("capm.11", "capm.11 : Min spacing of capm and met3 not overlapping capm : 0.5um") +log("END: 89/44 (capm)") + +# CAP2M +log("START: 97/44 (cap2m)") +cap2m.width(1.0, euclidian).output("cap2m.1", "cap2m.1 : min. cap2m width : 1.0um") +cap2m.space(0.84, euclidian).output("cap2m.2a", "cap2m.2a : min. cap2m spacing : 0.84um") +m4.interacting(cap2m).isolated(1.2, euclidian).output("cap2m.2b", "cap2m.2b : min. cap2m spacing : 1.2um") +(m4.interacting(cap2m)).isolated(1.2, euclidian).output("cap2m.2b_a", "cap2m.2b_a : min. spacing of m4_bot_plate : 1.2um") +cap2m.and(m4).enclosing(m4, 0.14, euclidian).output("cap2m.3", "cap2m.3 : min. m4 enclosure of cap2m : 0.14um") +m4.enclosing(cap2m, 0.14, euclidian).output("cap2m.3_a", "cap2m.3_a : min. m4 enclosure of cap2m : 0.14um") +cap2m.enclosing(via4, 0.2, euclidian).output("cap2m.4", "cap2m.4 : min. cap2m enclosure of via4 : 0.14um") +cap2m.separation(via4, 0.2, euclidian).output("cap2m.5", "cap2m.5 : min. cap2m spacing to via4 : 0.14um") +(m4.not_interacting(cap2m)).separation(cap2m, 0.5, euclidian).output("cap2m.11", "cap2m.11 : Min spacing of cap2m and met4 not overlapping cap2m : 0.5um") +log("END: 97/44 (cap2m)") +end #FEOL + +if BEOL +log("BEOL section") + +# li +log("START: 67/20 (li)") +linotace = li.not(li.interacting(areaid_ce)) +linotace.width(0.17, euclidian).output("li.1", "li.1 : min. li width : 0.17um") +# This rule is taking a long time in some slots +linotace.edges.space(0.17, euclidian).output("li.3", "li.3 : min. li spacing : 0.17um") +licon_peri = licon.not(areaid_ce) +li_edges_with_less_enclosure = li.enclosing(licon_peri, 0.08, projection).second_edges +error_corners = li_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) +li_interact = licon_peri.interacting(error_corners.polygons(1.dbu)) +li_interact.output("li.5", "li.5 : min. li enclosure of licon of 2 adjacent edges : 0.08um") +linotace.with_area(nil, 0.0561).output("li.6", "li.6 : min. li area : 0.0561um²") +log("END: 67/20 (li)") + +# ct +log("START: 67/44 (mcon)") +mconnotace = mcon.not(areaid_ce) +if SEAL + ringMCON = mcon.drc(with_holes > 0) + rectMCON = mcon.not(ringMCON) +else + rectMCON = mcon +end +rectMCON_peri = rectMCON.not(areaid_ce) +rectMCON.non_rectangles.output("ct.1", "ct.1: non-ring mcon should be rectangular") +# rectMCON_peri.edges.without_length(0.17).output("ct.1_a/b", "ct.1_a/b : minimum/maximum width of mcon : 0.17um") +rectMCON_peri.drc(width < 0.17).output("ct.1_a", "ct.1_a : minimum width of mcon : 0.17um") +rectMCON_peri.drc(length > 0.17).output("ct.1_b", "ct.1_b : maximum length of mcon : 0.17um") +mcon.space(0.19, euclidian).output("ct.2", "ct.2 : min. mcon spacing : 0.19um") +if SEAL + ringMCON.width(0.17, euclidian).output("ct.3", "ct.3 : min. width of ring-shaped mcon : 0.17um") + ringMCON.drc(width >= 0.175).output("ct.3_a", "ct.3_a : max. width of ring-shaped mcon : 0.175um") + ringMCON.not(areaid_sl).output("ct.3_b", "ct.3_b: ring-shaped mcon must be enclosed by areaid_sl") +end +mconnotace.not(li).output("ct.4", "ct.4 : mcon should covered by li") +log("END: 67/44 (mcon)") + +# m1 +log("START: 68/20 (m1)") +m1.width(0.14, euclidian).output("m1.1", "m1.1 : min. m1 width : 0.14um") +huge_m1 = m1.sized(-1.5).sized(1.5).snap(0.005) & m1 +non_huge_m1 = m1.edges - huge_m1 +huge_m1 = huge_m1.edges.outside_part(m1.merged) + +non_huge_m1.space(0.14, euclidian).output("m1.2", "m1.2 : min. m1 spacing : 0.14um") + +(huge_m1.separation(non_huge_m1, 0.28, euclidian) + huge_m1.space(0.28, euclidian)).output("m1.3ab", "m1.3ab : min. 3um.m1 spacing m1 : 0.28um") + +#not_in_cell6 = layout(source.cell_obj).select("-s8cell_ee_plus_sseln_a", "-s8cell_ee_plus_sseln_b", "-s8cell_ee_plus_sselp_a", "-s8cell_ee_plus_sselp_b", "-s8fpls_pl8", "-s8fs_cmux4_fm") +not_in_cell6 = layout(source.cell_obj).select("-s8cell_ee_plus_sseln_a", "-s8cell_ee_plus_sseln_b", "-s8cell_ee_plus_sselp_a", "-s8cell_ee_plus_sselp_b", "-s8fs_cmux4_fm") +not_in_cell6_m1 = not_in_cell6.input(m1_wildcard) + +not_in_cell6_m1.enclosing(mconnotace, 0.03, euclidian).output("791_m1.4", "791_m1.4 : min. m1 enclosure of mcon : 0.03um") +mconnotace.not(m1).output("m1.4", "m1.4 : mcon periphery must be enclosed by m1") +in_cell6 = layout(source.cell_obj).select("-*", "+s8cell_ee_plus_sseln_a", "+s8cell_ee_plus_sseln_b", "+s8cell_ee_plus_sselp_a", "+s8cell_ee_plus_sselp_b", "+s8fpls_pl8", "+s8fs_cmux4_fm") +in_cell6_m1 = in_cell6.input(m1_wildcard) +in_cell6_m1.enclosing(mcon, 0.005, euclidian).output("m1.4a", "m1.4a : min. m1 enclosure of mcon for specific cells : 0.005um") + +in_cell6_m1.not(m1).output('m1.4a_a', 'm1.4a_a : mcon periph must be enclosed by met1 for specific cells') + +m1.with_area(0..0.083).output("m1.6", "m1.6 : min. m1 area : 0.083um²") + +m1.holes.with_area(0..0.14).output("m1.7", "m1.7 : min. m1 with holes area : 0.14um²") +if FLOATING_MET + m1.not_interacting(via.or(mcon)).output("m1.x", "floating met1, must interact with via1") +end + +if backend_flow = AL + #Could flag false positive, fix would be to add .rectangles for m1 + mconnotace_edges_with_less_enclosure_m1 = m1.enclosing(mconnotace, 0.06, projection).second_edges + error_corners_m1 = mconnotace_edges_with_less_enclosure_m1.width(angle_limit(100.0), 1.dbu) + mconnotace_interact_m1 = mconnotace.interacting(error_corners_m1.polygons(1.dbu)) + mconnotace_interact_m1.output("m1.5", "m1.5 : min. m1 enclosure of mcon of 2 adjacent edges : 0.06um") +end +log("END: 68/20 (m1)") + +# via +log("START: 68/44 (via)") +if backend_flow = AL + if SEAL + ringVIA = via.drc(with_holes > 0) + rectVIA = via.not(ringVIA) + else + rectVIA = via + end + + via_not_mt = rectVIA.not(areaid_mt) + + via_not_mt.non_rectangles.output("via.1a", "via.1a : via outside of moduleCut should be rectangular") + via_not_mt.width(0.15, euclidian).output("via.1a_a", "via.1a_a : min. width of via outside of moduleCut : 0.15um") + # via_not_mt.edges.without_length(nil, 0.15 + 1.dbu).output("via.1a_b", "via.1a_b : maximum length of via : 0.15um") + via_not_mt.drc(length > 0.15).output("via.1a_b", "via.1a_b : maximum length of via : 0.15um") + + via.space(0.17, euclidian).output("via.2", "via.2 : min. via spacing : 0.17um") + + if SEAL + ringVIA.width(0.2, euclidian).output("via.3", "via.3 : min. width of ring-shaped via : 0.2um") + ringVIA.drc(width >= 0.205).output("via.3_a", "via.3_a : max. width of ring-shaped via : 0.205um") + ringVIA.not(areaid_sl).output("via.3_b", "via.3_b: ring-shaped via must be enclosed by areaid_sl") + end + + m1.edges.enclosing(rectVIA.drc(width == 0.15), 0.055, euclidian).output("via.4a", "via.4a : min. m1 enclosure of 0.15um via : 0.055um") + rectVIA.squares.drc(width == 0.15).not(m1).output("via.4a_a", "via.4a_a : 0.15um via must be enclosed by met1") + + via1_edges_with_less_enclosure_m1 = m1.edges.enclosing(rectVIA.drc(width == 0.15), 0.085, projection).second_edges + error_corners_via1 = via1_edges_with_less_enclosure_m1.width(angle_limit(100.0), 1.dbu) + via2_interact = via.interacting(error_corners_via1.polygons(1.dbu)) + via2_interact.output("via.5a", "via.5a : min. m1 enclosure of 0.15um via of 2 adjacent edges : 0.085um") + +end +log("END: 68/44 (via)") + +# m2 +log("START: 69/20 (m2)") +m2.width(0.14, euclidian).output("m2.1", "m2.1 : min. m2 width : 0.14um") + +huge_m2 = m2.sized(-1.5).sized(1.5).snap(0.005) & m2 +non_huge_m2 = m2.edges - huge_m2 +huge_m2 = huge_m2.edges.outside_part(m2.merged) +via_outside_periphery = via.not(areaid_ce) + +non_huge_m2.space(0.14, euclidian).output("m2.2", "m2.2 : min. m2 spacing : 0.14um") + +(huge_m2.separation(non_huge_m2, 0.28, euclidian) + huge_m2.space(0.28, euclidian)).output("m2.3ab", "m2.3ab : min. 3um.m2 spacing m2 : 0.28um") + +m2.with_area(0..0.0676).output("m2.6", "m2.6 : min. m2 area : 0.0676um²") +m2.holes.with_area(0..0.14).output("m2.7", "m2.7 : min. m2 holes area : 0.14um²") +if FLOATING_MET + m2.not_interacting(via.or(via2)).output("m2.x", "floating met2, must interact with via1 or via2") +end +if backend_flow = AL + m2.enclosing(via_outside_periphery, 0.055, euclidian).output("m2.4", "m2.4 : min. m2 enclosure of via : 0.055um") + via_outside_periphery.not(m2).output("m2.4_a", "m2.4_a : via in periphery must be enclosed by met2") + via_edges_with_less_enclosure_m2 = m2.enclosing(via, 0.085, projection).second_edges + error_corners = via_edges_with_less_enclosure_m2.width(angle_limit(100.0), 1.dbu) + via_interact = via.interacting(error_corners.polygons(1.dbu)) + via_interact.output("m2.5", "m2.5 : min. m2 enclosure of via of 2 adjacent edges : 0.085um") + +end +log("END: 69/20 (m2)") + +# via2 +log("START: 69/44 (via2)") +if backend_flow = AL + if SEAL + ringVIA2 = via2.drc(with_holes > 0) + rectVIA2 = via2.not(ringVIA2) + else + rectVIA2 = via2 + end + + via2_not_mt = rectVIA2.not(areaid_mt) + via2_not_mt.non_rectangles.output("via2.1a", "via2.1a : via2 outside of moduleCut should be rectangular") + via2_not_mt.width(0.2, euclidian).output("via2.1a_a", "via2.1a_a : min. width of via2 outside of moduleCut : 0.2um") + via2_not_mt.edges.without_length(nil, 0.2 + 1.dbu).output("via2.1a_b", "via2.1a_b : maximum length of via2 : 0.2um") + via2.space(0.2, euclidian).output("via2.2", "via2.2 : min. via2 spacing : 0.2um") + + if SEAL + ringVIA2.width(0.2, euclidian).output("via2.3", "via2.3 : min. width of ring-shaped via2 : 0.2um") + ringVIA2.drc(width >= 0.205).output("via2.3_a", "via2.3_a : max. width of ring-shaped via2 : 0.205um") + ringVIA2.not(areaid_sl).output("via2.3_b", "via2.3_b: ring-shaped via2 must be enclosed by areaid_sl") + end + + m2.enclosing(via2, 0.04, euclidian).output("via2.4", "via2.4 : min. m2 enclosure of via2 : 0.04um") + via2.not(m2).output("via2.4_a", "via2.4_a : via must be enclosed by met2") + + via2_edges_with_less_enclosure = m2.enclosing(via2, 0.085, projection).second_edges + error_corners = via2_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) + via2_interact = via2.interacting(error_corners.polygons(1.dbu)) + via2_interact.output("via2.5", "via2.5 : min. m3 enclosure of via2 of 2 adjacent edges : 0.085um") +end +log("END: 69/44 (via2)") + +# m3 +log("START: 70/20 (m3)") +m3.width(0.3, euclidian).output("m3.1", "m3.1 : min. m3 width : 0.3um") + +huge_m3 = m3.sized(-1.5).sized(1.5).snap(0.005) & m3 +non_huge_m3 = m3.edges - huge_m3 +huge_m3 = huge_m3.edges.outside_part(m3.merged) + +non_huge_m3.space(0.3, euclidian).output("m3.2", "m3.2 : min. m3 spacing : 0.3um") + +(huge_m3.separation(non_huge_m3, 0.4, euclidian) + huge_m3.space(0.4, euclidian)).output("m3.3cd", "m3.3cd : min. 3um.m3 spacing m3 : 0.4um") +if FLOATING_MET + m3.not_interacting(via2.or(via3)).output("m3.x", "floating met3, must interact with via2 or via3") +end +if backend_flow = AL + m3.enclosing(via2, 0.065, euclidian).output("m3.4", "m3.4 : min. m3 enclosure of via2 : 0.065um") + via2.not(m3).output("m3.4_a", "m3.4_a : via2 must be enclosed by met3") +end +log("END: 70/20 (m3)") + +# via3 +log("START: 70/44 (via3)") +if backend_flow = AL + if SEAL + ringVIA3 = via3.drc(with_holes > 0) + rectVIA3 = via3.not(ringVIA3) + else + rectVIA3 = via3 + end + + via3_not_mt = rectVIA3.not(areaid_mt) + via3_not_mt.non_rectangles.output("via3.1", "via3.1 : via3 outside of moduleCut should be rectangular") + via3_not_mt.width(0.2, euclidian).output("via3.1_a", "via3.1_a : min. width of via3 outside of moduleCut : 0.2um") + via3_not_mt.edges.without_length(nil, 0.2 + 1.dbu).output("via3.1_b", "via3.1_b : maximum length of via3 : 0.2um") + + via3.space(0.2, euclidian).output("via3.2", "via3.2 : min. via3 spacing : 0.2um") + m3.enclosing(via3, 0.06, euclidian).output("via3.4", "via3.4 : min. m3 enclosure of via3 : 0.06um") + rectVIA3.not(m3).output("via3.4_a", "via3.4_a : non-ring via3 must be enclosed by met3") + + via_edges_with_less_enclosure = m3.enclosing(via3, 0.09, projection).second_edges + error_corners = via_edges_with_less_enclosure.width(angle_limit(100.0), 1.dbu) + via3_interact = via3.interacting(error_corners.polygons(1.dbu)) + via3_interact.output("via3.5", "via3.5 : min. m3 enclosure of via3 of 2 adjacent edges : 0.09um") +end +log("END: 70/44 (via3)") + +# m4 +log("START: 71/20 (m4)") +m4.width(0.3, euclidian).output("m4.1", "m4.1 : min. m4 width : 0.3um") + +huge_m4 = m4.sized(-1.5).sized(1.5).snap(0.005) & m4 +non_huge_m4 = m4.edges - huge_m4 +huge_m4 = huge_m4.edges.outside_part(m4.merged) + +non_huge_m4.space(0.3, euclidian).output("m4.2", "m4.2 : min. m4 spacing : 0.3um") + +m4.with_area(0..0.240).output("m4.4a", "m4.4a : min. m4 area : 0.240um²") + +(huge_m4.separation(non_huge_m4, 0.4, euclidian) + huge_m4.space(0.4, euclidian)).output("m4.5ab", "m4.5ab : min. 3um.m4 spacing m4 : 0.4um") +if FLOATING_MET + m4.not_interacting(via3.or(via4)).output("m4.x", "floating met3, must interact with via3 or via4") +end +if backend_flow = AL + m4.enclosing(via3, 0.065, euclidian).output("m4.3", "m4.3 : min. m4 enclosure of via3 : 0.065um") + via3.not(m4).output("m4.3_a", "m4.3_a : via3 must be enclosed by met4") +end +log("END: 71/20 (m4)") + +# via4 +log("START: 71/44 (via4)") +if SEAL + ringVIA4 = via4.drc(with_holes > 0) + rectVIA4 = via4.not(ringVIA4) +else + rectVIA4 = via4 +end + +via4_not_mt = rectVIA4.not(areaid_mt) +via4_not_mt.non_rectangles.output("via4.1", "via4.1 : via4 outside of moduleCut should be rectangular") +rectVIA4.width(0.8, euclidian).output("via4.1_a", "via4.1_a : min. width of via4 outside of moduleCut : 0.8um") +rectVIA4.drc(length > 0.8).output("via4.1_b", "via4.1_b : maximum length of via4 : 0.8um") + +via4.space(0.8, euclidian).polygons.output("via4.2", "via4.2 : min. via4 spacing : 0.8um") + +if SEAL + ringVIA4.width(0.8, euclidian).output("via4.3", "via4.3 : min. width of ring-shaped via4 : 0.8um") + ringVIA4.drc(width >= 0.805).output("via4.3_a", "via4.3_a : max. width of ring-shaped via4 : 0.805um") + ringVIA4.not(areaid_sl).output("via4.3_b", "via4.3_b: ring-shaped via4 must be enclosed by areaid_sl") +end + +m4.enclosing(via4, 0.19, euclidian).output("via4.4", "via4.4 : min. m4 enclosure of via4 : 0.19um") +rectVIA4.not(m4).output("via4.4_a", "via4.4_a : m4 must enclose all via4") +log("END: 71/44 (via4)") + +# m5 +log("START: 72/20 (m5)") +m5.width(1.6, euclidian).output("m5.1", "m5.1 : min. m5 width : 1.6um") + +m5.space(1.6, euclidian).output("m5.2", "m5.2 : min. m5 spacing : 1.6um") + +m5.enclosing(via4, 0.31, euclidian).output("m5.3", "m5.3 : min. m5 enclosure of via4 : 0.31um") +via4.not(m5).output("m5.3_a", "m5.3_a : via must be enclosed by m5") +if FLOATING_MET + m5.not_interacting(via4).output("m5.x", "floating met5, must interact with via4") +end +m5.with_area(0..4.0).output("m5.4", "m5.4 : min. m5 area : 4.0um²") +log("END: 72/20 (m5)") + +# pad +log("START: 76/20 (pad)") +pad.space(1.27, euclidian).output("pad.2", "pad.2 : min. pad spacing : 1.27um") +log("END: 76/20 (pad)") + +end #BEOL + +if FEOL +log("FEOL section") + +# hvi +log("START: 75/20 (hvi)") +hvi_peri = hvi.not(areaid_ce) +hvi_peri.width(0.6, euclidian).output("hvi.1", "hvi.1 : min. hvi width : 0.6um") +hvi_peri.space(0.7, euclidian).output("hvi.2a", "hvi.2a : min. hvi spacing : 0.7um") +log("END: 75/20 (hvi)") + +# hvntm +log("START: 125/20 (hvntm)") +hvntm_peri = hvntm.not(areaid_ce) +hvntm_peri.width(0.7, euclidian).output("hvntm.1", "hvntm.1 : min. hvntm width : 0.7um") +hvntm_peri.space(0.7, euclidian).output("hvntm.2", "hvntm.2 : min. hvntm spacing : 0.7um") +log("END: 125/20 (hvntm)") + +end #FEOL + + +if OFFGRID +log("OFFGRID-ANGLES section") + +dnwell.ongrid(0.005).output("dnwell_OFFGRID", "x.1b : OFFGRID vertex on dnwell") +dnwell.with_angle(0 .. 45).output("dnwell_angle", "x.3a : non 45 degree angle dnwell") +nwell.ongrid(0.005).output("nwell_OFFGRID", "x.1b : OFFGRID vertex on nwell") +nwell.with_angle(0 .. 45).output("nwell_angle", "x.3a : non 45 degree angle nwell") +pwbm.ongrid(0.005).output("pwbm_OFFGRID", "x.1b : OFFGRID vertex on pwbm") +pwbm.with_angle(0 .. 45).output("pwbm_angle", "x.3a : non 45 degree angle pwbm") +pwde.ongrid(0.005).output("pwde_OFFGRID", "x.1b : OFFGRID vertex on pwde") +pwde.with_angle(0 .. 45).output("pwde_angle", "x.3a : non 45 degree angle pwde") +hvtp.ongrid(0.005).output("hvtp_OFFGRID", "x.1b : OFFGRID vertex on hvtp") +hvtp.with_angle(0 .. 45).output("hvtp_angle", "x.3a : non 45 degree angle hvtp") +hvtr.ongrid(0.005).output("hvtr_OFFGRID", "x.1b : OFFGRID vertex on hvtr") +hvtr.with_angle(0 .. 45).output("hvtr_angle", "x.3a : non 45 degree angle hvtr") +lvtn.ongrid(0.005).output("lvtn_OFFGRID", "x.1b : OFFGRID vertex on lvtn") +lvtn.with_angle(0 .. 45).output("lvtn_angle", "x.3a : non 45 degree angle lvtn") +ncm.ongrid(0.005).output("ncm_OFFGRID", "x.1b : OFFGRID vertex on ncm") +ncm.with_angle(0 .. 45).output("ncm_angle", "x.3a : non 45 degree angle ncm") +diff.ongrid(0.005).output("diff_OFFGRID", "x.1b : OFFGRID vertex on diff") +tap.ongrid(0.005).output("tap_OFFGRID", "x.1b : OFFGRID vertex on tap") +diff.not(areaid_en.and(uhvi)).with_angle(0 .. 90).output("diff_angle", "x.2 : non 90 degree angle diff") +diff.and(areaid_en.and(uhvi)).with_angle(0 .. 45).output("diff_angle", "x.2c : non 45 degree angle diff") +tap.not(areaid_en.and(uhvi)).with_angle(0 .. 90).output("tap_angle", "x.2 : non 90 degree angle tap") +tap.and(areaid_en.and(uhvi)).with_angle(0 .. 45).output("tap_angle", "x.2c : non 45 degree angle tap") +tunm.ongrid(0.005).output("tunm_OFFGRID", "x.1b : OFFGRID vertex on tunm") +tunm.with_angle(0 .. 45).output("tunm_angle", "x.3a : non 45 degree angle tunm") +poly.ongrid(0.005).output("poly_OFFGRID", "x.1b : OFFGRID vertex on poly") +poly.with_angle(0 .. 90).output("poly_angle", "x.2 : non 90 degree angle poly") +rpm.ongrid(0.005).output("rpm_OFFGRID", "x.1b : OFFGRID vertex on rpm") +rpm.with_angle(0 .. 45).output("rpm_angle", "x.3a : non 45 degree angle rpm") +npc.ongrid(0.005).output("npc_OFFGRID", "x.1b : OFFGRID vertex on npc") +npc.with_angle(0 .. 45).output("npc_angle", "x.3a : non 45 degree angle npc") +nsdm.ongrid(0.005).output("nsdm_OFFGRID", "x.1b : OFFGRID vertex on nsdm") +nsdm.with_angle(0 .. 45).output("nsdm_angle", "x.3a : non 45 degree angle nsdm") +psdm.ongrid(0.005).output("psdm_OFFGRID", "x.1b : OFFGRID vertex on psdm") +psdm.with_angle(0 .. 45).output("psdm_angle", "x.3a : non 45 degree angle psdm") +licon.ongrid(0.005).output("licon_OFFGRID", "x.1b : OFFGRID vertex on licon") +licon.with_angle(0 .. 90).output("licon_angle", "x.2 : non 90 degree angle licon") +li.ongrid(0.005).output("li_OFFGRID", "x.1b : OFFGRID vertex on li") +li.with_angle(0 .. 45).output("li_angle", "x.3a : non 45 degree angle li") +mcon.ongrid(0.005).output("ct_OFFGRID", "x.1b : OFFGRID vertex on mcon") +mcon.with_angle(0 .. 90).output("ct_angle", "x.2 : non 90 degree angle mcon") +vpp.ongrid(0.005).output("vpp_OFFGRID", "x.1b : OFFGRID vertex on vpp") +vpp.with_angle(0 .. 45).output("vpp_angle", "x.3a : non 45 degree angle vpp") +m1.ongrid(0.005).output("m1_OFFGRID", "x.1b : OFFGRID vertex on m1") +m1.with_angle(0 .. 45).output("m1_angle", "x.3a : non 45 degree angle m1") +via.ongrid(0.005).output("via_OFFGRID", "x.1b : OFFGRID vertex on via") +via.with_angle(0 .. 90).output("via_angle", "x.2 : non 90 degree angle via") +m2.ongrid(0.005).output("m2_OFFGRID", "x.1b : OFFGRID vertex on m2") +m2.with_angle(0 .. 45).output("m2_angle", "x.3a : non 45 degree angle m2") +via2.ongrid(0.005).output("via2_OFFGRID", "x.1b : OFFGRID vertex on via2") +via2.with_angle(0 .. 90).output("via2_angle", "x.2 : non 90 degree angle via2") +m3.ongrid(0.005).output("m3_OFFGRID", "x.1b : OFFGRID vertex on m3") +m3.with_angle(0 .. 45).output("m3_angle", "x.3a : non 45 degree angle m3") +via3.ongrid(0.005).output("via3_OFFGRID", "x.1b : OFFGRID vertex on via3") +via3.with_angle(0 .. 90).output("via3_angle", "x.2 : non 90 degree angle via3") +nsm.ongrid(0.005).output("nsm_OFFGRID", "x.1b : OFFGRID vertex on nsm") +nsm.with_angle(0 .. 45).output("nsm_angle", "x.3a : non 45 degree angle nsm") +m4.ongrid(0.005).output("m4_OFFGRID", "x.1b : OFFGRID vertex on m4") +m4.with_angle(0 .. 45).output("m4_angle", "x.3a : non 45 degree angle m4") +via4.ongrid(0.005).output("via4_OFFGRID", "x.1b : OFFGRID vertex on via4") +via4.with_angle(0 .. 90).output("via4_angle", "x.2 : non 90 degree angle via4") +m5.ongrid(0.005).output("m5_OFFGRID", "x.1b : OFFGRID vertex on m5") +m5.with_angle(0 .. 45).output("m5_angle", "x.3a : non 45 degree angle m5") +pad.ongrid(0.005).output("pad_OFFGRID", "x.1b : OFFGRID vertex on pad") +pad.with_angle(0 .. 45).output("pad_angle", "x.3a : non 45 degree angle pad") +mf.ongrid(0.005).output("mf_OFFGRID", "x.1b : OFFGRID vertex on mf") +mf.with_angle(0 .. 90).output("mf_angle", "x.2 : non 90 degree angle mf") +hvi.ongrid(0.005).output("hvi_OFFGRID", "x.1b : OFFGRID vertex on hvi") +hvi.with_angle(0 .. 45).output("hvi_angle", "x.3a : non 45 degree angle hvi") +hvntm.ongrid(0.005).output("hvntm_OFFGRID", "x.1b : OFFGRID vertex on hvntm") +hvntm.with_angle(0 .. 45).output("hvntm_angle", "x.3a : non 45 degree angle hvntm") +vhvi.ongrid(0.005).output("vhvi_OFFGRID", "x.1b : OFFGRID vertex on vhvi") +vhvi.with_angle(0 .. 45).output("vhvi_angle", "x.3a : non 45 degree angle vhvi") +uhvi.ongrid(0.005).output("uhvi_OFFGRID", "x.1b : OFFGRID vertex on uhvi") +uhvi.with_angle(0 .. 45).output("uhvi_angle", "x.3a : non 45 degree angle uhvi") +pwell_rs.ongrid(0.005).output("pwell_rs_OFFGRID", "x.1b : OFFGRID vertex on pwell_rs") +pwell_rs.with_angle(0 .. 45).output("pwell_rs_angle", "x.3a : non 45 degree angle pwell_rs") +areaid_re.ongrid(0.005).output("areaid_re_OFFGRID", "x.1b : OFFGRID vertex on areaid.re") + +end #OFFGRID +logger.info(" ") +logger.info("Cell exclusion list:") +logger.info(" rule | cell") +logger.info(" nwell.6 | sky130_fd_io__gpiov2_amux, sky130_fd_io__simple_pad_and_busses, sram") +logger.info(" nsd.1 | sram") +logger.info(" nsd.2 | sram") +logger.info(" psd.1 | sram") +logger.info(" psd.2 | sram") +logger.info(" ") +logger.info("release #{release}") \ No newline at end of file