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

Integrate LEF Parser #436

Closed
wants to merge 11 commits into from
4 changes: 3 additions & 1 deletion default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@
yosys-eqy,
yosys-ghdl,
yosys-f4pga-sdc,
# PIP
# Python
click,
cloup,
pyyaml,
Expand All @@ -52,6 +52,7 @@
psutil,
pytestCheckHook,
pyfakefs,
lef-parser,
system,
}:
buildPythonPackage rec {
Expand Down Expand Up @@ -118,6 +119,7 @@ buildPythonPackage rec {
ioplace-parser
psutil
klayout-pymod
lef-parser
]
++ includedTools;

Expand Down
20 changes: 20 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,16 @@

inputs = {
nixpkgs.url = github:nixos/nixpkgs/nixos-23.11;
lef-parser.url = github:efabless/lef_parser;
flake-compat.url = "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz";
};

inputs.lef-parser.inputs.nixpkgs.follows = "nixpkgs";

outputs = {
self,
nixpkgs,
lef-parser,
...
}: {
# Helper functions
Expand All @@ -44,6 +48,7 @@
inherit system;
overlays = [
(import ./nix/overlay.nix)
(new: old: { lef-parser = lef-parser.outputs.packages."${system}".default; })
];
})
);
Expand Down
61 changes: 58 additions & 3 deletions openlane/common/toolbox.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,9 @@
)

import libparse
import lef_parser
from deprecated.sphinx import deprecated


from .misc import mkdirp
from .types import Path
from .metrics import aggregate_metrics
Expand All @@ -61,8 +61,9 @@ def __init__(self, tmp_dir: str) -> None:
# "openlane_run/tmp" created in their PWD because of the global toolbox
self.tmp_dir = tmp_dir

self.remove_cells_from_lib = lru_cache(16, True)(self.remove_cells_from_lib) # type: ignore
self.create_blackbox_model = lru_cache(16, True)(self.create_blackbox_model) # type: ignore
self.remove_cells_from_lib = lru_cache(32, True)(self.remove_cells_from_lib) # type: ignore
self.create_blackbox_model = lru_cache(32, True)(self.create_blackbox_model) # type: ignore
self.header_from_lef = lru_cache(32, True)(self.header_from_lef) # type: ignore

