1#!/usr/bin/env python3
2
3import getopt, sys, re
4
5ignore_cells = set([
6    "ADTTRIBUF", "DL", "GIOBUG", "LUT_MUX", "MUX4",
7    "PLL40_2_FEEDBACK_PATH_DELAY", "PLL40_2_FEEDBACK_PATH_EXTERNAL",
8    "PLL40_2_FEEDBACK_PATH_PHASE_AND_DELAY", "PLL40_2_FEEDBACK_PATH_SIMPLE",
9    "PLL40_2F_FEEDBACK_PATH_DELAY", "PLL40_2F_FEEDBACK_PATH_EXTERNAL",
10    "PLL40_2F_FEEDBACK_PATH_PHASE_AND_DELAY", "PLL40_2F_FEEDBACK_PATH_SIMPLE",
11    "PLL40_FEEDBACK_PATH_DELAY", "PLL40_FEEDBACK_PATH_EXTERNAL",
12    "PLL40_FEEDBACK_PATH_PHASE_AND_DELAY", "PLL40_FEEDBACK_PATH_SIMPLE",
13    "PRE_IO_PIN_TYPE", "sync_clk_enable", "TRIBUF"
14])
15
16database = dict()
17sdf_inputs = list()
18txt_inputs = list()
19output_mode = "txt"
20label = "unknown"
21edgefile = None
22
23def usage():
24    print("""
25Usage: python3 timings.py [options] [sdf_file..]
26
27    -t filename
28        read TXT file
29
30    -l label
31        label for HTML file title
32
33    -h edgefile
34        output HTML, use specified edge file
35
36    -s
37        output SDF (not TXT) format
38""")
39    sys.exit(0)
40
41
42try:
43    opts, args = getopt.getopt(sys.argv[1:], "t:l:h:s")
44except:
45    usage()
46
47for o, a in opts:
48    if o == "-t":
49        txt_inputs.append(a)
50    elif o == "-l":
51        label = a
52    elif o == "-h":
53        output_mode = "html"
54        edgefile = a
55    elif o == "-s":
56        output_mode = "sdf"
57    else:
58        usage()
59
60sdf_inputs += args
61
62
63convert = lambda text: int(text) if text.isdigit() else text.lower()
64alphanum_key = lambda key: [ convert(c) for c in re.split('([0-9]+)', key) ]
65alphanum_key_list = lambda l: [len(l)] + [ alphanum_key(s) for s in l ]
66
67
68def skip_whitespace(text, cursor):
69    while cursor < len(text) and text[cursor] in [" ", "\t", "\r", "\n"]:
70        cursor += 1
71    return cursor
72
73
74def parse_sdf(text, cursor):
75    cursor = skip_whitespace(text, cursor)
76
77    if cursor < len(text) and text[cursor] == "(":
78        expr = []
79        cursor += 1
80        while cursor < len(text) and text[cursor] != ")":
81            child, cursor = parse_sdf(text, cursor)
82            expr.append(child)
83            cursor = skip_whitespace(text, cursor)
84        return expr, cursor+1
85
86    if cursor < len(text) and text[cursor] == '"':
87        expr = '"'
88        cursor += 1
89        while cursor < len(text) and text[cursor] != '"':
90            expr += text[cursor]
91            cursor += 1
92        return expr + '"', cursor+1
93
94    expr = ""
95    while cursor < len(text) and text[cursor] not in [" ", "\t", "\r", "\n", "(", ")"]:
96        expr += text[cursor]
97        cursor += 1
98    return expr, cursor
99
100
101def sdf_to_string(expr):
102    if type(expr) is list:
103        tokens = []
104        tokens.append("(")
105        first_child = True
106        for child in expr:
107            if not first_child:
108                tokens.append(" ")
109            tokens.append(sdf_to_string(child))
110            first_child = False
111        tokens.append(")")
112        return "".join(tokens)
113    else:
114        return expr
115
116
117def dump_sdf(expr, indent=""):
118    if type(expr) is list:
119        if len(expr) > 0 and expr[0] in ["IOPATH", "SETUP", "HOLD", "CELLTYPE", "INSTANCE", "SDFVERSION",
120                "DESIGN", "DATE", "VENDOR", "DIVIDER", "TIMESCALE", "RECOVERY", "REMOVAL"]:
121            print(indent + sdf_to_string(expr))
122        else:
123            print("%s(%s" % (indent, expr[0] if len(expr) > 0 else ""))
124            for child in expr[1:]:
125                dump_sdf(child, indent + "  ")
126            print("%s)" % indent)
127    else:
128        print("%s%s" % (indent, expr))
129
130
131def generalize_instances(expr):
132    if type(expr) is list:
133        if len(expr) == 2 and expr[0] == "INSTANCE":
134            expr[1] = "*"
135        for child in expr:
136            generalize_instances(child)
137
138
139def list_to_tuple(expr):
140    if type(expr) is list:
141        tup = []
142        for child in expr:
143            tup.append(list_to_tuple(child))
144        return tuple(tup)
145    return expr
146
147
148def uniquify_cells(expr):
149    cache = set()
150    filtered_expr = []
151
152    for child in expr:
153        t = list_to_tuple(child)
154        if t not in cache:
155            filtered_expr.append(child)
156            cache.add(t)
157
158    return filtered_expr
159
160
161def rewrite_celltype(celltype):
162    if celltype.startswith("PRE_IO_PIN_TYPE_"):
163        celltype = "PRE_IO_PIN_TYPE"
164
165    if celltype.startswith("Span4Mux"):
166        if celltype == "Span4Mux":
167            celltype = "Span4Mux_v4"
168        elif celltype == "Span4Mux_v":
169            celltype = "Span4Mux_v4"
170        elif celltype == "Span4Mux_h":
171            celltype = "Span4Mux_h4"
172        else:
173            match = re.match("Span4Mux_s(.*)_(h|v)", celltype)
174            if match:
175                celltype = "Span4Mux_%c%d" % (match.group(2), int(match.group(1)))
176
177    if celltype.startswith("Span12Mux"):
178        if celltype == "Span12Mux":
179            celltype = "Span12Mux_v12"
180        elif celltype == "Span12Mux_v":
181            celltype = "Span12Mux_v12"
182        elif celltype == "Span12Mux_h":
183            celltype = "Span12Mux_h12"
184        else:
185            match = re.match("Span12Mux_s(.*)_(h|v)", celltype)
186            if match:
187                celltype = "Span12Mux_%c%d" % (match.group(2), int(match.group(1)))
188
189    return celltype
190
191
192def add_entry(celltype, entry):
193    entry = sdf_to_string(entry)
194    entry = entry.replace("(posedge ", "posedge:")
195    entry = entry.replace("(negedge ", "negedge:")
196    entry = entry.replace("(", "")
197    entry = entry.replace(")", "")
198    entry = entry.split()
199    if celltype.count("FEEDBACK") == 0 and entry[0] == "IOPATH" and entry[2].startswith("PLLOUT"):
200        entry[3] = "*:*:*"
201        entry[4] = "*:*:*"
202    database[celltype].add(tuple(entry))
203
204
205###########################################
206# Parse SDF input files
207
208for filename in sdf_inputs:
209    print("### reading SDF file %s" % filename, file=sys.stderr)
210
211    intext = []
212    with open(filename, "r") as f:
213        for line in f:
214            line = re.sub("//.*", "", line)
215            intext.append(line)
216
217    sdfdata, _ = parse_sdf("".join(intext), 0)
218    generalize_instances(sdfdata)
219    sdfdata = uniquify_cells(sdfdata)
220
221    for cell in sdfdata:
222        if cell[0] != "CELL":
223            continue
224
225        celltype = None
226
227        for stmt in cell:
228            if stmt[0] == "CELLTYPE":
229                celltype = rewrite_celltype(stmt[1][1:-1])
230                if celltype == "SB_MAC16":
231                    try:
232                        with open(filename.replace(".sdf", ".dsp"), "r") as dspf:
233                            celltype = dspf.readline().strip()
234                    except:
235                        break
236                database.setdefault(celltype, set())
237
238            if stmt[0] == "DELAY":
239                assert stmt[1][0] == "ABSOLUTE"
240                for entry in stmt[1][1:]:
241                    assert entry[0] == "IOPATH"
242                    add_entry(celltype, entry)
243
244            if stmt[0] == "TIMINGCHECK":
245                for entry in stmt[1:]:
246                    add_entry(celltype, entry)
247
248
249###########################################
250# Parse TXT input files
251
252for filename in txt_inputs:
253    print("### reading TXT file %s" % filename, file=sys.stderr)
254    with open(filename, "r") as f:
255        celltype = None
256        for line in f:
257            line = line.split()
258            if len(line) > 1:
259                if line[0] == "CELL":
260                    celltype = rewrite_celltype(line[1])
261                    database.setdefault(celltype, set())
262                else:
263                    add_entry(celltype, line)
264
265
266###########################################
267# Filter database
268
269for celltype in ignore_cells:
270    if celltype in database:
271        del database[celltype]
272
273
274###########################################
275# Create SDF output
276
277if output_mode == "sdf":
278    print("(DELAYFILE")
279    print("  (SDFVERSION \"3.0\")")
280    print("  (TIMESCALE 1ps)")
281
282    def format_entry(entry):
283        text = []
284        for i in range(len(entry)):
285            if i > 2:
286                text.append("(%s)" % entry[i])
287            elif entry[i].startswith("posedge:"):
288                text.append("(posedge %s)" % entry[i].replace("posedge:", ""))
289            elif entry[i].startswith("negedge:"):
290                text.append("(negedge %s)" % entry[i].replace("negedge:", ""))
291            else:
292                text.append(entry[i])
293        return " ".join(text)
294
295    for celltype in sorted(database, key=alphanum_key):
296        print("  (CELL")
297        print("    (CELLTYPE \"%s\")" % celltype)
298        print("    (INSTANCE *)")
299
300        delay_abs_entries = list()
301        timingcheck_entries = list()
302        for entry in sorted(database[celltype], key=alphanum_key_list):
303            if entry[0] == "IOPATH":
304                delay_abs_entries.append(entry)
305            else:
306                timingcheck_entries.append(entry)
307
308        if len(delay_abs_entries) != 0:
309            print("    (DELAY")
310            print("      (ABSOLUTE")
311            for entry in delay_abs_entries:
312                print("        (%s)" % format_entry(entry))
313            print("      )")
314            print("    )")
315
316        if len(timingcheck_entries) != 0:
317            print("    (TIMINGCHECK")
318            for entry in timingcheck_entries:
319                print("      (%s)" % format_entry(entry))
320            print("    )")
321
322        print("  )")
323
324    print(")")
325
326
327###########################################
328# Create TXT output
329
330if output_mode == "txt":
331    for celltype in sorted(database, key=alphanum_key):
332        print("CELL %s" % celltype)
333        entries_lens = list()
334        for entry in database[celltype]:
335            for i in range(len(entry)):
336                if i < len(entries_lens):
337                    entries_lens[i] = max(entries_lens[i], len(entry[i]))
338                else:
339                    entries_lens.append(len(entry[i]))
340        for entry in sorted(database[celltype], key=alphanum_key_list):
341            for i in range(len(entry)):
342                print("%s%-*s" % ("  " if i != 0 else "", entries_lens[i] if i != len(entry)-1 else 0, entry[i]), end="")
343            print()
344        print()
345
346
347###########################################
348# Create HTML output
349
350if output_mode == "html":
351    print("<h1>IceStorm Timing Model: %s</h1>" % label)
352
353    edge_celltypes = set()
354    source_by_sink_desc = dict()
355    sink_by_source_desc = dict()
356
357    with open(edgefile, "r") as f:
358        for line in f:
359            source, sink = line.split()
360            source_cell, source_port = source.split(".")
361            sink_cell, sink_port = sink.split(".")
362
363            source_cell = rewrite_celltype(source_cell)
364            sink_cell = rewrite_celltype(sink_cell)
365
366            assert source_cell not in ignore_cells
367            assert sink_cell not in ignore_cells
368
369            if source_cell in ["GND", "VCC"]:
370                continue
371
372            source_by_sink_desc.setdefault(sink_cell, set())
373            sink_by_source_desc.setdefault(source_cell, set())
374
375            source_by_sink_desc[sink_cell].add((sink_port, source_cell, source_port))
376            sink_by_source_desc[source_cell].add((source_port, sink_cell, sink_port))
377
378            edge_celltypes.add(source_cell)
379            edge_celltypes.add(sink_cell)
380
381    print("<div style=\"-webkit-column-count: 3; -moz-column-count: 3; column-count: 3;\"><ul style=\"margin:0\">")
382    for celltype in sorted(database, key=alphanum_key):
383        if celltype not in edge_celltypes:
384            print("### ignoring unused cell type %s" % celltype, file=sys.stderr)
385        else:
386            print("<li><a href=\"#%s\">%s</a></li>" % (celltype, celltype))
387    print("</ul></div>")
388
389    for celltype in sorted(database, key=alphanum_key):
390        if celltype not in edge_celltypes:
391            continue
392
393        print("<p><hr></p>")
394        print("<h2><a name=\"%s\">%s</a></h2>" % (celltype, celltype))
395
396        if celltype in source_by_sink_desc:
397            print("<h3>Sources driving this cell type:</h3>")
398            print("<table width=\"600\" border>")
399            print("<tr><th>Input Port</th><th>Source Cell</th><th>Source Port</th></tr>")
400            for entry in sorted(source_by_sink_desc[celltype], key=alphanum_key_list):
401                print("<tr><td>%s</td><td><a href=\"#%s\">%s</a></td><td>%s</td></tr>" % (entry[0], entry[1], entry[1], entry[2]))
402            print("</table>")
403
404        if celltype in sink_by_source_desc:
405            print("<h3>Sinks driven by this cell type:</h3>")
406            print("<table width=\"600\" border>")
407            print("<tr><th>Output Port</th><th>Sink Cell</th><th>Sink Port</th></tr>")
408            for entry in sorted(sink_by_source_desc[celltype], key=alphanum_key_list):
409                print("<tr><td>%s</td><td><a href=\"#%s\">%s</a></td><td>%s</td></tr>" % (entry[0], entry[1], entry[1], entry[2]))
410            print("</table>")
411
412        delay_abs_entries = list()
413        timingcheck_entries = list()
414        for entry in sorted(database[celltype], key=alphanum_key_list):
415            if entry[0] == "IOPATH":
416                delay_abs_entries.append(entry)
417            else:
418                timingcheck_entries.append(entry)
419
420        if len(delay_abs_entries) > 0:
421            print("<h3>Propagation Delays:</h3>")
422            print("<table width=\"800\" border>")
423            print("<tr><th rowspan=\"2\">Input Port</th><th rowspan=\"2\">Output Port</th>")
424            print("<th colspan=\"3\">Low-High Transition</th><th colspan=\"3\">High-Low Transition</th></tr>")
425            print("<tr><th>Min</th><th>Typ</th><th>Max</th><th>Min</th><th>Typ</th><th>Max</th></tr>")
426            for entry in delay_abs_entries:
427                print("<tr><td>%s</td><td>%s</td>" % (entry[1].replace(":", " "), entry[2].replace(":", " ")), end="")
428                print("<td>%s</td><td>%s</td><td>%s</td>" % tuple(entry[3].split(":")), end="")
429                print("<td>%s</td><td>%s</td><td>%s</td>" % tuple(entry[4].split(":")), end="")
430                print("</tr>")
431            print("</table>")
432
433        if len(timingcheck_entries) > 0:
434            print("<h3>Timing Checks:</h3>")
435            print("<table width=\"800\" border>")
436            print("<tr><th rowspan=\"2\">Check Type</th><th rowspan=\"2\">Input Port</th>")
437            print("<th rowspan=\"2\">Output Port</th><th colspan=\"3\">Timing</th></tr>")
438            print("<tr><th>Min</th><th>Typ</th><th>Max</th></tr>")
439            for entry in timingcheck_entries:
440                print("<tr><td>%s</td><td>%s</td><td>%s</td>" % (entry[0], entry[1].replace(":", " "), entry[2].replace(":", " ")), end="")
441                print("<td>%s</td><td>%s</td><td>%s</td>" % tuple(entry[3].split(":")), end="")
442                print("</tr>")
443            print("</table>")
444