-
Notifications
You must be signed in to change notification settings - Fork 20
[util] FPGA resource utilisation reporting + visualiser utility #691
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
Open
thomas6785
wants to merge
1
commit into
lowRISC:main
Choose a base branch
from
thomas6785:utilisation_reporting
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| <!-- | ||
| # Copyright lowRISC contributors (COSMIC project). | ||
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
| --> | ||
|
|
||
| Generates: | ||
| - a text report of FPGA resource utilisation, organised hierarchically | ||
| - a HTML sunburst diagram showing BRAM utilisation by instance | ||
| - a HTML sunburst diagram showing LUT+FF utilisation by instance | ||
|
|
||
| The sunburst diagrams are interactive -- click on a wedge to explore its children. | ||
|
|
||
| To generate, return to the repository root and run: | ||
| ``` | ||
| ./util/utilisation_reporting/report_utilisation.sh | ||
| ``` | ||
| Expect this to take a minute or two as it will need to open the design in Vivado. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| # Copyright lowRISC contributors (COSMIC project). | ||
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| # Open the implementated design | ||
| open_run impl_1 | ||
|
|
||
| # Report utilisation and dump to a file | ||
| report_utilization -hierarchical -hierarchical_depth 0 -file scratch/util_full.txt | ||
| # depth of 0 is interpreted as infinite | ||
|
|
||
| # If you're reading this file, you may be interested in some other options: | ||
|
|
||
| # Generate a spreadsheet report instead of text file (only works in GUI mode): | ||
| #report_utilization -hierarchical -spreadsheet_depth 0 -spreadsheet_file util.xlsx | ||
| # Generate a hierarchical report with less depth (useful for large designs): | ||
| #report_utilization -hierarchical -spreadsheet_depth 3 -spreadsheet_file util_depth3.xlsx |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,72 @@ | ||
| # Copyright lowRISC contributors (COSMIC project). | ||
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| from dataclasses import dataclass, field | ||
| from pathlib import Path | ||
| from typing import NamedTuple | ||
|
|
||
|
|
||
| class Utilisation(NamedTuple): | ||
| total_luts: int | ||
| logic_luts: int | ||
| lutrams: int | ||
| srls: int | ||
| ffs: int | ||
| ramb36: int | ||
| ramb18: int | ||
| dsp: int | ||
|
|
||
|
|
||
| @dataclass | ||
| class Node: | ||
| name: str # instance name, e.g. "u_ddr3_mig" or "(u_ddr3_mig)" for self-logic | ||
| module: str # module/cell type, e.g. "u_xlnx_mig_7_ddr3" | ||
| util: Utilisation # this row's own reported utilisation | ||
| children: list = field(default_factory=list) | ||
|
|
||
|
|
||
| def extract_utilisation_table(path: str) -> str: | ||
| """Thin wrapper that finds the 'utilisation by Hierarchy' table in a raw Vivado | ||
| report_utilization file and returns just its data rows, stripping the file header, | ||
| table of contents, and footnotes.""" | ||
| with Path(path).open(encoding="utf8") as f: | ||
| lines = f.readlines() | ||
| header = next(i for i, j in enumerate(lines) if "Instance" in j and "Module" in j) | ||
| data_start = header + 2 # skip the header row and the border beneath it | ||
| data_end = next(i for i in range(data_start, len(lines)) if lines[i].startswith("+")) | ||
| return "".join(lines[data_start:data_end]) | ||
|
|
||
|
|
||
| def parse_utilisation_report(path: str) -> Node: | ||
| """Parse a Vivado `report_utilization -hierarchical` text report into a Node tree.""" | ||
| rows = [] | ||
| for line in extract_utilisation_table(path).splitlines(): | ||
| parts = line.split("|")[1:-1] | ||
| # Vivado should emit instance name, module name, and eight resource-count columns: | ||
| if len(parts) != len(Utilisation._fields) + 2: | ||
| continue | ||
| instance, module, *nums = parts | ||
| try: | ||
| nums = [int(n.strip()) for n in nums] | ||
| except ValueError: | ||
| continue # stray blank/border line | ||
| depth = ( | ||
| len(instance) - len(instance.lstrip(" ")) | ||
| ) // 2 # each 2 leading spaces is one level of hierarchical depth | ||
| rows.append((depth, instance.strip(), module.strip(), Utilisation(*nums))) | ||
|
|
||
| root = None | ||
| stack = [] # list of (depth, node), innermost last | ||
| for depth, name, module, util in rows: | ||
| if "(" in name: | ||
| continue # skip self-logic rows, which are redundant with their parent | ||
| node = Node(name, module, util) | ||
| while stack and stack[-1][0] >= depth: | ||
| stack.pop() | ||
| if stack: | ||
| stack[-1][1].children.append(node) | ||
| else: | ||
| root = node | ||
| stack.append((depth, node)) | ||
| return root |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| #!/usr/bin/env -S bash -eu | ||
| # Copyright lowRISC contributors (COSMIC project). | ||
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| # This should be run from the root of the repository | ||
|
|
||
| if [ ! -d "./build/lowrisc_mocha_chip_mocha_genesys2_0" ]; then | ||
| echo "[report_utilisation.sh] ERROR: ./build/lowrisc_mocha_chip_mocha_genesys2_0 directory does not exist" | ||
| echo "Did you:" | ||
| echo " 1. Build the design first?" | ||
| echo " 2. Run util/utilisation_reporting/report_utilisation.sh from the root of the repository?" | ||
| exit 1 | ||
| fi | ||
|
|
||
| mkdir -p scratch | ||
| vivado -mode batch -source util/utilisation_reporting/get_reports.tcl ./build/lowrisc_mocha_chip_mocha_genesys2_0/synth-vivado/lowrisc_mocha_chip_mocha_genesys2_0.xpr | ||
| uv run python3 util/utilisation_reporting/sunburst.py scratch/util_full.txt |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,156 @@ | ||
| # Copyright lowRISC contributors (COSMIC project). | ||
| # Licensed under the Apache License, Version 2.0, see LICENSE for details. | ||
| # SPDX-License-Identifier: Apache-2.0 | ||
|
|
||
| import plotly.graph_objects as go | ||
| from parse_report import Node, parse_utilisation_report | ||
|
|
||
|
|
||
| def name2id(name: str) -> str: | ||
| """Convert a node name to a unique ID for use in the sunburst chart.""" | ||
| replacements = { | ||
| "[": "_", | ||
| "]": "_", | ||
| "(": "_", | ||
| ")": "_", | ||
| " ": "_", | ||
| ".": "_", | ||
| } | ||
| for old, new in replacements.items(): | ||
| name = name.replace(old, new) | ||
| return name | ||
|
|
||
|
|
||
| def build_sunburst_arrays(root: Node, value_getter=lambda n: n.util.total_luts): | ||
| """Plotly expects a sunburst chart to be defined by four parallel arrays: ids, labels, parents, | ||
| and values. This function walks the Node tree and builds those arrays.""" | ||
| ids = [] | ||
| labels = [] | ||
| parents = [] | ||
| values = [] | ||
|
|
||
| def walk(node: Node, path: str): | ||
| node_id = path | ||
|
|
||
| # Recurse into children (and tally their utilisation as we go) | ||
| child_util_sum = 0 | ||
| for child in node.children: | ||
| child_path = f"{node_id}.{name2id(child.name)}" | ||
| child_util_sum += walk(child, child_path) | ||
|
|
||
| # Add this node to the arrays too | ||
| ids.append(node_id) | ||
| labels.append(node.name) | ||
| this_util = max( | ||
| value_getter(node), child_util_sum | ||
| ) | ||
| # if the node's own utilisation is less than the sum of its children, use the sum of its | ||
| # children instead. Vivado will occasionally have a parent equal to less than the sum of its | ||
| # children due to cross-boundary optimisation. We will take the sum of the children in that | ||
| # case, otherwise we can't display it | ||
| values.append(this_util) | ||
|
|
||
| # Root has no parent | ||
| if "." in node_id: | ||
| parents.append(node_id.rsplit(".", 1)[0]) | ||
| else: | ||
| parents.append("") # Plotly root marker | ||
|
|
||
| return this_util # return the total utilisation as this will be useful for the parent | ||
|
|
||
| walk(root, root.name) | ||
| return ids, labels, parents, values | ||
|
|
||
|
|
||
| def ff_getter(node: Node): | ||
| """Return the number of flip-flops in this node's own reported utilisation.""" | ||
| return node.util.ffs | ||
|
|
||
|
|
||
| def lut_getter(node: Node): | ||
| """Return the number of LUT's in this node's own reported utilisation.""" | ||
| return node.util.total_luts | ||
|
|
||
|
|
||
| def ff_plus_lut_getter(node: Node): | ||
| """Return the number of flip-flops and LUT's in this node's own reported utilisation.""" | ||
| return node.util.ffs + node.util.total_luts | ||
|
|
||
|
|
||
| def bram_getter(node: Node): | ||
| """Return the number of BRAM18-equivalent blocks in this node's own reported utilisation.""" | ||
| return node.util.ramb18 + node.util.ramb36 * 2 | ||
|
|
||
|
|
||
| def generate_sunburst(root, value_getter, outfile, title): | ||
| """ | ||
| Given the root of a Node tree, generate a sunburst plot and output to a HTML file. | ||
| The value_getter function is used to extract the value to be plotted from each node | ||
| (e.g. number of LUTs, number of flip-flops, etc.) | ||
| """ | ||
| raw_data = build_sunburst_arrays(root, value_getter=value_getter) | ||
| ids, labels, parents, values = raw_data | ||
|
|
||
| # depth visible at any one time, to avoid clutter. Users can zoom in to see more detail | ||
| plot_maxdepth = 4 | ||
|
|
||
| # "total" means the value of a parent node INCLUDES the values of its children. Usually it the | ||
| # parent will be slightly larger than the sum of its children, but note that occasionally a | ||
| # parent will actually be smaller than the sum of its children due to Vivado's cross-boundary | ||
| # optimisation; Plotly will work around this if it occurs | ||
| branch_value_style = "total" | ||
|
|
||
| # Create the sunburst plot | ||
| fig = go.Figure( | ||
| go.Sunburst( | ||
| ids=ids, | ||
| labels=labels, | ||
| parents=parents, | ||
| values=values, | ||
| branchvalues=branch_value_style, | ||
| maxdepth=plot_maxdepth, | ||
| ) | ||
| ) | ||
|
|
||
| # Set some behaviours for interacting with it | ||
| fig.update_traces( | ||
| marker={"colors":None}, # force Plotly to regenerate colours | ||
| leaf={"opacity":1}, # ensures leaves get distinct colours | ||
| ) | ||
| fig.update_layout( | ||
| title=title, | ||
| title_x=0.5, | ||
| title_font_size=22, | ||
| title_font_color="darkblue" | ||
| ) | ||
|
|
||
| fig.write_html(outfile) | ||
|
|
||
|
|
||
| if __name__ == "__main__": | ||
| # If we are run as a script, take the first command line argument as a path | ||
| # to a Vivado `report_utilization -hierarchical` text report, parse it, and | ||
| # generate two sunburst plots: one for BRAM utilisation and one for FF+LUT | ||
| # utilisation. | ||
| import sys | ||
|
|
||
| if len(sys.argv) < 2: | ||
| print("Usage: python sunburst.py <path_to_vivado_report_utilisation.txt>") | ||
| sys.exit(1) | ||
|
|
||
| tree_root = parse_utilisation_report(sys.argv[1]) | ||
|
|
||
| generate_sunburst( | ||
| tree_root, | ||
| value_getter=bram_getter, | ||
| outfile="bram_util.html", | ||
| title="BRAM Utilisation" | ||
| ) | ||
| generate_sunburst( | ||
| tree_root, | ||
| value_getter=ff_plus_lut_getter, | ||
| outfile="ff_lut_util.html", | ||
| title="FF+LUT Utilisation" | ||
| ) | ||
|
|
||
| print("Sunburst plots generated: bram_util.html and ff_lut_util.html") |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.