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

Open
wants to merge 12 commits into
base: dev
Choose a base branch
from
6 changes: 6 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@
antenna fixing after detailed routing
* Added `DRT_ANTENNA_MARGIN` which is similar to `GRT_ANTENNA_MARGIN` but for
the aforementioned antenna repair iterations
* DRC reports are now converted to `xml` and readable by KLayout

* Created `OpenROAD.DumpRCValues`

Expand Down Expand Up @@ -265,6 +266,11 @@
* `FILL_CELL`, `DECAP_CELL`, `EXTRA_GDS_FILES`, `FALLBACK_SDC_FILE` were all
renamed, see Misc. Enhancements/Bugfixes.

* `openlane.common.drc`

* `BoundingBox` changed from `Tuple` to `dataclass` with additional optional
`info` property.

# 2.3.3

## Steps
Expand Down
2 changes: 1 addition & 1 deletion openlane/common/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@
ScopedFile,
)
from .toolbox import Toolbox
from .drc import DRC, Violation
from .drc import DRC, Violation, BoundingBox
from . import cli
from .tpe import get_tpe, set_tpe
from .ring_buffer import RingBuffer
95 changes: 88 additions & 7 deletions openlane/common/drc.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Copyright 2020-2023 Efabless Corporation
# Copyright 2020-2025 Efabless Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -18,9 +18,19 @@
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, Iterable

BoundingBox = Tuple[Decimal, Decimal, Decimal, Decimal] # microns

@dataclass
class BoundingBox(Iterable[Decimal]):
llx: Decimal
lly: Decimal
urx: Decimal
ury: Decimal
info: Optional[str] = None

def __iter__(self):
return iter([self.llx, self.lly, self.urx, self.ury])


@dataclass
Expand Down Expand Up @@ -54,6 +64,75 @@ 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 line.strip() == "":
continue
if state == State.vio_type:
vio_match = re_violation.match(line)
assert (
vio_match is not None
), f"Error while parsing drc report file: Could not match violation line '{line}'"
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
), f"Error while parsing drc report file: Could not match source line '{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
), f"Error while parsing drc report file: Could not match bbox line '{line}'"
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,
Expand Down Expand Up @@ -125,7 +204,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],
Expand Down Expand Up @@ -155,7 +234,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,
Expand Down Expand Up @@ -239,7 +318,9 @@ 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)
value = ET.Element("value")
value.text = f"text: '{bounding_box.info}'"
xf.write(value)
20 changes: 18 additions & 2 deletions openlane/steps/openroad.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import subprocess
import tempfile
import textwrap
import pathlib
from abc import abstractmethod
from base64 import b64encode
from concurrent.futures import Future, ThreadPoolExecutor
Expand All @@ -37,6 +38,7 @@
Path,
Filter,
TclUtils,
DRC as DRCObject,
_get_process_limit,
aggregate_metrics,
get_script_dir,
Expand Down Expand Up @@ -1732,7 +1734,7 @@ class DetailedRouting(OpenROADStep):
Variable(
"DRT_SAVE_DRC_REPORT_ITERS",
Optional[int],
"Report DRC on each specified iteration. Set to 1 when DRT_SAVE_DRC_REPORT_ITERS in enabled",
"Write a DRC report every N iterations. If DRT_SAVE_SNAPSHOTS is enabled, there is an implicit default value of 1.",
),
]

Expand All @@ -1743,7 +1745,21 @@ def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
kwargs, env = self.extract_env(kwargs)
env["DRT_THREADS"] = env.get("DRT_THREADS", str(_get_process_limit()))
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_paths = list(pathlib.Path(self.step_dir).rglob("*.drc*"))
for path in drc_paths:
drc, _ = DRCObject.from_openroad(
open(path, encoding="utf8"), self.config["DESIGN_NAME"]
)

drc.to_klayout_xml(open(pathlib.Path(str(path) + ".xml"), "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
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "openlane"
version = "3.0.0.dev15"
version = "3.0.0.dev16"
description = "An infrastructure for implementing chip design flows"
authors = ["Efabless Corporation and Contributors <[email protected]>"]
readme = "Readme.md"
Expand Down
Loading
Loading