From 056baddfcbbbc544b1323989f3d71b80ddf8cb96 Mon Sep 17 00:00:00 2001 From: Kareem Farid Date: Mon, 8 Jan 2024 13:13:33 +0200 Subject: [PATCH] Add antenna report summary table (#328) * `OpenROAD.CheckAntennas`: * Added new `antenna_summary.rpt` file with a summary table for antennas matching that of OpenLane 1 --- openlane/steps/openroad.py | 104 ++++++++++++++++++++++++++++++++++++- 1 file changed, 103 insertions(+), 1 deletion(-) diff --git a/openlane/steps/openroad.py b/openlane/steps/openroad.py index 71ffdf9c9..00df15c69 100644 --- a/openlane/steps/openroad.py +++ b/openlane/steps/openroad.py @@ -219,7 +219,7 @@ class OpenROADStep(TclStep): ] @abstractmethod - def get_script_path(self): + def get_script_path(self) -> str: pass def prepare_env(self, env: dict, state: State) -> dict: @@ -1077,6 +1077,104 @@ class CheckAntennas(OpenROADStep): def get_script_path(self): return os.path.join(get_script_dir(), "openroad", "antenna_check.tcl") + def __summarize_antenna_report(self, report_file: str, output_file: str): + """ + Extracts the list of violating nets from an ARC report file" + """ + + class AntennaViolation: + def __init__(self, net, pin, required_ratio, partial_ratio, layer): + self.net = net + self.pin = pin + self.required_ratio = float(required_ratio) + self.partial_ratio = float(partial_ratio) + self.layer = layer + self.partial_to_required = self.partial_ratio / self.required_ratio + + def __lt__(self, other): + return self.partial_to_required < other.partial_to_required + + net_pattern = re.compile(r"\s*Net:\s*(\S+)") + required_ratio_pattern = re.compile(r"\s*Required ratio:\s+([\d.]+)") + partial_ratio_pattern = re.compile(r"\s*Partial area ratio:\s+([\d.]+)") + layer_pattern = re.compile(r"\s*Layer:\s+(\S+)") + pin_pattern = re.compile(r"\s*Pin:\s+(\S+)") + + required_ratio = None + layer = None + partial_ratio = None + required_ratio = None + pin = None + net = None + violations: List[AntennaViolation] = [] + + net_pattern = re.compile(r"\s*Net:\s*(\S+)") + required_ratio_pattern = re.compile(r"\s*Required ratio:\s+([\d.]+)") + partial_ratio_pattern = re.compile(r"\s*Partial area ratio:\s+([\d.]+)") + layer_pattern = re.compile(r"\s*Layer:\s+(\S+)") + pin_pattern = re.compile(r"\s*Pin:\s+(\S+)") + + with open(report_file, "r") as f: + for line in f: + pin_new = pin_pattern.match(line) + required_ratio_new = required_ratio_pattern.match(line) + partial_ratio_new = partial_ratio_pattern.match(line) + layer_new = layer_pattern.match(line) + net_new = net_pattern.match(line) + required_ratio = ( + required_ratio_new.group(1) + if required_ratio_new is not None + else required_ratio + ) + partial_ratio = ( + partial_ratio_new.group(1) + if partial_ratio_new is not None + else partial_ratio + ) + layer = layer_new.group(1) if layer_new is not None else layer + pin = pin_new.group(1) if pin_new is not None else pin + net = net_new.group(1) if net_new is not None else net + + if "VIOLATED" in line: + violations.append( + AntennaViolation( + net=net, + pin=pin, + partial_ratio=partial_ratio, + layer=layer, + required_ratio=required_ratio, + ) + ) + + violations.sort(reverse=True) + + # Partial/Required: 2.36, Required: 3091.96, Partial: 7298.29, + # Net: net384, Pin: _22354_/A, Layer: met5 + table = rich.table.Table() + decimal_places = 2 + row = [] + table.add_column("Partial/Required") + table.add_column("Required") + table.add_column("Partial") + table.add_column("Net") + table.add_column("Pin") + table.add_column("Layer") + for violation in violations: + row = [ + f"{violation.partial_to_required:.{decimal_places}f}", + f"{violation.required_ratio:.{decimal_places}f}", + f"{violation.partial_ratio:.{decimal_places}f}", + f"{violation.net}", + f"{violation.pin}", + f"{violation.layer}", + ] + table.add_row(*row) + + if not options.get_condensed_mode(): + console.print(table) + with open(output_file, "w") as f: + rich.print(table, file=f) + def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]: views_updates, metrics_updates = super().run(state_in, **kwargs) @@ -1084,6 +1182,10 @@ def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]: open(os.path.join(self.step_dir, "antenna.rpt")), open(os.path.join(self.step_dir, "antenna_net_list.txt"), "w"), ) + self.__summarize_antenna_report( + os.path.join(self.step_dir, "antenna.rpt"), + os.path.join(self.step_dir, "antenna_summary.rpt"), + ) return views_updates, metrics_updates