blob: eb0f2ce1b6c213f4cf4ae359fbf96b639539bc22 [file] [log] [blame]
#!/usr/bin/env python3
#--------------------------------------------------------
# make_icon_from_soft.py --
#
# Create an electric icon (manually) from information taken from
# a verilog module.
#-----------------------------------------------------------------
import os
import re
import sys
import json
import datetime
import subprocess
def create_symbol(projectpath, verilogfile, project, destfile=None, debug=False, dolist=False):
if not os.path.exists(projectpath):
print('No path to project ' + projectpath)
return 1
if not os.path.isfile(verilogfile):
print('No path to verilog file ' + verilogfile)
return 1
if not os.path.exists(projectpath + '/elec'):
print('No electric subdirectory /elec/ in project.')
return 1
if not destfile:
delibdir = projectpath + '/elec/' + project + '.delib'
if not os.path.isdir(delibdir):
print('No electric library ' + project + '.delib in project.')
return 1
if os.path.isfile(delibdir + '/' + project + '.ic'):
print('Symbol file ' + project + '.ic exists already.')
print('Please remove it if you want to overwrite it.')
return 1
# By default, put the icon file in the project's electric library
destfile = projectpath + '/elec/' + project + '.delib/' + project + '.ic'
desthdr = projectpath + '/elec/' + project + '.delib/header'
else:
if os.path.isfile(destfile):
print('Symbol file ' + project + '.ic exists already.')
print('Please remove it if you want to overwrite it.')
return 1
destdir = os.path.split(destfile)[0]
desthdr = destdir + '/header'
if not os.path.isdir(destdir):
os.makedirs(destdir)
# Original verilog source can be very complicated to parse. Run through
# qflow's vlog2Verilog tool to get a much simplified header, which also
# preprocesses the verilog, handles parameters, etc.
vdir = os.path.split(verilogfile)[0]
vtempfile = vdir + '/vtemp.out'
p = subprocess.run(['/usr/local/share/qflow/bin/vlog2Verilog',
'-p', '-o', vtempfile, verilogfile], stdout = subprocess.PIPE)
if not os.path.exists(vtempfile):
print('Error: Failed to create preprocessed verilog from ' + verilogfile)
return 1
# Okay, ready to go. Now read the verilog source file and get the list
# of pins.
commstr1 = '/\*.*\*/'
commstr2 = '//[^\n]*\n'
c1rex = re.compile(commstr1)
c2rex = re.compile(commstr2)
# Find and isolate the module and its pin list.
modstr = 'module[ \t]+' + project + '[ \t]*\(([^\)]+)\)[ \t\n]*;'
modrex = re.compile(modstr)
# End parsing on any of these tokens
endrex = re.compile('[ \t]*(initial|function|task|always)')
inpins = []
outpins = []
iopins = []
invecs = []
outvecs = []
iovecs = []
with open(vtempfile, 'r') as ifile:
vlines = ifile.read()
# Remove comments
vlines2 = c2rex.sub('\n', c1rex.sub('', vlines))
# Find and isolate the module pin list
modpinslines = modrex.findall(vlines2)
modpinsstart = modrex.search(vlines2)
if modpinsstart:
startc = modpinsstart.span()[0]
else:
startc = 0
modpinsend = endrex.search(vlines2[startc:])
if modpinsend:
endc = modpinsend.span()[0]
else:
endc = len(vlines2)
vlines2 = vlines2[startc:endc]
# Find the module (there should be only one) and get pins if in the
# format with input / output declarations in the module heading.
pinlist = []
if len(modpinslines) > 0:
modpins = modpinslines[0]
pinlist = re.sub('[\t\n]', '', modpins).split(',')
# If each pinlist entry is only one word, then look for following
# lines "input", "output", etc., and compile them into a similar
# list. Then parse each list entry.
knownreal = {}
knownpower = {}
knownground = {}
if len(pinlist) > 0 and len(pinlist[0].split()) == 1:
invecrex = re.compile('\n[ \t]*input[ \t]*\[[ \t]*([0-9]+)[ \t]*:[ \t]*([0-9]+)[ \t]*\][ \t]*([^;]+);')
insigrex = re.compile('\n[ \t]*input[ \t]+([^\[;]+);')
outvecrex = re.compile('\n[ \t]*output[ \t]*\[[ \t]*([0-9]+)[ \t]*:[ \t]*([0-9]+)[ \t]*\][ \t]*([^;]+);')
outsigrex = re.compile('\n[ \t]*output[ \t]+([^;\[]+);')
iovecrex = re.compile('\n[ \t]*inout[ \t]*\[[ \t]*([0-9]+)[ \t]*:[ \t]*([0-9]+)[ \t]*\][ \t]*([^;]+);')
iosigrex = re.compile('\n[ \t]*inout [ \t]+([^;\[]+);')
# Find input, output, and inout lines
for test in insigrex.findall(vlines2):
pinname = list(item.strip() for item in test.split(','))
inpins.extend(pinname)
for test in outsigrex.findall(vlines2):
pinname = list(item.strip() for item in test.split(','))
outpins.extend(pinname)
for test in iosigrex.findall(vlines2):
pinname = list(item.strip() for item in test.split(','))
iopins.extend(pinname)
for test in invecrex.finditer(vlines2):
tpin = test.group(3).split(',')
for pin in tpin:
pinname = pin.strip() + '[' + test.group(1) + ':' + test.group(2) + ']'
invecs.append(pinname)
for test in outvecrex.finditer(vlines2):
tpin = test.group(3).split(',')
for pin in tpin:
pinname = pin.strip() + '[' + test.group(1) + ':' + test.group(2) + ']'
outvecs.append(pinname)
for test in iovecrex.finditer(vlines2):
tpin = test.group(3).split(',')
for pin in tpin:
pinname = pin.strip() + '[' + test.group(1) + ':' + test.group(2) + ']'
iovecs.append(pinname)
# Apply syntax checks (to do: check for "real" above)
powerrec = re.compile('VDD|VCC', re.IGNORECASE)
groundrec = re.compile('VSS|GND|GROUND', re.IGNORECASE)
for pinname in inpins + outpins + iopins + invecs + outvecs + iovecs:
pmatch = powerrec.match(pinname)
gmatch = groundrec.match(pinname)
if pmatch:
knownpower[pinname] = True
if gmatch:
knownground[pinname] = True
else:
# Get pin lists from module pin list. These are simpler to
# parse, since they have to be enumerated one by one.
invecrex = re.compile('[ \t]*input[ \t]*\[[ \t]*([0-9]+)[ \t]*:[ \t]*([0-9]+)[ \t]*\][ \t]*(.+)')
insigrex = re.compile('[ \t]*input[ \t]+([a-zA-Z_][^ \t]+)')
outvecrex = re.compile('[ \t]*output[ \t]*\[[ \t]*([0-9]+)[ \t]*:[ \t]*([0-9]+)[ \t]*\][ \t]*(.+)')
outsigrex = re.compile('[ \t]*output[ \t]+([a-zA-Z_][^ \t]+)')
iovecrex = re.compile('[ \t]*inout[ \t]*\[[ \t]*([0-9]+)[ \t]*:[ \t]*([0-9]+)[ \t]*\][ \t]*(.+)')
iosigrex = re.compile('[ \t]*inout[ \t]+([a-zA-Z_][^ \t]+)')
realrec = re.compile('[ \t]+real[ \t]+')
logicrec = re.compile('[ \t]+logic[ \t]+')
wirerec = re.compile('[ \t]+wire[ \t]+')
powerrec = re.compile('VDD|VCC', re.IGNORECASE)
groundrec = re.compile('VSS|GND|GROUND', re.IGNORECASE)
for pin in pinlist:
# Pull out any reference to "real", "logic", or "wire" to get pin name
ppin = realrec.sub(' ', logicrec.sub(' ', wirerec.sub(' ', pin.strip())))
pinname = None
# Make syntax checks
rmatch = realrec.match(pin)
pmatch = powerrec.match(pin)
gmatch = groundrec.match(pin)
imatch = insigrex.match(ppin)
if imatch:
pinname = imatch.group(1)
inpins.append(pinname)
omatch = outsigrex.match(ppin)
if omatch:
pinname = omatch.group(1)
outpins.append(pinname)
bmatch = iosigrex.match(ppin)
if bmatch:
pinname = bmatch.group(1)
iopins.append(pinname)
ivmatch = invecrex.match(ppin)
if ivmatch:
pinname = ivmatch.group(3) + '[' + ivmatch.group(1) + ':' + ivmatch.group(2) + ']'
invecs.append(pinname)
ovmatch = outvecrex.match(ppin)
if ovmatch:
pinname = ovmatch.group(3) + '[' + ovmatch.group(1) + ':' + ovmatch.group(2) + ']'
outvecs.append(pinname)
bvmatch = iovecrex.match(ppin)
if bvmatch:
pinname = bvmatch.group(3) + '[' + bvmatch.group(1) + ':' + bvmatch.group(2) + ']'
iovecs.append(pinname)
# Apply syntax checks
if pinname:
if rmatch:
knownreal[pinname] = True
if pmatch and rmatch:
knownpower[pinname] = True
if gmatch and rmatch:
knownground[pinname] = True
if (os.path.exists(vtempfile)):
os.remove(vtempfile)
if len(inpins) + len(outpins) + len(iopins) + len(invecs) + len(outvecs) + len(iovecs) == 0:
print('Failure to parse pin list for module ' + project + ' out of verilog source.')
return 1
if debug:
print("Input pins of module " + project + ":")
for pin in inpins:
print(pin)
print("Output pins of module " + project + ":")
for pin in outpins:
print(pin)
print("Bidirectional pins of module " + project + ":")
for pin in iopins:
print(pin)
# If "dolist" is True, then create a list of pin records in the style used by
# project.json, and return the list.
if dolist == True:
pinlist = []
for pin in inpins:
pinrec = {}
pinrec["name"] = pin
pinrec["description"] = "(add description here)"
if pin in knownreal:
pinrec["type"] = 'signal'
else:
pinrec["type"] = 'digital'
pinrec["Vmin"] = "-0.5"
pinrec["Vmax"] = "VDD + 0.3"
pinrec["dir"] = "input"
pinlist.append(pinrec)
for pin in outpins:
pinrec = {}
pinrec["name"] = pin
pinrec["description"] = "(add description here)"
if pin in knownreal:
pinrec["type"] = 'signal'
else:
pinrec["type"] = 'digital'
pinrec["Vmin"] = "-0.5"
pinrec["Vmax"] = "VDD + 0.3"
pinrec["dir"] = "output"
pinlist.append(pinrec)
for pin in iopins:
pinrec = {}
pinrec["name"] = pin
pinrec["description"] = "(add description here)"
if pin in knownpower:
pinrec["type"] = 'power'
pinrec["Vmin"] = "3.6"
pinrec["Vmax"] = "3.0"
elif pin in knownground:
pinrec["type"] = 'ground'
pinrec["Vmin"] = "0"
pinrec["Vmax"] = "0"
elif pin in knownreal:
pinrec["type"] = 'signal'
pinrec["Vmin"] = "-0.5"
pinrec["Vmax"] = "VDD + 0.3"
else:
pinrec["type"] = 'digital'
pinrec["Vmin"] = "-0.5"
pinrec["Vmax"] = "VDD + 0.3"
pinrec["dir"] = "inout"
pinlist.append(pinrec)
for pin in invecs:
pinrec = {}
pinrec["name"] = pin
pinrec["description"] = "(add description here)"
if pin in knownreal:
pinrec["type"] = 'signal'
else:
pinrec["type"] = 'digital'
pinrec["dir"] = "input"
pinrec["Vmin"] = "-0.5"
pinrec["Vmax"] = "VDD + 0.3"
pinlist.append(pinrec)
for pin in outvecs:
pinrec = {}
pinrec["name"] = pin
pinrec["description"] = "(add description here)"
if pin in knownreal:
pinrec["type"] = 'signal'
else:
pinrec["type"] = 'digital'
pinrec["dir"] = "output"
pinrec["Vmin"] = "-0.5"
pinrec["Vmax"] = "VDD + 0.3"
pinlist.append(pinrec)
for pin in iovecs:
pinrec = {}
pinrec["name"] = pin
pinrec["description"] = "(add description here)"
if pin in knownpower:
pinrec["type"] = 'power'
pinrec["Vmin"] = "3.6"
pinrec["Vmax"] = "3.0"
elif pin in knownground:
pinrec["type"] = 'ground'
pinrec["Vmin"] = "0"
pinrec["Vmax"] = "0"
elif pin in knownreal:
pinrec["type"] = 'signal'
pinrec["Vmin"] = "-0.5"
pinrec["Vmax"] = "VDD + 0.3"
else:
pinrec["type"] = 'digital'
pinrec["Vmin"] = "-0.5"
pinrec["Vmax"] = "VDD + 0.3"
pinrec["dir"] = "inout"
pinlist.append(pinrec)
return pinlist
# Okay, we've got all the pins, now build the symbol.
leftpins = len(inpins) + len(invecs)
rightpins = len(outpins) + len(outvecs)
# Arbitrarily, bidirectional pins are put on bottom and vectors on top.
toppins = len(iovecs)
botpins = len(iopins)
height = 2 + max(leftpins, rightpins) * 10
width = 82 + max(toppins, botpins) * 10
# Enforce minimum height (minimum width enforced above)
if height < 40:
height = 40
# Run electric -v to get version string
p = subprocess.run(['electric', '-v'], stdout = subprocess.PIPE)
vstring = p.stdout.decode('utf-8').rstrip()
# Get timestamp
timestamp = str(int(datetime.datetime.now().strftime("%s")) * 1000)
with open(destfile, 'w') as ofile:
print('H' + project + '|' + vstring, file=ofile)
print('', file=ofile)
print('# Cell ' + project + ';1{ic}', file=ofile)
print('C' + project + ';1{ic}||artwork|' + timestamp + '|' + timestamp + '|E', file=ofile)
print('Ngeneric:Facet-Center|art@0||0|0||||AV', file=ofile)
print('NBox|art@1||0|0|' + str(width) + '|' + str(height) + '||', file=ofile)
pnum = 0
# Title
print('Ngeneric:Invisible-Pin|pin@' + str(pnum) + '||0|5|||||ART_message(BD5G5;)S' + project, file=ofile)
pnum += 1
# Fill in left side pins
px = -(width / 2)
py = -(height / 2) + 5
for pin in inpins:
print('Nschematic:Wire_Pin|pin@' + str(pnum) + '||' + str(px - 10) + '|' + str(py) + '|1|1||', file=ofile)
pnum += 1
print('NPin|pin@' + str(pnum) + '||' + str(px - 10) + '|' + str(py) + '|1|1||', file=ofile)
pnum += 1
print('NPin|pin@' + str(pnum) + '||' + str(px) + '|' + str(py) + '|1|1||', file=ofile)
pnum += 1
py += 10
for pin in invecs:
print('Nschematic:Bus_Pin|pin@' + str(pnum) + '||' + str(px - 10) + '|' + str(py) + '|1|1||', file=ofile)
pnum += 1
print('NPin|pin@' + str(pnum) + '||' + str(px - 10) + '|' + str(py) + '|1|1||', file=ofile)
pnum += 1
print('NPin|pin@' + str(pnum) + '||' + str(px) + '|' + str(py) + '|1|1||', file=ofile)
pnum += 1
py += 10
# Fill in right side pins
px = (width / 2)
py = -(height / 2) + 5
for pin in outpins:
print('Nschematic:Wire_Pin|pin@' + str(pnum) + '||' + str(px + 10) + '|' + str(py) + '|1|1||', file=ofile)
pnum += 1
print('NPin|pin@' + str(pnum) + '||' + str(px + 10) + '|' + str(py) + '|1|1||', file=ofile)
pnum += 1
print('NPin|pin@' + str(pnum) + '||' + str(px) + '|' + str(py) + '|1|1||', file=ofile)
pnum += 1
py += 10
for pin in outvecs:
print('Nschematic:Bus_Pin|pin@' + str(pnum) + '||' + str(px + 10) + '|' + str(py) + '|1|1||', file=ofile)
pnum += 1
print('NPin|pin@' + str(pnum) + '||' + str(px + 10) + '|' + str(py) + '|1|1||', file=ofile)
pnum += 1
print('NPin|pin@' + str(pnum) + '||' + str(px) + '|' + str(py) + '|1|1||', file=ofile)
pnum += 1
py += 10
# Fill in bottom side pins
py = -(height / 2)
px = -(width / 2) + 45
for pin in iopins:
print('Nschematic:Wire_Pin|pin@' + str(pnum) + '||' + str(px) + '|' + str(py - 10) + '|1|1||', file=ofile)
pnum += 1
print('NPin|pin@' + str(pnum) + '||' + str(px) + '|' + str(py) + '|1|1||', file=ofile)
pnum += 1
print('NPin|pin@' + str(pnum) + '||' + str(px) + '|' + str(py - 10) + '|1|1||', file=ofile)
pnum += 1
px += 10
# Fill in top side pins
py = (height / 2)
px = -(width / 2) + 45
for pin in iovecs:
print('Nschematic:Bus_Pin|pin@' + str(pnum) + '||' + str(px) + '|' + str(py + 10) + '|1|1||', file=ofile)
pnum += 1
print('NPin|pin@' + str(pnum) + '||' + str(px) + '|' + str(py) + '|1|1||', file=ofile)
pnum += 1
print('NPin|pin@' + str(pnum) + '||' + str(px) + '|' + str(py + 10) + '|1|1||', file=ofile)
pnum += 1
px += 10
# Start back at pin 1 and retain the same order when drawing wires
pnum = 1
nnum = 0
px = -(width / 2)
py = -(height / 2) + 5
for pin in inpins:
pnum += 1
print('ASolid|net@' + str(nnum) + '|||FS0|pin@' + str(pnum) + '||' + str(px - 10) + '|' + str(py) + '|pin@' + str(pnum + 1) + '||' + str(px) + '|' + str(py), file=ofile)
pnum += 2
nnum += 1
py += 10
for pin in invecs:
pnum += 1
print('ASolid|net@' + str(nnum) + '|||FS0|pin@' + str(pnum) + '||' + str(px - 10) + '|' + str(py) + '|pin@' + str(pnum + 1) + '||' + str(px) + '|' + str(py), file=ofile)
pnum += 2
nnum += 1
py += 10
px = (width / 2)
py = -(height / 2) + 5
for pin in outpins:
pnum += 1
print('ASolid|net@' + str(nnum) + '|||FS0|pin@' + str(pnum) + '||' + str(px + 10) + '|' + str(py) + '|pin@' + str(pnum + 1) + '||' + str(px) + '|' + str(py), file=ofile)
pnum += 2
nnum += 1
py += 10
for pin in outvecs:
pnum += 1
print('ASolid|net@' + str(nnum) + '|||FS0|pin@' + str(pnum) + '||' + str(px + 10) + '|' + str(py) + '|pin@' + str(pnum + 1) + '||' + str(px) + '|' + str(py), file=ofile)
pnum += 2
nnum += 1
py += 10
py = -(height / 2)
px = -(width / 2) + 45
for pin in iopins:
pnum += 1
print('ASolid|net@' + str(nnum) + '|||FS0|pin@' + str(pnum) + '||' + str(px) + '|' + str(py) + '|pin@' + str(pnum + 1) + '||' + str(px) + '|' + str(py - 10), file=ofile)
pnum += 2
nnum += 1
px += 10
py = (height / 2)
px = -(width / 2) + 45
for pin in iovecs:
pnum += 1
print('ASolid|net@' + str(nnum) + '|||FS0|pin@' + str(pnum) + '||' + str(px) + '|' + str(py) + '|pin@' + str(pnum + 1) + '||' + str(px) + '|' + str(py + 10), file=ofile)
pnum += 2
nnum += 1
px += 10
# Add the exports (which are the only nontrivial elements)
pnum = 1
for pin in inpins:
print('E' + pin + '||D6G4;X12.0;Y0.0;|pin@' + str(pnum) + '||I', file=ofile)
pnum += 3
for pin in invecs:
print('E' + pin + '||D6G4;X12.0;Y0.0;|pin@' + str(pnum) + '||I', file=ofile)
pnum += 3
for pin in outpins:
print('E' + pin + '||D4G4;X-12.0;Y0.0;|pin@' + str(pnum) + '||O', file=ofile)
pnum += 3
for pin in outvecs:
print('E' + pin + '||D4G4;X-12.0;Y0.0;|pin@' + str(pnum) + '||O', file=ofile)
pnum += 3
for pin in iopins:
print('E' + pin + '||D6G4;RX0.0;Y12.0;|pin@' + str(pnum) + '||B', file=ofile)
pnum += 3
for pin in iovecs:
print('E' + pin + '||D6G4;RRRX0.0;Y-12.0;|pin@' + str(pnum) + '||B', file=ofile)
pnum += 3
# X marks the spot, or at least the end.
print('X', file=ofile)
if not os.path.isfile(desthdr):
with open(desthdr, 'w') as ofile:
print('# header information:', file=ofile)
print('H' + project + '|' + vstring, file=ofile)
print('', file=ofile)
print('# Views:', file=ofile)
print('Vicon|ic', file=ofile)
print('', file=ofile)
print('# Tools:', file=ofile)
print('Ouser|DefaultTechnology()Sschematic', file=ofile)
print('Osimulation|VerilogUseAssign()BT', file=ofile)
print('C____SEARCH_FOR_CELL_FILES____', file=ofile)
return 0
def usage():
print("make_icon_from_soft.py <project_path> [<verilog_source>] [<output_file>]")
print("")
print(" where <project_path> is the path to a standard efabless project, and")
print(" <verilog_source> is the path to a verilog source file.")
print("")
print(" The module name must be the same as the project's ip-name.")
print("")
print(" <verilog_source> is assumed to be in verilog/source/<ip-name>.v by")
print(" default if not otherwise specified.")
print("")
print(" If <output_file> is not specified, output goes in the project's")
print(" electric library.")
print("")
if __name__ == '__main__':
arguments = []
options = []
for item in sys.argv[1:]:
if item[0] == '-':
options.append(item.strip('-'))
else:
arguments.append(item)
debug = True if 'debug' in options else False
numarg = len(arguments)
if numarg > 3 or numarg == 0:
usage()
sys.exit(0)
projectpath = arguments[0]
projdirname = os.path.split(projectpath)[1]
jsonfile = projectpath + '/project.json'
if not os.path.isfile(jsonfile):
# Legacy behavior is to have the JSON file name the same as the directory name.
jsonfile = projectpath + '/' + projdirname + '.json'
if not os.path.isfile(jsonfile):
print('Error: No project JSON file found for project ' + projdirname)
sys.exit(1)
project = None
with open(jsonfile, 'r') as ifile:
datatop = json.load(ifile)
dsheet = datatop['data-sheet']
project = dsheet['ip-name']
if not project:
print('Error: No project IP name in project JSON file.')
sys.exit(1)
if numarg > 1:
verilogfile = arguments[1]
else:
verilogfile = projectpath + '/verilog/source/' + project + '.v'
if not os.path.exists(verilogfile):
print('Error: No verilog file ' + verilogfile + ' found.')
print('Please specify full path as 2nd argument.')
sys.exit(1)
if numarg > 2:
destfile = arguments[2]
else:
destfile = projectpath + '/elec/' + project + '.delib/' + project + '.ic'
if os.path.exists(destfile):
print('Error: Icon file ' + destfile + ' already exists.')
print('Please delete any unwanted original before running this script.')
sys.exit(1)
result = create_symbol(projectpath, verilogfile, project, destfile, debug)
sys.exit(result)