|  | #!/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.") |