Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: convert OR detailed router drc file to klayout xml #607

Draft
wants to merge 6 commits into
base: dev
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 73 additions & 5 deletions openlane/common/drc.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry to be pedantic but that's still an API break. If you were iterating over .bounding_boxes using:

for (llx, lly, urx, ury) in violation.bounding_boxes:
   pass

This code wouldn't work after this patch.


Can you reason why BoundingBoxWithDescription is needed? Can the information not be captured in description?

Copy link
Collaborator Author

@kareefardi kareefardi Nov 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why won't the code work? If violations.bounding_boxes is of type List[BoundingBox] code will still work.

description is common for all bounding boxes for a certain category. To add the additional information in the image below you need a text field with the bounding box. In the image Short is the description of certain category while the other is the additional text.

image

Copy link
Collaborator Author

@kareefardi kareefardi Nov 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I made another update. I think it is more reasonable.
Edit:
Not yet. local hook failed, fixing it..

default_factory=list
)

@property
def layer(self) -> str:
Expand All @@ -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<type>.*)$")
re_src = re.compile(r"srcs: (?P<src1>\S+)( (?P<src2>\S+))?")
re_bbox = re.compile(
r"bbox = \( (?P<llx>\S+), (?P<lly>\S+) \) - \( (?P<urx>\S+), (?P<ury>\S+) \) on Layer (?P<layer>\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,
Expand Down Expand Up @@ -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],
Expand Down Expand Up @@ -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)
4 changes: 2 additions & 2 deletions openlane/scripts/openroad/drt.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -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
write_views
18 changes: 17 additions & 1 deletion openlane/steps/openroad.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@
from ..common import (
Path,
TclUtils,
DRC as DRCObject,
get_script_dir,
mkdirp,
aggregate_metrics,
Expand Down Expand Up @@ -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()
Expand Down
Loading