blob: 9a118417ffe2be5c2150e15850c462f55f2638a2 [file] [log] [blame]
Michael Schaffner3d160992020-03-31 18:37:53 -07001# Copyright lowRISC contributors.
2# Licensed under the Apache License, Version 2.0, see LICENSE for details.
3# SPDX-License-Identifier: Apache-2.0
4r"""
5Class describing synthesis configuration object
6"""
7
8import logging as log
Michael Schaffner3d160992020-03-31 18:37:53 -07009from pathlib import Path
10
Rupert Swarbrick6cc20112020-04-24 09:44:35 +010011import hjson
Michael Schaffner3d160992020-03-31 18:37:53 -070012from tabulate import tabulate
13
Michael Schaffner3d160992020-03-31 18:37:53 -070014from OneShotCfg import OneShotCfg
Srikrishna Iyer559daed2020-12-04 18:22:18 -080015from utils import VERBOSE, print_msg_list, subst_wildcards
Michael Schaffner3d160992020-03-31 18:37:53 -070016
17
18class SynCfg(OneShotCfg):
19 """Derivative class for synthesis purposes.
20 """
Michael Schaffner3d160992020-03-31 18:37:53 -070021
Rupert Swarbricka23dfec2020-09-07 10:01:28 +010022 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 Schaffner3d160992020-03-31 18:37:53 -070026 # Set the title for synthesis results.
27 self.results_title = self.name.upper() + " Synthesis Results"
28
Michael Schaffner3d160992020-03-31 18:37:53 -070029 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 Schaffnercb61dc42020-07-10 16:36:39 -070038 results_str += "### " + self.timestamp_long + "\n"
Srikrishna Iyerdddf93b2020-12-04 18:24:46 -080039 if self.revision:
40 results_str += "### " + self.revision + "\n"
Srikrishna Iyer559daed2020-12-04 18:22:18 -080041 results_str += "### Branch: " + self.branch + "\n"
Michael Schaffnercb61dc42020-07-10 16:36:39 -070042 results_str += "\n"
Michael Schaffner3d160992020-03-31 18:37:53 -070043
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 Johnsonfe79c4b2020-07-08 10:31:08 -0700123 # note that if this is a primary config, the results will
Michael Schaffner3d160992020-03-31 18:37:53 -0700124 # 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 Iyerdddf93b2020-12-04 18:24:46 -0800149 if self.revision:
150 results_str += "### " + self.revision + "\n"
Srikrishna Iyer559daed2020-12-04 18:22:18 -0800151 results_str += "### Branch: " + self.branch + "\n"
Michael Schaffner3d160992020-03-31 18:37:53 -0700152 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 Schaffner065297d2020-07-14 21:05:55 -0700165 with result_data.open() as results_file:
Michael Schaffner3d160992020-03-31 18:37:53 -0700166 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 Swarbrick6cc20112020-04-24 09:44:35 +0100245 if name == self.result["top"]:
246 continue
Michael Schaffner3d160992020-03-31 18:37:53 -0700247 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 Swarbrick6cc20112020-04-24 09:44:35 +0100332 row = [_create_entry(power[0], 1.0E-3) + " / " +
Michael Schaffner3d160992020-03-31 18:37:53 -0700333 _create_entry(power[0], 1.0E-3, total_power),
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100334 _create_entry(power[1], 1.0E-3) + " / " +
Michael Schaffner3d160992020-03-31 18:37:53 -0700335 _create_entry(power[1], 1.0E-3, total_power),
Rupert Swarbrick6cc20112020-04-24 09:44:35 +0100336 _create_entry(power[2], 1.0E-3) + " / " +
Michael Schaffner3d160992020-03-31 18:37:53 -0700337 _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 Schaffner8fc927c2020-06-22 15:43:32 -0700353 # 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 Schaffner0409c2c2020-07-14 22:19:40 -0700363
364 # Synthesis fails if any warning or error message has occurred
365 self.errors_seen = False
Michael Schaffner373f6d12020-07-10 14:46:34 -0700366 fail_msgs = ""
Michael Schaffner8fc927c2020-06-22 15:43:32 -0700367 for _, key in hdr_key_pairs:
368 if key in self.result['messages']:
Michael Schaffner78e73482020-07-16 10:49:50 -0700369 if self.result['messages'].get(key):
Michael Schaffner0409c2c2020-07-14 22:19:40 -0700370 self.errors_seen = True
371 break
Michael Schaffner8fc927c2020-06-22 15:43:32 -0700372
Michael Schaffner0409c2c2020-07-14 22:19:40 -0700373 if self.errors_seen:
Michael Schaffner373f6d12020-07-10 14:46:34 -0700374 fail_msgs += "\n### Errors and Warnings for Build Mode `'" + mode.name + "'`\n"
Michael Schaffner8fc927c2020-06-22 15:43:32 -0700375 for hdr, key in hdr_key_pairs:
376 msgs = self.result['messages'].get(key)
Michael Schaffner373f6d12020-07-10 14:46:34 -0700377 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 Schaffner8fc927c2020-06-22 15:43:32 -0700389
Michael Schaffner3d160992020-03-31 18:37:53 -0700390 # TODO: add support for pie / bar charts for area splits and
391 # QoR history
392
Michael Schaffner3d160992020-03-31 18:37:53 -0700393 # Write results to the scratch area
Srikrishna Iyer559daed2020-12-04 18:22:18 -0800394 results_file = self.scratch_path + "/results_" + self.timestamp + ".md"
395 with open(results_file, 'w') as f:
Michael Schaffner3d160992020-03-31 18:37:53 -0700396 f.write(self.results_md)
397
Srikrishna Iyer559daed2020-12-04 18:22:18 -0800398 log.log(VERBOSE, "[results page]: [%s] [%s]", self.name, results_file)
Michael Schaffner3d160992020-03-31 18:37:53 -0700399 return self.results_md