@deprecated(
version="2.0.0b1",
Expand Down Expand Up @@ -427,6 +428,60 @@ class State(IntEnum):

return out_paths

def header_from_lef(
self,
input_lef: str,
power_define: str,
):
def get_decls(macro: lef_parser.Macro):
pg_decls = []
other_decls = []
pg_pins = [
port
for port in macro.ports.values()
if port.kind in ["POWER", "GROUND"]
]
other_pins = [
port
for port in macro.ports.values()
if port.kind not in ["POWER", "GROUND"]
]
for info in pg_pins:
bus_postfix = ""
if info.msb is not None:
bus_postfix = f"[{info.msb}:{info.lsb}]"
pg_decls.append(f"{info.direction.lower()}{bus_postfix} {info.name}")
for info in other_pins:
bus_postfix = ""
if info.msb is not None:
bus_postfix = f"[{info.msb}:{info.lsb}]"
other_decls.append(f"{info.direction.lower()}{bus_postfix} {info.name}")
return (pg_decls, other_decls)

mkdirp(self.tmp_dir)
basename = os.path.splitext(os.path.basename(input_lef))[0]
out_path = os.path.join(self.tmp_dir, f"{basename}-{uuid.uuid4().hex}.bb.v")
with open(out_path, "w") as f:
lef = lef_parser.parse(input_lef)
for macro in lef.macros.values():
pg_decls, other_decls = get_decls(macro)
print("// Auto-generated by OpenLane", file=f)
print(f"module {macro.name}(", file=f)
print(f"`ifdef {power_define}", file=f)
last_pos = f.tell()
for decl in pg_decls:
print(f" {decl}", file=f, end="")
last_pos = f.tell()
print(",", file=f)
print("`endif", file=f)
for decl in other_decls:
print(f" {decl}", file=f, end="")
last_pos = f.tell()
print(",", file=f)
f.seek(last_pos) # Overwrite ,\n
print("\n);\nendmodule", file=f)
return out_path

def create_blackbox_model(
self,
input_models: Union[frozenset, Tuple[str, ...]],
Expand Down
4 changes: 2 additions & 2 deletions openlane/flows/classic.py
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ class Classic(SequentialFlow):
OpenROAD.CheckMacroInstances,
OpenROAD.STAPrePNR,
OpenROAD.Floorplan,
Odb.CheckMacroAntennaProperties,
Misc.CheckMacroAntennaProperties,
Odb.SetPowerConnections,
Odb.ManualMacroPlacement,
OpenROAD.CutRows,
Expand Down Expand Up @@ -272,7 +272,7 @@ class Classic(SequentialFlow):
Magic.StreamOut,
KLayout.StreamOut,
Magic.WriteLEF,
Odb.CheckDesignAntennaProperties,
Misc.CheckDesignAntennaProperties,
KLayout.XOR,
Checker.XOR,
Magic.DRC,
Expand Down
120 changes: 120 additions & 0 deletions openlane/steps/misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
# limitations under the License.
import os
from typing import Tuple
from io import TextIOWrapper

import lef_parser

from .step import ViewsUpdate, MetricsUpdate, Step
from ..common import Path
Expand Down Expand Up @@ -158,3 +161,120 @@ def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
with open(report_file, "w") as f:
print(f"{antenna_report}\n{lvs_report}\n{drc_report}", file=f)
return {}, {}


def get_macro_antenna_info(macro: lef_parser.Macro, f: TextIOWrapper):
inout_pins = []
input_pins = []
output_pins = []
for pin in macro.pins.values():
if pin.kind in ["POWER", "GROUND", "ANALOG"]:
continue
if pin.direction == "INOUT" and (
pin.antennaDiffArea is None and pin.antennaGateArea is None
):
inout_pins.append(pin.name)
elif pin.direction == "INPUT" and pin.antennaGateArea is None:
input_pins.append(pin.name)
elif pin.direction == "OUTPUT" and pin.antennaDiffArea is None:
output_pins.append(pin.name)
if len(inout_pins) + len(input_pins) + len(output_pins):
print(f"* {macro.name}", file=f)
if inout_pins:
print(
" * INOUT pin(s) without antenna gate information nor antenna diffusion information:",
file=f,
)
for pin_name in inout_pins:
print(f" * {pin_name}", file=f)
if input_pins:
print(
" * INPUT pin(s) without antenna gate information:",
file=f,
)
for pin_name in input_pins:
print(f" * {pin_name}", file=f)
if output_pins:
print(
" * OUTPUT pin(s) without antenna diffusion information:",
file=f,
)
for pin_name in input_pins:
print(f" * {pin_name}, file=f")
return True
return False


@Step.factory.register()
class CheckMacroAntennaProperties(Step):
"""
Sanity-checks the LEF files of input macros for antenna information:
* Antenna Gate Area for Inputs
* Antenna Diffusion Area for Inouts
* Either or Both for Inouts

If a pin is missing this information, estimates for the antenna effect and
diode insertion may not be accurate. However, it may also be missing
because the pin(s) in question are not internally connected to anything.
"""

id = "Misc.CheckMacroAntennaProperties"
name = "Check Antenna Properties of Macros Pins in Their LEF Views"
inputs = []
outputs = []

def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
missing_values_found = False
log = self.get_log_path()
with open(log, "w") as f:
for macro in self.toolbox.get_macro_views(self.config, DesignFormat.LEF):
lef = lef_parser.parse(macro)
for lef_macro in lef.macros.values():
missing_values_found = (
missing_values_found or get_macro_antenna_info(lef_macro, f)
)
if not missing_values_found:
print("* No macros found with missing antenna information.", file=f)
else:
self.warn(
f"One or more macros have missing antenna information on their pin(s): {os.path.relpath(log)}"
)
return {}, {}


@Step.factory.register()
class CheckDesignAntennaProperties(Step):
"""
Sanity-checks the output LEF for antenna information:
* Antenna Gate Area for Inputs
* Antenna Diffusion Area for Inouts
* Either or Both for Inouts

If a pin is missing this information, designs instantiating this Macro
may get bad estimates for the antenna effect and
diode insertion may not be accurate. However, it may also be missing
because the pin(s) in question are not internally connected to anything.
"""

id = "Misc.CheckDesignAntennaProperties"
name = "Check Antenna Properties of the Design's LEF view"
inputs = [DesignFormat.LEF]
outputs = []

def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
lef = lef_parser.parse(str(state_in[DesignFormat.LEF]))
log = self.get_log_path()
with open(log, "w") as f:
missing_values_found = get_macro_antenna_info(
lef.macros[self.config["DESIGN_NAME"]], f
)
if not missing_values_found:
print(
"* Design LEF successfully generated with antenna information.",
file=f,
)
else:
self.warn(
f"Generated LEF for the design is missing antenna information on some pins: {os.path.relpath(log)}"
)
return {}, {}
11 changes: 10 additions & 1 deletion openlane/steps/verilator.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
blackboxes = []

model_list: List[str] = []
lefs: List[str] = []
model_set: Set[str] = set()

if cell_verilog_models := self.config["CELL_VERILOG_MODELS"]:
Expand All @@ -106,13 +107,16 @@ def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
DesignFormat.VERILOG_HEADER,
DesignFormat.POWERED_NETLIST,
DesignFormat.NETLIST,
DesignFormat.LEF,
],
)
for view, format in macro_views:
str_view = str(view)
if format == DesignFormat.VERILOG_HEADER:
blackboxes.append(str(view))
elif format == DesignFormat.LEF:
lefs.append(str_view)
else:
str_view = str(view)
if str_view not in model_set:
model_set.add(str_view)
model_list.append(str_view)
Expand All @@ -139,6 +143,11 @@ def run(self, state_in: State, **kwargs) -> Tuple[ViewsUpdate, MetricsUpdate]:
)
blackboxes.append(bb_path)

