diff --git a/precheck/precheck.py b/precheck/precheck.py index 366d3bc..f8a4100 100644 --- a/precheck/precheck.py +++ b/precheck/precheck.py @@ -58,7 +58,7 @@ def magic_drc(gds: str, toplevel: str): raise PrecheckFailure("Magic DRC failed") -def klayout_drc(gds: str, check: str): +def klayout_drc(gds: str, check: str, script=f"{PDK_NAME}_mr.drc"): logging.info(f"Running klayout {check} on {gds}") report_file = f"{REPORTS_PATH}/drc_{check}.xml" klayout = subprocess.run( @@ -66,7 +66,7 @@ def klayout_drc(gds: str, check: str): "klayout", "-b", "-r", - f"tech-files/{PDK_NAME}_mr.drc", + f"tech-files/{script}", "-rd", f"{check}=true", "-rd", @@ -126,6 +126,14 @@ def main(): ["KLayout FEOL", lambda: klayout_drc(args.gds, "feol")], ["KLayout BEOL", lambda: klayout_drc(args.gds, "beol")], ["KLayout offgrid", lambda: klayout_drc(args.gds, "offgrid")], + [ + "KLayout pin label overlapping drawing", + lambda: klayout_drc( + args.gds, + "pin_label_purposes_overlapping_drawing", + "pin_label_purposes_overlapping_drawing.rb.drc", + ), + ], ["KLayout Checks", lambda: klayout_checks(args.gds)], ] @@ -144,7 +152,7 @@ def main(): except Exception as e: error_count += 1 elapsed_time = time.time() - start_time - markdown_table += f"| {name} | Fail: {str(e)} |\n" + markdown_table += f"| {name} | ❌ Fail: {str(e)} |\n" test_case.set("time", str(round(elapsed_time, 2))) error = ET.SubElement(test_case, "error", message=str(e)) error.text = traceback.format_exc() diff --git a/precheck/reports/.gitignore b/precheck/reports/.gitignore index 28123ad..ec7b089 100644 --- a/precheck/reports/.gitignore +++ b/precheck/reports/.gitignore @@ -1,6 +1,7 @@ drc_beol.xml drc_feol.xml drc_offgrid.xml +drc_pin_label_purposes_overlapping_drawing.xml magic_drc.mag magic_drc.txt results.md diff --git a/precheck/tech-files/README.txt b/precheck/tech-files/README.txt index 51418ca..d99ee3b 100644 --- a/precheck/tech-files/README.txt +++ b/precheck/tech-files/README.txt @@ -1 +1,2 @@ sky130A_mr.drc from https://github.com/efabless/mpw_precheck revision 4efb33553ee6c2acdb0fa9a6a5f87af099f5c1d5 +pin_label_purposes_overlapping_drawing.rb.drc from https://github.com/efabless/mpw_precheck revision f6b9c3d3f00694f96dce8444149449b4719180f0 diff --git a/precheck/tech-files/pin_label_purposes_overlapping_drawing.rb.drc b/precheck/tech-files/pin_label_purposes_overlapping_drawing.rb.drc new file mode 100644 index 0000000..7368b58 --- /dev/null +++ b/precheck/tech-files/pin_label_purposes_overlapping_drawing.rb.drc @@ -0,0 +1,286 @@ +# usage: +# klayout -r pin_label_purposes_overlapping_drawing <-rd threads=THREADS_NUM> <-rd tile=TILE_SIZE> <-rd flat_mode=true> <-rd check_pwell=true> +# +# Flag pin.not(drawing) for all known pin/label layers in sky130A. +# WARNING: if markerFile is RELATIVE-PATH it is written in SAME-DIR as input-GDS. +# +# pwell & pwelliso not checked by default. Believe labelling pwell without drawn pwell is legal. +# +# TODO?: are results correct if deep mode, for pin & drawing in different hier. levels? +# +# Exit status (does not work in klayout 0.23.11; does in 0.24 and later): +# 1 : I/O error or other internal error (uncaught exceptions). +# 2...127 : means 1... rules did flag error(s). If over 126 rules had errors, status is 127 max. +# That is this # is the how many rule-types had non-zero errors, NOT total errors count. +# 0 : no rules flagged errors. +# If process dies thru signal, exit status is 128+SIGNUM, so that range is reserved. +# i.e. if kernel oom-killer sends kill -9: status=137. +# +# Runs klayout (in batch) to do partial/crude layer grid checks; output to a MarkerDB (*.lyrdb) +# Crude because no partitioning is done, to enforce unique grid requirements by areaid. +# +# Script starts as regular ruby, then exec's via klayout passing self to it. +# (klayout requirement is this script-name *must* end in .drc). +# +# Known reasons why mult. klayout-versions produce non-identical output: +# 1. In some earlier versions (than 0.27), markerReport may state "wrong" generator: +# :/built-in-macros/drc.lym +# the "better" generator would look like: +# drc: script='/gdsSky130Apin1.drc' +# 2. When errors are flagged, the ordering of errors may differ between klayout versions. +# + +# include RBA +begin + +if $flat_mode == "true" + flat # flat, with or without tiling +else + deep +end + +if $check_pwell == "true" + $check_pwell = true +else + $check_pwell = false +end + +if !$threads || $threads.to_i == 0 + $threads=`nproc` +end + +if $tile + if $tile.to_f > 0 + flat + tiles($tile) + puts "Tiling Enabled" + # no border + # tile_borders(0) + no_borders + end +end + +threads($threads.to_i) +title = "pin_label_purposes_overlapping_drawing.rb.drc, input=#{$input}, topcell=#{$top_cell_name}" +puts "Running pin_label_purposes_overlapping_drawing.rb.drc on file=#{$input}, topcell=#{$top_cell_name}, output to #{$report}" +puts " deep:#{is_deep?} tiled:#{is_tiled?} threads:#{$threads}" + +STDOUT.flush + +source($input, $top_cell_name) +report(title, $report) + +# $ruleHeader = "Checks with errors:" +$ruleHeader = "--- #err|description, table for cell: %s" % [$top_cell_name] +$didHeader = false +$errs = 0 +$totals = 0 +$rules = 0 + +# +# Direct rule checks like: +# m2.width(1.5).output("m2 width < 1.5") +# write to report() but don't give opportunity to count/print which rules did flag. +# Instead: +# rule(m2.width(1.5), "m2 width < 1.5") +# +# Wish to use direct-form, and just tabulate/post-process report after the fact. +# This form (empty or not) still does not nicely report/summarize error-count per-rule. + +# Return value not meaningful if the is_empty? fails (in 0.26.9 or earlier). +# +def rule(marker, msg) + $rules = $rules + 1 + empty = false + size = -1 + emptyFails = false + + # test if marker is empty. + # marker.is_empty? : works in 0.27, not 0.26 and earlier. + # marker.size : does not work in any so far. + # marker.bbox appears universal, works in klayout versions 0.23.11 ... 0.27. + # In case it fails, catch exception and revert to less information in our stdout. + begin + size = marker.data.size + empty = (size == 0) + # works all versions: size = marker.data.size + # works all versions: empty = marker.bbox.to_s == "()" + # fails pre 0.27: empty = marker.is_empty? + rescue StandardError => e + # when can't determine which rules flagged errors, signal not to summarize. + emptyFails = true + empty = false + size = -1 + if $errs >= 0 + $errs = -8 + end + if ($errs & 1) == 0 + puts "turned off marker empty detect..." + end + $errs = $errs | 1 + end + if ! empty + marker.output(msg) + if ! emptyFails + if $errs == 0 && ! $didHeader + puts $ruleHeader + $didHeader = true + end + $errs = $errs + 1 + $totals = $totals + size + puts "%8d %s" % [size, msg] + return 1 + end + end + return 0 +end + +# call like: +# +# pinCheck( "met1", 68,20, 68,16, 68, 5 ) +# +# where 68,20 is met1/drawing, and purposes 16 & 5 are pin,label +# +# It is an error if called: +# ... with less than 5 args, i.e. MUST have at least one non-drawing layer-purpose-pair. +# e.g.: pinCheck( "met1", 68,20 ) +# ... with an odd number of arguments after the first three. +# e.g.: pinCheck( "met1", 68,20, 68 ) +# e.g.: pinCheck( "met1", 68,20, 68,16, 68 ) +# +# Variations: +# pinCheck: do the regular AndNot pin check +# pinSkip: do not do the check, but still report on which lpps have data vs empty. +def pinCheck(name, layn, typen, *nonMaskLpps) + pinCheckIf(true, name, layn, typen, *nonMaskLpps) +end +def pinSkip(name, layn, typen, *nonMaskLpps) + pinCheckIf(false, name, layn, typen, *nonMaskLpps) +end +def pinCheckIf(checkp, name, layn, typen, *nonMaskLpps) + if nonMaskLpps.length == 0 || nonMaskLpps.length % 2 != 0 + STDERR.puts "pinCheck called with empty or odd-number of args for non-drawing layer-purpose-pairs." + exit 1 + end + + lyfmt = "%22s" + lyfmt2 = "%12s" + + ly = polygons(layn, typen) # get main drawing + drpair = "#{layn}/#{typen}" + # isEmpty(ly, name) + lye = (ly.is_empty?) ? "EMP" : "dat" + sumry = [ lyfmt % "#{name}:#{drpair}/#{lye}" ] + + nonMaskLpps.each_slice(2) {|lay, typ| + l2 = polygons(lay, typ) + l2e = (l2.is_empty?) ? "EMP" : "dat" + sumry += [ lyfmt2 % "#{lay}/#{typ}/#{l2e}" ] + + msg = "#{lay}/#{typ}: #{name}, pin/label not-over drawing:#{drpair}" + if checkp + rule( l2.not(ly), msg ) + end + } + if checkp + mode = " " + else + mode = "NO-Check" + end + # force header output (if not yet done) + if ! $didHeader + puts $ruleHeader + $didHeader = true + end + puts ("%8s ---- " % mode) + sumry.join(" ") + +end + +# verbose input(), flag if its empty. description is a string. +def inputVerb(layn, typen, desc) + ly = input(layn, typen) + isEmpty(ly, desc) + return ly +end + +def isEmpty(layer, desc) + if layer.is_empty? + puts "--EMPTY : #{desc}" + else + puts "data : #{desc}" + end +end + +# check all layer-purpose-pairs found in input-layout: +# Report ALL that are pin-purpose. + +#? should these be checked against pwell/drawing: pwelliso_pin - 44/16 pwelliso_label - 44/5 + pinCheckIf($check_pwell, + "pwell", 64,44, 122,16, 64,59, 44,16, 44,5) + pinCheck( "nwell", 64,20, 64,16, 64,5) + pinCheck( "diff", 65,20, 65,16, 65,6) + pinCheck( "tap", 65,44, 65,48, 65,5) + pinCheck( "poly", 66,20, 66,16, 66,5) + pinCheck( "licon1", 66,44, 66,58) + pinCheck( "li1", 67,20, 67,16, 67,5) + pinCheck( "mcon", 67,44, 67,48) + pinCheck( "met1", 68,20, 68,16, 68,5) + pinCheck( "via", 68,44, 68,58) + pinCheck( "met2", 69,20, 69,16, 69,5) + pinCheck( "via2", 69,44, 69,58) + pinCheck( "met3", 70,20, 70,16, 70,5) + pinCheck( "via3", 70,44, 70,48) + pinCheck( "met4", 71,20, 71,16, 71,5) + pinCheck( "via4", 71,44, 71,48) + pinCheck( "met5", 72,20, 72,16, 72,5) + pinCheck( "pad", 76,20, 76,5, 76,16) + pinCheck( "pnp", 82,44, 82,59) + pinCheck( "npn", 82,20, 82,5) + pinCheck( "rdl", 74,20, 74,16, 74,5) + pinCheck( "inductor", 82,24, 82,25) + +end +# How to tabulate as-if-flat "error-counts by error-message" from current report()? +# In old versions, we can't seem to determine here if a check flagged errors ($errs == -1). +if $errs >= 0 + puts "%8d total error(s) among %d error type(s), %d checks, cell: %s" % [$totals, $errs, $rules, $top_cell_name] + # puts "#{$errs} of #{$rules} checks have errors" +else + puts "#{$rules} checks" + $errs = 0 # so exit status == 0. +end +puts "Writing report..." + +# if we roll-over to 256, exit-status seen by shell is zero. +# uncaught I/O errors will yield (built-in) exit status of 1. +if $errs > 0 + $errs = $errs + 1 +end +if $errs > 127 + $errs = 127 +end + +# experimental: report own peak process-stats. BUT: report-file isn't really written +# until we exit (during exit). So these results are not 100% accurate. +# VmHWM: max-resident-size, VmPeak: max virtual-size. +# don't need: pid=Process.pid +if File.readable?("/proc/self/status") + puts File.foreach("/proc/self/status").grep(/^(VmPeak|VmHWM)/) +end + +# does not work (to set exit-status) in 0.23.11. +# Does work in 0.24.2, 0.27! +exit $errs + +# For 0.23.11 we could set exit-status as below, but: +# if we do: the report() does not get written! +# +# for exit! we'd lose buffered output unless we flush: +# STDOUT.flush +# STDERR.flush +# Kernel.exit!($errs) +# +# emacs syntax-mode: +# Local Variables: +# mode:ruby +# End: