# 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: