Skip to content

[DEMO] Timing report HTML generation integration status demo#9770

Closed
oharboe wants to merge 2 commits intoThe-OpenROAD-Project:masterfrom
Pinata-Consulting:timing-html-report
Closed

[DEMO] Timing report HTML generation integration status demo#9770
oharboe wants to merge 2 commits intoThe-OpenROAD-Project:masterfrom
Pinata-Consulting:timing-html-report

Conversation

@oharboe
Copy link
Copy Markdown
Collaborator

@oharboe oharboe commented Mar 15, 2026

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.

image

Usage

cd tools/OpenROAD

# Build timing report for any ORFS stage
bazelisk build //test/orfs/gcd:gcd_synth_timing
bazelisk build //test/orfs/mock-array:MockArray_4x4_base_synth_timing

# Open in browser
bazelisk run //test/orfs/gcd:gcd_synth_timing
bazelisk run //test/orfs/mock-array:MockArray_4x4_base_synth_timing

Adding timing reports to any ORFS design is one line in BUILD:

load("//test/orfs:timing.bzl", "orfs_timing_stages")

orfs_timing_stages(
    name = "gcd",
    stages = ["synth", "floorplan", "place", "cts", "grt", "route"],
    timing_script = "//etc:timing_report",
)

What it produces

A single self-contained HTML file (~300KB for MockArray) with:

  • Endpoint Slack Histogram — nice-bucket algorithm ported from chartsWidget.cpp, red/green bars, dropdowns for path group and clock filtering
  • Timing Report table — columns match Qt TimingPathsModel: Capture Clock, Required, Arrival, Slack, Skew, Logic Delay, Logic Depth, Fanout, Start, End (all in ps)
  • Data Path Details — per-arc Pin, Fanout, ↑/↓, Time, Delay, Slew, Load (matching TimingPathDetailModel)
  • Unconstrained pin count below histogram
  • Draggable sashes between panels

Known Issues

STA search state crash (blocker for C++ extension points)

The C++ Timing::getTimingPaths(), getClockInfo(), and getSlackHistogram() 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/sta to properly reset search state between Python API calls.

Histogram buckets differ slightly from Qt

The JS snapInterval algorithm matches chartsWidget.cpp but unconstrained endpoint filtering differs slightly.

See docs/timing_report_todo.md for 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 — MockArrayasap7 | Setup WNS 0.0000 ns TNS 0.0000 ns | Hold WNS 0.0000 ns

Setup Hold
WNS 0.0000 ns 0.0000 ns
TNS 0.0000 ns 0.0000 ns
Endpoints 5172
Slack distribution (setup):
                     █    ▃     ▃         ▂▆   2120 endpoints
    0.000                            0.000 ns
Clock Domains
Clock Period (ns) Sources
clock 250.0 ``
Cell Type Breakdown (top 10 by delay)
Cell Count Total Delay (ns) Avg Slew (ns)
MockArray 200 0.0000 0.0000

Full interactive report: see 1_timing.html artifact

Test plan

  • bazelisk build //test/orfs/gcd:gcd_synth_timing passes
  • bazelisk build //test/orfs/mock-array:MockArray_4x4_base_synth_timing passes
  • bazelisk run opens HTML in browser with histogram + path table + detail
  • Path group dropdown shows all groups (including empty ones)
  • Clicking histogram bar filters path table
  • Screenshots match Qt GUI layout

🤖 Generated with Claude Code

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +50 to +51
except Exception:
return []
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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 []

Comment on lines +92 to +97
# 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)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
# 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()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
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
Comment on lines +605 to +611
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;
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Comment on lines +45 to +49
lib_files = [
f
for f in stage[PdkInfo].libs.to_list()
if "NLDM" in f.path and "_FF_" in f.path
]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Copy link
Copy Markdown
Contributor

@github-actions github-actions bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clang-tidy made some suggestions

src/Timing.cc Outdated
}
}
for (const sta::Pin* pin : clk->pins()) {
info.sources.push_back(network->pathName(pin));
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use emplace_back instead of push_back [modernize-use-emplace]

Suggested change
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;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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();
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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;
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

warning: use std::max instead of > [readability-use-std-min-max]

Suggested change
}
max_fanout = std::max(node_fanout, max_fanout);

@oharboe oharboe force-pushed the timing-html-report branch from a9639df to 4e36086 Compare March 15, 2026 23:20
oharboe added a commit to Pinata-Consulting/OpenROAD that referenced this pull request Mar 16, 2026
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>
oharboe added a commit to Pinata-Consulting/OpenROAD that referenced this pull request Mar 16, 2026
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>
@oharboe
Copy link
Copy Markdown
Collaborator Author

oharboe commented Mar 16, 2026

@maliberty @tspyrou Let's discuss. This is a demo/recipe for what's needed to make this work.

@oharboe oharboe closed this Mar 16, 2026
@oharboe oharboe reopened this Mar 18, 2026
@oharboe oharboe force-pushed the timing-html-report branch from 4e36086 to 89e5ff2 Compare March 18, 2026 15:12
@oharboe oharboe changed the title Timing report HTML generation (integration demo) [DEMO] Timing report HTML generation integration status demo Mar 18, 2026
@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@oharboe oharboe force-pushed the timing-html-report branch from 89e5ff2 to b35c7f7 Compare March 18, 2026 23:31
@github-actions
Copy link
Copy Markdown
Contributor

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>
@oharboe oharboe force-pushed the timing-html-report branch from b35c7f7 to ce0520d Compare March 18, 2026 23:46
@github-actions
Copy link
Copy Markdown
Contributor

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>
@oharboe oharboe force-pushed the timing-html-report branch from ce0520d to d06a7d2 Compare March 18, 2026 23:55
@github-actions
Copy link
Copy Markdown
Contributor

clang-tidy review says "All clean, LGTM! 👍"

@oharboe
Copy link
Copy Markdown
Collaborator Author

oharboe commented Mar 21, 2026

Wip.

@oharboe oharboe closed this Mar 21, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant