blob: ed061500f47551c9c25c78f9cc3790fba45a3a1c [file] [log] [blame]
Cindy Chene0d2c8c2020-06-18 11:01:36 -07001# Copyright lowRISC contributors.
2# Licensed under the Apache License, Version 2.0, see LICENSE for details.
3# SPDX-License-Identifier: Apache-2.0
4
5import logging as log
6from pathlib import Path
7
8import hjson
9from tabulate import tabulate
10
11from OneShotCfg import OneShotCfg
Michael Schaffner53b23c32022-08-16 19:45:24 -070012from utils import subst_wildcards
Cindy Chene0d2c8c2020-06-18 11:01:36 -070013
14
Cindy Chenf59a6b32021-03-29 11:05:13 -070015class FormalCfg(OneShotCfg):
16 """Derivative class for running formal tools.
Cindy Chene0d2c8c2020-06-18 11:01:36 -070017 """
Rupert Swarbricka23dfec2020-09-07 10:01:28 +010018
Cindy Chenf59a6b32021-03-29 11:05:13 -070019 flow = 'formal'
Rupert Swarbricka23dfec2020-09-07 10:01:28 +010020
21 def __init__(self, flow_cfg_file, hjson_data, args, mk_config):
Cindy Chen4b39fcb2021-09-27 23:50:10 -070022 # Options set from command line
23 self.batch_mode_prefix = "" if args.gui else "-batch"
24
Rupert Swarbricka23dfec2020-09-07 10:01:28 +010025 super().__init__(flow_cfg_file, hjson_data, args, mk_config)
Cindy Chene0d2c8c2020-06-18 11:01:36 -070026 self.header = ["name", "errors", "warnings", "proven", "cex", "undetermined",
27 "covered", "unreachable", "pass_rate", "cov_rate"]
Cindy Chen53ce3bb2021-04-13 00:22:41 -070028
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 Chene0d2c8c2020-06-18 11:01:36 -070035 self.summary_header = ["name", "pass_rate", "stimuli_cov", "coi_cov", "prove_cov"]
Cindy Chen53ce3bb2021-04-13 00:22:41 -070036 self.results_title = self.name.upper() + " Formal " + self.sub_flow.upper() + " Results"
Cindy Chene0d2c8c2020-06-18 11:01:36 -070037
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 Chenf59a6b32021-03-29 11:05:13 -070072 def get_summary(self, result):
Cindy Chene0d2c8c2020-06-18 11:01:36 -070073 summary = []
Cindy Chenf59a6b32021-03-29 11:05:13 -070074 formal_summary = result.get("summary")
75 if formal_summary is None:
76 results_str = "No summary information found\n"
Cindy Chene0d2c8c2020-06-18 11:01:36 -070077 summary.append("N/A")
78 else:
79 colalign = ("center", ) * len(self.header)
80 table = [self.header]
81 table.append([
82 self.name,
Cindy Chenf59a6b32021-03-29 11:05:13 -070083 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 Chene0d2c8c2020-06-18 11:01:36 -070092 ])
Cindy Chenf59a6b32021-03-29 11:05:13 -070093 summary.append(formal_summary["pass_rate"])
Cindy Chene0d2c8c2020-06-18 11:01:36 -070094 if len(table) > 1:
95 results_str = tabulate(table, headers="firstrow", tablefmt="pipe",
96 colalign=colalign)
97 else:
Cindy Chenf59a6b32021-03-29 11:05:13 -070098 results_str = "No content in summary\n"
Cindy Chene0d2c8c2020-06-18 11:01:36 -070099 summary.append("N/A")
100 return results_str, summary
101
Cindy Chenf59a6b32021-03-29 11:05:13 -0700102 def get_coverage(self, result):
Cindy Chene0d2c8c2020-06-18 11:01:36 -0700103 summary = []
Cindy Chenf59a6b32021-03-29 11:05:13 -0700104 formal_coverage = result.get("coverage")
105 if formal_coverage is None:
106 results_str = "No coverage information found\n"
Cindy Chene0d2c8c2020-06-18 11:01:36 -0700107 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 Chenf59a6b32021-03-29 11:05:13 -0700113 formal_coverage["stimuli"],
114 formal_coverage["coi"],
115 formal_coverage["proof"]
Cindy Chene0d2c8c2020-06-18 11:01:36 -0700116 ])
Cindy Chenf59a6b32021-03-29 11:05:13 -0700117 summary.append(formal_coverage["stimuli"])
118 summary.append(formal_coverage["coi"])
119 summary.append(formal_coverage["proof"])
Cindy Chene0d2c8c2020-06-18 11:01:36 -0700120
121 if len(cov_table) > 1:
122 results_str = tabulate(cov_table, headers="firstrow",
123 tablefmt="pipe", colalign=cov_colalign)
124
125 else:
Cindy Chenf59a6b32021-03-29 11:05:13 -0700126 results_str = "No content in formal_coverage\n"
Cindy Chene0d2c8c2020-06-18 11:01:36 -0700127 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 Chene0d2c8c2020-06-18 11:01:36 -0700134 results_str = "## " + self.results_title + " (Summary)\n\n"
Michael Schaffnercb61dc42020-07-10 16:36:39 -0700135 results_str += "### " + self.timestamp_long + "\n"
Srikrishna Iyerdddf93b2020-12-04 18:24:46 -0800136 if self.revision:
137 results_str += "### " + self.revision + "\n"
Srikrishna Iyer559daed2020-12-04 18:22:18 -0800138 results_str += "### Branch: " + self.branch + "\n"
Michael Schaffnercb61dc42020-07-10 16:36:39 -0700139 results_str += "\n"
Cindy Chene0d2c8c2020-06-18 11:01:36 -0700140
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 Chene0d2c8c2020-06-18 11:01:36 -0700157 return self.results_summary_md
158
Rupert Swarbricka2892a52020-10-12 08:50:08 +0100159 def _gen_results(self, results):
Cindy Chene0d2c8c2020-06-18 11:01:36 -0700160 # This function is called after the regression and looks for
Cindy Chenf59a6b32021-03-29 11:05:13 -0700161 # results.hjson file with aggregated results from the formal logfile.
Cindy Chene0d2c8c2020-06-18 11:01:36 -0700162 # 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 Chenf59a6b32021-03-29 11:05:13 -0700172 # "summary": {
Cindy Chene0d2c8c2020-06-18 11:01:36 -0700173 # "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 Chenf59a6b32021-03-29 11:05:13 -0700188 # shows formal coverage. It will have the following format:
189 # "coverage": {
Cindy Chene0d2c8c2020-06-18 11:01:36 -0700190 # 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 Iyerdddf93b2020-12-04 18:24:46 -0800196 if self.revision:
197 results_str += "### " + self.revision + "\n"
Srikrishna Iyer559daed2020-12-04 18:22:18 -0800198 results_str += "### Branch: " + self.branch + "\n"
Srikrishna Iyer27733192021-04-02 02:01:16 -0700199 results_str += "### Tool: " + self.tool.upper() + "\n"
Cindy Chene0d2c8c2020-06-18 11:01:36 -0700200 summary = [self.name] # cfg summary for publish results
201
Srikrishna Iyer27733192021-04-02 02:01:16 -0700202 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 Chene0d2c8c2020-06-18 11:01:36 -0700209 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 Chen53ce3bb2021-04-13 00:22:41 -0700220 results_str += "\n\n## Formal " + self.sub_flow.upper() + " Results\n"
Srikrishna Iyer27733192021-04-02 02:01:16 -0700221 formal_result_str, formal_summary = self.get_summary(self.result)
222 results_str += formal_result_str
223 summary += formal_summary
Cindy Chene0d2c8c2020-06-18 11:01:36 -0700224
Srikrishna Iyer27733192021-04-02 02:01:16 -0700225 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 Chene0d2c8c2020-06-18 11:01:36 -0700234
Srikrishna Iyer27733192021-04-02 02:01:16 -0700235 if results[mode] != "P":
236 results_str += "\n## List of Failures\n" + ''.join(
Cindy Chen7ddf09a2021-04-20 12:21:07 -0700237 mode.launcher.fail_msg.message)
Srikrishna Iyer27733192021-04-02 02:01:16 -0700238
239 messages = self.result.get("messages")
240 if messages is not None:
241 results_str += self.parse_dict_to_str(messages)
Cindy Chene0d2c8c2020-06-18 11:01:36 -0700242
Cindy Chene0d2c8c2020-06-18 11:01:36 -0700243 self.results_md = results_str
Cindy Chene0d2c8c2020-06-18 11:01:36 -0700244
Cindy Chene0d2c8c2020-06-18 11:01:36 -0700245 # Generate result summary
Cindy Chene0d2c8c2020-06-18 11:01:36 -0700246 self.result_summary[self.name] = summary
247
Cindy Chene0d2c8c2020-06-18 11:01:36 -0700248 return self.results_md
249
250 def _publish_results(self):
Cindy Chen53ce3bb2021-04-13 00:22:41 -0700251 ''' our agreement with tool vendors allows us to publish the summary
Cindy Chenf59a6b32021-03-29 11:05:13 -0700252 results (as in gen_results_summary).
Cindy Chen53ce3bb2021-04-13 00:22:41 -0700253
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 Chene0d2c8c2020-06-18 11:01:36 -0700258 '''
Cindy Chen53ce3bb2021-04-13 00:22:41 -0700259 if self.publish_report:
260 self.publish_results_md = self.gen_results_summary()
261 super()._publish_results()
262 else:
263 return