From dfc2a68fc76a29b57adcebd693a3a85a1c25af40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Pontes=20Garc=C3=ADa?= Date: Mon, 22 Sep 2025 14:50:51 -0400 Subject: [PATCH 1/2] WIP area profiler heuristic from Yosys --- graph.pdf | Bin 0 -> 17955 bytes tools/area-profiler/README.md | 39 ++++ tools/area-profiler/area_profiler/__init__.py | 3 + tools/area-profiler/area_profiler/parse.py | 204 ++++++++++++++++++ tools/area-profiler/area_profiler/plot.py | 90 ++++++++ tools/area-profiler/pyproject.toml | 15 ++ 6 files changed, 351 insertions(+) create mode 100644 graph.pdf create mode 100644 tools/area-profiler/README.md create mode 100644 tools/area-profiler/area_profiler/__init__.py create mode 100644 tools/area-profiler/area_profiler/parse.py create mode 100644 tools/area-profiler/area_profiler/plot.py create mode 100644 tools/area-profiler/pyproject.toml diff --git a/graph.pdf b/graph.pdf new file mode 100644 index 0000000000000000000000000000000000000000..6918600a9d5baf11abd650c04e1f797a1d27b104 GIT binary patch literal 17955 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 literal 0 HcmV?d00001 diff --git a/tools/area-profiler/README.md b/tools/area-profiler/README.md new file mode 100644 index 0000000000..742ba194e1 --- /dev/null +++ b/tools/area-profiler/README.md @@ -0,0 +1,39 @@ +# Area estimation tool + +This tool estimates and visualizes hardware design areas from Yosys IL and stat files. Yosys IL and stat files can be obtained from a Verilog file via: + +```bash +yosys -p "read_verilog -sv inline.sv; hierarchy -top main; opt; write_rtlil inline.il; tee -o inline.json stat -json" +``` + +## Install + +```bash +uv tool install . +``` + +## Usage + +```bash +aprof-parse -h +aprof-plot -h +``` + +### Commands + +**`aprof-parse`** – convert IL + stat files into JSON summary + +```bash +aprof parse [-o OUTPUT] +``` + +- `-o` optional output JSON (default stdout) + +**`aprof-plot`** – visualize JSON summary + +```bash +aprof plot [-o OUTPUT] +``` + +- `MODE` one of `bar`, `treemap` +- `-o` optional output HTML (default depends on mode) diff --git a/tools/area-profiler/area_profiler/__init__.py b/tools/area-profiler/area_profiler/__init__.py new file mode 100644 index 0000000000..c18cf56661 --- /dev/null +++ b/tools/area-profiler/area_profiler/__init__.py @@ -0,0 +1,3 @@ +"""WIP.""" + +__version__ = "0.1.0" diff --git a/tools/area-profiler/area_profiler/parse.py b/tools/area-profiler/area_profiler/parse.py new file mode 100644 index 0000000000..1432d5d3b0 --- /dev/null +++ b/tools/area-profiler/area_profiler/parse.py @@ -0,0 +1,204 @@ +import pathlib +import sys +import re +import json +from dataclasses import dataclass, asdict, is_dataclass +import argparse + +toplevel: str = "main" + + +# Intermediate representation types +@dataclass +class CellWithParams: + """ + Class representing a cell and its parameters. + """ + + cell_name: str + cell_type: str + cell_params: dict[str, int] + + +""" +Map from modules to cell names to cells with parameters. +""" +type ModuleCellTypes = dict[str, dict[str, CellWithParams]] + +# Output representation types +""" +Map representing resources used by a cell. +""" +type Rsrc = dict[str, int] + + +@dataclass +class CellRsrc: + """ + Class representing a cell and its resources. + """ + + cell_name: str + cell_type: str + cell_width: int | None + generated: bool + rsrc: Rsrc + + +""" +Map between qualified cell names and cell resource values. +""" +type DesignRsrc = dict[str, CellRsrc] + + +def parse_il_file_old(path: str) -> ModuleCellTypes: + module_to_name_to_type: ModuleCellTypes = {} + current_module = None + with open(path, "r") as f: + for line in f: + line = line.strip() + if line.startswith("module"): + current_module = line.split()[1] + module_to_name_to_type[current_module] = {} + elif line.startswith("cell"): + match = re.match(r"cell\s+(\S+)\s+(\S+)", line) + if match: + cell_type, cell_name = match.groups() + module_to_name_to_type[current_module][cell_name] = cell_type + return module_to_name_to_type + + +def parse_il_file(path: str) -> ModuleCellTypes: + module_to_name_to_type: ModuleCellTypes = {} + current_module = None + current_cell = None + with open(path, "r") as f: + for line in f: + line = line.strip() + if line.startswith("module"): + current_module = line.split()[1] + module_to_name_to_type[current_module] = {} + elif line.startswith("cell") and current_module: + current_cell = line.split()[2] + cell_type = line.split()[1] + module_to_name_to_type[current_module][current_cell] = CellWithParams( + current_cell, cell_type, {} + ) + elif line.startswith("parameter") and current_cell: + param_name = line.split()[1] + param_val = line.split()[2] + module_to_name_to_type[current_module][current_cell].cell_params[ + param_name + ] = param_val + elif line.startswith("end") and current_cell: + current_cell = None + elif line.startswith("end") and current_module: + current_module = None + return module_to_name_to_type + + +def flatten_il_rec_helper( + module_to_name_to_type: ModuleCellTypes, module: str, pref: str +): + design_map: DesignRsrc = {} + for cell_name, cell_with_params in module_to_name_to_type[module].items(): + generated_type = cell_with_params.cell_type[0] == "$" + generated_name = cell_name[0] == "$" + if generated_type: + width = max( + { + int(v) + for k, v in cell_with_params.cell_params.items() + if k.endswith("WIDTH") + }, + default=None, + ) + if cell_with_params.cell_type.startswith("$paramod"): + new_width = cell_with_params.cell_type.split("\\")[2] + width = int(new_width.split("'")[1], 2) + design_map[f"{pref}.{cell_name[1:]}"] = CellRsrc( + cell_name[1:], + cell_with_params.cell_type[1:], + width, + generated_name, + {}, + ) + else: + design_map |= flatten_il_rec_helper( + module_to_name_to_type, + cell_with_params.cell_type, + f"{pref}.{cell_name[1:]}", + ) + return design_map + + +def flatten_il(module_to_name_to_type: ModuleCellTypes): + return flatten_il_rec_helper(module_to_name_to_type, "\\main", "main") + + +def parse_stat_file(path: str) -> dict: + with open(path, "r") as f: + return json.load(f) + + +def populate_stats(design_map: DesignRsrc, stat: dict): + for k, v in design_map.items(): + if v.cell_type.startswith("paramod"): + filtered_rsrc = { + k: v + for k, v in stat["modules"][f"${v.cell_type}"].items() + if isinstance(v, int) + } + design_map[k].rsrc.update(filtered_rsrc) + v.cell_type = v.cell_type.split("\\")[1] + + +def main(): + parser = argparse.ArgumentParser( + description="Utility to process Yosys IL and stat files and dump design map as JSON" + ) + parser.add_argument( + "il_file", + type=pathlib.Path, + help="path to the IL file" + ) + parser.add_argument( + "stat_file", + type=pathlib.Path, + help="path to the stat file" + ) + parser.add_argument( + "-o", + "--output", + type=pathlib.Path, + help="output JSON", + ) + args = parser.parse_args() + + name_to_type = parse_il_file(args.il_file) + design_map = flatten_il(name_to_type) + stat = parse_stat_file(args.stat_file) + populate_stats(design_map, stat) + + output_path = args.output + + if output_path: + with open(output_path, "w") as f: + json.dump( + design_map, + f, + indent=2, + default=lambda o: asdict(o) if is_dataclass(o) else str, + ) + else: + print( + json.dumps( + design_map, + indent=2, + default=lambda o: asdict(o) if is_dataclass(o) else str, + ) + ) + + +if __name__ == "__main__": + main() diff --git a/tools/area-profiler/area_profiler/plot.py b/tools/area-profiler/area_profiler/plot.py new file mode 100644 index 0000000000..0a777f1fc0 --- /dev/null +++ b/tools/area-profiler/area_profiler/plot.py @@ -0,0 +1,90 @@ +import argparse +import json +import plotly.express as px +from collections import defaultdict +from pathlib import Path + +AREA_WEIGHTS = { + "and": 1.0, + "or": 1.0, + "not": 0.5, + "eq": 3.0, + "logic_not": 2.0, + "mux": 4.0, + "std_wire": 0.2, + "std_reg": 8.0, +} + +def load_data(path: Path): + with open(path) as f: + return json.load(f) + +def compute_areas(data): + areas = [] + for name, cell in data.items(): + t = cell["cell_type"] + w = cell["cell_width"] + weight = AREA_WEIGHTS.get(t, 1.0) + area = weight * w + areas.append({"cell_name": name, "cell_type": t, "width": w, "area": area}) + return areas + +def make_bar_chart(areas, output): + type_area = defaultdict(float) + for a in areas: + type_area[a["cell_type"]] += a["area"] + summary = [{"cell_type": t, "total_area": area} for t, area in type_area.items()] + + fig = px.bar( + summary, + x="cell_type", + y="total_area", + title="estimated area", + labels={"total_area": "Estimated area"}, + ) + fig.write_html(output) + +def make_treemap(areas, output): + fig = px.treemap( + areas, + path=["cell_type", "cell_name"], + values="area", + title="estimated area treemap", + ) + fig.write_html(output) + +def main(): + parser = argparse.ArgumentParser( + description="Estimate and plot cell areas based on a heuristic" + ) + parser.add_argument( + "input", + type=Path, + help="path to input JSON file", + ) + parser.add_argument( + "mode", + choices=["bar", "treemap"], + help="visualization type", + ) + parser.add_argument( + "-o", + "--output", + type=Path, + help="output HTML file (default: area_by_type.html for bar, area_treemap.html for treemap)", + ) + + args = parser.parse_args() + + data = load_data(args.input) + areas = compute_areas(data) + + if args.mode == "bar": + output = args.output or Path("area_by_type.html") + make_bar_chart(areas, output) + elif args.mode == "treemap": + output = args.output or Path("area_treemap.html") + make_treemap(areas, output) + +if __name__ == "__main__": + main() diff --git a/tools/area-profiler/pyproject.toml b/tools/area-profiler/pyproject.toml new file mode 100644 index 0000000000..eda1951215 --- /dev/null +++ b/tools/area-profiler/pyproject.toml @@ -0,0 +1,15 @@ +[build-system] +requires = ["flit_core >=3.2,<4"] +build-backend = "flit_core.buildapi" + +[project] +name = "area-profiler" +authors = [{ name = "The Calyx Authors" }] +classifiers = ["License :: OSI Approved :: MIT License"] +dynamic = ["version", "description"] +dependencies = ["pandas", "plotly"] +readme = "README.md" + +[project.scripts] +aprof-parse = "area_profiler.parse:main" +aprof-plot = "area_profiler.plot:main" From e9c7687f9cfe6fd85f42b16a1e2f744787dad47c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pedro=20Pontes=20Garc=C3=ADa?= Date: Mon, 22 Sep 2025 14:55:26 -0400 Subject: [PATCH 2/2] Better readme with Yosys info --- tools/area-profiler/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/tools/area-profiler/README.md b/tools/area-profiler/README.md index 742ba194e1..fe687981ca 100644 --- a/tools/area-profiler/README.md +++ b/tools/area-profiler/README.md @@ -8,10 +8,14 @@ yosys -p "read_verilog -sv inline.sv; hierarchy -top main; opt; write_rtlil inli ## Install +The tool can be installed with: + ```bash uv tool install . ``` +Additionally, on `havarti`, feel free to use Pedro's installation of the Yosys environment, located in `/scratch/pedro`. The environment can be loaded using the `environment` or `environment.fish` scripts. + ## Usage ```bash