From 489ed9fdbf1fada38ef7bfb91ef6987d01b33c97 Mon Sep 17 00:00:00 2001 From: Kareem Farid Date: Fri, 29 Nov 2024 00:31:05 +0200 Subject: [PATCH] feat: convert OR detailed router drc file to klayout xml Signed-off-by: Kareem Farid --- openlane/common/drc.py | 83 +++++++++++++++++++++++++++++-- openlane/scripts/openroad/drt.tcl | 4 +- openlane/steps/openroad.py | 14 +++++- 3 files changed, 93 insertions(+), 8 deletions(-) diff --git a/openlane/common/drc.py b/openlane/common/drc.py index 18a4d05f7..f8301fe1a 100644 --- a/openlane/common/drc.py +++ b/openlane/common/drc.py @@ -20,7 +20,14 @@ from dataclasses import dataclass, field, asdict from typing import List, Optional, Tuple, Dict -BoundingBox = Tuple[Decimal, Decimal, Decimal, Decimal] # microns + +@dataclass +class BoundingBox: + llx: Decimal + lly: Decimal + urx: Decimal + ury: Decimal + description: Optional[str] = None @dataclass @@ -54,6 +61,67 @@ class DRC: module: str violations: Dict[str, Violation] + @classmethod + def from_openroad( + Self, + report: io.TextIOWrapper, + module: str, + ) -> Tuple["DRC", int]: + class State(IntEnum): + vio_type = 0 + src = 1 + bbox = 10 + + re_violation = re.compile(r"violation type: (?P.*)$") + re_src = re.compile(r"srcs: (?P\S+)( (?P\S+))?") + re_bbox = re.compile( + r"bbox = \( (?P\S+), (?P\S+) \) - \( (?P\S+), (?P\S+) \) on Layer (?P\S+)" + ) + bbox_count = 0 + violations: Dict[str, Violation] = {} + state = State.vio_type + vio_type = src1 = src2 = lly = llx = urx = ury = "" + for line in report: + line = line.strip() + if state == State.vio_type: + vio_match = re_violation.match(line) + assert vio_match is not None + vio_type = vio_match.group("type") + state = State.src + elif state == State.src: + src_match = re_src.match(line) + assert src_match is not None, line + src1 = src_match.group("src1") + src2 = src_match.group("src2") + state = State.bbox + elif state == State.bbox: + bbox_match = re_bbox.match(line) + assert bbox_match is not None + llx = bbox_match.group("llx") + lly = bbox_match.group("lly") + urx = bbox_match.group("urx") + ury = bbox_match.group("ury") + layer = bbox_match.group("layer") + bbox_count += 1 + bounding_box = BoundingBox( + Decimal(llx), + Decimal(lly), + Decimal(urx), + Decimal(ury), + f"{src1} to {src2}", + ) + violation = (layer, vio_type) + description = vio_type + if violations.get(vio_type) is not None: + violations[vio_type].bounding_boxes.append(bounding_box) + else: + violations[vio_type] = Violation( + [violation], description, [bounding_box] + ) + state = State.vio_type + + return (Self(module, violations), bbox_count) + @classmethod def from_magic( Self, @@ -125,7 +193,7 @@ class State(IntEnum): f"invalid bounding box at line {i}: bounding box has {len(coord_list)}/4 elements" ) - bounding_box: BoundingBox = ( + bounding_box = BoundingBox( coord_list[0], coord_list[1], coord_list[2], @@ -155,7 +223,7 @@ def from_magic_feedback( "Invalid syntax: 'box' command has less than 4 arguments" ) lx, ly, ux, uy = components[0:4] - last_bounding_box = ( + last_bounding_box = BoundingBox( Decimal(lx) * cif_scale, Decimal(ly) * cif_scale, Decimal(ux) * cif_scale, @@ -239,7 +307,12 @@ def to_klayout_xml(self, out: io.BufferedIOBase): multiplicity.text = str(len(violation.bounding_boxes)) xf.write(cell, category, visited, multiplicity) with xf.element("values"): - llx, lly, urx, ury = bounding_box value = ET.Element("value") - value.text = f"polygon: ({llx},{lly};{urx},{lly};{urx},{ury};{llx},{ury})" + value.text = f"polygon: ({bounding_box.llx},{bounding_box.lly};{bounding_box.urx},{bounding_box.lly};{bounding_box.urx},{bounding_box.ury};{bounding_box.llx},{bounding_box.ury})" xf.write(value) + if bounding_box.description: + value = ET.Element("value") + value.text = ( + f"text: '{bounding_box.description}'" + ) + xf.write(value) diff --git a/openlane/scripts/openroad/drt.tcl b/openlane/scripts/openroad/drt.tcl index 5e8d13397..24332254b 100755 --- a/openlane/scripts/openroad/drt.tcl +++ b/openlane/scripts/openroad/drt.tcl @@ -29,9 +29,9 @@ if { [info exists ::env(DRT_MAX_LAYER)] } { detailed_route\ -bottom_routing_layer $min_layer\ -top_routing_layer $max_layer\ - -output_drc $::env(STEP_DIR)/$::env(DESIGN_NAME).drc\ + -output_drc $::env(_DRC_REPORT_PATH)\ -droute_end_iter $::env(DRT_OPT_ITERS)\ -or_seed 42\ -verbose 1 -write_views \ No newline at end of file +write_views diff --git a/openlane/steps/openroad.py b/openlane/steps/openroad.py index a358145e0..b7b02878e 100644 --- a/openlane/steps/openroad.py +++ b/openlane/steps/openroad.py @@ -72,6 +72,7 @@ from ..common import ( Path, TclUtils, + DRC as DRCObject, get_script_dir, mkdirp, aggregate_metrics, @@ -1615,9 +1616,20 @@ def get_script_path(self): def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]: kwargs, env = self.extract_env(kwargs) + report_path = os.path.join(self.step_dir, "reports", "drc.rpt") + klayout_db_path = os.path.join(self.step_dir, "reports", "drc.xml") + mkdirp(os.path.join(self.step_dir, "reports")) env["DRT_THREADS"] = env.get("DRT_THREADS", str(_get_process_limit())) + env["_DRC_REPORT_PATH"] = report_path info(f"Running TritonRoute with {env['DRT_THREADS']} threads…") - return super().run(state_in, env=env, **kwargs) + views_updates, metrics_updates = super().run(state_in, env=env, **kwargs) + drc, _ = DRCObject.from_openroad( + open(report_path, encoding="utf8"), self.config["DESIGN_NAME"] + ) + + drc.to_klayout_xml(open(klayout_db_path, "wb")) + + return views_updates, metrics_updates @Step.factory.register()