You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			428 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			428 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			Python
		
	
#!/usr/bin/python3
 | 
						|
#
 | 
						|
# Parse table comments and create equations.
 | 
						|
 | 
						|
from optparse import OptionParser
 | 
						|
import re
 | 
						|
from shutil import copyfile
 | 
						|
 | 
						|
#--------------------------------------------------------------------------------------------------
 | 
						|
# Initialize
 | 
						|
 | 
						|
TYPE_INPUT = 0
 | 
						|
TYPE_OUTPUT = 1
 | 
						|
TYPE_SKIP = 99
 | 
						|
 | 
						|
lines = []
 | 
						|
tableMatches = []
 | 
						|
tableNames = []
 | 
						|
tableLines = []
 | 
						|
tables = {}
 | 
						|
 | 
						|
failOnError = True
 | 
						|
inFile = 'test.vhdl'
 | 
						|
outFileExt = 'vtable'
 | 
						|
overwrite = True
 | 
						|
backupExt = 'orig'
 | 
						|
backup = True
 | 
						|
noisy = False
 | 
						|
quiet = False
 | 
						|
verilog = False
 | 
						|
 | 
						|
#--------------------------------------------------------------------------------------------------
 | 
						|
# Handle command line
 | 
						|
 | 
						|
usage = 'vtable [options] inFile'
 | 
						|
 | 
						|
parser = OptionParser(usage)
 | 
						|
parser.add_option('-f', '--outfile', dest='outFile', help='output file, default=[inFile]' + outFileExt)
 | 
						|
parser.add_option('-o', '--overwrite', dest='overwrite', help='overwrite inFile, default=' + str(overwrite))
 | 
						|
parser.add_option('-b', '--backup', dest='backup', help='backup original file, default=' + str(backup))
 | 
						|
parser.add_option('-q', '--quiet', dest='quiet', action='store_true', help='quiet messages, default=' + str(quiet))
 | 
						|
parser.add_option('-n', '--noisy', dest='noisy', action='store_true', help='noisy messages, default=' + str(noisy))
 | 
						|
parser.add_option('-V', '--verilog', dest='verilog', action='store_true', help='source is verilog, default=' + str(verilog))
 | 
						|
 | 
						|
options, args = parser.parse_args()
 | 
						|
 | 
						|
if len(args) != 1:
 | 
						|
    parser.error(usage)
 | 
						|
    quit(-1)
 | 
						|
else:
 | 
						|
    inFile = args[0]
 | 
						|
 | 
						|
if options.overwrite == '0':
 | 
						|
    overwrite = False
 | 
						|
elif options.overwrite == '1':
 | 
						|
    overwrite == True
 | 
						|
    if options.outFile is not None:
 | 
						|
        parser.error('Can\'t specify outfile and overrite!')
 | 
						|
        quit(-1)
 | 
						|
elif options.overwrite is not None:
 | 
						|
    parser.error('overwrite: 0|1')
 | 
						|
    quit(-1)
 | 
						|
 | 
						|
if options.quiet is not None:
 | 
						|
    quiet = True
 | 
						|
 | 
						|
if options.noisy is not None:
 | 
						|
    noisy = True
 | 
						|
 | 
						|
if options.verilog is not None:
 | 
						|
    verilog = True
 | 
						|
 | 
						|
if options.backup == '0':
 | 
						|
    backup = False
 | 
						|
elif options.backup == '1':
 | 
						|
    backup == True
 | 
						|
elif options.backup is not None:
 | 
						|
    parser.error('backup: 0|1')
 | 
						|
    quit(-1)
 | 
						|
 | 
						|
if options.outFile is not None:
 | 
						|
    outFile = options.outFile
 | 
						|
elif overwrite:
 | 
						|
    outFile = inFile
 | 
						|
else:
 | 
						|
    outFile = inFile + '.' + outFileExt
 | 
						|
 | 
						|
backupFile = inFile + '.' + backupExt
 | 
						|
 | 
						|
#--------------------------------------------------------------------------------------------------
 | 
						|
# Objects
 | 
						|
 | 
						|
class Signal:
 | 
						|
 | 
						|
    def __init__(self, name, type):
 | 
						|
        self.name = name;
 | 
						|
        self.type = type;
 | 
						|
 | 
						|
