Skip to content

CTS: improve NDR for clock nets - #11136

Draft
jfgava wants to merge 3 commits into
The-OpenROAD-Project:masterfrom
The-OpenROAD-Project-staging:cts-improve-cg-ndr
Draft

CTS: improve NDR for clock nets#11136
jfgava wants to merge 3 commits into
The-OpenROAD-Project:masterfrom
The-OpenROAD-Project-staging:cts-improve-cg-ndr

Conversation

@jfgava

@jfgava jfgava commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Summary

Each clock gater spawns its own TreeBuilder, and NDRs were applied per builder, each with its own level numbering restarting at 0, so every gater output net (and every gated subtree's clknet_0) looked like a root and always got the NDR, in all three strategies. The macro/register forked trees had the same problem. Two more defects fell out of it: levels were only tagged on the last subnet of each H-tree segment, so repeater nets inside a trunk (level_ == -1) were skipped even in full; and each builder created a redundant CTS_NDR_n rule.

Type of Change

  • Bug fix

Impact

computeClockTreeLevels() walks the real clock tree in the DB after all trees are written, numbering levels from the root outward across builder boundaries:

  • A gater (or a forked register tree) is a level boundary: the subtree continues the parent's numbering (gclk1 at level 2, nested gclk2 at level 3 in the test design) instead of being level 0.

  • Levels count tree levels, not buffer stages — a trunk broken up by repeaters keeps one level, so half never cuts a single trunk in half. Verified against array_max_wl: the computed levels line up exactly with the H-tree topology levels (clknet__*).

  • A tree's input net shares level 0 with its root buffer's nets, so root_only still covers clk + clknet_0_* as before.

  • Leaf nets stay excluded, except nets that feed a gater subtree — the tree continues below them, so they count as trunks.

  • One shared CTS_NDR_0 rule for the whole design instead of one per builder.

Strategies: root_only → level 0; half → levels 0..⌈N/2⌉-1; full → all non-leaf levels. set_debug_level CTS ndr 1 reports the level count, the applied range, and each net with its level.

Example jpeg ihp-sg13g2

jpeg-clk-tree

root_only
[DEBUG CTS-ndr] Applied NDR to: clk (level 0)
[DEBUG CTS-ndr] Applied NDR to: clknet_0_clk (level 0)

half
[DEBUG CTS-ndr] Applied NDR to: clk (level 0)
[DEBUG CTS-ndr] Applied NDR to: clknet_0_clk (level 0)
[DEBUG CTS-ndr] Applied NDR to: clknet_2_3_0_clk (level 1)
[DEBUG CTS-ndr] Applied NDR to: clknet_2_2_0_clk (level 1)
[DEBUG CTS-ndr] Applied NDR to: clknet_2_1_0_clk (level 1)
[DEBUG CTS-ndr] Applied NDR to: clknet_2_0_0_clk (level 1)

full
[DEBUG CTS-ndr] Applied NDR to: clk (level 0)
[DEBUG CTS-ndr] Applied NDR to: clknet_0_clk (level 0)
[DEBUG CTS-ndr] Applied NDR to: clknet_2_3_0_clk (level 1)
[DEBUG CTS-ndr] Applied NDR to: clknet_2_2_0_clk (level 1)
[DEBUG CTS-ndr] Applied NDR to: clknet_2_1_0_clk (level 1)
[DEBUG CTS-ndr] Applied NDR to: clknet_2_0_0_clk (level 1)
[DEBUG CTS-ndr] Applied NDR to: clknet_3_7_0_clk (level 2)
[DEBUG CTS-ndr] Applied NDR to: clknet_3_6_0_clk (level 2)
[DEBUG CTS-ndr] Applied NDR to: clknet_3_5_0_clk (level 2)
[DEBUG CTS-ndr] Applied NDR to: clknet_3_4_0_clk (level 2)
[DEBUG CTS-ndr] Applied NDR to: clknet_3_3_0_clk (level 2)
[DEBUG CTS-ndr] Applied NDR to: clknet_3_2_0_clk (level 2)
[DEBUG CTS-ndr] Applied NDR to: clknet_3_1_0_clk (level 2)
[DEBUG CTS-ndr] Applied NDR to: clknet_3_0_0_clk (level 2)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_63_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_62_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_61_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_60_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_59_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_58_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_57_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_56_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_55_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_54_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_53_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_52_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_51_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_50_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_49_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_48_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_47_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_46_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_45_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_44_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_43_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_42_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_41_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_40_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_39_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_38_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_37_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_36_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_35_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_34_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_33_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_32_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_31_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_30_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_29_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_28_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_27_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_26_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_25_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_24_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_23_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_22_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_21_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_20_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_19_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_18_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_17_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_16_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_15_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_14_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_13_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_12_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_11_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_10_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_9_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_8_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_7_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_6_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_5_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_4_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_3_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_2_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_1_0_clk (level 3)
[DEBUG CTS-ndr] Applied NDR to: clknet_6_0_0_clk (level 3)

Verification

  • I have verified that the local build succeeds (./etc/Build.sh).
  • I have run the relevant tests and they pass.
  • My code follows the repository's formatting guidelines.
  • I have included tests to prevent regressions.
  • I have signed my commits (DCO).

Related Issues

Issue 8579

jfgava added 3 commits July 31, 2026 19:58
Signed-off-by: Jonas Gava <jfgava@precisioninno.com>
… clock gaters

Signed-off-by: Jonas Gava <jfgava@precisioninno.com>
@jfgava jfgava self-assigned this Aug 13, 2026

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

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.

Code Review

This pull request refactors the clock tree level numbering and Non-Default Rule (NDR) application in TritonCTS to support numbering across tree builder boundaries, such as clock gaters, using a global breadth-first traversal. It also updates the default NDR strategy to half and cleans up unused level-tracking variables. A review comment suggests using std::unordered_set instead of std::vector for collecting child nets to avoid duplicate entries and incorrect level increments when a net connects to multiple input pins of the same instance.

Comment thread src/cts/src/TritonCTS.cpp
Comment on lines +1884 to +1903
std::vector<odb::dbNet*> childNets;
for (odb::dbITerm* iterm : net->getITerms()) {
if (!iterm->isInputSignal()) {
continue;
}
for (odb::dbITerm* outTerm : iterm->getInst()->getITerms()) {
if (!outTerm->isOutputSignal()) {
continue;
}
odb::dbNet* outNet = outTerm->getNet();
if (outNet == nullptr || !treeNets.contains(outNet)) {
// Not part of a clock tree built by CTS: a sink, or a gater whose
// fanin was too small for a tree of its own.
continue;
}
if (subTreeRoots.contains(outNet)) {
feedsSubTree.insert(net);
}
childNets.push_back(outNet);
}

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

Using a std::vector for childNets can lead to duplicate entries if a net connects to multiple input pins of the same instance (e.g., in complex clock gater or clock mux cells). This would cause childNets.size() > 1 to be incorrectly true, falsely incrementing the tree level even when there is no actual branching.

Using a std::unordered_set instead of std::vector ensures that only unique child nets are collected, making the level calculation robust against duplicate pin connections.

      std::unordered_set<odb::dbNet*> childNets;
      for (odb::dbITerm* iterm : net->getITerms()) {
        if (!iterm->isInputSignal()) {
          continue;
        }
        for (odb::dbITerm* outTerm : iterm->getInst()->getITerms()) {
          if (!outTerm->isOutputSignal()) {
            continue;
          }
          odb::dbNet* outNet = outTerm->getNet();
          if (outNet == nullptr || !treeNets.contains(outNet)) {
            // Not part of a clock tree built by CTS: a sink, or a gater whose
            // fanin was too small for a tree of its own.
            continue;
          }
          if (subTreeRoots.contains(outNet)) {
            feedsSubTree.insert(net);
          }
          childNets.insert(outNet);
        }
      }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant