Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 1 | # Copyright lowRISC contributors. |
| 2 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| 3 | # SPDX-License-Identifier: Apache-2.0 |
| 4 | r""" |
| 5 | Class describing synthesis configuration object |
| 6 | """ |
| 7 | |
| 8 | import logging as log |
Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 9 | from pathlib import Path |
| 10 | |
Rupert Swarbrick | 6cc2011 | 2020-04-24 09:44:35 +0100 | [diff] [blame] | 11 | import hjson |
Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 12 | from tabulate import tabulate |
| 13 | |
Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 14 | from OneShotCfg import OneShotCfg |
Srikrishna Iyer | 559daed | 2020-12-04 18:22:18 -0800 | [diff] [blame] | 15 | from utils import VERBOSE, print_msg_list, subst_wildcards |
Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 16 | |
| 17 | |
| 18 | class SynCfg(OneShotCfg): |
| 19 | """Derivative class for synthesis purposes. |
| 20 | """ |
Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 21 | |
Rupert Swarbrick | a23dfec | 2020-09-07 10:01:28 +0100 | [diff] [blame] | 22 | flow = 'syn' |
| 23 | |
| 24 | def __init__(self, flow_cfg_file, hjson_data, args, mk_config): |
| 25 | super().__init__(flow_cfg_file, hjson_data, args, mk_config) |
Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 26 | # Set the title for synthesis results. |
| 27 | self.results_title = self.name.upper() + " Synthesis Results" |
| 28 | |
Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 29 | def gen_results_summary(self): |
| 30 | ''' |
| 31 | Gathers the aggregated results from all sub configs |
| 32 | ''' |
| 33 | |
| 34 | # Generate results table for runs. |
| 35 | log.info("Create summary of synthesis results") |
| 36 | |
| 37 | results_str = "## " + self.results_title + " (Summary)\n\n" |
Michael Schaffner | cb61dc4 | 2020-07-10 16:36:39 -0700 | [diff] [blame] | 38 | results_str += "### " + self.timestamp_long + "\n" |
Srikrishna Iyer | dddf93b | 2020-12-04 18:24:46 -0800 | [diff] [blame] | 39 | if self.revision: |
| 40 | results_str += "### " + self.revision + "\n" |
Srikrishna Iyer | 559daed | 2020-12-04 18:22:18 -0800 | [diff] [blame] | 41 | results_str += "### Branch: " + self.branch + "\n" |
Michael Schaffner | cb61dc4 | 2020-07-10 16:36:39 -0700 | [diff] [blame] | 42 | results_str += "\n" |
Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 43 | |
| 44 | self.results_summary_md = results_str + "\nNot supported yet.\n" |
| 45 | |
| 46 | print(self.results_summary_md) |
| 47 | |
| 48 | # Return only the tables |
| 49 | return self.results_summary_md |
| 50 | |
| 51 | def _gen_results(self): |
| 52 | # ''' |
| 53 | # The function is called after the regression has completed. It looks |
| 54 | # for a regr_results.hjson file with aggregated results from the |
| 55 | # synthesis run. The hjson needs to have the following (potentially |
| 56 | # empty) fields |
| 57 | # |
| 58 | # results = { |
| 59 | # "tool": "dc", |
| 60 | # "top" : <name of toplevel>, |
| 61 | # |
| 62 | # "messages": { |
| 63 | # "flow_errors" : [], |
| 64 | # "flow_warnings" : [], |
| 65 | # "analyze_errors" : [], |
| 66 | # "analyze_warnings" : [], |
| 67 | # "elab_errors" : [], |
| 68 | # "elab_warnings" : [], |
| 69 | # "compile_errors" : [], |
| 70 | # "compile_warnings" : [], |
| 71 | # }, |
| 72 | # |
| 73 | # "timing": { |
| 74 | # # per timing group (ususally a clock domain) |
| 75 | # # in nano seconds |
| 76 | # <group> : { |
| 77 | # "tns" : <value>, |
| 78 | # "wns" : <value>, |
| 79 | # "period" : <value>, |
| 80 | # ... |
| 81 | # } |
| 82 | # }, |
| 83 | # |
| 84 | # "area": { |
| 85 | # # gate equivalent of a NAND2 gate |
| 86 | # "ge" : <value>, |
| 87 | # |
| 88 | # # summary report, in GE |
| 89 | # "comb" : <value>, |
| 90 | # "buf" : <value>, |
| 91 | # "reg" : <value>, |
| 92 | # "macro" : <value>, |
| 93 | # "total" : <value>, |
| 94 | # |
| 95 | # # hierchical report of first submodule level |
| 96 | # "instances" : { |
| 97 | # <name> : { |
| 98 | # "comb" : <value>, |
| 99 | # "buf" : <value>, |
| 100 | # "reg" : <value>, |
| 101 | # "macro" : <value>, |
| 102 | # "total" : <value>, |
| 103 | # }, |
| 104 | # ... |
| 105 | # }, |
| 106 | # }, |
| 107 | # |
| 108 | # "power": { |
| 109 | # "net" : <value>, |
| 110 | # "int" : <value>, |
| 111 | # "leak" : <value>, |
| 112 | # }, |
| 113 | # |
| 114 | # "units": { |
| 115 | # "voltage" : <value>, |
| 116 | # "capacitance" : <value>, |
| 117 | # "time" : <value>, |
| 118 | # "dynamic" : <value>, |
| 119 | # "static" : <value>, |
| 120 | # } |
| 121 | # } |
| 122 | # |
Scott Johnson | fe79c4b | 2020-07-08 10:31:08 -0700 | [diff] [blame] | 123 | # note that if this is a primary config, the results will |
Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 124 | # be generated using the _gen_results_summary function |
| 125 | # ''' |
| 126 | |
| 127 | def _create_entry(val, norm=1.0, total=None, perctag="%"): |
| 128 | """ |
| 129 | Create normalized entry with an optional |
| 130 | percentage appended in brackets. |
| 131 | """ |
| 132 | if val is not None and norm is not None: |
| 133 | if total is not None: |
| 134 | perc = float(val) / float(total) * 100.0 |
| 135 | entry = "%2.1f %s" % (perc, perctag) |
| 136 | else: |
| 137 | value = float(val) / norm |
| 138 | entry = "%2.1f" % (value) |
| 139 | else: |
| 140 | entry = "--" |
| 141 | |
| 142 | return entry |
| 143 | |
| 144 | self.result = {} |
| 145 | |
| 146 | # Generate results table for runs. |
| 147 | results_str = "## " + self.results_title + "\n\n" |
| 148 | results_str += "### " + self.timestamp_long + "\n" |
Srikrishna Iyer | dddf93b | 2020-12-04 18:24:46 -0800 | [diff] [blame] | 149 | if self.revision: |
| 150 | results_str += "### " + self.revision + "\n" |
Srikrishna Iyer | 559daed | 2020-12-04 18:22:18 -0800 | [diff] [blame] | 151 | results_str += "### Branch: " + self.branch + "\n" |
Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 152 | results_str += "### Synthesis Tool: " + self.tool.upper() + "\n\n" |
| 153 | |
| 154 | # TODO: extend this to support multiple build modes |
| 155 | for mode in self.build_modes: |
| 156 | |
| 157 | # results_str += "## Build Mode: " + mode.name + "\n\n" |
| 158 | |
| 159 | result_data = Path( |
| 160 | subst_wildcards(self.build_dir, {"build_mode": mode.name}) + |
| 161 | '/results.hjson') |
| 162 | log.info("looking for result data file at %s", result_data) |
| 163 | |
| 164 | try: |
Michael Schaffner | 065297d | 2020-07-14 21:05:55 -0700 | [diff] [blame] | 165 | with result_data.open() as results_file: |
Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 166 | self.result = hjson.load(results_file, use_decimal=True) |
| 167 | except IOError as err: |
| 168 | log.warning("%s", err) |
| 169 | self.result = { |
| 170 | "messages": { |
| 171 | "flow_errors": ["IOError: %s" % err], |
| 172 | "flow_warnings": [], |
| 173 | "analyze_errors": [], |
| 174 | "analyze_warnings": [], |
| 175 | "elab_errors": [], |
| 176 | "elab_warnings": [], |
| 177 | "compile_errors": [], |
| 178 | "compile_warnings": [], |
| 179 | }, |
| 180 | } |
| 181 | |
| 182 | # Message summary |
| 183 | # results_str += "### Tool Message Summary\n\n" |
| 184 | if "messages" in self.result: |
| 185 | |
| 186 | header = [ |
| 187 | "Build Mode", "Flow Warnings", "Flow Errors", |
| 188 | "Analyze Warnings", "Analyze Errors", "Elab Warnings", |
| 189 | "Elab Errors", "Compile Warnings", "Compile Errors" |
| 190 | ] |
| 191 | colalign = ("left", ) + ("center", ) * (len(header) - 1) |
| 192 | table = [header] |
| 193 | |
| 194 | messages = self.result["messages"] |
| 195 | table.append([ |
| 196 | mode.name, |
| 197 | str(len(messages["flow_warnings"])) + " W ", |
| 198 | str(len(messages["flow_errors"])) + " E ", |
| 199 | str(len(messages["analyze_warnings"])) + " W ", |
| 200 | str(len(messages["analyze_errors"])) + " E ", |
| 201 | str(len(messages["elab_warnings"])) + " W ", |
| 202 | str(len(messages["elab_errors"])) + " E ", |
| 203 | str(len(messages["compile_warnings"])) + " W ", |
| 204 | str(len(messages["compile_errors"])) + " E ", |
| 205 | ]) |
| 206 | |
| 207 | if len(table) > 1: |
| 208 | results_str += tabulate(table, |
| 209 | headers="firstrow", |
| 210 | tablefmt="pipe", |
| 211 | colalign=colalign) + "\n\n" |
| 212 | else: |
| 213 | results_str += "No messages found\n\n" |
| 214 | else: |
| 215 | results_str += "No messages found\n\n" |
| 216 | |
| 217 | # Hierarchical Area report |
| 218 | results_str += "### Circuit Complexity in [kGE]\n\n" |
| 219 | if "area" in self.result: |
| 220 | |
| 221 | header = [ |
| 222 | "Instance", "Comb ", "Buf/Inv", "Regs", "Macros", "Total", |
| 223 | "Total [%]" |
| 224 | ] |
| 225 | colalign = ("left", ) + ("center", ) * (len(header) - 1) |
| 226 | table = [header] |
| 227 | |
| 228 | # print top-level summary first |
| 229 | row = ["**" + self.result["top"] + "**"] |
| 230 | try: |
| 231 | kge = float(self.result["area"]["ge"]) * 1000.0 |
| 232 | |
| 233 | for field in ["comb", "buf", "reg", "macro", "total"]: |
| 234 | row += [ |
| 235 | "**" + |
| 236 | _create_entry(self.result["area"][field], kge) + |
| 237 | "**" |
| 238 | ] |
| 239 | |
| 240 | row += ["**--**"] |
| 241 | table.append(row) |
| 242 | |
| 243 | # go through submodules |
| 244 | for name in self.result["area"]["instances"].keys(): |
Rupert Swarbrick | 6cc2011 | 2020-04-24 09:44:35 +0100 | [diff] [blame] | 245 | if name == self.result["top"]: |
| 246 | continue |
Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 247 | row = [name] |
| 248 | for field in ["comb", "buf", "reg", "macro", "total"]: |
| 249 | row += [ |
| 250 | _create_entry( |
| 251 | self.result["area"]["instances"][name] |
| 252 | [field], kge) |
| 253 | ] |
| 254 | |
| 255 | # add percentage of total |
| 256 | row += [ |
| 257 | _create_entry( |
| 258 | self.result["area"]["instances"][name][field], |
| 259 | kge, self.result["area"]["total"], "%u") |
| 260 | ] |
| 261 | |
| 262 | table.append(row) |
| 263 | |
| 264 | except TypeError: |
| 265 | results_str += "Gate equivalent is not properly defined\n\n" |
| 266 | |
| 267 | if len(table) > 1: |
| 268 | results_str += tabulate(table, |
| 269 | headers="firstrow", |
| 270 | tablefmt="pipe", |
| 271 | colalign=colalign) + "\n\n" |
| 272 | else: |
| 273 | results_str += "No area report found\n\n" |
| 274 | else: |
| 275 | results_str += "No area report found\n\n" |
| 276 | |
| 277 | # Timing report |
| 278 | results_str += "### Timing in [ns]\n\n" |
| 279 | if "timing" in self.result and "units" in self.result: |
| 280 | |
| 281 | header = ["Clock", "Period", "WNS", "TNS"] |
| 282 | colalign = ("left", ) + ("center", ) * (len(header) - 1) |
| 283 | table = [header] |
| 284 | |
| 285 | for clock in self.result["timing"].keys(): |
| 286 | row = [clock] |
| 287 | row += [ |
| 288 | _create_entry( |
| 289 | self.result["timing"][clock]["period"], |
| 290 | 1.0E-09 / float(self.result["units"]["time"])), |
| 291 | _create_entry( |
| 292 | self.result["timing"][clock]["wns"], 1.0E-09 / |
| 293 | float(self.result["units"]["time"])) + " EN", |
| 294 | _create_entry( |
| 295 | self.result["timing"][clock]["tns"], 1.0E-09 / |
| 296 | float(self.result["units"]["time"])) + " EN" |
| 297 | ] |
| 298 | table.append(row) |
| 299 | |
| 300 | if len(table) > 1: |
| 301 | results_str += tabulate(table, |
| 302 | headers="firstrow", |
| 303 | tablefmt="pipe", |
| 304 | colalign=colalign) + "\n\n" |
| 305 | else: |
| 306 | results_str += "No timing report found\n\n" |
| 307 | else: |
| 308 | results_str += "No timing report found\n\n" |
| 309 | |
| 310 | # Power report |
| 311 | results_str += "### Power Estimates in [mW]\n\n" |
| 312 | if "power" in self.result and "units" in self.result: |
| 313 | |
| 314 | header = ["Network", "Internal", "Leakage", "Total"] |
| 315 | colalign = ("center", ) * len(header) |
| 316 | table = [header] |
| 317 | |
| 318 | try: |
| 319 | self.result["power"]["net"] |
| 320 | |
| 321 | power = [ |
| 322 | float(self.result["power"]["net"]) * |
| 323 | float(self.result["units"]["dynamic"]), |
| 324 | float(self.result["power"]["int"]) * |
| 325 | float(self.result["units"]["dynamic"]), |
| 326 | float(self.result["power"]["leak"]) * |
| 327 | float(self.result["units"]["static"]) |
| 328 | ] |
| 329 | |
| 330 | total_power = sum(power) |
| 331 | |
Rupert Swarbrick | 6cc2011 | 2020-04-24 09:44:35 +0100 | [diff] [blame] | 332 | row = [_create_entry(power[0], 1.0E-3) + " / " + |
Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 333 | _create_entry(power[0], 1.0E-3, total_power), |
Rupert Swarbrick | 6cc2011 | 2020-04-24 09:44:35 +0100 | [diff] [blame] | 334 | _create_entry(power[1], 1.0E-3) + " / " + |
Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 335 | _create_entry(power[1], 1.0E-3, total_power), |
Rupert Swarbrick | 6cc2011 | 2020-04-24 09:44:35 +0100 | [diff] [blame] | 336 | _create_entry(power[2], 1.0E-3) + " / " + |
Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 337 | _create_entry(power[2], 1.0E-3, total_power), |
| 338 | _create_entry(total_power, 1.0E-3)] |
| 339 | |
| 340 | table.append(row) |
| 341 | # in case fp values are NoneType |
| 342 | except TypeError: |
| 343 | results_str += "No power report found\n\n" |
| 344 | |
| 345 | if len(table) > 1: |
| 346 | results_str += tabulate(table, |
| 347 | headers="firstrow", |
| 348 | tablefmt="pipe", |
| 349 | colalign=colalign) + "\n\n" |
| 350 | else: |
| 351 | results_str += "No power report found\n\n" |
| 352 | |
Michael Schaffner | 8fc927c | 2020-06-22 15:43:32 -0700 | [diff] [blame] | 353 | # Append detailed messages if they exist |
| 354 | # Note that these messages are omitted in publication mode |
| 355 | hdr_key_pairs = [("Flow Warnings", "flow_warnings"), |
| 356 | ("Flow Errors", "flow_errors"), |
| 357 | ("Analyze Warnings", "analyze_warnings"), |
| 358 | ("Analyze Errors", "analyze_errors"), |
| 359 | ("Elab Warnings", "elab_warnings"), |
| 360 | ("Elab Errors", "elab_errors"), |
| 361 | ("Compile Warnings", "compile_warnings"), |
| 362 | ("Compile Errors", "compile_errors")] |
Michael Schaffner | 0409c2c | 2020-07-14 22:19:40 -0700 | [diff] [blame] | 363 | |
| 364 | # Synthesis fails if any warning or error message has occurred |
| 365 | self.errors_seen = False |
Michael Schaffner | 373f6d1 | 2020-07-10 14:46:34 -0700 | [diff] [blame] | 366 | fail_msgs = "" |
Michael Schaffner | 8fc927c | 2020-06-22 15:43:32 -0700 | [diff] [blame] | 367 | for _, key in hdr_key_pairs: |
| 368 | if key in self.result['messages']: |
Michael Schaffner | 78e7348 | 2020-07-16 10:49:50 -0700 | [diff] [blame] | 369 | if self.result['messages'].get(key): |
Michael Schaffner | 0409c2c | 2020-07-14 22:19:40 -0700 | [diff] [blame] | 370 | self.errors_seen = True |
| 371 | break |
Michael Schaffner | 8fc927c | 2020-06-22 15:43:32 -0700 | [diff] [blame] | 372 | |
Michael Schaffner | 0409c2c | 2020-07-14 22:19:40 -0700 | [diff] [blame] | 373 | if self.errors_seen: |
Michael Schaffner | 373f6d1 | 2020-07-10 14:46:34 -0700 | [diff] [blame] | 374 | fail_msgs += "\n### Errors and Warnings for Build Mode `'" + mode.name + "'`\n" |
Michael Schaffner | 8fc927c | 2020-06-22 15:43:32 -0700 | [diff] [blame] | 375 | for hdr, key in hdr_key_pairs: |
| 376 | msgs = self.result['messages'].get(key) |
Michael Schaffner | 373f6d1 | 2020-07-10 14:46:34 -0700 | [diff] [blame] | 377 | fail_msgs += print_msg_list("#### " + hdr, msgs, self.max_msg_count) |
| 378 | |
| 379 | # the email and published reports will default to self.results_md if they are |
| 380 | # empty. in case they need to be sanitized, override them and do not append |
| 381 | # detailed messages. |
| 382 | if self.sanitize_email_results: |
| 383 | self.email_results_md = results_str |
| 384 | if self.sanitize_publish_results: |
| 385 | self.publish_results_md = results_str |
| 386 | |
| 387 | # locally generated result always contains all details |
| 388 | self.results_md = results_str + fail_msgs |
Michael Schaffner | 8fc927c | 2020-06-22 15:43:32 -0700 | [diff] [blame] | 389 | |
Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 390 | # TODO: add support for pie / bar charts for area splits and |
| 391 | # QoR history |
| 392 | |
Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 393 | # Write results to the scratch area |
Srikrishna Iyer | 559daed | 2020-12-04 18:22:18 -0800 | [diff] [blame] | 394 | results_file = self.scratch_path + "/results_" + self.timestamp + ".md" |
| 395 | with open(results_file, 'w') as f: |
Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 396 | f.write(self.results_md) |
| 397 | |
Srikrishna Iyer | 559daed | 2020-12-04 18:22:18 -0800 | [diff] [blame] | 398 | log.log(VERBOSE, "[results page]: [%s] [%s]", self.name, results_file) |
Michael Schaffner | 3d16099 | 2020-03-31 18:37:53 -0700 | [diff] [blame] | 399 | return self.results_md |