class Table:
 | 
						|
 | 
						|
    def __init__(self, name):
 | 
						|
        self.name = name
 | 
						|
        self.source = []
 | 
						|
        self.signals = {}
 | 
						|
        self.signalsByCol = {}
 | 
						|
        self.typesByCol = {}
 | 
						|
        self.specs = [] # list of specsByCol
 | 
						|
        self.equations = []
 | 
						|
        self.added = False
 | 
						|
 | 
						|
    def validate(self):
 | 
						|
        # check that all signals have a good type
 | 
						|
        for col in self.signalsByCol:
 | 
						|
            if col not in self.typesByCol:
 | 
						|
                error('Table ' + self.name + ': no signal type for ' + self.signalsByCol[col])
 | 
						|
            elif self.typesByCol[col] == None:
 | 
						|
                error('Table ' + self.name + ': bad signal type (' + str(self.typesByCol[col]) + ') for ' + str(self.signalsByCol[col]))
 | 
						|
 | 
						|
    def makeRTL(self, form=None):
 | 
						|
        outputsByCol = {}
 | 
						|
 | 
						|
 | 
						|
        #for col,type in self.typesByCol.items():
 | 
						|
        for col in sorted(self.typesByCol):
 | 
						|
          type = self.typesByCol[col]
 | 
						|
          if type == TYPE_OUTPUT:
 | 
						|
            if col in self.signalsByCol:
 | 
						|
               outputsByCol[col] = self.signalsByCol[col]
 | 
						|
            else:
 | 
						|
               print(self.signalsByCol)
 | 
						|
               print(self.typesByCol)
 | 
						|
               error('Table ' + self.name + ': output is specified in col ' + str(col) + ' but no signal exists')
 | 
						|
 | 
						|
        #for sigCol,sig in outputsByCol.items():
 | 
						|
        for sigCol in sorted(outputsByCol):
 | 
						|
            sig = outputsByCol[sigCol]
 | 
						|
            if not verilog:
 | 
						|
               line = sig + ' <= '
 | 
						|
            else:
 | 
						|
               line = 'assign ' + sig + ' = '
 | 
						|
            nonzero = False
 | 
						|
            for specsByCol in self.specs:
 | 
						|
                terms = []
 | 
						|
                if sigCol not in specsByCol:
 | 
						|
                    #error('* Output ' + sig + ' has no specified value for column ' + str(col))
 | 
						|
                    1 # no error, can be dontcare
 | 
						|
                elif specsByCol[sigCol] == '1':
 | 
						|
                    for col,val in specsByCol.items():
 | 
						|
                        if col not in self.typesByCol:
 | 
						|
                            if noisy:
 | 
						|
                                error('Table ' + self.name +': unexpected value in spec column ' + str(col) + ' (' + str(val) + ') - no associated signal', False) #wtf UNTIL CAN HANDLE COMMENTS AT END!!!!!!!!!!!!!!!!!!!
 | 
						|
                        elif self.typesByCol[col] == TYPE_INPUT:
 | 
						|
                            if val == '0':
 | 
						|
                                terms.append(opNot + self.signalsByCol[col])
 | 
						|
                                if nonzero and len(terms) == 1:
 | 
						|
                                    line = line + ') ' + opOr + '\n  (';
 | 
						|
                                elif len(terms) == 1:
 | 
						|
                                    line = line + '\n  ('
 | 
						|
                                nonzero = True
 | 
						|
                            elif val == '1':
 | 
						|
                                terms.append(self.signalsByCol[col])
 | 
						|
                                if nonzero and len(terms) == 1:
 | 
						|
                                    line = line + ') ' + opOr + '\n  (';
 | 
						|
                                elif len(terms) == 1:
 | 
						|
                                    line = line + '\n  ('
 | 
						|
                                nonzero = True
 | 
						|
                            else:
 | 
						|
                                error('Table ' + self.name +': unexpected value in spec column ' + str(col) + ' (' + str(val) + ')')
 | 
						|
                if len(terms) > 0:
 | 
						|
                    line = line + (' ' + opAnd + ' ').join(terms)
 | 
						|
            if not nonzero:
 | 
						|
                line = line + zero + ";";
 | 
						|
            else:
 | 
						|
                line = line + ');'
 | 
						|
            self.equations.append(line)
 | 
						|
 | 
						|
        return self.equations
 | 
						|
 | 
						|
    def printv(self):
 | 
						|
        self.makeRTL()
 | 
						|
        print('\n'.join(self.equations))
 | 
						|
 | 
						|
    def printinfo(self):
 | 
						|
        print('Table: ' + self.name)
 | 
						|
        print
 | 
						|
        for l in self.source:
 | 
						|
            print(l)
 | 
						|
        print
 | 
						|
        print('Signals by column:')
 | 
						|
        for col in sorted(self.signalsByCol):
 | 
						|
            print('{0:>3}. {1:} ({2:}) '.format(col, self.signalsByCol[col], 'in' if self.typesByCol[col] == TYPE_INPUT else 'out'))
 | 
						|
 | 
						|
 | 
						|
