| 1 | 3 | gdevic | #!/usr/bin/env python
 | 
      
         | 2 |  |  | #
 | 
      
         | 3 |  |  | # This script reads A-Z80 instruction timing data from a spreadsheet text file
 | 
      
         | 4 |  |  | # and generates a Verilog include file defining the control block execution matrix.
 | 
      
         | 5 |  |  | # Macros in the timing spreadsheet are substituted using a list of keys stored
 | 
      
         | 6 |  |  | # in the macros file. See the macro file for the format information.
 | 
      
         | 7 |  |  | #
 | 
      
         | 8 |  |  | # Input timing file is exported from the Excel file as a TAB-delimited text file.
 | 
      
         | 9 |  |  | #
 | 
      
         | 10 |  |  | #-------------------------------------------------------------------------------
 | 
      
         | 11 |  |  | #  Copyright (C) 2014  Goran Devic
 | 
      
         | 12 |  |  | #
 | 
      
         | 13 |  |  | #  This program is free software; you can redistribute it and/or modify it
 | 
      
         | 14 |  |  | #  under the terms of the GNU General Public License as published by the Free
 | 
      
         | 15 |  |  | #  Software Foundation; either version 2 of the License, or (at your option)
 | 
      
         | 16 |  |  | #  any later version.
 | 
      
         | 17 |  |  | #
 | 
      
         | 18 |  |  | #  This program is distributed in the hope that it will be useful, but WITHOUT
 | 
      
         | 19 |  |  | #  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 | 
      
         | 20 |  |  | #  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
 | 
      
         | 21 |  |  | #  more details.
 | 
      
         | 22 |  |  | #-------------------------------------------------------------------------------
 | 
      
         | 23 |  |  | import string
 | 
      
         | 24 |  |  | import sys
 | 
      
         | 25 |  |  | import csv
 | 
      
         | 26 |  |  | import os
 | 
      
         | 27 |  |  |  
 | 
      
         | 28 |  |  | # Input file exported from a timing spreadsheet:
 | 
      
         | 29 |  |  | fname = "Timings.csv"
 | 
      
         | 30 |  |  |  
 | 
      
         | 31 |  |  | # Input file containing macro substitution keys
 | 
      
         | 32 |  |  | kname = "timing_macros.i"
 | 
      
         | 33 |  |  |  
 | 
      
         | 34 |  |  | # Set this to 1 if you want abbreviated matrix (no-action lines removed)
 | 
      
         | 35 |  |  | abbr = 1
 | 
      
         | 36 |  |  |  
 | 
      
         | 37 |  |  | # Set this to 1 if you want debug $display() printout on each PLA line
 | 
      
         | 38 |  |  | debug = 0
 | 
      
         | 39 |  |  |  
 | 
      
         | 40 |  |  | # Print this string in front of every line that starts with "ctl_". This helps
 | 
      
         | 41 |  |  | # formatting the output to be more readable.
 | 
      
         | 42 |  |  | ctl_prefix = "\n"+" "*19
 | 
      
         | 43 |  |  |  
 | 
      
         | 44 |  |  | # Read in the content of the macro substitution file
 | 
      
         | 45 |  |  | macros = []
 | 
      
         | 46 |  |  | with open(kname, 'r') as f:
 | 
      
         | 47 |  |  |     for line in f:
 | 
      
         | 48 |  |  |         if len(line.strip())>0 and line[0]!='/':
 | 
      
         | 49 |  |  |             # Wrap up non-starting //-style comments into /* ... */ so the
 | 
      
         | 50 |  |  |             # line can be concatenated while preserving comments
 | 
      
         | 51 |  |  |             if line.find("//")>0:
 | 
      
         | 52 |  |  |                 macros.append( line.rstrip().replace("//", "/*", 1) + " */" )
 | 
      
         | 53 |  |  |             else:
 | 
      
         | 54 |  |  |                 macros.append(line.rstrip())
 | 
      
         | 55 |  |  |  
 | 
      
         | 56 |  |  | # List of errors / keys and macros that did not match. We stash them as we go
 | 
      
         | 57 |  |  | # and then print at the end so it is easier to find them
 | 
      
         | 58 |  |  | errors = []
 | 
      
         | 59 |  |  |  
 | 
      
         | 60 |  |  | # Returns a substitution string given the section name (key) and the macro token
 | 
      
         | 61 |  |  | # This is done by simply traversing macro substitution list of lines, finding a
 | 
      
         | 62 |  |  | # section that starts with a :key and copying the substitution lines verbatim.
 | 
      
         | 63 |  |  | def getSubst(key, token):
 | 
      
         | 64 |  |  |     subst = []
 | 
      
         | 65 |  |  |     multiline = False
 | 
      
         | 66 |  |  |     validset = False
 | 
      
         | 67 |  |  |     if key=="Comments":                 # Special case: ignore "Comments" column!
 | 
      
         | 68 |  |  |         return ""
 | 
      
         | 69 |  |  |     for l in macros:
 | 
      
         | 70 |  |  |         if multiline==True:
 | 
      
         | 71 |  |  |             # Multiline copies lines until a char at [0] is not a space
 | 
      
         | 72 |  |  |             if len(l.strip())==0 or l[0]!=' ':
 | 
      
         | 73 |  |  |                 return '\n' + "\n".join(subst)
 | 
      
         | 74 |  |  |             else:
 | 
      
         | 75 |  |  |                 subst.append(l)
 | 
      
         | 76 |  |  |         lx = l.split(' ')               # Split the string and then ignore (duplicate)
 | 
      
         | 77 |  |  |         lx = filter(None, lx)           # spaces in the list left by the split()
 | 
      
         | 78 |  |  |         if l.startswith(":"):           # Find and recognize a matching set (key) section
 | 
      
         | 79 |  |  |             if validset:                # Error if there is a new section going from the macthing one
 | 
      
         | 80 |  |  |                 break                   # meaning we did not find our macro in there
 | 
      
         | 81 |  |  |             if l[1:]==key:
 | 
      
         | 82 |  |  |                 validset = True
 | 
      
         | 83 |  |  |         elif validset and lx[0]==token:
 | 
      
         | 84 |  |  |             if len(lx)==1:
 | 
      
         | 85 |  |  |                 return ""
 | 
      
         | 86 |  |  |             if lx[1]=='\\':             # Multi-line macro state starts with '\' character
 | 
      
         | 87 |  |  |                 multiline = True
 | 
      
         | 88 |  |  |                 continue
 | 
      
         | 89 |  |  |             lx.pop(0)
 | 
      
         | 90 |  |  |             s = " ".join(lx)
 | 
      
         | 91 |  |  |             return ' ' + s.strip()
 | 
      
         | 92 |  |  |     err = "{0} not in {1}".format(token, key)
 | 
      
         | 93 |  |  |     if err not in errors:
 | 
      
         | 94 |  |  |         errors.append(err)
 | 
      
         | 95 |  |  |     return " --- {0} ?? {1} --- ".format(token, key)
 | 
      
         | 96 |  |  |  
 | 
      
         | 97 |  |  | # Read the content of a file and using the csv reader and remove any quotes from the input fields
 | 
      
         | 98 |  |  | content = []                            # Content of the spreadsheet timing file
 | 
      
         | 99 |  |  | with open(fname, 'rb') as csvFile:
 | 
      
         | 100 |  |  |     reader = csv.reader(csvFile, delimiter='\t', quotechar='"')
 | 
      
         | 101 |  |  |     for row in reader:
 | 
      
         | 102 |  |  |         content.append('\t'.join(row))
 | 
      
         | 103 |  |  |  
 | 
      
         | 104 |  |  | # The first line is special: it contains names of sets for our macro substitutions
 | 
      
         | 105 |  |  | tkeys = {}                              # Spreadsheet table column keys
 | 
      
         | 106 |  |  | tokens = content.pop(0).split('\t')
 | 
      
         | 107 |  |  | for col in range(len(tokens)):
 | 
      
         | 108 |  |  |     if len(tokens[col])==0:
 | 
      
         | 109 |  |  |         continue
 | 
      
         | 110 |  |  |     tkeys[col] = tokens[col]
 | 
      
         | 111 |  |  |  
 | 
      
         | 112 |  |  | # Process each line separately (stateless processor)
 | 
      
         | 113 |  |  | imatrix = []    # Verilog execution matrix code
 | 
      
         | 114 |  |  | for line in content:
 | 
      
         | 115 |  |  |     col = line.split('\t')              # Split the string into a list of columns
 | 
      
         | 116 |  |  |     col_clean = filter(None, col)       # Removed all empty fields (between the separators)
 | 
      
         | 117 |  |  |     if len(col_clean)==0:               # Ignore completely empty lines
 | 
      
         | 118 |  |  |         continue
 | 
      
         | 119 |  |  |  
 | 
      
         | 120 |  |  |     if col_clean[0].startswith('//'):   # Print comment lines
 | 
      
         | 121 |  |  |         imatrix.append(col_clean[0])
 | 
      
         | 122 |  |  |  
 | 
      
         | 123 |  |  |     if col_clean[0].startswith("#end"): # Print the end of a condition
 | 
      
         | 124 |  |  |         imatrix.append("end\n")
 | 
      
         | 125 |  |  |  
 | 
      
         | 126 |  |  |     if col_clean[0].startswith('#if'):  # Print the start of a condition
 | 
      
         | 127 |  |  |         s = col_clean[0]
 | 
      
         | 128 |  |  |         tag = s.find(":")
 | 
      
         | 129 |  |  |         condition = s[4:tag]
 | 
      
         | 130 |  |  |         imatrix.append("if ({0}) begin".format(condition.strip()))
 | 
      
         | 131 |  |  |         if debug and len(s[tag:])>1:    # Print only in debug and there is something to print
 | 
      
         | 132 |  |  |             imatrix.append("    $display(\"{0}\");".format(s[4:]))
 | 
      
         | 133 |  |  |  
 | 
      
         | 134 |  |  |     # We recognize 2 kinds of timing statements based on the starting characters:
 | 
      
         | 135 |  |  |     # "#0"..        common timings using M and T cycles (M being optional)
 | 
      
         | 136 |  |  |     # "#always"     timing that does not depend on M and T cycles (ex. ALU operations)
 | 
      
         | 137 |  |  |     if col_clean[0].startswith('#0') or col_clean[0].startswith('#always'):
 | 
      
         | 138 |  |  |         # M and T states are hard-coded in the table at the index 1 and 2
 | 
      
         | 139 |  |  |         if col_clean[0].startswith('#0'):
 | 
      
         | 140 |  |  |             if col[1]=='?':     # M is optional, use '?' to skip it
 | 
      
         | 141 |  |  |                 state = "    if (T{0}) begin ".format(col[2])
 | 
      
         | 142 |  |  |             else:
 | 
      
         | 143 |  |  |                 state = "    if (M{0} && T{1}) begin ".format(col[1], col[2])
 | 
      
         | 144 |  |  |         else:
 | 
      
         | 145 |  |  |             state = "    begin "
 | 
      
         | 146 |  |  |  
 | 
      
         | 147 |  |  |         # Loop over all other columns and perform verbatim substitution
 | 
      
         | 148 |  |  |         action = ""
 | 
      
         | 149 |  |  |         for i in range(3,len(col)):
 | 
      
         | 150 |  |  |             # There may be multiple tokens separated by commas
 | 
      
         | 151 |  |  |             tokList = col[i].strip().split(',')
 | 
      
         | 152 |  |  |             tokList =  filter(None, tokList)   # Filter out empty lines
 | 
      
         | 153 |  |  |             for token in tokList:
 | 
      
         | 154 |  |  |                 token = token.strip()
 | 
      
         | 155 |  |  |                 if i in tkeys and len(token)>0:
 | 
      
         | 156 |  |  |                     macro = getSubst(tkeys[i], token)
 | 
      
         | 157 |  |  |                     if macro.strip().startswith("ctl_"):
 | 
      
         | 158 |  |  |                         action += ctl_prefix
 | 
      
         | 159 |  |  |                     action += macro
 | 
      
         | 160 |  |  |                     if state.find("ERROR")>=0:
 | 
      
         | 161 |  |  |                         print "{0} {1}".format(state, action)
 | 
      
         | 162 |  |  |                         break
 | 
      
         | 163 |  |  |  
 | 
      
         | 164 |  |  |         # Complete and write out a line
 | 
      
         | 165 |  |  |         if abbr and len(action)==0:
 | 
      
         | 166 |  |  |             continue
 | 
      
         | 167 |  |  |         imatrix.append("{0}{1} end".format(state, action))
 | 
      
         | 168 |  |  |  
 | 
      
         | 169 |  |  | # Create a file containing the logic matrix code
 | 
      
         | 170 |  |  | with open('exec_matrix.i', 'w') as file:
 | 
      
         | 171 |  |  |     file.write("// Automatically generated by genmatrix.py\n")
 | 
      
         | 172 |  |  |     # If there were errors, print them first (and output to the console)
 | 
      
         | 173 |  |  |     if len(errors)>0:
 | 
      
         | 174 |  |  |         for error in errors:
 | 
      
         | 175 |  |  |             print error
 | 
      
         | 176 |  |  |             file.write(error + "\n")
 | 
      
         | 177 |  |  |         file.write("-" * 80 + "\n")
 | 
      
         | 178 |  |  |     for item in imatrix:
 | 
      
         | 179 |  |  |         file.write("{}\n".format(item))
 | 
      
         | 180 |  |  |  
 | 
      
         | 181 |  |  | # Touch a file that includes 'exec_matrix.i' to ensure it will recompile correctly
 | 
      
         | 182 |  |  | os.utime("execute.sv", None)
 |