Made significant updates to netlist_to_layout.py (although still needs some additional refinement and testing). Corrected an error in the pfet device defaults in the sky130.tcl PDK for magic.
diff --git a/common/netlist_to_layout.py b/common/netlist_to_layout.py index d6fca22..0130340 100755 --- a/common/netlist_to_layout.py +++ b/common/netlist_to_layout.py
@@ -23,12 +23,17 @@ import subprocess def generate_layout_start(library, ofile=sys.stdout): + global debugmode + if debugmode: + print('Writing layout generating script.') + # Write a couple of simplifying procedures print('#!/usr/bin/env wish', file=ofile) print('#-------------------------------------', file=ofile) print('# Script to create layout from netlist', file=ofile) print('# Source this in magic.', file=ofile) print('#-----------------------------------------', file=ofile) + print('drc off', file=ofile) print('proc move_forward {instname} {', file=ofile) print(' select cell $instname', file=ofile) print(' set anum [lindex [array -list count] 1]', file=ofile) @@ -69,24 +74,35 @@ return ofile def generate_layout_add(subname, subpins, complist, library, ofile=sys.stdout): - parmrex = re.compile('([^=]+)=([^=]+)', re.IGNORECASE) - exprrex = re.compile('\'([^\']+)\'', re.IGNORECASE) - librex = re.compile('(.*)__(.*)', re.IGNORECASE) + global debugmode + if debugmode: + if subpins: + print(' Generating layout for subcircuit ' + subname + '.') + else: + print(' Generating layout for top level circuit ' + subname + '.') - print('load ' + subname, file=ofile) + gparmrex = re.compile('([^= \t]+)=([^=]+)') + sparmrex = re.compile('([^= \t]+)=([^= \t]+)[ \t]*(.*)') + expr1rex = re.compile('([^= \t]+)=\'([^\']+)\'[ \t]*(.*)') + expr2rex = re.compile('([^= \t]+)=\{([^\}]+)\}[ \t]*(.*)') + tokrex = re.compile('([^ \t]+)[ \t]*(.*)') + + if subname: + print('load ' + subname + ' -quiet', file=ofile) print('box 0um 0um 0um 0um', file=ofile) print('', file=ofile) # Generate all of the pins as labels - pinlist = subpins.split() - i = 0 - for pin in pinlist: - # Escape [ and ] in pin name - pin_esc = pin.replace('[', '\[').replace(']', '\]') - # To be done: watch for key=value parameters - print('add_pin ' + pin_esc + ' ' + str(i), file=ofile) - i += 1 + if subpins: + pinlist = subpins.split() + i = 0 + for pin in pinlist: + # Escape [ and ] in pin name + pin_esc = pin.replace('[', '\[').replace(']', '\]') + # To be done: watch for key=value parameters + print('add_pin ' + pin_esc + ' ' + str(i), file=ofile) + i += 1 # Set initial position for importing cells print('box size 0 0', file=ofile) @@ -95,41 +111,80 @@ print('box position ${posx}i ${posy}i', file=ofile) for comp in complist: - params = {} - tokens = comp.split() - # Diagnostic - # print("Adding component " + tokens[0]) - instname = tokens[0] - mult = 1 - for token in tokens[1:]: - rmatch = parmrex.match(token) - if rmatch: - parmname = rmatch.group(1).upper() - parmval = rmatch.group(2) - params[parmname] = parmval - if parmname.upper() == 'M': - try: - mult = int(parmval) - except ValueError: - # This takes care of multiplier expressions, as long - # as they don't reference parameter names themselves. - mult = eval(eval(parmval)) + pinlist = [] + paramlist = [] + + # Parse into pins, device name, and parameters. Make sure parameters + # incorporate quoted expressions as {} or ''. + rest = comp + while rest and rest != '': + gmatch = gparmrex.match(rest) + if gmatch: + break else: - # Last one that isn't a parameter will be kept - devtype = token + tmatch = tokrex.match(rest) + if tmatch: + token = tmatch.group(1) + pinlist.append(token) + rest = tmatch.group(2) + else: + rest = '' + + while rest and rest != '': + ematch = expr1rex.match(rest) + if ematch: + pname = ematch.group(1) + value = ematch.group(2) + paramlist.append((pname, '{' + value + '}')) + rest = ematch.group(3) + else: + ematch = expr2rex.match(rest) + if ematch: + pname = ematch.group(1) + value = ematch.group(2) + paramlist.append((pname, '{' + value + '}')) + rest = ematch.group(3) + else: + smatch = sparmrex.match(rest) + if smatch: + pname = smatch.group(1) + value = smatch.group(2) + paramlist.append((pname, value)) + rest = smatch.group(3) + else: + print('Error parsing line "' + comp + '"') + print('at: "' + rest + '"') + rest = '' - # If devtype is a cellname in the form "<lib>__<cell>" then check if <cell> is - # in the user's /ip/ directory. If so, recast devtype to just <cell>. - ematch = librex.match(devtype) - if ematch: - cellname = ematch.group(2) - if os.path.exists(os.path.expanduser('~/design/ip/' + cellname)): - devtype = cellname + if len(pinlist) < 2: + print('Error: No device type found in line "' + comp + '"') + print('Tokens found are: ' + ', '.join(pinlist)) + continue - # devtype is assumed to be in library. If not, it will throw an error and - # attempt to use 'getcell' on devtype. NOTE: Current usage is to not pass - # a library to netlist_to_layout.py but to rely on the PDK Tcl script to - # define variable PDKNAMESPACE, which is the namespace to use for low-level + instname = pinlist[0] + devtype = pinlist[-1] + pinlist = pinlist[0:-1] + + # Diagnostic + if debugmode: + print(' Adding component ' + devtype + ' instance ' + instname) + + mult = 1 + for param in paramlist: + parmname = param[0] + parmval = param[1] + if parmname.upper() == 'M': + try: + mult = int(parmval) + except ValueError: + # This takes care of multiplier expressions, as long + # as they don't reference parameter names themselves. + mult = eval(eval(parmval)) + + # devtype is assumed to be in library. If not, it will attempt to use + # 'getcell' on devtype. NOTE: Current usage is to not pass a library + # to netlist_to_layout.py but to rely on the PDK Tcl script to define + # variable PDKNAMESPACE, which is the namespace to use for low-level # components, and may not be the same name as the technology node. if library: libdev = library + '::' + devtype @@ -141,9 +196,9 @@ # Output all parameters. Parameters not used by the toolkit are ignored # by the toolkit. outparts.append('-spice') - for item in params: - outparts.append(str(item).lower()) - outparts.append(params[item]) + for param in paramlist: + outparts.append(str(param[0]).lower()) + outparts.append(param[1]) outstring = ' '.join(outparts) print('if {[catch {' + outstring + '}]} {', file=ofile) @@ -156,10 +211,58 @@ print('save ' + subname, file=ofile) def generate_layout_end(ofile=sys.stdout): + global debugmode + print('resumeall', file=ofile) - print('refresh', file=ofile) print('writeall force', file=ofile) - print('quit', file=ofile) + print('quit -noprompt', file=ofile) + +def parse_layout(topname, lines, ofile): + global debugmode + + subrex = re.compile('.subckt[ \t]+(.*)$', re.IGNORECASE) + devrex = re.compile('[xmcrbdivq]([^ \t]+)[ \t](.*)$', re.IGNORECASE) + namerex = re.compile('([^= \t]+)[ \t]+(.*)$', re.IGNORECASE) + endsrex = re.compile('^[ \t]*\.ends', re.IGNORECASE) + + insub = False + subname = '' + subpins = '' + complist = [] + toplist = [] + + for line in lines: + if not insub: + lmatch = subrex.match(line) + if lmatch: + rest = lmatch.group(1) + smatch = namerex.match(rest) + if smatch: + subname = smatch.group(1) + subpins = smatch.group(2) + insub = True + else: + print('File ' + inputfile + ': Failure to parse line ' + line) + else: + dmatch = devrex.match(line) + if dmatch: + toplist.append(line) + else: + lmatch = endsrex.match(line) + if lmatch: + insub = False + generate_layout_add(subname, subpins, complist, library, ofile) + subname = None + subpins = None + complist = [] + else: + dmatch = devrex.match(line) + if dmatch: + complist.append(line) + + # Add any top-level components + if toplist: + generate_layout_add(topname, None, toplist, library, ofile) def usage(): print('Usage:') @@ -171,11 +274,13 @@ print('') print('Options:') print(' -keep Keep the working script after completion.') + print(' -debug Provide verbose output while generating script.') print(' -help Print this help text.') # Main procedure if __name__ == '__main__': + global debugmode # Parse command line for options and arguments optionlist = [] @@ -236,50 +341,22 @@ # Read SPICE netlist with open(inputfile, 'r') as ifile: + if debugmode: + print('Reading file ' + inputfile) spicetext = ifile.read() - subrex = re.compile('.subckt[ \t]+(.*)$', re.IGNORECASE) - devrex = re.compile('[xmcrbdi]([^ \t]+)[ \t](.*)$', re.IGNORECASE) - namerex = re.compile('([^= \t]+)[ \t]+(.*)$', re.IGNORECASE) - endsrex = re.compile('^[ \t]*\.ends', re.IGNORECASE) - # Contatenate continuation lines spicelines = spicetext.replace('\n+', ' ').splitlines() - insub = False - subname = '' - subpins = '' - complist = [] + filename = os.path.split(inputfile)[1] + topname = os.path.splitext(filename)[0] + scriptfile = 'generate_layout.tcl' scriptpath = os.path.join(magpath, scriptfile) with open(scriptpath, 'w') as ofile: generate_layout_start(library, ofile) - for line in spicelines: - if not insub: - lmatch = subrex.match(line) - if lmatch: - rest = lmatch.group(1) - smatch = namerex.match(rest) - if smatch: - subname = smatch.group(1) - subpins = smatch.group(2) - insub = True - else: - print('File ' + inputfile + ': Failure to parse line ' + line) - else: - lmatch = endsrex.match(line) - if lmatch: - insub = False - generate_layout_add(subname, subpins, complist, library, ofile) - subname = None - subpins = None - complist = [] - else: - dmatch = devrex.match(line) - if dmatch: - complist.append(line) - + parse_layout(topname, spicelines, ofile) generate_layout_end(ofile) myenv = os.environ.copy()
diff --git a/sky130/magic/sky130.tcl b/sky130/magic/sky130.tcl index 506ce78..a653c4b 100644 --- a/sky130/magic/sky130.tcl +++ b/sky130/magic/sky130.tcl
@@ -4132,7 +4132,9 @@ topc 1 botc 1 poverlap 0 doverlap 1 lmin 0.15 wmin 0.42 \ compatible {sky130_fd_pr__pfet_01v8 \ sky130_fd_pr__pfet_01v8_lvt sky130_fd_pr__pfet_01v8_hvt \ - sky130_fd_pr__pfet_g5v0d10v5} full_metal 1} + sky130_fd_pr__pfet_g5v0d10v5} full_metal 1 \ + viasrc 100 viadrn 100 viagate 100 \ + viagb 0 viagr 0 viagl 0 viagt 0} } proc sky130::sky130_fd_pr__pfet_01v8_lvt_defaults {} { @@ -4141,7 +4143,9 @@ topc 1 botc 1 poverlap 0 doverlap 1 lmin 0.35 wmin 0.42 \ compatible {sky130_fd_pr__pfet_01v8 \ sky130_fd_pr__pfet_01v8_lvt sky130_fd_pr__pfet_01v8_hvt \ - sky130_fd_pr__pfet_g5v0d10v5} full_metal 1} + sky130_fd_pr__pfet_g5v0d10v5} full_metal 1 \ + viasrc 100 viadrn 100 viagate 100 \ + viagb 0 viagr 0 viagl 0 viagt 0} } proc sky130::sky130_fd_pr__pfet_01v8_hvt_defaults {} { @@ -4150,7 +4154,9 @@ topc 1 botc 1 poverlap 0 doverlap 1 lmin 0.15 wmin 0.42 \ compatible {sky130_fd_pr__pfet_01v8 \ sky130_fd_pr__pfet_01v8_lvt sky130_fd_pr__pfet_01v8_hvt \ - sky130_fd_pr__pfet_g5v0d10v5} full_metal 1} + sky130_fd_pr__pfet_g5v0d10v5} full_metal 1 \ + viasrc 100 viadrn 100 viagate 100 \ + viagb 0 viagr 0 viagl 0 viagt 0} } proc sky130::sky130_fd_pr__pfet_g5v0d10v5_defaults {} { @@ -4159,7 +4165,9 @@ topc 1 botc 1 poverlap 0 doverlap 1 lmin 0.50 wmin 0.42 \ compatible {sky130_fd_pr__pfet_01v8 \ sky130_fd_pr__pfet_01v8_lvt sky130_fd_pr__pfet_01v8_hvt \ - sky130_fd_pr__pfet_g5v0d10v5} full_metal 1} + sky130_fd_pr__pfet_g5v0d10v5} full_metal 1 \ + viasrc 100 viadrn 100 viagate 100 \ + viagb 0 viagr 0 viagl 0 viagt 0} } #----------------------------------------------------------------