| #!/usr/bin/env python3 | |
| # Copyright lowRISC contributors. | |
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. | |
| # SPDX-License-Identifier: Apache-2.0 | |
| """ | |
| This script provides common DV simulation specific utilities. | |
| """ | |
| import re | |
| from collections import OrderedDict | |
| from typing import List, Tuple | |
| # Capture the summary results as a list of lists. | |
| # The text coverage report is passed as input to the function, in addition to | |
| # the tool used. The tool returns a 2D list if the coverage report file was read | |
| # and the coverage was extracted successfully. It returns a tuple of: | |
| # List of metrics and values | |
| # Final coverage total | |
| # | |
| # Raises the appropriate exception if the coverage summary extraction fails. | |
| def get_cov_summary_table(cov_report_txt, tool): | |
| with open(cov_report_txt, 'r') as f: | |
| if tool == 'xcelium': | |
| return xcelium_cov_summary_table(f) | |
| if tool == 'vcs': | |
| return vcs_cov_summary_table(f) | |
| raise NotImplementedError(f"{tool} is unsupported for cov extraction.") | |
| # Same desc as above, but specific to Xcelium and takes an opened input stream. | |
| def xcelium_cov_summary_table(buf): | |
| for line in buf: | |
| if "name" in line: | |
| # Strip the line and remove the unwanted "* Covered" string. | |
| metrics = line.strip().replace("* Covered", "").split() | |
| # Change first item to 'Score'. | |
| metrics[0] = 'Score' | |
| # Gather the list of metrics. | |
| items = OrderedDict() | |
| for metric in metrics: | |
| items[metric] = {} | |
| items[metric]['covered'] = 0 | |
| items[metric]['total'] = 0 | |
| # Next line is a separator. | |
| line = buf.readline() | |
| # Subsequent lines are coverage items to be aggregated. | |
| for line in buf: | |
| line = re.sub(r"%\s+\(", "%(", line) | |
| values = line.strip().split() | |
| for i, value in enumerate(values): | |
| value = value.strip() | |
| m = re.search(r"\((\d+)/(\d+).*\)", value) | |
| if m: | |
| items[metrics[i]]['covered'] += int(m.group(1)) | |
| items[metrics[i]]['total'] += int(m.group(2)) | |
| items['Score']['covered'] += int(m.group(1)) | |
| items['Score']['total'] += int(m.group(2)) | |
| # Capture the percentages and the aggregate. | |
| values = [] | |
| cov_total = None | |
| for metric in items.keys(): | |
| if items[metric]['total'] == 0: | |
| values.append("-- %") | |
| else: | |
| value = items[metric]['covered'] / items[metric][ | |
| 'total'] * 100 | |
| value = "{0:.2f} %".format(round(value, 2)) | |
| values.append(value) | |
| if metric == 'Score': | |
| cov_total = value | |
| return [items.keys(), values], cov_total | |
| # If we reached here, then we were unable to extract the coverage. | |
| raise SyntaxError(f"Coverage data not found in {buf.name}!") | |
| # Same desc as above, but specific to VCS and takes an opened input stream. | |
| def vcs_cov_summary_table(buf): | |
| for line in buf: | |
| match = re.match("total coverage summary", line, re.IGNORECASE) | |
| if match: | |
| # Metrics on the next line. | |
| line = buf.readline().strip() | |
| metrics = line.split() | |
| # Values on the next. | |
| line = buf.readline().strip() | |
| # Pretty up the values - add % sign for ease of post | |
| # processing. | |
| values = [] | |
| for val in line.split(): | |
| val += " %" | |
| values.append(val) | |
| # first row is coverage total | |
| cov_total = values[0] | |
| return [metrics, values], cov_total | |
| # If we reached here, then we were unable to extract the coverage. | |
| raise SyntaxError(f"Coverage data not found in {buf.name}!") | |
| def get_job_runtime(log_text: List, tool: str) -> Tuple[float, str]: | |
| """Returns the job runtime (wall clock time) along with its units. | |
| EDA tools indicate how long the job ran in terms of CPU time in the log | |
| file. This method invokes the tool specific method which parses the log | |
| text and returns the runtime as a floating point value followed by its | |
| units as a tuple. | |
| `log_text` is the job's log file contents as a list of lines. | |
| `tool` is the EDA tool used to run the job. | |
| Returns the runtime, units as a tuple. | |
| Raises NotImplementedError exception if the EDA tool is not supported. | |
| """ | |
| if tool == 'xcelium': | |
| return xcelium_job_runtime(log_text) | |
| elif tool == 'vcs': | |
| return vcs_job_runtime(log_text) | |
| else: | |
| raise NotImplementedError(f"{tool} is unsupported for job runtime " | |
| "extraction.") | |
| def vcs_job_runtime(log_text: List) -> Tuple[float, str]: | |
| """Returns the VCS job runtime (wall clock time) along with its units. | |
| Search pattern example: | |
| CPU time: 22.170 seconds to compile + .518 seconds to elab + 1.901 \ | |
| seconds to link | |
| CPU Time: 0.610 seconds; Data structure size: 1.6Mb | |
| Returns the runtime, units as a tuple. | |
| Raises RuntimeError exception if the search pattern is not found. | |
| """ | |
| pattern = r"^CPU [tT]ime:\s*(\d+\.?\d*?)\s*(seconds|minutes|hours).*$" | |
| for line in reversed(log_text): | |
| m = re.search(pattern, line) | |
| if m: | |
| return float(m.group(1)), m.group(2)[0] | |
| raise RuntimeError("Job runtime not found in the log.") | |
| def xcelium_job_runtime(log_text: List) -> Tuple[float, str]: | |
| """Returns the Xcelium job runtime (wall clock time) along with its units. | |
| Search pattern example: | |
| TOOL: xrun(64) 21.09-s006: Exiting on Aug 01, 2022 at 00:21:18 PDT \ | |
| (total: 00:00:05) | |
| Returns the runtime, units as a tuple. | |
| Raises RuntimeError exception if the search pattern is not found. | |
| """ | |
| pattern = (r"^TOOL:\s*xrun.*: Exiting on .*\(total:\s*(\d+):(\d+):(\d+)\)" | |
| r"\s*$") | |
| for line in reversed(log_text): | |
| m = re.search(pattern, line) | |
| if m: | |
| t = int(m.group(1)) * 3600 + int(m.group(2)) * 60 + int(m.group(3)) | |
| return t, "s" | |
| raise RuntimeError("Job runtime not found in the log.") | |
| def get_simulated_time(log_text: List, tool: str) -> Tuple[float, str]: | |
| """Returns the simulated time along with its units. | |
| EDA tools indicate how long the design was simulated for in the log file. | |
| This method invokes the tool specific method which parses the log text and | |
| returns the simulated time as a floating point value followed by its | |
| units (typically, pico|nano|micro|milliseconds) as a tuple. | |
| `log_text` is the job's log file contents as a list of lines. | |
| `tool` is the EDA tool used to run the job. | |
| Returns the simulated, units as a tuple. | |
| Raises NotImplementedError exception if the EDA tool is not supported. | |
| """ | |
| if tool == 'xcelium': | |
| return xcelium_simulated_time(log_text) | |
| elif tool == 'vcs': | |
| return vcs_simulated_time(log_text) | |
| else: | |
| raise NotImplementedError(f"{tool} is unsupported for simulated time " | |
| "extraction.") | |
| def xcelium_simulated_time(log_text: List) -> Tuple[float, str]: | |
| """Returns the Xcelium simulated time along with its units. | |
| Search pattern example: | |
| Simulation complete via $finish(2) at time 11724965 PS + 13 | |
| Returns the simulated time, units as a tuple. | |
| Raises RuntimeError exception if the search pattern is not found. | |
| """ | |
| pattern = r"^Simulation complete .* at time (\d+\.?\d*?)\s*(.?[sS]).*$" | |
| for line in reversed(log_text): | |
| m = re.search(pattern, line) | |
| if m: | |
| return float(m.group(1)), m.group(2).lower() | |
| raise RuntimeError("Simulated time not found in the log.") | |
| def vcs_simulated_time(log_text: List) -> Tuple[float, str]: | |
| """Returns the VCS simulated time along with its units. | |
| Search pattern example: | |
| V C S S i m u l a t i o n R e p o r t | |
| Time: 12241752 ps | |
| Returns the simulated time, units as a tuple. | |
| Raises RuntimeError exception if the search pattern is not found. | |
| """ | |
| pattern = r"^Time:\s*(\d+\.?\d*?)\s*(.?[sS])\s*$" | |
| next_line = "" | |
| for line in reversed(log_text): | |
| if "V C S S i m u l a t i o n R e p o r t" in line: | |
| m = re.search(pattern, next_line) | |
| if m: | |
| return float(m.group(1)), m.group(2).lower() | |
| next_line = line | |
| raise RuntimeError("Simulated time not found in the log.") |