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