blob: d6fca228e418e89af44c72703460eef1a12451bb [file] [log] [blame]
#!/usr/bin/env python3
#-----------------------------------------------------------------------
# netlist_to_layout.py
#-----------------------------------------------------------------------
#
# Generate a magic layout from a SPICE netlist, running magic in batch
# mode and calling up the PDK selections non-interactively for each
# component in the netlist.
#
#---------------------------------------------------------------------
# Written by Tim Edwards
# efabless, inc.
# November 17, 2016
# Updated December 17, 2016
# Version 1.0
# Imported December 22, 2020 to open_pdks
# Updated February 10, 2021 for use running on netlist alone
#---------------------------------------------------------------------
import os
import re
import sys
import subprocess
def generate_layout_start(library, ofile=sys.stdout):
# 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('proc move_forward {instname} {', file=ofile)
print(' select cell $instname', file=ofile)
print(' set anum [lindex [array -list count] 1]', file=ofile)
print(' set xpitch [lindex [array -list pitch] 0]', file=ofile)
print(' set bbox [box values]', file=ofile)
print(' set posx [lindex $bbox 0]', file=ofile)
print(' set posy [lindex $bbox 1]', file=ofile)
print(' set width [expr [lindex $bbox 2] - $posx]', file=ofile)
print(' set posx [expr $posx + $width + $xpitch * $anum]', file=ofile)
print(' box position ${posx}i ${posy}i', file=ofile)
print(' return [lindex $bbox 3]', file=ofile)
print('}', file=ofile)
print('', file=ofile)
print('proc get_and_move_inst {cellname instname anum} {', file=ofile)
print(' set newinst [getcell $cellname]', file=ofile)
print(' select cell $newinst', file=ofile)
print(' if {$newinst == ""} {return}', file=ofile)
print(' identify $instname', file=ofile)
print(' if {$anum > 1} {array 1 $anum}', file=ofile)
print(' set bbox [box values]', file=ofile)
print(' set posx [lindex $bbox 2]', file=ofile)
print(' set posy [lindex $bbox 1]', file=ofile)
print(' box position ${posx}i ${posy}i', file=ofile)
print(' return [lindex $bbox 3]', file=ofile)
print('}', file=ofile)
print('', file=ofile)
print('proc add_pin {pinname portnum} {', file=ofile)
print(' box size 1um 1um', file=ofile)
print(' paint m1', file=ofile)
print(' label $pinname FreeSans 16 0 0 0 c m1', file=ofile)
print(' port make $portnum', file=ofile)
print(' box move s 2um', file=ofile)
print('}', file=ofile)
print('', file=ofile)
if not library:
print('namespace import ${PDKNAMESPACE}::*', file=ofile)
print('suspendall', file=ofile)
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)
print('load ' + subname, 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
# Set initial position for importing cells
print('box size 0 0', file=ofile)
print('set posx 0', file=ofile)
print('set posy [expr {round(3 / [cif scale out])}]', file=ofile)
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))
else:
# Last one that isn't a parameter will be kept
devtype = token
# 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
# 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
# components, and may not be the same name as the technology node.
if library:
libdev = library + '::' + devtype
else:
libdev = '${PDKNAMESPACE}::' + devtype
outparts = []
outparts.append('magic::gencell ' + libdev + ' ' + instname)
# 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])
outstring = ' '.join(outparts)
print('if {[catch {' + outstring + '}]} {', file=ofile)
print(' get_and_move_inst ' + devtype + ' ' + instname
+ ' ' + str(mult), file=ofile)
print('} else {', file=ofile)
print(' move_forward ' + instname, file=ofile)
print('}', file=ofile)
print('', file=ofile)
print('save ' + subname, file=ofile)
def generate_layout_end(ofile=sys.stdout):
print('resumeall', file=ofile)
print('refresh', file=ofile)
print('writeall force', file=ofile)
print('quit', file=ofile)
def usage():
print('Usage:')
print(' netlist_to_layout.py <filename> [<namespace>] [-options]')
print('')
print('Arguments:')
print(' <filename> is the path to the SPICE netlist to import to magic')
print(' <namespace> is the namespace of the PDK')
print('')
print('Options:')
print(' -keep Keep the working script after completion.')
print(' -help Print this help text.')
# Main procedure
if __name__ == '__main__':
# Parse command line for options and arguments
optionlist = []
arguments = []
for item in sys.argv[1:]:
if item.find('-', 0) == 0:
optionlist.append(item)
else:
arguments.append(item)
if len(arguments) > 0:
inputfile = arguments[0]
if len(arguments) > 1:
library = arguments[1]
else:
library = None
else:
usage()
sys.exit(0)
debugmode = False
keepmode = False
for item in optionlist:
result = item.split('=')
if result[0] == '-help':
usage()
sys.exit(0)
elif result[0] == '-debug':
debugmode = True
elif result[0] == '-keep':
keepmode = True
else:
usage()
sys.exit(1)
netpath = os.path.split(inputfile)[0]
if netpath == '':
netpath = os.getcwd()
if os.path.splitext(inputfile)[1] == '.sch':
print('Sorry, automatic conversion of schematic to netlist not yet supported.')
sys.exit(1)
netroot = os.path.split(netpath)[0]
magpath = os.path.join(netroot, 'mag')
if not os.path.isdir(magpath):
print('Error: Layout path "' + magpath + '" does not exist or is not readable.')
sys.exit(1)
# NOTE: There should be some attempt to find the installed PDK magicrc file
# if there is no mag/ directory.
rcfile = '.magicrc'
rcfilepath = os.path.join(magpath, rcfile)
if not os.path.isfile(rcfilepath):
print('Error: No startup script file "' + rcfilepath + '"')
sys.exit(1)
# Read SPICE netlist
with open(inputfile, 'r') as ifile:
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 = []
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)
generate_layout_end(ofile)
myenv = os.environ.copy()
myenv['MAGTYPE'] = 'mag'
# Run the layout generator
mproc = subprocess.run(['magic', '-dnull', '-noconsole', '-rcfile',
rcfile, scriptfile],
stdin = subprocess.DEVNULL, stdout = subprocess.PIPE,
stderr = subprocess.PIPE, cwd = magpath,
env = myenv, universal_newlines = True)
if mproc.stdout:
for line in mproc.stdout.splitlines():
print(line)
if mproc.stderr:
print('Error message output from magic:')
for line in mproc.stderr.splitlines():
print(line)
if mproc.returncode != 0:
print('ERROR: Magic exited with status ' + str(mproc.returncode))
# Clean up
if not keepmode:
os.remove(scriptpath)
sys.exit(0)