| # usage: |
| # klayout -r pin_label_purposes_overlapping_drawing <-rd threads=THREADS_NUM> <-rd tile=TILE_SIZE> <-rd flat_mode=true> <-rd check_pwell=true> <gdsFile> <topcellName> <markerReportOutFileName> |
| # |
| # 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: |
| # <generator>:/built-in-macros/drc.lym</generator> |
| # the "better" generator would look like: |
| # <generator>drc: script='<yourPath>/gdsSky130Apin1.drc'</generator> |
| # 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: |