#--------------------------------------------------------------------------------------------------
 | 
						|
# Functions
 | 
						|
 | 
						|
def error(msg, quitOverride=None):
 | 
						|
    print('*** ' + msg)
 | 
						|
    if quitOverride == False:
 | 
						|
        1
 | 
						|
    elif (quitOverride == None) or failOnError:
 | 
						|
        quit(-10)
 | 
						|
    elif quitOverride:
 | 
						|
        quit(-10)
 | 
						|
 | 
						|
#--------------------------------------------------------------------------------------------------
 | 
						|
# Do something
 | 
						|
 | 
						|
if not verilog:
 | 
						|
   openBracket = '('
 | 
						|
   closeBracket = ')'
 | 
						|
   opAnd = 'and'
 | 
						|
   opOr = 'or'
 | 
						|
   opNot = 'not '
 | 
						|
   zero = "'0'"
 | 
						|
   tablePattern = re.compile(r'^\s*?--tbl(?:\s+([^\s]+).*$|\s*$)')
 | 
						|
   tableGenPattern = re.compile(r'^\s*?--vtable(?:\s+([^\s]+).*$)')
 | 
						|
   commentPattern = re.compile(r'^\s*?(--.*$|\s*$)')
 | 
						|
   tableLinePattern = re.compile(r'^.*?--(.*)')
 | 
						|
   namePattern = re.compile(r'([a-zA-z\d_\(\)\.\[\]]+)')
 | 
						|
else:
 | 
						|
   openBracket = '['
 | 
						|
   closeBracket = ']'
 | 
						|
   opAnd = '&'
 | 
						|
   opOr = '|'
 | 
						|
   opNot = '~'
 | 
						|
   zero = "'b0"
 | 
						|
   tablePattern = re.compile(r'^\s*?\/\/tbl(?:\s+([^\s]+).*$|\s*$)')
 | 
						|
   tableGenPattern = re.compile(r'^\s*?\/\/vtable(?:\s+([^\s]+).*$)')
 | 
						|
   commentPattern = re.compile(r'^\s*?(\/\/.*$|\s*$)')
 | 
						|
   tableLinePattern = re.compile(r'^.*?\/\/(.*)')
 | 
						|
   namePattern = re.compile(r'([a-zA-z\d_\(\)\.\[\]]+)')
 | 
						|
 | 
						|
# find the lines with table spec
 | 
						|
try:
 | 
						|
    inf = open(inFile)
 | 
						|
    for i, line in enumerate(inf):
 | 
						|
        lines.append(line.strip('\n'))
 | 
						|
        for match in re.finditer(tablePattern, line):
 | 
						|
            tableMatches.append(i)
 | 
						|
    inf.close()
 | 
						|
except Exception as e:
 | 
						|
    error('Error opening input file ' + inFile + '\n' + str(e), True)
 | 
						|
 | 
						|
# validate matches; should be paired, nothing but comments and empties; table may be named
 | 
						|
# between them
 | 
						|
 | 
						|
