diff --git a/openlane/common/drc.py b/openlane/common/drc.py index 18a4d05f7..7f39076bc 100644 --- a/openlane/common/drc.py +++ b/openlane/common/drc.py @@ -18,16 +18,20 @@ from enum import IntEnum from decimal import Decimal, InvalidOperation from dataclasses import dataclass, field, asdict -from typing import List, Optional, Tuple, Dict +from typing import List, Optional, Tuple, Dict, Union + BoundingBox = Tuple[Decimal, Decimal, Decimal, Decimal] # microns +BoundingBoxWithDescription = Tuple[Decimal, Decimal, Decimal, Decimal, str] # microns @dataclass class Violation: rules: List[Tuple[str, str]] # (layer, rule) description: str - bounding_boxes: List[BoundingBox] = field(default_factory=list) + bounding_boxes: List[Union[BoundingBox, BoundingBoxWithDescription]] = field( + default_factory=list + ) @property def layer(self) -> str: @@ -54,6 +58,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, "Error while parsing drc report file" + 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, "Error while parsing drc report file" + 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, "Error while parsing drc report file" + 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 = ( + 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 +190,7 @@ class State(IntEnum): f"invalid bounding box at line {i}: bounding box has {len(coord_list)}/4 elements" ) - bounding_box: BoundingBox = ( + bounding_box = ( coord_list[0], coord_list[1], coord_list[2], @@ -239,7 +304,10 @@ 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[0]},{bounding_box[1]};{bounding_box[2]},{bounding_box[1]};{bounding_box[2]},{bounding_box[3]};{bounding_box[0]},{bounding_box[3]})" xf.write(value) + if len(bounding_box) == 5: + value = ET.Element("value") + value.text = f"text: '{bounding_box[4]}'" + 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 11cd6efe3..dd987f53a 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,24 @@ 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, violation_count = DRCObject.from_openroad( + open(report_path, encoding="utf8"), self.config["DESIGN_NAME"] + ) + + drc.to_klayout_xml(open(klayout_db_path, "wb")) + if violation_count > 0: + self.warn( + f"DRC errors found after routing. View the report file at {report_path}.\nView KLayout xml file at {klayout_db_path}" + ) + + return views_updates, metrics_updates @Step.factory.register()