blob: 25f44bab9a735532fabbf1804fa57e9c18ceb3bd [file] [log] [blame]
S Skandha Deepsita126ed4d2021-06-25 00:57:32 +05301#!/usr/bin/env python3
2# SPDX-FileCopyrightText: 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# SPDX-License-Identifier: Apache-2.0
16
17#
18# check_density.py ---
19#
20# Run density checks on the final (filled) GDS.
21#
22
23import sys
24import os
25import re
26import select
27import subprocess
28
29def usage():
30 print("Usage:")
31 print("check_density.py [<path_to_project>] [-keep]")
32 print("")
33 print("where:")
34 print(" <path_to_project> is the path to the project top level directory.")
35 print("")
36 print(" If <path_to_project> is not given, then it is assumed to be the cwd.")
37 print(" If '-keep' is specified, then keep the check script.")
38 return 0
39
40
41if __name__ == '__main__':
42
43 optionlist = []
44 arguments = []
45
46 debugmode = False
47 keepmode = False
48
49 for option in sys.argv[1:]:
50 if option.find('-', 0) == 0:
51 optionlist.append(option)
52 else:
53 arguments.append(option)
54
55 if len(arguments) > 1:
56 print("Wrong number of arguments given to check_density.py.")
57 usage()
58 sys.exit(0)
59
60 if len(arguments) == 1:
61 user_project_path = arguments[0]
62 else:
63 user_project_path = os.getcwd()
64
65 # Check for valid user path
66
67 if not os.path.isdir(user_project_path):
68 print('Error: Project path "' + user_project_path + '" does not exist or is not readable.')
69 sys.exit(1)
70
71 # Check for valid user ID
72 user_id_value = None
73 if os.path.isfile(user_project_path + '/info.yaml'):
74 with open(user_project_path + '/info.yaml', 'r') as ifile:
75 infolines = ifile.read().splitlines()
76 for line in infolines:
77 kvpair = line.split(':')
78 if len(kvpair) == 2:
79 key = kvpair[0].strip()
80 value = kvpair[1].strip()
81 if key == 'project_id':
82 user_id_value = value.strip('"\'')
83 break
84
85 if user_id_value:
86 project = 'caravel'
87 project_with_id = 'caravel_' + user_id_value
88 else:
89 print('Error: No project_id found in info.yaml file.')
90 sys.exit(1)
91
92 if '-debug' in optionlist:
93 debugmode = True
94 if '-keep' in optionlist:
95 keepmode = True
96
97 magpath = user_project_path + '/mag'
98 rcfile = magpath + '/.magicrc'
99
100 with open(magpath + '/check_density.tcl', 'w') as ofile:
101 print('#!/bin/env wish', file=ofile)
102 print('crashbackups stop', file=ofile)
103 print('drc off', file=ofile)
104 print('snap internal', file=ofile)
105
106 print('set starttime [orig_clock format [orig_clock seconds] -format "%D %T"]', file=ofile)
107 print('puts stdout "Started reading GDS: $starttime"', file=ofile)
108 print('', file=ofile)
109 print('flush stdout', file=ofile)
110 print('update idletasks', file=ofile)
111
112 # Read final project from .gds
113 print('gds readonly true', file=ofile)
114 print('gds rescale false', file=ofile)
115 print('gds read ../gds/' + project_with_id + '.gds', file=ofile)
116 print('', file=ofile)
117
118 print('set midtime [orig_clock format [orig_clock seconds] -format "%D %T"]', file=ofile)
119 print('puts stdout "Starting density checks: $midtime"', file=ofile)
120 print('', file=ofile)
121 print('flush stdout', file=ofile)
122 print('update idletasks', file=ofile)
123
124 # Get step box dimensions (700um for size and 70um for FOM step)
125 # Use 350um for stepping on other layers.
126 print('box values 0 0 0 0', file=ofile)
127 # print('box size 700um 700um', file=ofile)
128 # print('set stepbox [box values]', file=ofile)
129 # print('set stepwidth [lindex $stepbox 2]', file=ofile)
130 # print('set stepheight [lindex $stepbox 3]', file=ofile)
131
132 print('box size 70um 70um', file=ofile)
133 print('set stepbox [box values]', file=ofile)
134 print('set stepsizex [lindex $stepbox 2]', file=ofile)
135 print('set stepsizey [lindex $stepbox 3]', file=ofile)
136
137 print('select top cell', file=ofile)
138 print('expand', file=ofile)
139
140 # Modify the box to be inside the seal ring area (shrink 5um)
141 print('box grow c -5um', file=ofile)
142 print('set fullbox [box values]', file=ofile)
143
144 print('set xmax [lindex $fullbox 2]', file=ofile)
145 print('set xmin [lindex $fullbox 0]', file=ofile)
146 print('set fullwidth [expr {$xmax - $xmin}]', file=ofile)
147 print('set xtiles [expr {int(ceil(($fullwidth + 0.0) / $stepsizex))}]', file=ofile)
148 print('set ymax [lindex $fullbox 3]', file=ofile)
149 print('set ymin [lindex $fullbox 1]', file=ofile)
150 print('set fullheight [expr {$ymax - $ymin}]', file=ofile)
151 print('set ytiles [expr {int(ceil(($fullheight + 0.0) / $stepsizey))}]', file=ofile)
152 print('box size $stepsizex $stepsizey', file=ofile)
153 print('set xbase [lindex $fullbox 0]', file=ofile)
154 print('set ybase [lindex $fullbox 1]', file=ofile)
155 print('', file=ofile)
156
157 print('puts stdout "XTILES: $xtiles"', file=ofile)
158 print('puts stdout "YTILES: $ytiles"', file=ofile)
159 print('', file=ofile)
160
161 # Need to know what fraction of a full tile is the last row and column
162 print('set xfrac [expr {($xtiles * $stepsizex - $fullwidth + 0.0) / $stepsizex}]', file=ofile)
163 print('set yfrac [expr {($ytiles * $stepsizey - $fullheight + 0.0) / $stepsizey}]', file=ofile)
164 print('puts stdout "XFRAC: $xfrac"', file=ofile)
165 print('puts stdout "YFRAC: $yfrac"', file=ofile)
166
167 print('cif ostyle density', file=ofile)
168
169 # Process density at steps. For efficiency, this is done in 70x70 um
170 # areas, dumped to a file, and then aggregated into the 700x700 areas.
171
172 print('for {set y 0} {$y < $ytiles} {incr y} {', file=ofile)
173 print(' for {set x 0} {$x < $xtiles} {incr x} {', file=ofile)
174 print(' set xlo [expr $xbase + $x * $stepsizex]', file=ofile)
175 print(' set ylo [expr $ybase + $y * $stepsizey]', file=ofile)
176 print(' set xhi [expr $xlo + $stepsizex]', file=ofile)
177 print(' set yhi [expr $ylo + $stepsizey]', file=ofile)
178 print(' box values $xlo $ylo $xhi $yhi', file=ofile)
179
180 # Flatten this area
181 print(' flatten -dobbox -nolabels tile', file=ofile)
182 print(' load tile', file=ofile)
183 print(' select top cell', file=ofile)
184
185 # Run density check for each layer
186 print(' puts stdout "Density results for tile x=$x y=$y"', file=ofile)
187
188 print(' set fdens [cif list cover fom_all]', file=ofile)
189 print(' set pdens [cif list cover poly_all]', file=ofile)
190 print(' set ldens [cif list cover li_all]', file=ofile)
191 print(' set m1dens [cif list cover m1_all]', file=ofile)
192 print(' set m2dens [cif list cover m2_all]', file=ofile)
193 print(' set m3dens [cif list cover m3_all]', file=ofile)
194 print(' set m4dens [cif list cover m4_all]', file=ofile)
195 print(' set m5dens [cif list cover m5_all]', file=ofile)
196 print(' puts stdout "FOM: $fdens"', file=ofile)
197 print(' puts stdout "POLY: $pdens"', file=ofile)
198 print(' puts stdout "LI1: $ldens"', file=ofile)
199 print(' puts stdout "MET1: $m1dens"', file=ofile)
200 print(' puts stdout "MET2: $m2dens"', file=ofile)
201 print(' puts stdout "MET3: $m3dens"', file=ofile)
202 print(' puts stdout "MET4: $m4dens"', file=ofile)
203 print(' puts stdout "MET5: $m5dens"', file=ofile)
204 print(' flush stdout', file=ofile)
205 print(' update idletasks', file=ofile)
206
207 print(' load ' + project_with_id, file=ofile)
208 print(' cellname delete tile', file=ofile)
209
210 print(' }', file=ofile)
211 print('}', file=ofile)
212
213 print('set endtime [orig_clock format [orig_clock seconds] -format "%D %T"]', file=ofile)
214 print('puts stdout "Ended: $endtime"', file=ofile)
215 print('', file=ofile)
216
217
218 myenv = os.environ.copy()
219 # Real views are necessary for the DRC checks
220 myenv['MAGTYPE'] = 'mag'
221
222 print('Running density checks on file ' + project_with_id + '.gds', flush=True)
223
224 mproc = subprocess.Popen(['magic', '-dnull', '-noconsole',
225 '-rcfile', rcfile, magpath + '/check_density.tcl'],
226 stdin = subprocess.DEVNULL,
227 stdout = subprocess.PIPE,
228 stderr = subprocess.PIPE,
229 cwd = magpath,
230 env = myenv,
231 universal_newlines = True)
232
233 # Use signal to poll the process and generate any output as it arrives
234
235 dlines = []
236
237 while mproc:
238 status = mproc.poll()
239 if status != None:
240 try:
241 output = mproc.communicate(timeout=1)
242 except ValueError:
243 print('Magic forced stop, status ' + str(status))
244 sys.exit(1)
245 else:
246 outlines = output[0]
247 errlines = output[1]
248 for line in outlines.splitlines():
249 dlines.append(line)
250 print(line)
251 for line in errlines.splitlines():
252 print(line)
253 print('Magic exited with status ' + str(status))
254 if int(status) != 0:
255 sys.exit(int(status))
256 else:
257 break
258 else:
259 n = 0
260 while True:
261 n += 1
262 if n > 100:
263 n = 0
264 status = mproc.poll()
265 if status != None:
266 break
267 sresult = select.select([mproc.stdout, mproc.stderr], [], [], 0.5)[0]
268 if mproc.stdout in sresult:
269 outstring = mproc.stdout.readline().strip()
270 dlines.append(outstring)
271 print(outstring)
272 elif mproc.stderr in sresult:
273 outstring = mproc.stderr.readline().strip()
274 print(outstring)
275 else:
276 break
277
278 fomfill = []
279 polyfill = []
280 lifill = []
281 met1fill = []
282 met2fill = []
283 met3fill = []
284 met4fill = []
285 met5fill = []
286 xtiles = 0
287 ytiles = 0
288 xfrac = 0.0
289 yfrac = 0.0
290
291 for line in dlines:
292 dpair = line.split(':')
293 if len(dpair) == 2:
294 layer = dpair[0]
295 try:
296 density = float(dpair[1].strip())
297 except:
298 continue
299 if layer == 'FOM':
300 fomfill.append(density)
301 elif layer == 'POLY':
302 polyfill.append(density)
303 elif layer == 'LI1':
304 lifill.append(density)
305 elif layer == 'MET1':
306 met1fill.append(density)
307 elif layer == 'MET2':
308 met2fill.append(density)
309 elif layer == 'MET3':
310 met3fill.append(density)
311 elif layer == 'MET4':
312 met4fill.append(density)
313 elif layer == 'MET5':
314 met5fill.append(density)
315 elif layer == 'XTILES':
316 xtiles = int(dpair[1].strip())
317 elif layer == 'YTILES':
318 ytiles = int(dpair[1].strip())
319 elif layer == 'XFRAC':
320 xfrac = float(dpair[1].strip())
321 elif layer == 'YFRAC':
322 yfrac = float(dpair[1].strip())
323
324 if ytiles == 0 or xtiles == 0:
325 print('Failed to read XTILES or YTILES from output.')
326 sys.exit(1)
327
328 total_tiles = (ytiles - 9) * (xtiles - 9)
329
330 print('')
331 print('Density results (total tiles = ' + str(total_tiles) + '):')
332
333 # For FOM, step at 70um intervals (same as 70um check area)
334 fomstep = 1
335
336 # For poly, step only at 700um intervals (10 * 70um check area)
337 polystep = 10
338
339 # For all metals, step only at 350um intervals (5 * 70um check area)
340 metalstep = 5
341
342 # Full areas are 10 x 10 tiles = 100. But the right and top sides are
343 # not full tiles, so the full area must be prorated.
344
345 sideadjust = 90.0 + (10.0 * xfrac)
346 topadjust = 90.0 + (10.0 * yfrac)
347 corneradjust = 81.0 + (9.0 * xfrac) + (9.0 * yfrac) + (xfrac * yfrac)
348
349 print('')
350 print('FOM Density:')
351 for y in range(0, ytiles - 9, fomstep):
352 if y == ytiles - 10:
353 atotal = topadjust
354 else:
355 atotal = 100.0
356 for x in range(0, xtiles - 9, fomstep):
357 if x == xtiles - 10:
358 if y == ytiles - 10:
359 atotal = corneradjust
360 else:
361 atotal = sideadjust
362 fomaccum = 0
363 for w in range(y, y + 10):
364 base = xtiles * w + x
365 fomaccum += sum(fomfill[base : base + 10])
366
367 fomaccum /= atotal
368 print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(fomaccum))
369 if fomaccum < 0.33:
370 print('***Error: FOM Density < 33%')
371 elif fomaccum > 0.57:
372 print('***Error: FOM Density > 57%')
373
374 print('')
375 print('POLY Density:')
376 for y in range(0, ytiles - 9, polystep):
377 if y == ytiles - 10:
378 atotal = topadjust
379 else:
380 atotal = 100.0
381 for x in range(0, xtiles - 9, polystep):
382 if x == xtiles - 10:
383 if y == ytiles - 10:
384 atotal = corneradjust
385 else:
386 atotal = sideadjust
387 polyaccum = 0
388 for w in range(y, y + 10):
389 base = xtiles * w + x
390 polyaccum += sum(polyfill[base : base + 10])
391
392 polyaccum /= atotal
393 print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(polyaccum))
394
395 print('')
396 print('LI Density:')
397 for y in range(0, ytiles - 9, metalstep):
398 if y == ytiles - 10:
399 atotal = topadjust
400 else:
401 atotal = 100.0
402 for x in range(0, xtiles - 9, metalstep):
403 if x == xtiles - 10:
404 if y == ytiles - 10:
405 atotal = corneradjust
406 else:
407 atotal = sideadjust
408 liaccum = 0
409 for w in range(y, y + 10):
410 base = xtiles * w + x
411 liaccum += sum(lifill[base : base + 10])
412
413 liaccum /= atotal
414 print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(liaccum))
415 if liaccum < 0.35:
416 print('***Error: LI Density < 35%')
417 elif liaccum > 0.60:
418 print('***Error: LI Density > 60%')
419
420 print('')
421 print('MET1 Density:')
422 for y in range(0, ytiles - 9, metalstep):
423 if y == ytiles - 10:
424 atotal = topadjust
425 else:
426 atotal = 100.0
427 for x in range(0, xtiles - 9, metalstep):
428 if x == xtiles - 10:
429 if y == ytiles - 10:
430 atotal = corneradjust
431 else:
432 atotal = sideadjust
433 met1accum = 0
434 for w in range(y, y + 10):
435 base = xtiles * w + x
436 met1accum += sum(met1fill[base : base + 10])
437
438 met1accum /= atotal
439 print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(met1accum))
440 if met1accum < 0.35:
441 print('***Error: MET1 Density < 35%')
442 elif met1accum > 0.60:
443 print('***Error: MET1 Density > 60%')
444
445 print('')
446 print('MET2 Density:')
447 for y in range(0, ytiles - 9, metalstep):
448 if y == ytiles - 10:
449 atotal = topadjust
450 else:
451 atotal = 100.0
452 for x in range(0, xtiles - 9, metalstep):
453 if x == xtiles - 10:
454 if y == ytiles - 10:
455 atotal = corneradjust
456 else:
457 atotal = sideadjust
458 met2accum = 0
459 for w in range(y, y + 10):
460 base = xtiles * w + x
461 met2accum += sum(met2fill[base : base + 10])
462
463 met2accum /= atotal
464 print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(met2accum))
465 if met2accum < 0.35:
466 print('***Error: MET2 Density < 35%')
467 elif met2accum > 0.60:
468 print('***Error: MET2 Density > 60%')
469
470 print('')
471 print('MET3 Density:')
472 for y in range(0, ytiles - 9, metalstep):
473 if y == ytiles - 10:
474 atotal = topadjust
475 else:
476 atotal = 100.0
477 for x in range(0, xtiles - 9, metalstep):
478 if x == xtiles - 10:
479 if y == ytiles - 10:
480 atotal = corneradjust
481 else:
482 atotal = sideadjust
483 met3accum = 0
484 for w in range(y, y + 10):
485 base = xtiles * w + x
486 met3accum += sum(met3fill[base : base + 10])
487
488 met3accum /= atotal
489 print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(met3accum))
490 if met3accum < 0.35:
491 print('***Error: MET3 Density < 35%')
492 elif met3accum > 0.60:
493 print('***Error: MET3 Density > 60%')
494
495 print('')
496 print('MET4 Density:')
497 for y in range(0, ytiles - 9, metalstep):
498 if y == ytiles - 10:
499 atotal = topadjust
500 else:
501 atotal = 100.0
502 for x in range(0, xtiles - 9, metalstep):
503 if x == xtiles - 10:
504 if y == ytiles - 10:
505 atotal = corneradjust
506 else:
507 atotal = sideadjust
508 met4accum = 0
509 for w in range(y, y + 10):
510 base = xtiles * w + x
511 met4accum += sum(met4fill[base : base + 10])
512
513 met4accum /= atotal
514 print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(met4accum))
515 if met4accum < 0.35:
516 print('***Error: MET4 Density < 35%')
517 elif met4accum > 0.60:
518 print('***Error: MET4 Density > 60%')
519
520 print('')
521 print('MET5 Density:')
522 for y in range(0, ytiles - 9, metalstep):
523 if y == ytiles - 10:
524 atotal = topadjust
525 else:
526 atotal = 100.0
527 for x in range(0, xtiles - 9, metalstep):
528 if x == xtiles - 10:
529 if y == ytiles - 10:
530 atotal = corneradjust
531 else:
532 atotal = sideadjust
533 met5accum = 0
534 for w in range(y, y + 10):
535 base = xtiles * w + x
536 met5accum += sum(met5fill[base : base + 10])
537
538 met5accum /= atotal
539 print('Tile (' + str(x) + ', ' + str(y) + '): ' + str(met5accum))
540 if met5accum < 0.45:
541 print('***Error: MET5 Density < 45%')
542 elif met5accum > 0.76:
543 print('***Error: MET5 Density > 76%')
544
545 print('')
546 print('Whole-chip density results:')
547
548 atotal = ((xtiles - 1.0) * (ytiles - 1.0)) + ((ytiles - 1.0) * xfrac) + ((xtiles - 1.0) * yfrac) + (xfrac * yfrac)
549
550 fomaccum = sum(fomfill) / atotal
551 print('')
552 print('FOM Density: ' + str(fomaccum))
553 if fomaccum < 0.33:
554 print('***Error: FOM Density < 33%')
555 elif fomaccum > 0.57:
556 print('***Error: FOM Density > 57%')
557
558 polyaccum = sum(polyfill) / atotal
559 print('')
560 print('POLY Density: ' + str(polyaccum))
561
562 liaccum = sum(lifill) / atotal
563 print('')
564 print('LI Density: ' + str(liaccum))
565 if liaccum < 0.35:
566 print('***Error: LI Density < 35%')
567 elif liaccum > 0.60:
568 print('***Error: LI Density > 60%')
569
570 met1accum = sum(met1fill) / atotal
571 print('')
572 print('MET1 Density: ' + str(met1accum))
573 if met1accum < 0.35:
574 print('***Error: MET1 Density < 35%')
575 elif met1accum > 0.60:
576 print('***Error: MET1 Density > 60%')
577
578 met2accum = sum(met2fill) / atotal
579 print('')
580 print('MET2 Density: ' + str(met2accum))
581 if met2accum < 0.35:
582 print('***Error: MET2 Density < 35%')
583 elif met2accum > 0.60:
584 print('***Error: MET2 Density > 60%')
585
586 met3accum = sum(met3fill) / atotal
587 print('')
588 print('MET3 Density: ' + str(met3accum))
589 if met3accum < 0.35:
590 print('***Error: MET3 Density < 35%')
591 elif met3accum > 0.60:
592 print('***Error: MET3 Density > 60%')
593
594 met4accum = sum(met4fill) / atotal
595 print('')
596 print('MET4 Density: ' + str(met4accum))
597 if met4accum < 0.35:
598 print('***Error: MET4 Density < 35%')
599 elif met4accum > 0.60:
600 print('***Error: MET4 Density > 60%')
601
602 met5accum = sum(met5fill) / atotal
603 print('')
604 print('MET5 Density: ' + str(met5accum))
605 if met5accum < 0.45:
606 print('***Error: MET5 Density < 45%')
607 elif met5accum > 0.76:
608 print('***Error: MET5 Density > 76%')
609
610 if not keepmode:
611 if os.path.isfile(magpath + '/check_density.tcl'):
612 os.remove(magpath + '/check_density.tcl')
613
614 print('')
615 print('Done!')
616 sys.exit(0)