Srikrishna Iyer | 39ffebd | 2020-03-30 11:53:12 -0700 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | # Copyright lowRISC contributors. |
| 3 | # Licensed under the Apache License, Version 2.0, see LICENSE for details. |
| 4 | # SPDX-License-Identifier: Apache-2.0 |
| 5 | """ |
| 6 | This script provides common DV simulation specific utilities. |
| 7 | """ |
| 8 | |
| 9 | import re |
| 10 | from collections import OrderedDict |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 11 | from typing import List, Tuple |
Srikrishna Iyer | 39ffebd | 2020-03-30 11:53:12 -0700 | [diff] [blame] | 12 | |
| 13 | |
| 14 | # Capture the summary results as a list of lists. |
| 15 | # The text coverage report is passed as input to the function, in addition to |
| 16 | # the tool used. The tool returns a 2D list if the coverage report file was read |
| 17 | # and the coverage was extracted successfully. It returns a tuple of: |
| 18 | # List of metrics and values |
| 19 | # Final coverage total |
Srikrishna Iyer | 9b38141 | 2021-08-10 23:11:43 -0700 | [diff] [blame] | 20 | # |
| 21 | # Raises the appropriate exception if the coverage summary extraction fails. |
Srikrishna Iyer | 39ffebd | 2020-03-30 11:53:12 -0700 | [diff] [blame] | 22 | def get_cov_summary_table(cov_report_txt, tool): |
Srikrishna Iyer | 9b38141 | 2021-08-10 23:11:43 -0700 | [diff] [blame] | 23 | with open(cov_report_txt, 'r') as f: |
| 24 | if tool == 'xcelium': |
| 25 | return xcelium_cov_summary_table(f) |
| 26 | if tool == 'vcs': |
| 27 | return vcs_cov_summary_table(f) |
| 28 | raise NotImplementedError(f"{tool} is unsupported for cov extraction.") |
Srikrishna Iyer | 39ffebd | 2020-03-30 11:53:12 -0700 | [diff] [blame] | 29 | |
| 30 | |
| 31 | # Same desc as above, but specific to Xcelium and takes an opened input stream. |
| 32 | def xcelium_cov_summary_table(buf): |
| 33 | for line in buf: |
| 34 | if "name" in line: |
| 35 | # Strip the line and remove the unwanted "* Covered" string. |
| 36 | metrics = line.strip().replace("* Covered", "").split() |
| 37 | # Change first item to 'Score'. |
| 38 | metrics[0] = 'Score' |
| 39 | |
Srikrishna Iyer | 21721e3 | 2021-08-11 02:42:03 -0700 | [diff] [blame] | 40 | # Gather the list of metrics. |
Srikrishna Iyer | 39ffebd | 2020-03-30 11:53:12 -0700 | [diff] [blame] | 41 | items = OrderedDict() |
| 42 | for metric in metrics: |
| 43 | items[metric] = {} |
| 44 | items[metric]['covered'] = 0 |
| 45 | items[metric]['total'] = 0 |
Srikrishna Iyer | 21721e3 | 2021-08-11 02:42:03 -0700 | [diff] [blame] | 46 | |
Srikrishna Iyer | 39ffebd | 2020-03-30 11:53:12 -0700 | [diff] [blame] | 47 | # Next line is a separator. |
| 48 | line = buf.readline() |
Srikrishna Iyer | 21721e3 | 2021-08-11 02:42:03 -0700 | [diff] [blame] | 49 | |
Srikrishna Iyer | 39ffebd | 2020-03-30 11:53:12 -0700 | [diff] [blame] | 50 | # Subsequent lines are coverage items to be aggregated. |
| 51 | for line in buf: |
| 52 | line = re.sub(r"%\s+\(", "%(", line) |
| 53 | values = line.strip().split() |
| 54 | for i, value in enumerate(values): |
| 55 | value = value.strip() |
Srikrishna Iyer | 9b38141 | 2021-08-10 23:11:43 -0700 | [diff] [blame] | 56 | m = re.search(r"\((\d+)/(\d+).*\)", value) |
Srikrishna Iyer | 39ffebd | 2020-03-30 11:53:12 -0700 | [diff] [blame] | 57 | if m: |
| 58 | items[metrics[i]]['covered'] += int(m.group(1)) |
| 59 | items[metrics[i]]['total'] += int(m.group(2)) |
| 60 | items['Score']['covered'] += int(m.group(1)) |
| 61 | items['Score']['total'] += int(m.group(2)) |
| 62 | # Capture the percentages and the aggregate. |
| 63 | values = [] |
| 64 | cov_total = None |
| 65 | for metric in items.keys(): |
Rupert Swarbrick | 6cc2011 | 2020-04-24 09:44:35 +0100 | [diff] [blame] | 66 | if items[metric]['total'] == 0: |
| 67 | values.append("-- %") |
Srikrishna Iyer | 39ffebd | 2020-03-30 11:53:12 -0700 | [diff] [blame] | 68 | else: |
| 69 | value = items[metric]['covered'] / items[metric][ |
| 70 | 'total'] * 100 |
| 71 | value = "{0:.2f} %".format(round(value, 2)) |
| 72 | values.append(value) |
Rupert Swarbrick | 6cc2011 | 2020-04-24 09:44:35 +0100 | [diff] [blame] | 73 | if metric == 'Score': |
| 74 | cov_total = value |
Srikrishna Iyer | 21721e3 | 2021-08-11 02:42:03 -0700 | [diff] [blame] | 75 | return [items.keys(), values], cov_total |
Srikrishna Iyer | 39ffebd | 2020-03-30 11:53:12 -0700 | [diff] [blame] | 76 | |
| 77 | # If we reached here, then we were unable to extract the coverage. |
Srikrishna Iyer | 9b38141 | 2021-08-10 23:11:43 -0700 | [diff] [blame] | 78 | raise SyntaxError(f"Coverage data not found in {buf.name}!") |
Srikrishna Iyer | 39ffebd | 2020-03-30 11:53:12 -0700 | [diff] [blame] | 79 | |
| 80 | |
| 81 | # Same desc as above, but specific to VCS and takes an opened input stream. |
| 82 | def vcs_cov_summary_table(buf): |
| 83 | for line in buf: |
| 84 | match = re.match("total coverage summary", line, re.IGNORECASE) |
| 85 | if match: |
| 86 | # Metrics on the next line. |
| 87 | line = buf.readline().strip() |
| 88 | metrics = line.split() |
| 89 | # Values on the next. |
| 90 | line = buf.readline().strip() |
| 91 | # Pretty up the values - add % sign for ease of post |
| 92 | # processing. |
| 93 | values = [] |
| 94 | for val in line.split(): |
| 95 | val += " %" |
| 96 | values.append(val) |
| 97 | # first row is coverage total |
| 98 | cov_total = values[0] |
Srikrishna Iyer | 9b38141 | 2021-08-10 23:11:43 -0700 | [diff] [blame] | 99 | return [metrics, values], cov_total |
Srikrishna Iyer | 39ffebd | 2020-03-30 11:53:12 -0700 | [diff] [blame] | 100 | |
| 101 | # If we reached here, then we were unable to extract the coverage. |
Srikrishna Iyer | 9b38141 | 2021-08-10 23:11:43 -0700 | [diff] [blame] | 102 | raise SyntaxError(f"Coverage data not found in {buf.name}!") |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 103 | |
| 104 | |
| 105 | def get_job_runtime(log_text: List, tool: str) -> Tuple[float, str]: |
| 106 | """Returns the job runtime (wall clock time) along with its units. |
| 107 | |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 108 | EDA tools indicate how long the job ran in terms of CPU time in the log |
| 109 | file. This method invokes the tool specific method which parses the log |
| 110 | text and returns the runtime as a floating point value followed by its |
| 111 | units as a tuple. |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 112 | |
| 113 | `log_text` is the job's log file contents as a list of lines. |
| 114 | `tool` is the EDA tool used to run the job. |
| 115 | Returns the runtime, units as a tuple. |
| 116 | Raises NotImplementedError exception if the EDA tool is not supported. |
| 117 | """ |
| 118 | if tool == 'xcelium': |
| 119 | return xcelium_job_runtime(log_text) |
| 120 | elif tool == 'vcs': |
| 121 | return vcs_job_runtime(log_text) |
| 122 | else: |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 123 | raise NotImplementedError(f"{tool} is unsupported for job runtime " |
| 124 | "extraction.") |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 125 | |
| 126 | |
| 127 | def vcs_job_runtime(log_text: List) -> Tuple[float, str]: |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 128 | """Returns the VCS job runtime (wall clock time) along with its units. |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 129 | |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 130 | Search pattern example: |
| 131 | CPU time: 22.170 seconds to compile + .518 seconds to elab + 1.901 \ |
| 132 | seconds to link |
| 133 | CPU Time: 0.610 seconds; Data structure size: 1.6Mb |
| 134 | |
| 135 | Returns the runtime, units as a tuple. |
| 136 | Raises RuntimeError exception if the search pattern is not found. |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 137 | """ |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 138 | pattern = r"^CPU [tT]ime:\s*(\d+\.?\d*?)\s*(seconds|minutes|hours).*$" |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 139 | for line in reversed(log_text): |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 140 | m = re.search(pattern, line) |
| 141 | if m: |
| 142 | return float(m.group(1)), m.group(2)[0] |
| 143 | raise RuntimeError("Job runtime not found in the log.") |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 144 | |
| 145 | |
| 146 | def xcelium_job_runtime(log_text: List) -> Tuple[float, str]: |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 147 | """Returns the Xcelium job runtime (wall clock time) along with its units. |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 148 | |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 149 | Search pattern example: |
| 150 | TOOL: xrun(64) 21.09-s006: Exiting on Aug 01, 2022 at 00:21:18 PDT \ |
| 151 | (total: 00:00:05) |
| 152 | |
| 153 | Returns the runtime, units as a tuple. |
| 154 | Raises RuntimeError exception if the search pattern is not found. |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 155 | """ |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 156 | pattern = (r"^TOOL:\s*xrun.*: Exiting on .*\(total:\s*(\d+):(\d+):(\d+)\)" |
| 157 | r"\s*$") |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 158 | for line in reversed(log_text): |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 159 | m = re.search(pattern, line) |
| 160 | if m: |
| 161 | t = int(m.group(1)) * 3600 + int(m.group(2)) * 60 + int(m.group(3)) |
| 162 | return t, "s" |
| 163 | raise RuntimeError("Job runtime not found in the log.") |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 164 | |
| 165 | |
| 166 | def get_simulated_time(log_text: List, tool: str) -> Tuple[float, str]: |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 167 | """Returns the simulated time along with its units. |
| 168 | |
| 169 | EDA tools indicate how long the design was simulated for in the log file. |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 170 | This method invokes the tool specific method which parses the log text and |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 171 | returns the simulated time as a floating point value followed by its |
| 172 | units (typically, pico|nano|micro|milliseconds) as a tuple. |
| 173 | |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 174 | `log_text` is the job's log file contents as a list of lines. |
| 175 | `tool` is the EDA tool used to run the job. |
| 176 | Returns the simulated, units as a tuple. |
| 177 | Raises NotImplementedError exception if the EDA tool is not supported. |
| 178 | """ |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 179 | if tool == 'xcelium': |
| 180 | return xcelium_simulated_time(log_text) |
| 181 | elif tool == 'vcs': |
| 182 | return vcs_simulated_time(log_text) |
| 183 | else: |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 184 | raise NotImplementedError(f"{tool} is unsupported for simulated time " |
| 185 | "extraction.") |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 186 | |
| 187 | |
| 188 | def xcelium_simulated_time(log_text: List) -> Tuple[float, str]: |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 189 | """Returns the Xcelium simulated time along with its units. |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 190 | |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 191 | Search pattern example: |
| 192 | Simulation complete via $finish(2) at time 11724965 PS + 13 |
| 193 | |
| 194 | Returns the simulated time, units as a tuple. |
| 195 | Raises RuntimeError exception if the search pattern is not found. |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 196 | """ |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 197 | pattern = r"^Simulation complete .* at time (\d+\.?\d*?)\s*(.?[sS]).*$" |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 198 | for line in reversed(log_text): |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 199 | m = re.search(pattern, line) |
| 200 | if m: |
| 201 | return float(m.group(1)), m.group(2).lower() |
| 202 | raise RuntimeError("Simulated time not found in the log.") |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 203 | |
| 204 | |
| 205 | def vcs_simulated_time(log_text: List) -> Tuple[float, str]: |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 206 | """Returns the VCS simulated time along with its units. |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 207 | |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 208 | Search pattern example: |
| 209 | V C S S i m u l a t i o n R e p o r t |
| 210 | Time: 12241752 ps |
| 211 | |
| 212 | Returns the simulated time, units as a tuple. |
| 213 | Raises RuntimeError exception if the search pattern is not found. |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 214 | """ |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 215 | pattern = r"^Time:\s*(\d+\.?\d*?)\s*(.?[sS])\s*$" |
| 216 | next_line = "" |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 217 | for line in reversed(log_text): |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 218 | if "V C S S i m u l a t i o n R e p o r t" in line: |
| 219 | m = re.search(pattern, next_line) |
Cindy Chen | f4890c4 | 2022-07-20 13:52:21 -0700 | [diff] [blame] | 220 | if m: |
Srikrishna Iyer | a8485b5 | 2022-08-01 13:29:12 -0700 | [diff] [blame] | 221 | return float(m.group(1)), m.group(2).lower() |
| 222 | next_line = line |
| 223 | raise RuntimeError("Simulated time not found in the log.") |