for i in range(0, len(tableMatches), 2):
 | 
						|
 | 
						|
    if i + 1 > len(tableMatches) - 1:
 | 
						|
        error('Mismatched table tags.\nFound so far: ' + ', '.join(tableNames), True)
 | 
						|
 | 
						|
    tLines = lines[tableMatches[i]:tableMatches[i+1]+1]
 | 
						|
    tableLines.append(tLines)
 | 
						|
    tName = re.match(tablePattern, lines[tableMatches[i]]).groups()[0]
 | 
						|
    if tName is None:
 | 
						|
        tName = 'noname_' + str(tableMatches[i] + 1)
 | 
						|
    tableNames.append(tName)
 | 
						|
 | 
						|
    for line in tLines:
 | 
						|
        if not re.match(commentPattern, line):
 | 
						|
            error('Found noncomment, nonempty line in table ' + tName + ':\n' + line, True)
 | 
						|
 | 
						|
print('Found tables: ' + ', '.join(tableNames))
 | 
						|
 | 
						|
# build table objects
 | 
						|
 | 
						|
for table, tName in zip(tableLines, tableNames):
 | 
						|
    print('Parsing ' + tName + '...')
 | 
						|
    namesByCol = {}
 | 
						|
    colsByName = {}
 | 
						|
    bitsByCol = {}
 | 
						|
    typesByCol = {}
 | 
						|
    specs = []
 | 
						|
 | 
						|
# parse the table - do by Table.parse()
 | 
						|
    tLines = table[1:-1]     # exclude --tbl
 | 
						|
    for line in tLines:
 | 
						|
        if line.strip() == '':
 | 
						|
          continue
 | 
						|
        try:
 | 
						|
          spec = re.search(tableLinePattern, line).groups()[0]
 | 
						|
        except Exception as e:
 | 
						|
          error('Problem parsing table line:\n' + line, True)
 | 
						|
        if len(spec) > 0:
 | 
						|
            if spec[0] == 'n':
 | 
						|
                for match in re.finditer(namePattern, spec[1:]):
 | 
						|
                    # col 0 is first col after n
 | 
						|
                    namesByCol[match.start()] = match.groups()[0]
 | 
						|
                    colsByName[match.groups()[0]] = match.start()
 | 
						|
            elif spec[0] == 'b':
 | 
						|
                for i, c in enumerate(spec[1:]):
 | 
						|
                    if c == ' ' or c == '|':
 | 
						|
                        continue
 | 
						|
                    try:
 | 
						|
                        bit = int(c)
 | 
						|
                    except:
 | 
						|
                        error('Unexpected char in bit line at position ' + str(i) + ' (' + c + ')\n' + line)
 | 
						|
                        bit = None
 | 
						|
                    if i in bitsByCol and bitsByCol[i] is not None:
 | 
						|
                        bitsByCol[i] = bitsByCol[i]*10+bit
 | 
						|
                    else:
 | 
						|
                        bitsByCol[i] = bit
 | 
						|
            elif spec[0] == 't':
 | 
						|
                for i, c in enumerate(spec[1:]):
 | 
						|
                    if c.lower() == 'i':
 | 
						|
                        typesByCol[i] = TYPE_INPUT
 | 
						|
                    elif c.lower() == 'o':
 | 
						|
                        typesByCol[i] = TYPE_OUTPUT
 | 
						|
                    elif c.lower() == '*':
 | 
						|
                        typesByCol[i] = TYPE_SKIP
 | 
						|
                    elif c != ' ':
 | 
						|
                        error('Unexpected char in type line at position ' + str(i) + ' (' + c + ')\n' + line)
 | 
						|
                        typesByCol[i] = None
 | 
						|
                    else:
 | 
						|
                        typesByCol[i] = None
 | 
						|
            elif spec[0] == 's':
 | 
						|
                specsByCol = {}
 | 
						|
                for i, c in enumerate(spec[1:]):
 | 
						|
                    if c == '0' or c == '1':
 | 
						|
                        specsByCol[i] = c
 | 
						|
                specs.append(specsByCol)
 | 
						|
            else:
 | 
						|
                #print('other:')
 | 
						|
                #print(line)
 | 
						|
                1
 | 
						|
 | 
						|
# create table object
 | 
						|
 | 
						|
# add strand to name where defined; don't combine for now into vector
 | 
						|
# consecutive strands belong to the last defined name
 | 
						|
    lastName = None
 | 
						|
    lastCol = 0
 | 
						|
    signalsByCol = {}
 | 
						|
 | 
						|
    for col,name in namesByCol.items():     # load with unstranded names
 | 
						|
        signalsByCol[col] = name
 | 
						|
 | 
						|
