[DEMO] Timing report HTML generation integration status demo#9770
[DEMO] Timing report HTML generation integration status demo#9770oharboe wants to merge 2 commits intoThe-OpenROAD-Project:masterfrom
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces an impressive feature for generating self-contained HTML timing reports. The implementation is comprehensive, covering C++ API extensions, Python scripting for report generation, and Bazel rules for integration. My review focuses on improving the correctness of the generated reports, enhancing robustness, and increasing maintainability. I've identified a few areas for improvement, including the calculation of logic delay, error handling, and configuration management.
etc/timing_report.py
Outdated
| except Exception: | ||
| return [] |
There was a problem hiding this comment.
Catching a generic Exception can hide unexpected errors and make debugging more difficult. It's better to catch specific exceptions that you expect to handle, such as json.JSONDecodeError if the file content is not valid JSON, or IOError for file-related issues.
| except Exception: | |
| return [] | |
| except json.JSONDecodeError as e: | |
| print(f"[timing_report] ERROR: Failed to parse JSON from report_checks output: {e}", file=sys.stderr) | |
| return [] |
etc/timing_report.py
Outdated
| # Compute logic depth | ||
| cells = set() | ||
| for a in p["arcs"]: | ||
| if not a["net"] and a["cell"]: | ||
| cells.add(a["to"]) | ||
| p["logic_depth"] = len(cells) |
There was a problem hiding this comment.
The logic_delay for each path is not being calculated, causing the report to incorrectly display the arrival time instead. You can calculate logic_delay here by summing the delays of the non-net arcs in the path.
After this change, you should also update line 524 in the Javascript template from <td>${{ps(p.logic_delay || p.arrival)}}</td> to <td>${{ps(p.logic_delay)}}</td> to ensure the correct value is displayed.
| # Compute logic depth | |
| cells = set() | |
| for a in p["arcs"]: | |
| if not a["net"] and a["cell"]: | |
| cells.add(a["to"]) | |
| p["logic_depth"] = len(cells) | |
| # Compute logic depth and logic delay | |
| cells = set() | |
| logic_delay = 0.0 | |
| for a in p["arcs"]: | |
| if not a["net"] and a["cell"]: | |
| cells.add(a["to"]) | |
| logic_delay += a["delay"] | |
| p["logic_depth"] = len(cells) | |
| p["logic_delay"] = logic_delay |
| file=sys.stderr) | ||
| sys.exit(1) | ||
|
|
||
| lib_files = os.environ.get("LIB_FILES", "").split() |
There was a problem hiding this comment.
If the LIB_FILES environment variable is not set or is an empty string, os.environ.get("LIB_FILES", "").split() will result in a list containing an empty string ['']. This could cause issues later when trying to read the liberty file. It's safer to handle the case of an empty string explicitly.
| lib_files = os.environ.get("LIB_FILES", "").split() | |
| lib_files_str = os.environ.get("LIB_FILES", "") | |
| lib_files = lib_files_str.split() if lib_files_str else [] |
src/Timing.cc
Outdated
| if (!is_net && !pin_is_clock && inst) { | ||
| if (logic_insts.find(inst) == logic_insts.end()) { | ||
| logic_insts.insert(inst); | ||
| logic_depth_count++; | ||
| logic_delay_total += pin_delay; | ||
| } | ||
| } |
There was a problem hiding this comment.
The current implementation of logic_delay_total includes delays from all instance arcs that are not on a clock path. However, the standard definition of logic delay typically excludes buffers and inverters. To improve accuracy, consider checking if the instance is a buffer or inverter and excluding its delay from logic_delay_total.
You could use design_->getResizer()->isBuffer(lib_cell) and is_inverter for this check, which would require including rsz/Resizer.hh.
| lib_files = [ | ||
| f | ||
| for f in stage[PdkInfo].libs.to_list() | ||
| if "NLDM" in f.path and "_FF_" in f.path | ||
| ] |
There was a problem hiding this comment.
Hardcoding the filter for liberty files based on "NLDM" and "_FF_" in the path is brittle and may not work for all PDKs or corners. As noted in your documentation, this should be improved. A more robust solution would be to propagate the correct list of library files from the orfs_flow rule, possibly through a custom provider, rather than relying on string matching within this rule.
src/Timing.cc
Outdated
| } | ||
| } | ||
| for (const sta::Pin* pin : clk->pins()) { | ||
| info.sources.push_back(network->pathName(pin)); |
There was a problem hiding this comment.
warning: use emplace_back instead of push_back [modernize-use-emplace]
| info.sources.push_back(network->pathName(pin)); | |
| info.sources.emplace_back(network->pathName(pin)); |
|
|
||
| const bool is_setup = (minmax == Max); | ||
| sta::SceneSeq scenes = sta->scenes(); | ||
| sta::StringSeq group_names; |
There was a problem hiding this comment.
warning: no header providing "sta::StringSeq" is directly included [misc-include-cleaner]
src/Timing.cc:35:
- #include "sta/TimingArc.hh"
+ #include "sta/StringUtil.hh"
+ #include "sta/TimingArc.hh"| sta::StringSeq group_names; | ||
|
|
||
| sta::Search* search = sta->search(); | ||
| sta::PathEndSeq path_ends = search->findPathEnds( |
There was a problem hiding this comment.
warning: no header providing "sta::PathEndSeq" is directly included [misc-include-cleaner]
src/Timing.cc:35:
- #include "sta/TimingArc.hh"
+ #include "sta/SearchClass.hh"
+ #include "sta/TimingArc.hh"|
|
||
| for (auto& path_end : path_ends) { | ||
| TimingPathInfo path_info; | ||
| sta::Path* path = path_end->path(); |
There was a problem hiding this comment.
warning: no header providing "sta::Path" is directly included [misc-include-cleaner]
src/Timing.cc:27:
- #include "sta/PathEnd.hh"
+ #include "sta/Path.hh"
+ #include "sta/PathEnd.hh"
src/Timing.cc
Outdated
| } | ||
| if (node_fanout > max_fanout) { | ||
| max_fanout = node_fanout; | ||
| } |
There was a problem hiding this comment.
warning: use std::max instead of > [readability-use-std-min-max]
| } | |
| max_fanout = std::max(node_fanout, max_fanout); |
a9639df to
4e36086
Compare
A fun demo with a serious undertone: all of this was done automatically with zero programming in ca. 30 minutes. The trick is now to identify use cases, stakeholders, and minimize the cognitive load for maintainers and users. This also demonstrates the democratization of the EDA build flow. Do what you need to do for your chip and use-case. The source code for the OpenROAD project is the user-interface to Claude. The grander vision (see The-OpenROAD-Project#9770): today the Qt GUI stands between you and what you want to do. The Qt GUI is an OpenROAD developer debugging tool. The user will use Claude to generate directly what they need — whether that's a standalone HTML visualization, a custom report, or an interactive tool tailored to their specific use-case. No generic GUI can anticipate every user's needs, but an AI that reads the source code can build exactly what's needed on the fly. The serious undertone to generating a standalone static HTML: we've identified and addressed the deployment use-case — a single file with zero dependencies that anyone can open in a browser, share as a link, or attach to a PR. No servers, no installs, no runtime dependencies. This is how you ship tooling that people actually use. The unit tests close the feedback loop so Claude can quickly add extension points: write extension, add test, run bazelisk test, iterate — all within a single conversation. This is just a fun feature and has no practical value. Also fix Resizer.cc makeScenes call to match updated STA API (StringSeq passed by pointer). How to run: # Demo mode (instant, no build needed) python3 test/orfs/mock-array/game.py --demo # With real routing data (builds MockArray, ca. 30 min) bazelisk run //test/orfs/mock-array:game # Unit tests (49 tests, headless) bazelisk test //test/orfs/mock-array:test_game How to release (anyone can play by clicking the link): bazelisk run //test/orfs/mock-array:game gh release create game-v1.0 \ --title "DRC Destroyer" \ --notes "Download drc_destroyer.html and open in any browser." \ /tmp/drc_destroyer.html Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
A fun demo with a serious undertone: all of this was done automatically with zero programming in ca. 30 minutes. The trick is now to identify use cases, stakeholders, and minimize the cognitive load for maintainers and users. This also demonstrates the democratization of the EDA build flow. Do what you need to do for your chip and use-case. The source code for the OpenROAD project is the user-interface to Claude. The grander vision (see The-OpenROAD-Project#9770): today the Qt GUI stands between you and what you want to do. The Qt GUI is an OpenROAD developer debugging tool. The user will use Claude to generate directly what they need — whether that's a standalone HTML visualization, a custom report, or an interactive tool tailored to their specific use-case. No generic GUI can anticipate every user's needs, but an AI that reads the source code can build exactly what's needed on the fly. The serious undertone to generating a standalone static HTML: we've identified and addressed the deployment use-case — a single file with zero dependencies that anyone can open in a browser, share as a link, or attach to a PR. No servers, no installs, no runtime dependencies. This is how you ship tooling that people actually use. The unit tests close the feedback loop so Claude can quickly add extension points: write extension, add test, run bazelisk test, iterate — all within a single conversation. This is just a fun feature and has no practical value. Also fix Resizer.cc makeScenes call to match updated STA API (StringSeq passed by pointer). How to run: # Demo mode (instant, no build needed) python3 test/orfs/mock-array/game.py --demo # With real routing data (builds MockArray, ca. 30 min) bazelisk run //test/orfs/mock-array:game # Unit tests (49 tests, headless) bazelisk test //test/orfs/mock-array:test_game How to release (anyone can play by clicking the link): bazelisk run //test/orfs/mock-array:game gh release create game-v1.0 \ --title "DRC Destroyer" \ --notes "Download drc_destroyer.html and open in any browser." \ /tmp/drc_destroyer.html Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
|
@maliberty @tspyrou Let's discuss. This is a demo/recipe for what's needed to make this work. |
4e36086 to
89e5ff2
Compare
|
clang-tidy review says "All clean, LGTM! 👍" |
89e5ff2 to
b35c7f7
Compare
|
clang-tidy review says "All clean, LGTM! 👍" |
Self-contained HTML timing report generated from any ORFS stage using the Timing Python API. Produces an interactive report with endpoint slack histogram, timing path table with arc detail, draggable sashes, and path group/clock filtering. Also generates a markdown summary for PR comments. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
b35c7f7 to
ce0520d
Compare
|
clang-tidy review says "All clean, LGTM! 👍" |
Custom Starlark rule (_timing_gen) and macro (orfs_timing_stages) that wire up PdkInfo liberty files, ODB, and SDC from any ORFS stage to the timing report generator. Adds timing targets for gcd and MockArray (Element + 4x4_base) across all stages (synth through route). Usage: bazelisk build //test/orfs/gcd:gcd_synth_timing bazelisk run //test/orfs/gcd:gcd_synth_timing Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> Signed-off-by: Øyvind Harboe <oyvind.harboe@zylin.com>
ce0520d to
d06a7d2
Compare
|
clang-tidy review says "All clean, LGTM! 👍" |
|
Wip. |
Summary
No more click and wait in timing reports. Run a build overnight and get histogram and timing reports in seconds without having to load timing information.
This is the first demo of a grander vision of static HTML GUI and the Qt GUI being a developer tool and the single source of truth for the HTML GUI.
Self-contained HTML timing reports generated from any ORFS stage, matching the OpenROAD Qt GUI (Charts + Timing Report widgets) as closely as possible without modifying
src/sta.This is an integration demo. Individual features and bug fixes will be broken out into separate PRs. This branch will be force-rebased on top of those changes as they land, serving as a living status report.
Usage
Adding timing reports to any ORFS design is one line in BUILD:
What it produces
A single self-contained HTML file (~300KB for MockArray) with:
chartsWidget.cpp, red/green bars, dropdowns for path group and clock filteringTimingPathsModel: Capture Clock, Required, Arrival, Slack, Skew, Logic Delay, Logic Depth, Fanout, Start, End (all in ps)TimingPathDetailModel)Known Issues
STA search state crash (blocker for C++ extension points)
The C++
Timing::getTimingPaths(),getClockInfo(), andgetSlackHistogram()abort when called from the Bazel Python API. The root cause is that STA's internal search state is left in a condition where subsequent calls crash.ensureGraph()+searchPreamble()don't help.Workaround: timing paths extracted via Tcl
report_checks -format json. Histogram bucketing done in JS.Fix needed: changes to
src/stato properly reset search state between Python API calls.Histogram buckets differ slightly from Qt
The JS
snapIntervalalgorithm matcheschartsWidget.cppbut unconstrained endpoint filtering differs slightly.See
docs/timing_report_todo.mdfor the full Qt GUI discrepancy list.An .md file for pull requests?
There's also an .md file with information that could be used in a pull request, but that's a little bit off topic, but here is what it looks like currently. Perhaps drop this and focus on .html in this PR.
PASS Timing —
MockArray—asap7| Setup WNS0.0000 nsTNS0.0000 ns| Hold WNS0.0000 ns0.0000 ns0.0000 ns0.0000 ns0.0000 nsClock Domains
clock250.0Cell Type Breakdown (top 10 by delay)
MockArray0.00000.0000Full interactive report: see
1_timing.htmlartifactTest plan
bazelisk build //test/orfs/gcd:gcd_synth_timingpassesbazelisk build //test/orfs/mock-array:MockArray_4x4_base_synth_timingpassesbazelisk runopens HTML in browser with histogram + path table + detail🤖 Generated with Claude Code