dft: implement scan_opt using the 2-Opt algorithm#10176
dft: implement scan_opt using the 2-Opt algorithm#10176donn wants to merge 1 commit intoThe-OpenROAD-Project:masterfrom
Conversation
- dft::Dft: - isolate writing the data to odb to its own function - store scan chains for later optimization after stitching - dft::Dft::scanOpt(): use scan chain driver/sink information plus instance placements to optimize the total scan chain length using the 2Opt algorithm - dft::ScanChain: - add estimateInternalTWL() to return an estimated total wire length for the scan chain based on cell origins (for verification/testing purposes) - sortScanCells now returns an estimated total wire length (and requires lambdas passed to it to do so as well) - dft::ScanPin: add getLocation() as a a method to get terminal locations whether it's a bterm or an iterm - odb::dbDft: Add reset() method to clear all existing info - utl::Logger::error(): replace sprintf with snprintf so clang doesn't report an error on every single use of the function. Signed-off-by: Mohamed Gaber <me@donn.website>
|
I know I still need to update the Readme but given the average turnaround on contributing to the dft module is about a month I figured may as well just get started and get feedback first |
There was a problem hiding this comment.
Code Review
This pull request implements scan chain optimization using a 2-Opt heuristic to reduce total wire length and introduces several architectural improvements to the DFT module, such as the writeToOdb method and state resetting in dbDft. Key feedback includes addressing a critical issue where a side effect inside an assert would be stripped in release builds, potentially causing incorrect behavior. Additionally, a return statement in the optimization loop should be changed to continue to avoid skipping valid chains when one fails, and an initialization of a size_t index to -1 needs to be corrected to prevent out-of-bounds access.
| } | ||
| points[++i] = sink; | ||
| score += odb::Point::manhattanDistance(last, sink); | ||
| assert(++i == N); |
There was a problem hiding this comment.
The assert contains a side effect (++i). In release builds where NDEBUG is defined, the assertion and its expression are removed, meaning i will not be incremented. This will lead to incorrect behavior in release builds. Side effects should be moved outside of the assert.
| assert(++i == N); | |
| ++i; | |
| assert(i == N); |
There was a problem hiding this comment.
Well i is not used after this point but fair enough
| 14, | ||
| "Cannot optimize: source/sink pins don't exist or have no " | ||
| "placement."); | ||
| return; |
There was a problem hiding this comment.
The return statement here will cause the entire scanOpt function to exit if a single scan chain cannot be optimized (e.g., due to missing source/sink pins or placement). This prevents other valid scan chains from being optimized. It should be replaced with continue to skip only the current chain.
| return; | |
| continue; |
| size_t N = cells.size() + 2; | ||
|
|
||
| std::vector<odb::Point> points(N); | ||
| size_t i = -1; |
There was a problem hiding this comment.
In this repository, static_cast<size_t>(-1) is the standard sentinel value for an invalid size_t index because 0 is often a valid index. However, using -1 (which wraps to the maximum value of size_t) as an active index for points[i] will cause an out-of-bounds access. If i is intended to be a valid starting index, it should be initialized to 0.
| size_t i = -1; | |
| size_t i = 0; | |
| points[i] = source; |
References
- When checking for failure or sentinel values in size_t, use static_cast<size_t>(-1) because 0 is a valid index.
There was a problem hiding this comment.
Doing this instead seems a bit clearer and achieves the same thing.
size_t i = 0;
points[i++] = source;| return; | ||
| } | ||
|
|
||
| odb::Point src = odb::Point(src_x, src_y); |
There was a problem hiding this comment.
warning: no header providing "odb::Point" is directly included [misc-include-cleaner]
src/dft/src/Dft.cpp:25:
- #include "utl/Logger.h"
+ #include "odb/geom.h"
+ #include "utl/Logger.h"| odb::Point src = odb::Point(src_x, src_y); | ||
| odb::Point sink = odb::Point(sink_x, sink_y); | ||
|
|
||
| int64_t twl_internal = chain->estimateInternalTWL(); |
There was a problem hiding this comment.
warning: no header providing "int64_t" is directly included [misc-include-cleaner]
src/dft/src/Dft.cpp:5:
- #include <memory>
+ #include <cstdint>
+ #include <memory>| int64_t twl_internal = chain->estimateInternalTWL(); | ||
| int64_t twl = twl_internal; | ||
| const auto& scan_cells = chain->getScanCells(); | ||
| if (scan_cells.size() > 0) { |
There was a problem hiding this comment.
warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty]
| if (scan_cells.size() > 0) { | |
| if (!scan_cells.empty()) { |
Additional context
/usr/include/c++/13/bits/stl_vector.h:1087: method 'vector'::empty() defined here
empty() const _GLIBCXX_NOEXCEPT
^| // Sort to reduce wire length | ||
| odb::Point f_src(src_x, src_y); | ||
| odb::Point f_sink(sink_x, sink_y); | ||
| if (rising.size() > 0) { |
There was a problem hiding this comment.
warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty]
| if (rising.size() > 0) { | |
| if (!rising.empty()) { |
Additional context
/usr/include/c++/13/bits/stl_vector.h:1087: method 'vector'::empty() defined here
empty() const _GLIBCXX_NOEXCEPT
^| = OptimizeScanWirelength2Opt(f_src, f_sink, falling, logger_); | ||
|
|
||
| odb::Point r_src(src_x, src_y); | ||
| if (falling.size() > 0) { |
There was a problem hiding this comment.
warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty]
| if (falling.size() > 0) { | |
| if (!falling.empty()) { |
Additional context
/usr/include/c++/13/bits/stl_vector.h:1087: method 'vector'::empty() defined here
empty() const _GLIBCXX_NOEXCEPT
^| utl::Logger* logger); | ||
|
|
||
| int64_t OptimizeScanWirelength2Opt( | ||
| const odb::Point source, |
There was a problem hiding this comment.
warning: parameter 'source' is const-qualified in the function declaration; const-qualification of parameters only has an effect in function definitions [readability-avoid-const-params-in-decls]
| const odb::Point source, | |
| odb::Point source, |
|
|
||
| int64_t OptimizeScanWirelength2Opt( | ||
| const odb::Point source, | ||
| const odb::Point sink, |
There was a problem hiding this comment.
warning: parameter 'sink' is const-qualified in the function declaration; const-qualification of parameters only has an effect in function definitions [readability-avoid-const-params-in-decls]
| const odb::Point sink, | |
| odb::Point sink, |
| const odb::Point sink, | ||
| std::vector<std::unique_ptr<ScanCell>>& cells, | ||
| utl::Logger* logger, | ||
| size_t max_iters = 30); |
There was a problem hiding this comment.
warning: no header providing "size_t" is directly included [misc-include-cleaner]
src/dft/src/architect/Opt.hh:5:
- #include <memory>
+ #include <cstddef>
+ #include <memory>| = OptimizeScanWirelengthNN(falling, logger_); | ||
| auto rising_wire_length = OptimizeScanWirelengthNN(rising, logger_); | ||
| int64_t distance_between_falling_and_rising = 0; | ||
| if (rising.size() && falling.size()) { |
There was a problem hiding this comment.
warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty]
| if (rising.size() && falling.size()) { | |
| if (!rising.empty() && falling.size()) { |
Additional context
/usr/include/c++/13/bits/stl_vector.h:1087: method 'vector'::empty() defined here
empty() const _GLIBCXX_NOEXCEPT
^| = OptimizeScanWirelengthNN(falling, logger_); | ||
| auto rising_wire_length = OptimizeScanWirelengthNN(rising, logger_); | ||
| int64_t distance_between_falling_and_rising = 0; | ||
| if (rising.size() && falling.size()) { |
There was a problem hiding this comment.
warning: the 'empty' method should be used to check for emptiness instead of 'size' [readability-container-size-empty]
| if (rising.size() && falling.size()) { | |
| if (rising.size() && !falling.empty()) { |
Additional context
/usr/include/c++/13/bits/stl_vector.h:1087: method 'vector'::empty() defined here
empty() const _GLIBCXX_NOEXCEPT
^|
Anyone have advice on how I should make dbDft.reset "generated" |
|
@fgaray review if you have time. |
| build_linux | ||
| build_mac | ||
| test/results | ||
| /sandbox |
| return total_wire_length; | ||
| } | ||
|
|
||
| int64_t ScanChain::estimateInternalTWL() |
There was a problem hiding this comment.
TWL = total wire length?? A more descriptive name would be helpful
| return 0; | ||
| } | ||
|
|
||
| size_t N = cells.size() + 2; |
There was a problem hiding this comment.
use snake_case for variables, a more meaningful name, and make it const
|
|
||
| int64_t best_score = score; | ||
|
|
||
| // Already "optimal" (unplaced) |
There was a problem hiding this comment.
can't be unplaced as we already returned on unplaced above.
| const odb::Point sink, | ||
| std::vector<std::unique_ptr<ScanCell>>& cells, | ||
| utl::Logger* logger, | ||
| size_t max_iters) |
|
Will implement all suggestions when I get a moment 🫡 |
Summary
Type of Change
Impact
Measurable reduction in routing time versus the unoptimized scan chains.
Verification
./etc/Build.sh).