blob: fa6f783c450375a63ca8de3c7a9e510de0d2379d [file] [log] [blame]
mkk91ada922020-12-02 09:08:26 -08001#!/bin/bash
agorararmard5d91acf2020-12-10 18:57:03 +02002# SPDX-FileCopyrightText: 2015, 2020 Efabless Corporation
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15#
16# SPDX-License-Identifier: Apache-2.0
mkk91ada922020-12-02 09:08:26 -080017# Copyright (C) 2015, 2020 efabless Corporation. All Rights Reserved.
18# filter out most options, so magic Natively sees/handles *only* -T <file>.
19# for-bash\
20 declare -a C ; declare -a N ; export _CE= _NE= _M0= ;\
21 for i in "$@" ; do _M0="$_M0${_M0:+ }\"${i//\"/\\\"}\""; done ;\
22 while getopts "NFT:S:l:P:" o; do \
23 : echo got "optchar $o, with optarg $OPTARG" ;\
24 case "$o" in S) \
25 C+=(-${o} "$OPTARG") ;\
26 continue ; esac ;\
27 case "$o" in P) \
28 C+=(-${o} "$OPTARG") ;\
29 continue ; esac ;\
30 case "$o" in F|N) \
31 C+=(-${o}) ;\
32 continue ; esac ;\
33 case "$o" in l) \
34 C+=(-${o} "$OPTARG") ;\
35 continue ; esac ;\
36 case "$o" in T) \
37 N+=(-${o} "$OPTARG") ;\
38 continue ; esac ;\
39 done ;\
40 shift $((OPTIND-1)) ;\
41 for i in "${C[@]}" ; do _CE="$_CE${_CE:+ }\"${i//\"/\\\"}\""; done ;\
42 for i in "${N[@]}" ; do _NE="$_NE${_NE:+ }\"${i//\"/\\\"}\""; done ;\
43 exec magic -dnull -noconsole "${N[@]}" <"$0"
44# for-magic:
45# magicDrc: run magic-DRC in batch on a .mag file, tabulate/pareto the error counts.
46#
47# magicDrc [-T <techfilePath>] [-S <drcStyleName>] [-P <N> ] [-l FILE_NAME] <magFileName>
48# -T name specific techfile (def .tech extension), passed to magic itself only, overrides tech implied by magFileName
49# -S if given, changes from techfile's default drc style (perhaps "drc(fast)") to named style, for example: -S "drc(full)"
50# -l if given, enumerates EVERY individual error bbox to the FILE_NAME
51# -N if given, do Not use -dereference option of load for topcell (not available in older magics)
52# -F flatten top cell in-memory only, not saved (experimental)
53# -P do crude drc performance measurement. At top-cell, do 'drc find' <N> times and report time per call.
54# Stdout will log a pareto of error type by count regardless.
55#
56# <magFileName>: names a .mag file, the toplevel of the hier. to DRC/pareto
57#
58# Normal magic init. files are STILL sourced: ~/.magicrc and either $CWD/.magicrc or $CWD/magic_setup.
59# (This would NOT happen if -rcfile magic cmd-line option were used).
60#
61# WARNING: Before 8.1.70, *.mag on cmd-line that was only found in cell search path set by .magicrc inits,
62# would FAIL to determine the default tech-file.
63#
64# rb@ef 2015-06-30 author
65# rb 2020-03-11 embed some library functions, to standalone from efabless-opengalaxy env, test via magic-8.2.194
66#
67# magic itself outputs following usage message though -rcfile doesn't appear to work (in some versions):
68# Usage: magic [-g gPort] [-d devType] [-m monType] [-i tabletPort] [-D] [-F objFile saveFile]
69# [-T technology] [-rcfile startupFile | -norcfile][-noconsole] [-nowindow] [-wrapper] [file]
70#
71set Prog "magicDrc"
72
73set argv [eval "list $env(_M0)"] ;# orig. mix of native plus custom args, for logging all args to script
74
75proc usage {args} {
76 if {[llength $args] > 0} {
77 puts "ERROR: ${::Prog}: [join $args]"
78 }
79 puts {usage: [ -T <techfilePath> ] [-S <drcStyleName>] [-N] [-l FILE_NAME] <magFileName>}
80 puts " -T name specific techfile, passed to magic itself only, overrides tech implied by magFileName"
81 puts " -S if given, changes from techfile's default drc style (perhaps \"drc(fast)\") to named style, for example: -S \"drc(full)\""
82 puts " -l if given, enumerates EVERY individual error bbox to the FILE_NAME"
83 puts " -N if given, do not use -dereference option of load for topcell (not available in older magics)"
84 puts " Stdout will log a pareto of error type by count regardless."
85 puts ""
86 puts " Recommend to run in dir with a ./.magicrc (or ./magic_setup) to configure magic's"
87 puts " cell search path, thru addpath statements, to locate all cells."
88}
89
90# optionally hardcode library proc-s (part of site-wide extensions - always available - in context of efabless/open-galaxy)
91# This is to make the script more standalone from efabless environment; but these capabilities should be native to magic.
92
93if {[info command scratchWritable] == {}} {
94 puts "${::Prog}: hardcoding library proc-s..."
95# Replacement for 'cellname list exists CELLNAME', to fix ambiguity for cell "0".
96# For cell "0" test for membership in 'cellname list allcells'.
97#
98# Instead of returning 0 for (non-existent) and cellname for exists,
99# returns regular 0/1 instead for non-existent/exists.
100#
101# Therefore NOT direct replacement for uses of 'cellname list exists CELL'.
102# Requires code changes.
103proc cellnameExists {cell} {
104 expr {$cell ne "0" && [cellname list exists $cell] eq $cell ||
105 $cell eq "0" && [lsearch -exact [cellname list allcells] $cell] > -1}
106}
107
108#
109# scratchWritable [-cleanup] [cellname1 ...] --
110#
111# Turn readonly cells writable in-memory, via redirect to scratch dir.
112# No cellname args: default is to process just all non-writable cells.
113# Explicit cellname arguments: ARE scatchified EVEN if ALREADY writable.
114# Limitation: Explicit named cell created in-mem, never saved, won't scratchify.
115# If just -cleanup: default is to only do cleanup: don't scratchify
116# any cells.
117#
118# -cleanup: Last scratch-dir, if any, and contents are deleted first.
119# No restoring old filepath of cells, save after cleanup will fail.
120#
121# Caller strongly recommended to first do: 'select top cell; expand'
122# to force whole hier. of a topcell to be loaded from disk into memory.
123#
124# This proc does not force expand cells. Before expanded, cells cannot be
125# checked whether writable, and cannot have filepath changed.
126#
127# For batch DRC, for 'drc listall count', every cell in-memory must
128# appear writable. This is the work-around (caller to 1st ensure
129# hier. is loaded): Reset filepath of readonly cells to a scratch dir,
130# make a dummy/empty .mag in scratch dir for each cell. Change cell's
131# writeable flag.
132#
133# Skipped cells:
134# In all cases, cells are skipped if
135# 'cellname filepath' matches ::scratchWritableDir (already scratchified),
136# This proc does NOT try and force expand; it presumes caller forced an expand
137# thus cells are skipped if:
138# 'cellname filepath' is "default" (can mean not expanded yet, or created never saved),
139# 'cellname filepath' is <CELLNAME>.mag, indicates failed expand (unbound).
140# Note: when filepath gives "default" or <CELLNAME>.mag, the writeable check not meaningful.
141#
142# How to scratchify all in-memory cells (still subject to internal skipping):
143# scratchWritable {*}[cellname list allcells]
144#
145# TODO: use a combo of filepath & flags likely can detect created in-mem,
146# and could redirect those too scratch dir if named explicitly.
147#
148# Side-effects:
149# Runs zero or one subprocess, '/bin/mktemp -d' to make a scratch dir.
150# Redirects where newly modified cells would be saved, if they ever are saved.
151# Make's a scratch dir that needs to be cleaned-up.
152# Leaves empty *.mag files in that scratch dir.
153#
154# Uses/requires proc cellnameExists.
155#
156# Same scratch-dir is reused if called multiple times, until next -cleanup.
157#
158# return value: list of cells not processed (skipped) for reasons cited above.
159# Non-existent cells are also skipped but not included in the return list.
160#
161if {![info exists ::scratchWritableDir]} {set ::scratchWritableDir {}}
162if {![info exists ::scratchWritableVerb]} {set ::scratchWritableVerb 0}
163proc scratchWritable {args} {
164 # parse -cleanup option
165 set clean [expr {[lindex $args 0] eq {-cleanup}}]
166 if {$clean} {
167 set args [lrange $args 1 end]
168 }
169
170 # If explicit cells given: don't limit to processing just readOnly cells.
171 set onlyReadonly [expr {$args == {}}]
172
173 # only if no -cleanup, does empty cell list imply all cells
174 set allcell [cellname list allcells]
175 if {!$clean && $args == {}} {
176 set args $allcell
177 }
178
179 # do cleanup
180 if {$clean} {
181 if {$::scratchWritableDir != {} && [file isdir $::scratchWritableDir]} {
182 set files [glob -dir $::scratchWritableDir -- {*.ext} {*.mag}]
183 lappend files $::scratchWritableDir
184 if {$::scratchWritableVerb} {
185 puts "scratchWritable: running, file delete $files"
186 }
187 eval {file delete} $files
188 set ::scratchWritableDir {}
189 }
190 }
191
192 # Filter out non-existent or unbound cells.
193 # Optionally filter already writable cells.
194 #
195 # Unbounds result from placements of cells that now don't exist:
196 # fail to expand. This proc does not try and force expand; it
197 # presumes a forced expand was already done by caller (if caller
198 # wished).
199 #
200 # Referenced/used cells are initially unexpanded, not yet even located
201 # located in the search path, 'cellname filepath' returns "default".
202 # If expand fails (not found in search path), then 'cellname filepath'
203 # returns <CELLNAME>.mag, if expand worked, the directory containing
204 # the cell.
205 #
206 # If cell was 'cellname create' made, but never saved also "default".
207 # Such a cell is writable. So filter "default" and <CELLNAME>.mag.
208 set skipped {}
209 set ercell1 {}
210 set docells {}
211 foreach cell $args {
212 # filter (without recording as skipped) non-existent cells.
213 if {![cellnameExists $cell]} { continue }
214
215 # filepath = "default": unexpanded (not loaded from disk),
216 # or created in-mem and never saved (is writable already
217 # though flags won't say so): skip both.
218 # TODO: use a combo of filepath & flags likely can detect created in-mem,
219 # and might be able to redirect them too to scratch dir if named explicitly.
220 set tmppath [cellname list filepath $cell]
221 if {$tmppath eq "default"} {
222 lappend skipped $cell
223 continue
224 }
225
226 # flags not meaningful, until expanded or expand attempted.
227 # After expand attempt (filepath != "default"), and flags
228 # can now be used to determine cell unbound: not available.
229 set flags [cellname list flags $cell]
230 if {[lsearch -exact $flags available] < 0} {
231 lappend ercell1 $cell
232 continue
233 }
234
235 if {$onlyReadonly &&
236 [cellname list writeable $cell] eq "writeable"} {
237 lappend skipped $cell
238 continue
239 }
240 lappend docells $cell
241 }
242
243 if {$::scratchWritableVerb} {
244 puts "scratchWritable: skipped cells: $skipped"
245 }
246
247 # don't make a scratch dir if no work to do
248 if {$docells == {}} {
249 if {$::scratchWritableVerb} {
250 puts "scratchWritable: scratch-directed 0 cells"
251 }
252 return $skipped
253 }
254
255 # make a scratch dir if needed
256 if {$::scratchWritableDir == {}} {
257 if {[catch {set dir [string trimright [exec /bin/mktemp -d]]} msg]} {
258 error "ERROR: scratchWritable, '/bin/mktemp -d' failed, $msg"
259 }
260 if {![file isdir $dir] || ![file writable $dir]} {
261 error "ERROR: scratchWritable, mktemp gave $dir, not a writable dir"
262 }
263 set ::scratchWritableDir $dir
264 }
265
266 set ercell2 {}
267 set okcell {}
268 set madef 0
269 foreach cell $docells {
270 # Relocate if needed: filepath doesn't already point to the scratch dir).
271 # 'cellname list filepath <cellNm>' -> appears to omit .mag extension,
272 # but disk-file needs the .mag in the path.
273 set trgr [file join $::scratchWritableDir "$cell"] ;# expected "lookup" path
274 set trgw [file join $::scratchWritableDir "$cell.mag"] ;# true "write" disk path
275 set src [cellname list filepath $cell]
276 if {[cellname list filepath $cell] ne $trgr && [cellname list filepath $cell] ne $trgw} {
277
278 # make empty .mag for the cell
279 if {[catch {set outmag [open $trgw w]} msg]} {
280 lappend ercell2 $cell
281 continue
282 }
283 incr madef
284 close $outmag
285
286 # relocate cell to new file
287 cellname list filepath $cell $::scratchWritableDir
288 }
289
290 # make cell writable
291 cellname list writeable $cell true
292 lappend okcell $cell
293 }
294
295 if {$::scratchWritableVerb} {
296 puts "scratchWritable: scratch-directed $madef cells"
297 }
298 if {$ercell1 != {} || $ercell2 != {}} {
299 set pre "ERROR: scratchWritable, "
300 set msg {}
301 if {$ercell1 != {}} {
302 lappend msg "$pre unbound cell(s): $ercell1"
303 }
304 if {$ercell2 != {}} {
305 lappend msg "$pre failed to make .mag for cell(s): $ercell2"
306 }
307 error [join $msg "\n"]
308 }
309 set skipped
310} ;# end proc scratchWritable
311}
312
313# without top-level proc around bulk of script, intermediate error statements don't abort script.
314proc main {argv} {
315
316# process name-value pair options, if any
317set nbrErr 0
318set ndx 0
319set max [llength $argv]
320set extTechOpt {} ;# -T ...
321set enumFilel {} ;# -l ... enum output file
322set variant {} ;# -S ... non-default drc style
323set flatten 0
324set perfN 0 ;# -P <N> do crude DRC perf. test
325set noderef 0 ;# -N disable dereference option of: 'load ... -dereference'
326
327while {$ndx < $max && [string match "-*" [lindex $argv $ndx]]} {
328 set opt [lindex $argv $ndx]
329 incr ndx
330 switch -exact -- $opt {
331 -T {
332 if {$ndx == $max} {
333 usage "missing tech-file argument for -T option"
334 exit 1
335 }
336 set extTechOpt [lindex $argv $ndx]
337 incr ndx
338 }
339 -S {
340 if {$ndx == $max} {
341 usage "missing drcStyle argument for -S option"
342 exit 1
343 }
344 set variant [lindex $argv $ndx]
345 incr ndx
346 }
347 -P {
348 if {$ndx == $max} {
349 usage "missing count argument for -P option"
350 exit 1
351 }
352 set perfN [lindex $argv $ndx]
353 incr ndx
354 }
355 -F {
356 set flatten 1
357 }
358 -N {
359 set noderef 1
360 }
361 -l {
362 if {$ndx == $max} {
363 usage "missing outputFile argument for -l option"
364 exit 1
365 }
366 set enumFilel [lindex $argv $ndx]
367 incr ndx
368 if {[catch {set enumOut [open $enumFilel w]} msg]} {
369 error "ERROR: ${::Prog}: failed to open-for-write '$enumFilel' threw error, $msg"
370 }
371 puts "${::Prog}: enumerating each error bbox to: $enumFilel"
372 }
373 default {
374 usage "unknown option: $opt"
375 exit 1
376 }
377 }
378}
379
380if {$ndx == $max} {
381 usage "missing magFileName argument, the topcell"
382 exit 1
383}
384
385# get cmd-line topcell, minus dir-path; and minus extension IFF ext is .mag
386set topc [file tail [lindex $argv $ndx]] ; incr ndx
387if {[file extension $topc] eq ".mag"} {
388 set topc [file rootname $topc]
389}
390set topcStr $topc
391
392# abort if user supplies extra args.
393if {$ndx != $max} {
394 usage "extra/unspported arg past magFileName, '[lindex $argv $ndx]'"
395 exit 1
396}
397
398# load the techfile
399if {$extTechOpt != ""} {
400 if {![file readable $extTechOpt]} {
401 error "ERROR: ${::Prog}: tech-file \"$extTechOpt\" is not readable."
402 }
403
404 tech load $extTechOpt
405
406 # Verify the cmd-line -T option (if any) is still the current 'tech filename'. If we didn't
407 # explicitly 'tech load' ourselves, the .magicrc or magic.setup might 'tech load' something else.
408 # The 'file join [pwd] ...' makes relative path absolute, but without resolving
409 # all symlinks (which 'file normalize' would do).
410 set techf2 [file join [pwd] [tech filename]]
411 set techf1 [file join [pwd] $extTechOpt]
412 if {$techf1 != $techf2} {
413 error "ERROR: ${::Prog}: failed tech-load \"$techf1\" (tech-filename=\"$techf2\" not a match)"
414 }
415}
416
417# if mag-cell were passed natively on magic cmd-line, this is too late:
418if {$noderef} {
419 load $topc
420} else {
421 load $topc -dereference
422}
423
424# error checks: ensure (1st) cmd-line cellname now in-memory, and is now the current cell
425
426set topcells [cellname list top]
427# filter (UNNAMED)
428set topcells [lsearch -exact -not -all -inline $topcells "(UNNAMED)"]
429# puts "cellname-list-top is: $topcells"
430
431# could use [cellname list flags $topc] and ensure non-null result (list with available),
432# but if it fails (cell not found), it generates unwanted stdout.
433if {[lsearch -exact [cellname list allcells] $topc] < 0} {
434 error "ERROR: ${::Prog}: cmd-line topcell \"$topc\" not in magic's list of allcells."
435}
436
437if {[lsearch -exact $topcells $topc] < 0} {
438 puts "WARNING: ${::Prog}: cmd-line topcell \"$topc\" not in magic's list of topcells: $topcells"
439}
440
441# crude way even in batch to determine the "current" cell; perhaps not yet the "Edit" cell
442# WARNING, if topcell locked elsewhere or not writable, it can't become the "Edit" cell.
443set topcw [cellname list window]
444if {$topcw ne $topc} {
445 error "ERROR: ${::Prog}: cmd-line topcell, $topc, is not the current cell, 'cellname list window'=$topcw"
446}
447
448# for topcell, filepath==default doesn't change by expand,
449# indicates unknown cell created in-memory by magic's startup sequence.
450if {[cellnameExists $topc] &&
451 [cellname list filepath $topc] eq "default"} {
452 puts "Search path for cells is \"[path search]\""
453 error "ERROR: ${::Prog}: cmd-line topcell, $topc, auto-created in-memory: not found in cell search path"
454}
455
456if {$flatten} {
457 # delete (UNNAMED) if any.
458 set trg "(UNNAMED)"
459 if {[cellnameExists $trg]} {cellname delete $trg}
460
461 # rename top cell to (UNNAMED)
462 cellname rename $topc $trg
463
464 # now Edit Cell contents are original top cell, but under name (UNNAMED)
465 # flatten Edit-Cell into original top cell name
466 puts "${::Prog}: flattening..."
467 flatten $topc
468
469 # load and edit new version of top cell. This is from in-memory, just making it current-cell.
470 # (So with or without -dereference is expected would have discernable effect by now;
471 # and since it's flattened there are no subcell instances either).
472 if {$noderef} {
473 load $topc
474 } else {
475 load $topc -dereference
476 }
477
478 # crude way even in batch to determine the "current" cell; perhaps not yet the "Edit" cell
479 # WARNING, if topcell locked elsewhere or not writable, it can't become the "Edit" cell.
480 set topcw [cellname list window]
481 if {$topcw ne $topc} {
482 error "ERROR: ${::Prog}: assertion failed, post-flatten, $topc, is not the current cell, 'cellname list window'=$topcw"
483 }
484
485 # should not be necessary:
486 select top cell
487 edit
488
489 # crude way even in batch to determine the "current" cell; perhaps not yet the "Edit" cell
490 # WARNING, if topcell locked elsewhere or not writable, it can't become the "Edit" cell.
491 set topcw [cellname list window]
492 if {$topcw ne $topc} {
493 error "ERROR: ${::Prog}: assertion-2 failed, post-flatten, $topc, is not the current cell, 'cellname list window'=$topcw"
494 }
495}
496
497# todo: Need a check for non-existent topcell (though magic reported not-found and auto-created it).
498# todo: We should locate fullpath to topcell on disk to record this in the log.
499#
500# WARNING, magic junkCell, or magic junkDir/junkCell (passing paths to cells that don't exist),
501# generate startup error messages (could not open cell), but magic creates the new cell in memory.
502# No simple way to detect this after the fact. Can walk the cell search path to verify it's on disk.
503# For the non-existent cell, magic also discards the dirpath from the cmd-line arg.
504# If it did exist at that path, magic opens it successfully, despite that dir not in search path.
505# A proper check for implicit create of non-existent cell should account for this effect too.
506
507# write a line with timestamp and all arguments to stdout (log)
508# (magic renames the TCL clock command)
509set clockp clock
510if {[info command $clockp] == {} && [info command orig_clock] != {}} {
511 set clockp orig_clock
512}
513set nowSec [$clockp seconds]
514set timestamp [$clockp format $nowSec -format "%Y-%m-%d.%T.%Z"]
515# Show quoted logged argv here so it's machine readable for replay purposes.
516puts "${::Prog}: timestamp: $timestamp, arguments: $::env(_M0)"
517
518puts "${::Prog}: running drc on topcell: $topcStr"
519puts "${::Prog}: tech-name: [tech name] -version: [tech version] -filename: [tech filename] -lambda [tech lambda]"
520
521# log the cell search path for this run. Emulates format output by plain "path" (but which prints more than one the cell search path).
522puts "Search path for cells is \"[path search]\""
523
524set res {}
525if {$variant != {}} {
526 if {[catch {set res [drc list style $variant]} msg]} {
527 puts "ERROR: ${::Prog}: but CONTINUING, 'drc style $variant' threw error, $msg"
528 }
529} else {
530 if {[catch {set res [drc list style]} msg]} {
531 puts "ERROR: ${::Prog}: but CONTINUING, 'drc list style' threw error, $msg"
532 }
533}
534if {$res != {}} {
535 puts "drc style reports:\n$res"
536}
537
538# just Manhattan is default, turn on euclidean, and log new mode
539drc euclidean on
540drc euclidean
541
542# 1st "select top cell": without it drc-list-count is blank, and error count reduced.
543# May be unnecessary in some cases.
544# WARNING: if topcell locked by another process, default box is NOT set to full top cell without this (as of 8.1.70 or earlier)
545select top cell
546# expand cell cells: scratchify step requires this up front else can't force all cells writable.
547expand
548
549# The expand triggered load of all subcells. Till then allcells may be incomplete.
550set allcells [cellname list allcells]
551# filter (UNNAMED)
552set allcells [lsearch -exact -not -all -inline $allcells "(UNNAMED)"]
553set nbrAllCells [llength $allcells]
554# puts "DEBUG: cellname-list-allcells are: $allcells"
555
556# TODO: do explicit separate unbound check here (don't rely on scratchWritable for this)
557
558# make allcells writable. Can error out:
559# if are unbounds, or couldn't make scratch dir or .mag files.
560set scratch [expr {!$flatten}]
561if {$scratch && [catch {scratchWritable} msg]} {
562 puts stderr "ERROR: ${::Prog}: aborting at scratchWritable due error(s):"
563 error $msg
564}
565
566# Erase all preexisting *.drtcl first. Else when cell transitions from
567# dirty in previous run (leaving *.drtcl), to clean, the old *.drtcl
568# remains.
569# TODO: only delete *.drtcl of cells in 'cellname list allcells'?
570# TODO: move this up, before scratchWritable?
571set files [glob -nocomplain -types {f} -- ./*.drtcl]
572if {$files != {}} {
573 # TODO: detect/report failure details better here?
574 puts "${::Prog}: deleting preexisting *.drtcl"
575 set msg {}
576 set delfail [catch {eval {file delete} $files} msg]
577 set files [glob -nocomplain -types {f} -- ./*.drtcl]
578 if {$delfail || $files != {}} {
579 puts "ERROR: ${::Prog}: failed to clean old ./*.drtcl files. $msg"
580 incr nbrErr
581 }
582}
583
584edit ;# Fails if topcell not writable, should not be not needed post scratchWritable
585
586set outScale [cif scale out]
587
588# "select top cell" and box [view bbox] should be equivalent in
589# placing a box around whole cell extent.
590# The box cmd ALSO prints lambda and micron user-friendly box data,
591# but it prints microns with not enough resolution,
592# (and no option to disable that flawed print out).
593#
594# todo: emulate box output in full, except for higher resolution,
595# here we only scale/print the overall bbox in microns.
596# select top cell ;# paranoid, reset the box to data extents post-expand
597# set bbox [view bbox]
598# set bbs {}
599# foreach oord $bbox {
600# lappend bbs [format "%.3f" [expr {$outScale * $oord}]]
601# }
602# puts "outScale: $outScale, view-bbox: $bbox"
603# puts "Root cell box2: ([lindex $bbs 0] [lindex $bbs 1]), ([lindex $bbs 2] [lindex $bbs 3])"
604
605# shouldn't need:
606# drc on
607
608# Want to use 'drc list count' to tell us which cells have errors, so we can
609# run 'drc listall why' on just those cells to enumerate details (which reruns
610# drc again unfortunately).
611
612# For accurate DRC (as of 8.1.70), specifically 'drc list count', need:
613# all-writable cells, then run: 'drc check' & 'drc catchup'.
614# Now we have all writable cells.
615set timeRepeat 1
616if {$perfN > 0} {
617 set timeRepeat $perfN
618}
619set timeres [time {
620 set drcCheckTime1 [time {drc check}]
621 set drcCheckTime2 [time {drc catchup}] } $timeRepeat]
622
623if {$perfN > 0} {
624 puts "perf: ${perfN}X 'drc check','drc catchup': $timeres"
625 puts "perf: last 'drc check' time: $drcCheckTime1"
626 puts "perf: last 'drc catchup' time: $drcCheckTime2"
627 drc statistics
628 drc rulestats
629}
630
631# todo: this 2nd select was in GDS version, test if needed in mag version:
632# 2nd select top cell needed else error count may be reduced (why? bbox does not change due to DRC)
633select top cell
634set outScale [cif scale out]
635set bbox [view bbox]
636set bbs {}
637foreach oord $bbox {
638 lappend bbs [format "%.3f" [expr {$outScale * $oord}]]
639}
640puts "outScale(ostyle=[cif list ostyle]): $outScale, view-bbox: $bbox"
641puts "Root cell box: ([lindex $bbs 0] [lindex $bbs 1]), ([lindex $bbs 2] [lindex $bbs 3])"
642# print several native bbox representations:
643box
644
645# listall vs list appear same as of 8.1.70 or earlier.
646# warning: celllist order is not stable, not repeatable; run to run on same data.
647# puts "DEBUG: (drc listall count total) is $drcListCountTot"
648set celllist [drc listall count]
649set celllist [lsearch -not -all -inline -index 0 -exact $celllist "(UNNAMED)"]
650# puts "DEBUG: (drc listall count) is [drc listall count]"
651set drcListCountTot [drc list count total]
652set nbrErrCells [llength $celllist]
653
654# TODO: major problem: 'drc listall why' repeated an every cell, will do subcells
655# multiple times, as many times as their depth in the hier.
656
657# canonicalize order of celllist, move topc to last (if present whatsoever).
658# force our own artificial entry for topc (zero errors) if not present (was clean)
659# puts "DEBUG: celllist before: $celllist"
660set topcPair [lsearch -inline -index 0 -exact $celllist $topc]
661set celllist [lsearch -not -all -inline -index 0 -exact $celllist $topc]
662set celllist [lsort -index 0 -dictionary $celllist]
663if {$topcPair == {}} {
664 # puts "DEBUG: $topc clean, forcing celllist entry for it"
665 set topcPair [list $topc 0]
666}
667lappend celllist $topcPair
668# puts "DEBUG: celllist after: $celllist"
669# puts "DEBUG: adjusted celllist(drc list count) is $celllist"
670
671# loop over celllist
672set doFeedback 1 ;# TODO: add cmd-line option to control this
673
674# collect 'dry listall why' for the cells in 'cell list count' with non-zero errors
675# If 'drc listall why' does report zero (shouldn't since we're only processing cells
676# with non-zero counts), it unavoidably writes to console a No drc errors found message.
677# We don't want such polluting our list of per-cell pareto's, so don't risk running
678# drc why in-line, in-between per-cell paretos.
679array set cell2why [list $topc {}] ;# default at least empty topcell why list
680foreach pair $celllist {
681 if {[lindex $pair 1] < 1} {continue} ;# only happens for topcell if topcell clean
682 set acell [lindex $pair 0]
683
684 # TODO: magic needs a useful error checkable load command.
685 # The 'load' writes errors to console/stdout, but never throws an error,
686 # nor gives a useful return value. i.e. These catch never catch.
687 if {$noderef} {
688 if {[catch {set res [load $acell]} msg]} {
689 puts "ERROR: ${::Prog}: 'load $acell' threw error, $msg"
690 exit 1
691 }
692 } else {
693 if {[catch {set res [load $acell -dereference]} msg]} {
694 puts "ERROR: ${::Prog}: 'load $acell -dereference' threw error, $msg"
695 exit 1
696 }
697 }
698 select top cell ;# paranoid, that without it, drc's are reduced
699
700 # optionally do crude DRC perf. analysis here. Only for top-cell, only if -P <N> option given.
701 set timeRepeat 1
702 if {$perfN > 0 && $topc eq $acell} {
703 set timeRepeat $perfN
704 }
705 set timeres [time {set cell2why($acell) [drc listall why]} $timeRepeat]
706 if {$perfN > 0 && $topc eq $acell} {
707 puts "perf: ${::Prog}: for '$acell', ${perfN}X 'drc listall why': $timeres"
708 }
709}
710
711# done with all magic-specifics here. Shouldn't need scratch dir any longer.
712# If this prints something (generally does), don't want it after the pareto table.
713
714# clean/remove the tmp scratch dir and contents
715# TODO: all fatal errors need to call a cleanup proc that includes this before abort
716if {$scratch && [catch {scratchWritable -cleanup} msg]} {
717 puts "ERROR: ${::Prog}: 'scratchWritable -cleanup' threw error, $msg"
718 incr nbrErr
719}
720
721set gtotal 0
722set gcells 0
723foreach pair $celllist {
724 puts ""
725 set acell [lindex $pair 0]
726 if {![info exists cell2why($acell)]} {
727 puts "ERROR: ${::Prog}: cell: $acell, assertion failed, no drc-why list for 'drc list count' pair: $pair"
728 # exit 1
729 continue
730 }
731 set whys $cell2why($acell)
732
733 # enumerate errors under box, plain "drc why" only reports unique types, no quantities
734 # as-yet-undocumented "drc listall why" gives: {errStr1 {errBox1 ...} errStr2 {errBox1 ...} ... }
735 set pareto {}
736 set total 0
737 set enumTotal 0
738 set types 0
739 set typeDup 0
740 set dups 0
741
742 set fbOut {}
743 # file path for feedback, keep in CWD
744 if {$doFeedback && $fbOut == {}} {
745 set fbOut "./$acell.drtcl"
746 if {![file writable $fbOut] &&
747 ([file exists $fbOut] || ![file writable [file dir $fbOut]])} {
748 puts stderr "ERROR: ${::Prog}: feedback output not writable, $fbOut"
749 incr nbrErr
750 set fbOut {}
751 } elseif {[catch {set outfb [open $fbOut w]} msg]} {
752 puts stderr "ERROR: ${::Prog}: failed to truncate previous feedback output, $fbOut : $msg"
753 incr nbrErr
754 set fbOut {}
755 }
756 }
757 foreach {str boxes} $whys {
758 # sort errors
759 set boxes [lsort -dictionary $boxes]
760
761 # for our pareto, gather data
762 set this [llength $boxes]
763 incr total $this
764 incr types
765 lappend pareto [list $this $str]
766
767 # for enumOut, emulate formatting of $CAD_ROOT/magic/tcl/drc.tcl, which is
768 # not tk pure: fails with complaint about winfo
769 # note: we walk these errors also in order to count/report stats on duplicates, even if not outputing enumerations
770 if {[info exists enumOut]} {
771 if {$types == 1} {
772 puts $enumOut "[join $pair]\n----------------------------------------"
773 }
774 puts $enumOut "${str}\n----------------------------------------"
775 }
776 set lastq {}
777 set thisDup 0
778 foreach quad $boxes {
779 set quadUM {}
780 foreach coord $quad {
781 set valum [expr {$coord * $outScale}]
782 set valumf [format "%.3f" $valum]
783 lappend quadUM "${valumf}um"
784 }
785 set dup [expr {$quad == $lastq}]
786 incr thisDup $dup
787 set line $quadUM
788 if {[info exists enumOut]} {
789 if {$dup} {
790 puts $enumOut "[join $line] #dup"
791 } else {
792 puts $enumOut [join $line]
793 }
794 }
795 if {$fbOut != {}} {
796 set line [join $quadUM]
797 regsub -all -- "(\[\[\"\$\\\\])" $str {\\\1} strdq
798 puts $outfb "[concat box $line]" nonewline
799 puts $outfb " ; feedback add \"$strdq\" medium" nonewline
800 if {$dup} {
801 puts $outfb " ;#dup"
802 } else {
803 puts $outfb ""
804 }
805 }
806
807 incr enumTotal
808 set lastq $quad
809 }
810 if {$thisDup} {
811 incr typeDup
812 incr dups $thisDup
813 }
814 if {[info exists enumOut]} {
815 puts $enumOut "----------------------------------------\n"
816 }
817 }
818
819 if {$fbOut != {}} {
820 close $outfb
821 set outfb {}
822 }
823
824 set pareto [lsort -integer -decreasing -index 0 $pareto]
825 if {$total > 0} {
826 puts "--- #err|description, table for cell: $acell"
827 }
828 foreach pair $pareto {
829 puts "[format {%8d} [lindex $pair 0]] [lindex $pair 1]"
830 }
831 if {$typeDup} {
832 puts "[format {%8d} $dups] total duplicate error(s) among $typeDup error type(s), cell: $acell"
833 }
834 puts "[format {%8d} $total] total error(s) among $types error type(s), cell: $acell"
835 # add to grand-totals
836 incr gcells
837 incr gtotal $total
838
839 # always compare the total from the enum to the pareto as error check
840 if {$total != $enumTotal} {
841 puts "ERROR: ${::Prog}: cell: $acell, assertion failed, pareto vs enum count mismatch: $total != $enumTotal"
842 incr nbrErr
843 }
844}
845
846# TODO: in the summary echo also techfile-full-path and drc-style name?
847# grand totals
848puts "[format {%8d} $nbrErrCells] of $nbrAllCells cell(s) report error(s)"
849puts "[format {%8d} $gtotal] grand-total error(s) across $gcells cell(s)"
850
851# wish to compare the drc-list-count-total to the pareto total.
852# Per te 2014-08-27 : it is not an error.
853# if {$total != $drcListCountTot} {
854# puts "info: ${::Prog}: drc-list-count-total vs drc-listall-why mismatch {drc list count total} gave $drcListCountTot, but {drc listall why} gave $total"
855# }
856
857if {[info exists enumOut]} {
858 close $enumOut
859}
860
861# set celllist4 [drc list count]
862# puts "DEBUG: drc list count0: $celllist0"
863# puts "DEBUG: drc list count1: $celllist1"
864# puts "DEBUG: drc list count2: $celllist2"
865# puts "DEBUG: drc list count3: $celllist3"
866# puts "DEBUG: native (drc list count) is $celllistn"
867# puts "DEBUG: drc list count4: $celllist4"
868
869# todo: implement super-pareto, ranked table of SUM of all DRC errs/counts from ALL cells.
870# (It still would not reflect as-if-flat hierarchical expansion due to repetition of instances).
871
872set nbrErr
873}
874
875# non-zero exit-status on errors, either if thrown by main, or counted and returned by main
876set nbrErr 0
877if {[catch {set nbrErr [main $argv]} msg]} {
878 puts stderr $msg
879 set nbrErr 1
880} elseif {$nbrErr > 0} {
881 puts "ERROR: ${::Prog}: script terminated with errors reported above."
882}
883exit $nbrErr
884
885# for emacs syntax-mode:
886# Local Variables:
887# mode:tcl
888# End: