Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -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 | |
| 5 | import logging as log |
| 6 | from pathlib import Path |
| 7 | |
| 8 | import hjson |
| 9 | from tabulate import tabulate |
| 10 | |
| 11 | from OneShotCfg import OneShotCfg |
Michael Schaffner | 53b23c3 | 2022-08-16 19:45:24 -0700 | [diff] [blame] | 12 | from utils import subst_wildcards |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 13 | |
| 14 | |
Cindy Chen | f59a6b3 | 2021-03-29 11:05:13 -0700 | [diff] [blame] | 15 | class FormalCfg(OneShotCfg): |
| 16 | """Derivative class for running formal tools. |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 17 | """ |
Rupert Swarbrick | a23dfec | 2020-09-07 10:01:28 +0100 | [diff] [blame] | 18 | |
Cindy Chen | f59a6b3 | 2021-03-29 11:05:13 -0700 | [diff] [blame] | 19 | flow = 'formal' |
Rupert Swarbrick | a23dfec | 2020-09-07 10:01:28 +0100 | [diff] [blame] | 20 | |
| 21 | def __init__(self, flow_cfg_file, hjson_data, args, mk_config): |
Cindy Chen | 4b39fcb | 2021-09-27 23:50:10 -0700 | [diff] [blame] | 22 | # Options set from command line |
| 23 | self.batch_mode_prefix = "" if args.gui else "-batch" |
| 24 | |
Rupert Swarbrick | a23dfec | 2020-09-07 10:01:28 +0100 | [diff] [blame] | 25 | super().__init__(flow_cfg_file, hjson_data, args, mk_config) |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 26 | self.header = ["name", "errors", "warnings", "proven", "cex", "undetermined", |
| 27 | "covered", "unreachable", "pass_rate", "cov_rate"] |
Cindy Chen | 53ce3bb | 2021-04-13 00:22:41 -0700 | [diff] [blame] | 28 | |
| 29 | # Default not to publish child cfg results. |
| 30 | if "publish_report" in hjson_data: |
| 31 | self.publish_report = hjson_data["publish_report"] |
| 32 | else: |
| 33 | self.publish_report = False |
| 34 | self.sub_flow = hjson_data['sub_flow'] |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 35 | self.summary_header = ["name", "pass_rate", "stimuli_cov", "coi_cov", "prove_cov"] |
Cindy Chen | 53ce3bb | 2021-04-13 00:22:41 -0700 | [diff] [blame] | 36 | self.results_title = self.name.upper() + " Formal " + self.sub_flow.upper() + " Results" |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 37 | |
| 38 | def parse_dict_to_str(self, input_dict, excl_keys = []): |
| 39 | # This is a helper function to parse dictionary items into a string. |
| 40 | # This function has an optional input "excl_keys" for user to exclude |
| 41 | # printing out certain items according to their keys. |
| 42 | # Note this function did not sort the input dictionary's key value |
| 43 | # before printing the keys and items. If input dictionary is not an |
| 44 | # OrderedDictionary, print out key order is not predictable. |
| 45 | # This function works for Hjson lib outputs because the lib uses an |
| 46 | # OrderDict when it reads dictionaries. |
| 47 | # Example Input: |
| 48 | # { |
| 49 | # "unreachable": ["prop1, prop2, prop3"], |
| 50 | # "cex" : ["prop1"], |
| 51 | # } |
| 52 | # Example Output: |
| 53 | # string = "unreachable: |
| 54 | # ``` |
| 55 | # prop1 |
| 56 | # prop2 |
| 57 | # prop3 |
| 58 | # ``` |
| 59 | # cex: |
| 60 | # ``` |
| 61 | # prop1 |
| 62 | # ```" |
| 63 | output_str = "" |
| 64 | for key, item in input_dict.items(): |
| 65 | if (key not in excl_keys) and item: |
| 66 | output_str += "\n" + key + ":\n" |
| 67 | output_str += "```\n" |
| 68 | output_str += "\n".join(item) |
| 69 | output_str += "\n```\n" |
| 70 | return output_str |
| 71 | |
Cindy Chen | f59a6b3 | 2021-03-29 11:05:13 -0700 | [diff] [blame] | 72 | def get_summary(self, result): |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 73 | summary = [] |
Cindy Chen | f59a6b3 | 2021-03-29 11:05:13 -0700 | [diff] [blame] | 74 | formal_summary = result.get("summary") |
| 75 | if formal_summary is None: |
| 76 | results_str = "No summary information found\n" |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 77 | summary.append("N/A") |
| 78 | else: |
| 79 | colalign = ("center", ) * len(self.header) |
| 80 | table = [self.header] |
| 81 | table.append([ |
| 82 | self.name, |
Cindy Chen | f59a6b3 | 2021-03-29 11:05:13 -0700 | [diff] [blame] | 83 | str(formal_summary["errors"]) + " E ", |
| 84 | str(formal_summary["warnings"]) + " W ", |
| 85 | str(formal_summary["proven"]) + " G ", |
| 86 | str(formal_summary["cex"]) + " E ", |
| 87 | str(formal_summary["undetermined"]) + " W ", |
| 88 | str(formal_summary["covered"]) + " G ", |
| 89 | str(formal_summary["unreachable"]) + " E ", |
| 90 | formal_summary["pass_rate"], |
| 91 | formal_summary["cov_rate"] |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 92 | ]) |
Cindy Chen | f59a6b3 | 2021-03-29 11:05:13 -0700 | [diff] [blame] | 93 | summary.append(formal_summary["pass_rate"]) |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 94 | if len(table) > 1: |
| 95 | results_str = tabulate(table, headers="firstrow", tablefmt="pipe", |
| 96 | colalign=colalign) |
| 97 | else: |
Cindy Chen | f59a6b3 | 2021-03-29 11:05:13 -0700 | [diff] [blame] | 98 | results_str = "No content in summary\n" |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 99 | summary.append("N/A") |
| 100 | return results_str, summary |
| 101 | |
Cindy Chen | f59a6b3 | 2021-03-29 11:05:13 -0700 | [diff] [blame] | 102 | def get_coverage(self, result): |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 103 | summary = [] |
Cindy Chen | f59a6b3 | 2021-03-29 11:05:13 -0700 | [diff] [blame] | 104 | formal_coverage = result.get("coverage") |
| 105 | if formal_coverage is None: |
| 106 | results_str = "No coverage information found\n" |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 107 | summary = ["N/A", "N/A", "N/A"] |
| 108 | else: |
| 109 | cov_header = ["stimuli", "coi", "proof"] |
| 110 | cov_colalign = ("center", ) * len(cov_header) |
| 111 | cov_table = [cov_header] |
| 112 | cov_table.append([ |
Cindy Chen | f59a6b3 | 2021-03-29 11:05:13 -0700 | [diff] [blame] | 113 | formal_coverage["stimuli"], |
| 114 | formal_coverage["coi"], |
| 115 | formal_coverage["proof"] |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 116 | ]) |
Cindy Chen | f59a6b3 | 2021-03-29 11:05:13 -0700 | [diff] [blame] | 117 | summary.append(formal_coverage["stimuli"]) |
| 118 | summary.append(formal_coverage["coi"]) |
| 119 | summary.append(formal_coverage["proof"]) |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 120 | |
| 121 | if len(cov_table) > 1: |
| 122 | results_str = tabulate(cov_table, headers="firstrow", |
| 123 | tablefmt="pipe", colalign=cov_colalign) |
| 124 | |
| 125 | else: |
Cindy Chen | f59a6b3 | 2021-03-29 11:05:13 -0700 | [diff] [blame] | 126 | results_str = "No content in formal_coverage\n" |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 127 | summary = ["N/A", "N/A", "N/A"] |
| 128 | return results_str, summary |
| 129 | |
| 130 | def gen_results_summary(self): |
| 131 | # Gathers the aggregated results from all sub configs |
| 132 | # The results_summary will only contain the passing rate and |
| 133 | # percentages of the stimuli, coi, and proven coverage |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 134 | results_str = "## " + self.results_title + " (Summary)\n\n" |
Michael Schaffner | cb61dc4 | 2020-07-10 16:36:39 -0700 | [diff] [blame] | 135 | results_str += "### " + self.timestamp_long + "\n" |
Srikrishna Iyer | dddf93b | 2020-12-04 18:24:46 -0800 | [diff] [blame] | 136 | if self.revision: |
| 137 | results_str += "### " + self.revision + "\n" |
Srikrishna Iyer | 559daed | 2020-12-04 18:22:18 -0800 | [diff] [blame] | 138 | results_str += "### Branch: " + self.branch + "\n" |
Michael Schaffner | cb61dc4 | 2020-07-10 16:36:39 -0700 | [diff] [blame] | 139 | results_str += "\n" |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 140 | |
| 141 | colalign = ("center", ) * len(self.summary_header) |
| 142 | table = [self.summary_header] |
| 143 | for cfg in self.cfgs: |
| 144 | try: |
| 145 | table.append(cfg.result_summary[cfg.name]) |
| 146 | except KeyError as e: |
| 147 | table.append([cfg.name, "ERROR", "N/A", "N/A", "N/A"]) |
| 148 | log.error("cfg: %s could not find generated results_summary: %s", cfg.name, e) |
| 149 | if len(table) > 1: |
| 150 | self.results_summary_md = results_str + tabulate( |
| 151 | table, headers="firstrow", tablefmt="pipe", colalign=colalign) |
| 152 | else: |
| 153 | self.results_summary_md = results_str |
| 154 | |
| 155 | log.info("[result summary]: %s", self.results_summary_md) |
| 156 | |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 157 | return self.results_summary_md |
| 158 | |
Rupert Swarbrick | a2892a5 | 2020-10-12 08:50:08 +0100 | [diff] [blame] | 159 | def _gen_results(self, results): |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 160 | # This function is called after the regression and looks for |
Cindy Chen | f59a6b3 | 2021-03-29 11:05:13 -0700 | [diff] [blame] | 161 | # results.hjson file with aggregated results from the formal logfile. |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 162 | # The hjson file is required to follow this format: |
| 163 | # { |
| 164 | # "messages": { |
| 165 | # "errors" : [] |
| 166 | # "warnings" : [] |
| 167 | # "cex" : ["property1", "property2"...], |
| 168 | # "undetermined": [], |
| 169 | # "unreachable" : [], |
| 170 | # }, |
| 171 | # |
Cindy Chen | f59a6b3 | 2021-03-29 11:05:13 -0700 | [diff] [blame] | 172 | # "summary": { |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 173 | # "errors" : 0 |
| 174 | # "warnings" : 2 |
| 175 | # "proven" : 20, |
| 176 | # "cex" : 5, |
| 177 | # "covered" : 18, |
| 178 | # "undetermined": 7, |
| 179 | # "unreachable" : 2, |
| 180 | # "pass_rate" : "90 %", |
| 181 | # "cover_rate" : "90 %" |
| 182 | # }, |
| 183 | # } |
| 184 | # The categories for property results are: proven, cex, undetermined, |
| 185 | # covered, and unreachable. |
| 186 | # |
| 187 | # If coverage was enabled then results.hjson will also have an item that |
Cindy Chen | f59a6b3 | 2021-03-29 11:05:13 -0700 | [diff] [blame] | 188 | # shows formal coverage. It will have the following format: |
| 189 | # "coverage": { |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 190 | # stimuli: "90 %", |
| 191 | # coi : "90 %", |
| 192 | # proof : "80 %" |
| 193 | # } |
| 194 | results_str = "## " + self.results_title + "\n\n" |
| 195 | results_str += "### " + self.timestamp_long + "\n" |
Srikrishna Iyer | dddf93b | 2020-12-04 18:24:46 -0800 | [diff] [blame] | 196 | if self.revision: |
| 197 | results_str += "### " + self.revision + "\n" |
Srikrishna Iyer | 559daed | 2020-12-04 18:22:18 -0800 | [diff] [blame] | 198 | results_str += "### Branch: " + self.branch + "\n" |
Srikrishna Iyer | 2773319 | 2021-04-02 02:01:16 -0700 | [diff] [blame] | 199 | results_str += "### Tool: " + self.tool.upper() + "\n" |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 200 | summary = [self.name] # cfg summary for publish results |
| 201 | |
Srikrishna Iyer | 2773319 | 2021-04-02 02:01:16 -0700 | [diff] [blame] | 202 | assert len(self.deploy) == 1 |
| 203 | mode = self.deploy[0] |
| 204 | |
| 205 | if results[mode] == "P": |
| 206 | result_data = Path(subst_wildcards(self.build_dir, |
| 207 | {"build_mode": mode.name}), |
| 208 | 'results.hjson') |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 209 | try: |
| 210 | with open(result_data, "r") as results_file: |
| 211 | self.result = hjson.load(results_file, use_decimal=True) |
| 212 | except IOError as err: |
| 213 | log.warning("%s", err) |
| 214 | self.result = { |
| 215 | "messages": { |
| 216 | "errors": ["IOError: %s" % err], |
| 217 | } |
| 218 | } |
| 219 | |
Cindy Chen | 53ce3bb | 2021-04-13 00:22:41 -0700 | [diff] [blame] | 220 | results_str += "\n\n## Formal " + self.sub_flow.upper() + " Results\n" |
Srikrishna Iyer | 2773319 | 2021-04-02 02:01:16 -0700 | [diff] [blame] | 221 | formal_result_str, formal_summary = self.get_summary(self.result) |
| 222 | results_str += formal_result_str |
| 223 | summary += formal_summary |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 224 | |
Srikrishna Iyer | 2773319 | 2021-04-02 02:01:16 -0700 | [diff] [blame] | 225 | if self.cov: |
| 226 | results_str += "\n\n## Coverage Results\n" |
| 227 | results_str += ("### Coverage html file dir: " + |
| 228 | self.scratch_path + "/default/formal-icarus\n\n") |
| 229 | cov_result_str, cov_summary = self.get_coverage(self.result) |
| 230 | results_str += cov_result_str |
| 231 | summary += cov_summary |
| 232 | else: |
| 233 | summary += ["N/A", "N/A", "N/A"] |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 234 | |
Srikrishna Iyer | 2773319 | 2021-04-02 02:01:16 -0700 | [diff] [blame] | 235 | if results[mode] != "P": |
| 236 | results_str += "\n## List of Failures\n" + ''.join( |
Cindy Chen | 7ddf09a | 2021-04-20 12:21:07 -0700 | [diff] [blame] | 237 | mode.launcher.fail_msg.message) |
Srikrishna Iyer | 2773319 | 2021-04-02 02:01:16 -0700 | [diff] [blame] | 238 | |
| 239 | messages = self.result.get("messages") |
| 240 | if messages is not None: |
| 241 | results_str += self.parse_dict_to_str(messages) |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 242 | |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 243 | self.results_md = results_str |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 244 | |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 245 | # Generate result summary |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 246 | self.result_summary[self.name] = summary |
| 247 | |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 248 | return self.results_md |
| 249 | |
| 250 | def _publish_results(self): |
Cindy Chen | 53ce3bb | 2021-04-13 00:22:41 -0700 | [diff] [blame] | 251 | ''' our agreement with tool vendors allows us to publish the summary |
Cindy Chen | f59a6b3 | 2021-03-29 11:05:13 -0700 | [diff] [blame] | 252 | results (as in gen_results_summary). |
Cindy Chen | 53ce3bb | 2021-04-13 00:22:41 -0700 | [diff] [blame] | 253 | |
| 254 | In default this method does nothing: detailed messages from each child |
| 255 | cfg will not be published. |
| 256 | If the publish_report argument is set to true, this method will only |
| 257 | publish a result summary of the child cfg. |
Cindy Chen | e0d2c8c | 2020-06-18 11:01:36 -0700 | [diff] [blame] | 258 | ''' |
Cindy Chen | 53ce3bb | 2021-04-13 00:22:41 -0700 | [diff] [blame] | 259 | if self.publish_report: |
| 260 | self.publish_results_md = self.gen_results_summary() |
| 261 | super()._publish_results() |
| 262 | else: |
| 263 | return |