for lef in lefs:
blackboxes.append(
self.toolbox.header_from_lef(lef, self.config["VERILOG_POWER_DEFINE"])
)

vlt_file = os.path.join(self.step_dir, "_deps.vlt")
with open(vlt_file, "w") as f:
f.write("`verilator_config\n")
Expand Down
7 changes: 7 additions & 0 deletions openlane/steps/yosys.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,13 +156,15 @@ def _generate_read_deps(
DesignFormat.POWERED_NETLIST,
DesignFormat.NETLIST,
DesignFormat.LIB,
DesignFormat.LEF,
]
if power_defines
else [
DesignFormat.VERILOG_HEADER,
DesignFormat.NETLIST,
DesignFormat.POWERED_NETLIST,
DesignFormat.LIB,
DesignFormat.LEF,
]
)
for view, format in toolbox.get_macro_views_by_priority(config, format_list):
Expand All @@ -171,6 +173,11 @@ def _generate_read_deps(
commands += (
f"read_liberty -lib -ignore_miss_dir -setattr blackbox {view_escaped}\n"
)
elif format == DesignFormat.LEF:
lef_header = toolbox.header_from_lef(
str(view), config.get("VERILOG_POWER_DEFINE", "")
)
commands += f"read_verilog -sv -lib {TclUtils.escape(lef_header)}\n"
else:
commands += f"read_verilog -sv -lib {TclUtils.join(verilog_include_args)} {view_escaped}\n"

Expand Down
1 change: 1 addition & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ libparse>=0.3.1,<1
psutil>=5.9.0
httpx>=0.22.0, <0.28
ioplace_parser~=0.1.0
lef-parser~=0.1.0
klayout>=0.28.17.post1,<0.29.0
1 change: 0 additions & 1 deletion requirements_dev.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ wheel

# lint
black>=23,<24
black[jupyter]
flake8>=4
flake8-no-implicit-concat==0.3.3
flake8-pytest-style
Expand Down
Loading