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