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()