blob: f371cbb44fc5c5048f9439d7bb7242a26d45e6cb [file] [log] [blame]
Tim Edwardsd14571f2021-01-06 14:49:27 -05001#!/usr/bin/env python3
Tim Edwards59a78462020-12-22 10:34:20 -05002#-----------------------------------------------------------------------
3# netlist_to_layout.py
4#-----------------------------------------------------------------------
5#
6# Generate a magic layout from a SPICE netlist, running magic in batch
7# mode and calling up the PDK selections non-interactively for each
8# component in the netlist.
9#
10#---------------------------------------------------------------------
11# Written by Tim Edwards
12# efabless, inc.
13# November 17, 2016
14# Updated December 17, 2016
15# Version 1.0
16# Imported December 22, 2020 to open_pdks
Tim Edwards1168a8b2021-02-10 22:06:54 -050017# Updated February 10, 2021 for use running on netlist alone
Tim Edwards59a78462020-12-22 10:34:20 -050018#---------------------------------------------------------------------
19
20import os
21import re
22import sys
23import subprocess
24
Tim Edwards1168a8b2021-02-10 22:06:54 -050025def generate_layout_start(library, ofile=sys.stdout):
Tim Edwards3e4e81e2021-02-13 17:13:05 -050026 global debugmode
27 if debugmode:
28 print('Writing layout generating script.')
29
Tim Edwards59a78462020-12-22 10:34:20 -050030 # Write a couple of simplifying procedures
31 print('#!/usr/bin/env wish', file=ofile)
32 print('#-------------------------------------', file=ofile)
33 print('# Script to create layout from netlist', file=ofile)
34 print('# Source this in magic.', file=ofile)
35 print('#-----------------------------------------', file=ofile)
Tim Edwards3e4e81e2021-02-13 17:13:05 -050036 print('drc off', file=ofile)
Tim Edwards59a78462020-12-22 10:34:20 -050037 print('proc move_forward {instname} {', file=ofile)
38 print(' select cell $instname', file=ofile)
39 print(' set anum [lindex [array -list count] 1]', file=ofile)
40 print(' set xpitch [lindex [array -list pitch] 0]', file=ofile)
41 print(' set bbox [box values]', file=ofile)
42 print(' set posx [lindex $bbox 0]', file=ofile)
43 print(' set posy [lindex $bbox 1]', file=ofile)
44 print(' set width [expr [lindex $bbox 2] - $posx]', file=ofile)
45 print(' set posx [expr $posx + $width + $xpitch * $anum]', file=ofile)
46 print(' box position ${posx}i ${posy}i', file=ofile)
47 print(' return [lindex $bbox 3]', file=ofile)
48 print('}', file=ofile)
49 print('', file=ofile)
50 print('proc get_and_move_inst {cellname instname anum} {', file=ofile)
51 print(' set newinst [getcell $cellname]', file=ofile)
52 print(' select cell $newinst', file=ofile)
53 print(' if {$newinst == ""} {return}', file=ofile)
54 print(' identify $instname', file=ofile)
55 print(' if {$anum > 1} {array 1 $anum}', file=ofile)
56 print(' set bbox [box values]', file=ofile)
57 print(' set posx [lindex $bbox 2]', file=ofile)
58 print(' set posy [lindex $bbox 1]', file=ofile)
59 print(' box position ${posx}i ${posy}i', file=ofile)
60 print(' return [lindex $bbox 3]', file=ofile)
61 print('}', file=ofile)
62 print('', file=ofile)
63 print('proc add_pin {pinname portnum} {', file=ofile)
64 print(' box size 1um 1um', file=ofile)
65 print(' paint m1', file=ofile)
66 print(' label $pinname FreeSans 16 0 0 0 c m1', file=ofile)
67 print(' port make $portnum', file=ofile)
68 print(' box move s 2um', file=ofile)
69 print('}', file=ofile)
70 print('', file=ofile)
71 if not library:
72 print('namespace import ${PDKNAMESPACE}::*', file=ofile)
73 print('suspendall', file=ofile)
74 return ofile
Tim Edwards1168a8b2021-02-10 22:06:54 -050075
76def generate_layout_add(subname, subpins, complist, library, ofile=sys.stdout):
Tim Edwards3e4e81e2021-02-13 17:13:05 -050077 global debugmode
78 if debugmode:
79 if subpins:
80 print(' Generating layout for subcircuit ' + subname + '.')
81 else:
82 print(' Generating layout for top level circuit ' + subname + '.')
Tim Edwards59a78462020-12-22 10:34:20 -050083
Tim Edwards3e4e81e2021-02-13 17:13:05 -050084 gparmrex = re.compile('([^= \t]+)=([^=]+)')
85 sparmrex = re.compile('([^= \t]+)=([^= \t]+)[ \t]*(.*)')
86 expr1rex = re.compile('([^= \t]+)=\'([^\']+)\'[ \t]*(.*)')
87 expr2rex = re.compile('([^= \t]+)=\{([^\}]+)\}[ \t]*(.*)')
88 tokrex = re.compile('([^ \t]+)[ \t]*(.*)')
89
90 if subname:
91 print('load ' + subname + ' -quiet', file=ofile)
Tim Edwards1168a8b2021-02-10 22:06:54 -050092
Tim Edwards59a78462020-12-22 10:34:20 -050093 print('box 0um 0um 0um 0um', file=ofile)
94 print('', file=ofile)
95
96 # Generate all of the pins as labels
Tim Edwards3e4e81e2021-02-13 17:13:05 -050097 if subpins:
98 pinlist = subpins.split()
99 i = 0
100 for pin in pinlist:
101 # Escape [ and ] in pin name
102 pin_esc = pin.replace('[', '\[').replace(']', '\]')
103 # To be done: watch for key=value parameters
104 print('add_pin ' + pin_esc + ' ' + str(i), file=ofile)
105 i += 1
Tim Edwards59a78462020-12-22 10:34:20 -0500106
107 # Set initial position for importing cells
108 print('box size 0 0', file=ofile)
109 print('set posx 0', file=ofile)
110 print('set posy [expr {round(3 / [cif scale out])}]', file=ofile)
111 print('box position ${posx}i ${posy}i', file=ofile)
112
113 for comp in complist:
Tim Edwards3e4e81e2021-02-13 17:13:05 -0500114 pinlist = []
115 paramlist = []
116
117 # Parse into pins, device name, and parameters. Make sure parameters
118 # incorporate quoted expressions as {} or ''.
119 rest = comp
120 while rest and rest != '':
121 gmatch = gparmrex.match(rest)
122 if gmatch:
123 break
Tim Edwards59a78462020-12-22 10:34:20 -0500124 else:
Tim Edwards3e4e81e2021-02-13 17:13:05 -0500125 tmatch = tokrex.match(rest)
126 if tmatch:
127 token = tmatch.group(1)
128 pinlist.append(token)
129 rest = tmatch.group(2)
130 else:
131 rest = ''
132
133 while rest and rest != '':
134 ematch = expr1rex.match(rest)
135 if ematch:
136 pname = ematch.group(1)
137 value = ematch.group(2)
138 paramlist.append((pname, '{' + value + '}'))
139 rest = ematch.group(3)
140 else:
141 ematch = expr2rex.match(rest)
142 if ematch:
143 pname = ematch.group(1)
144 value = ematch.group(2)
145 paramlist.append((pname, '{' + value + '}'))
146 rest = ematch.group(3)
147 else:
148 smatch = sparmrex.match(rest)
149 if smatch:
150 pname = smatch.group(1)
151 value = smatch.group(2)
152 paramlist.append((pname, value))
153 rest = smatch.group(3)
154 else:
155 print('Error parsing line "' + comp + '"')
156 print('at: "' + rest + '"')
157 rest = ''
Tim Edwards59a78462020-12-22 10:34:20 -0500158
Tim Edwards3e4e81e2021-02-13 17:13:05 -0500159 if len(pinlist) < 2:
160 print('Error: No device type found in line "' + comp + '"')
161 print('Tokens found are: ' + ', '.join(pinlist))
162 continue
Tim Edwards59a78462020-12-22 10:34:20 -0500163
Tim Edwards3e4e81e2021-02-13 17:13:05 -0500164 instname = pinlist[0]
165 devtype = pinlist[-1]
166 pinlist = pinlist[0:-1]
167
168 # Diagnostic
169 if debugmode:
170 print(' Adding component ' + devtype + ' instance ' + instname)
171
172 mult = 1
173 for param in paramlist:
174 parmname = param[0]
175 parmval = param[1]
176 if parmname.upper() == 'M':
177 try:
178 mult = int(parmval)
179 except ValueError:
180 # This takes care of multiplier expressions, as long
181 # as they don't reference parameter names themselves.
182 mult = eval(eval(parmval))
183
184 # devtype is assumed to be in library. If not, it will attempt to use
185 # 'getcell' on devtype. NOTE: Current usage is to not pass a library
186 # to netlist_to_layout.py but to rely on the PDK Tcl script to define
187 # variable PDKNAMESPACE, which is the namespace to use for low-level
Tim Edwards59a78462020-12-22 10:34:20 -0500188 # components, and may not be the same name as the technology node.
189 if library:
190 libdev = library + '::' + devtype
191 else:
192 libdev = '${PDKNAMESPACE}::' + devtype
193 outparts = []
194 outparts.append('magic::gencell ' + libdev + ' ' + instname)
195
196 # Output all parameters. Parameters not used by the toolkit are ignored
197 # by the toolkit.
198 outparts.append('-spice')
Tim Edwards3e4e81e2021-02-13 17:13:05 -0500199 for param in paramlist:
200 outparts.append(str(param[0]).lower())
201 outparts.append(param[1])
Tim Edwards59a78462020-12-22 10:34:20 -0500202
203 outstring = ' '.join(outparts)
204 print('if {[catch {' + outstring + '}]} {', file=ofile)
205 print(' get_and_move_inst ' + devtype + ' ' + instname
206 + ' ' + str(mult), file=ofile)
207 print('} else {', file=ofile)
208 print(' move_forward ' + instname, file=ofile)
209 print('}', file=ofile)
210 print('', file=ofile)
211 print('save ' + subname, file=ofile)
212
Tim Edwards1168a8b2021-02-10 22:06:54 -0500213def generate_layout_end(ofile=sys.stdout):
Tim Edwards3e4e81e2021-02-13 17:13:05 -0500214 global debugmode
215
Tim Edwards59a78462020-12-22 10:34:20 -0500216 print('resumeall', file=ofile)
Tim Edwards59a78462020-12-22 10:34:20 -0500217 print('writeall force', file=ofile)
Tim Edwards3e4e81e2021-02-13 17:13:05 -0500218 print('quit -noprompt', file=ofile)
219
Tim Edwards708a9422021-03-09 22:07:22 -0500220def parse_layout(topname, lines, library, ofile):
Tim Edwards3e4e81e2021-02-13 17:13:05 -0500221 global debugmode
222
223 subrex = re.compile('.subckt[ \t]+(.*)$', re.IGNORECASE)
224 devrex = re.compile('[xmcrbdivq]([^ \t]+)[ \t](.*)$', re.IGNORECASE)
225 namerex = re.compile('([^= \t]+)[ \t]+(.*)$', re.IGNORECASE)
226 endsrex = re.compile('^[ \t]*\.ends', re.IGNORECASE)
227
228 insub = False
229 subname = ''
230 subpins = ''
231 complist = []
232 toplist = []
233
234 for line in lines:
235 if not insub:
236 lmatch = subrex.match(line)
237 if lmatch:
238 rest = lmatch.group(1)
239 smatch = namerex.match(rest)
240 if smatch:
241 subname = smatch.group(1)
242 subpins = smatch.group(2)
243 insub = True
244 else:
245 print('File ' + inputfile + ': Failure to parse line ' + line)
246 else:
247 dmatch = devrex.match(line)
248 if dmatch:
249 toplist.append(line)
250 else:
251 lmatch = endsrex.match(line)
252 if lmatch:
253 insub = False
254 generate_layout_add(subname, subpins, complist, library, ofile)
255 subname = None
256 subpins = None
257 complist = []
258 else:
259 dmatch = devrex.match(line)
260 if dmatch:
261 complist.append(line)
262
263 # Add any top-level components
264 if toplist:
265 generate_layout_add(topname, None, toplist, library, ofile)
Tim Edwards1168a8b2021-02-10 22:06:54 -0500266
267def usage():
268 print('Usage:')
269 print(' netlist_to_layout.py <filename> [<namespace>] [-options]')
270 print('')
271 print('Arguments:')
272 print(' <filename> is the path to the SPICE netlist to import to magic')
273 print(' <namespace> is the namespace of the PDK')
274 print('')
275 print('Options:')
276 print(' -keep Keep the working script after completion.')
Tim Edwards3e4e81e2021-02-13 17:13:05 -0500277 print(' -debug Provide verbose output while generating script.')
Tim Edwards1168a8b2021-02-10 22:06:54 -0500278 print(' -help Print this help text.')
279
280# Main procedure
Tim Edwards59a78462020-12-22 10:34:20 -0500281
282if __name__ == '__main__':
Tim Edwards3e4e81e2021-02-13 17:13:05 -0500283 global debugmode
Tim Edwards59a78462020-12-22 10:34:20 -0500284
Tim Edwards1168a8b2021-02-10 22:06:54 -0500285 # Parse command line for options and arguments
286 optionlist = []
Tim Edwards59a78462020-12-22 10:34:20 -0500287 arguments = []
288 for item in sys.argv[1:]:
289 if item.find('-', 0) == 0:
Tim Edwards1168a8b2021-02-10 22:06:54 -0500290 optionlist.append(item)
Tim Edwards59a78462020-12-22 10:34:20 -0500291 else:
292 arguments.append(item)
293
294 if len(arguments) > 0:
295 inputfile = arguments[0]
296 if len(arguments) > 1:
297 library = arguments[1]
298 else:
299 library = None
300 else:
Tim Edwards1168a8b2021-02-10 22:06:54 -0500301 usage()
302 sys.exit(0)
Tim Edwards59a78462020-12-22 10:34:20 -0500303
Tim Edwards1168a8b2021-02-10 22:06:54 -0500304 debugmode = False
305 keepmode = False
306
307 for item in optionlist:
Tim Edwards59a78462020-12-22 10:34:20 -0500308 result = item.split('=')
309 if result[0] == '-help':
Tim Edwards1168a8b2021-02-10 22:06:54 -0500310 usage()
311 sys.exit(0)
Tim Edwards59a78462020-12-22 10:34:20 -0500312 elif result[0] == '-debug':
Tim Edwards1168a8b2021-02-10 22:06:54 -0500313 debugmode = True
314 elif result[0] == '-keep':
315 keepmode = True
Tim Edwards59a78462020-12-22 10:34:20 -0500316 else:
Tim Edwards1168a8b2021-02-10 22:06:54 -0500317 usage()
318 sys.exit(1)
Tim Edwards59a78462020-12-22 10:34:20 -0500319
Tim Edwards59a78462020-12-22 10:34:20 -0500320 netpath = os.path.split(inputfile)[0]
Tim Edwards1168a8b2021-02-10 22:06:54 -0500321 if netpath == '':
322 netpath = os.getcwd()
Tim Edwards59a78462020-12-22 10:34:20 -0500323
Tim Edwards1168a8b2021-02-10 22:06:54 -0500324 if os.path.splitext(inputfile)[1] == '.sch':
325 print('Sorry, automatic conversion of schematic to netlist not yet supported.')
326 sys.exit(1)
Tim Edwards59a78462020-12-22 10:34:20 -0500327
Tim Edwards1168a8b2021-02-10 22:06:54 -0500328 netroot = os.path.split(netpath)[0]
329 magpath = os.path.join(netroot, 'mag')
330 if not os.path.isdir(magpath):
331 print('Error: Layout path "' + magpath + '" does not exist or is not readable.')
332 sys.exit(1)
333
334 # NOTE: There should be some attempt to find the installed PDK magicrc file
335 # if there is no mag/ directory.
336 rcfile = '.magicrc'
337 rcfilepath = os.path.join(magpath, rcfile)
338 if not os.path.isfile(rcfilepath):
339 print('Error: No startup script file "' + rcfilepath + '"')
340 sys.exit(1)
Tim Edwards59a78462020-12-22 10:34:20 -0500341
342 # Read SPICE netlist
Tim Edwards59a78462020-12-22 10:34:20 -0500343 with open(inputfile, 'r') as ifile:
Tim Edwards3e4e81e2021-02-13 17:13:05 -0500344 if debugmode:
345 print('Reading file ' + inputfile)
Tim Edwards59a78462020-12-22 10:34:20 -0500346 spicetext = ifile.read()
347
Tim Edwards59a78462020-12-22 10:34:20 -0500348 # Contatenate continuation lines
349 spicelines = spicetext.replace('\n+', ' ').splitlines()
350
Tim Edwards3e4e81e2021-02-13 17:13:05 -0500351 filename = os.path.split(inputfile)[1]
352 topname = os.path.splitext(filename)[0]
353
Tim Edwards1168a8b2021-02-10 22:06:54 -0500354 scriptfile = 'generate_layout.tcl'
355 scriptpath = os.path.join(magpath, scriptfile)
Tim Edwards59a78462020-12-22 10:34:20 -0500356
Tim Edwards1168a8b2021-02-10 22:06:54 -0500357 with open(scriptpath, 'w') as ofile:
358 generate_layout_start(library, ofile)
Tim Edwards708a9422021-03-09 22:07:22 -0500359 parse_layout(topname, spicelines, library, ofile)
Tim Edwards1168a8b2021-02-10 22:06:54 -0500360 generate_layout_end(ofile)
361
362 myenv = os.environ.copy()
363 myenv['MAGTYPE'] = 'mag'
364
365 # Run the layout generator
366 mproc = subprocess.run(['magic', '-dnull', '-noconsole', '-rcfile',
367 rcfile, scriptfile],
368 stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
369 stderr = subprocess.PIPE, cwd = magpath,
370 env = myenv, universal_newlines = True)
371 if mproc.stdout:
372 for line in mproc.stdout.splitlines():
373 print(line)
374 if mproc.stderr:
375 print('Error message output from magic:')
376 for line in mproc.stderr.splitlines():
377 print(line)
378 if mproc.returncode != 0:
379 print('ERROR: Magic exited with status ' + str(mproc.returncode))
380
381 # Clean up
382 if not keepmode:
383 os.remove(scriptpath)
384
385 sys.exit(0)