# sort by col so consecutive columns can be easily tracked
 | 
						|
    #for col,val in bitsByCol.items():       # update with stranded names
 | 
						|
    for col in sorted(bitsByCol):
 | 
						|
        val = bitsByCol[col]
 | 
						|
 | 
						|
        if col > lastCol + 1:
 | 
						|
            lastName = None
 | 
						|
        if val is None:
 | 
						|
            lastName = None
 | 
						|
        if col in namesByCol:
 | 
						|
            if val is None:
 | 
						|
                signalsByCol[col] = namesByCol[col]
 | 
						|
            else:
 | 
						|
                lastName = namesByCol[col]
 | 
						|
                signalsByCol[col] = lastName + openBracket + str(val) + closeBracket
 | 
						|
        elif lastName is not None:
 | 
						|
            signalsByCol[col] = lastName + openBracket + str(val) + closeBracket
 | 
						|
        else:
 | 
						|
            error('Can\'t associate bit number ' + str(val) + ' in column ' + str(col) + ' with a signal name.')
 | 
						|
        lastCol = col
 | 
						|
 | 
						|
    t = Table(tName)
 | 
						|
    t.source = table
 | 
						|
    t.signalsByCol = signalsByCol
 | 
						|
    t.typesByCol = typesByCol
 | 
						|
    t.specs = specs
 | 
						|
 | 
						|
    tables[tName] = t
 | 
						|
 | 
						|
for name in tables:
 | 
						|
    t = tables[name]
 | 
						|
    t.validate()
 | 
						|
    t.makeRTL()
 | 
						|
 | 
						|
print()
 | 
						|
print('Results:')
 | 
						|
 | 
						|
# find the lines with generate spec and replace them with new version
 | 
						|
outLines = []
 | 
						|
inTable = False
 | 
						|
for i, line in enumerate(lines):
 | 
						|
    if not inTable:
 | 
						|
        match = re.search(tableGenPattern, line)
 | 
						|
        if match is not None:
 | 
						|
            tName = match.groups(1)[0]
 | 
						|
            if tName not in tables:
 | 
						|
                if tName == 1:
 | 
						|
                    tName = '<blank>'
 | 
						|
                error('Found vtable start for \'' + tName + '\' but didn\'t generate that table: line ' + str(i+1) + '\n' + line, True)
 | 
						|
            else:
 | 
						|
                outLines.append(line)
 | 
						|
                outLines += tables[tName].equations
 | 
						|
                tables[tName].added = True
 | 
						|
                inTable = True
 | 
						|
        else:
 | 
						|
            outLines.append(line)
 | 
						|
    else:
 | 
						|
        match = re.search(tableGenPattern, line)
 | 
						|
        if match is not None:
 | 
						|
            if match.groups(1)[0] != tName:
 | 
						|
                error('Found vtable end for \'' + match.groups(1)[0] + '\' but started table \'' + tName + '\': line ' + str(i+1) + '\n' + line, True)
 | 
						|
            outLines.append(line)
 | 
						|
            inTable = False
 | 
						|
        else:
 | 
						|
            1#print('stripped: ' + line)
 | 
						|
 | 
						|
if backup:
 | 
						|
    try:
 | 
						|
        copyfile(inFile, backupFile)
 | 
						|
    except Exception as e:
 | 
						|
        error('Error creating backup file!\n' + str(e), True)
 | 
						|
 | 
						|
try:
 | 
						|
    of = open(outFile, 'w')
 | 
						|
    for line in outLines:
 | 
						|
        of.write("%s\n" % line)
 | 
						|
except Exception as e:
 | 
						|
    error('Error writing output file ' + outFile + '!\n' + str(e), True)
 | 
						|
 | 
						|
print('Generated ' + str(len(tables)) + ' tables: ' + ', '.join(tableNames))
 | 
						|
notAdded = {}
 | 
						|
for table in tables:
 | 
						|
    if not tables[table].added:
 | 
						|
        notAdded[table] = True
 | 
						|
print('Output file: ' + outFile)
 | 
						|
if backup:
 | 
						|
    print('Backup file: ' + backupFile)
 | 
						|
if len(notAdded) != 0:
 | 
						|
    error('Tables generated but not added to file! ' + ', '.join(notAdded))
 |