Fix WebGUI timing path issue in a 3DBlox design - #11198
Fix WebGUI timing path issue in a 3DBlox design#11198jorge-ferreira-pii wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces support for multi-chiplet designs in the web visualization tools by updating pin resolution and timing path shape collection to handle multiple chiplets and apply coordinate transformations. The code review identified critical issues where standard, non-chiplet designs are broken because of empty chiplet vectors in request_handler.cpp and web.cpp, which requires falling back to the top-level block. Additionally, the reviewer recommended optimizing pin resolution by returning early when a prefixed pin is not found in its corresponding chiplet, and warned about potential crashes in timing_report.cpp due to the removal of safety checks on empty data_nodes vectors.
| const std::vector<ChipletNode>& chiplets = gen_->chiplets(); | ||
| collectTimingPathShapes( | ||
| chiplets, paths[path_index], new_rects, new_lines); | ||
|
|
||
| const std::string pin_name | ||
| = jsonOr<std::string>(req.json, "pin_name", ""); | ||
| if (!pin_name.empty()) { | ||
| static const Color kStageColor{.r = 255, .g = 255, .b = 0, .a = 180}; | ||
| auto [iterm, bterm] = resolvePin(block, pin_name); | ||
| auto [iterm, bterm, node] = resolvePin(chiplets, pin_name); |
There was a problem hiding this comment.
For standard (non-3DBlox) designs, gen_->chiplets() is empty. If chiplets is empty, calling collectTimingPathShapes(chiplets, ...) will not draw any shapes, and resolvePin(chiplets, ...) will fail to resolve any pins. This completely breaks timing path and pin highlighting for standard designs. We should check if chiplets is empty and fall back to using the single top-level block.
const std::vector<ChipletNode>& chiplets = gen_->chiplets();
if (!chiplets.empty()) {
collectTimingPathShapes(
chiplets, paths[path_index], new_rects, new_lines);
} else {
collectTimingPathShapes(
gen_->getBlock(), paths[path_index], new_rects, new_lines);
}
const std::string pin_name
= jsonOr<std::string>(req.json, "pin_name", "");
if (!pin_name.empty()) {
static const Color kStageColor{.r = 255, .g = 255, .b = 0, .a = 180};
odb::dbITerm* iterm = nullptr;
odb::dbBTerm* bterm = nullptr;
const ChipletNode* node = nullptr;
if (!chiplets.empty()) {
auto [i, b, n] = resolvePin(chiplets, pin_name);
iterm = i;
bterm = b;
node = n;
} else {
auto [i, b] = resolvePin(gen_->getBlock(), pin_name);
iterm = i;
bterm = b;
}| std::vector<ColoredRect> rects; | ||
| std::vector<FlightLine> lines; | ||
| collectTimingPathShapes(block, path, rects, lines); | ||
| collectTimingPathShapes(generator_->chiplets(), path, rects, lines); |
There was a problem hiding this comment.
Similar to the issue in request_handler.cpp, if generator_->chiplets() is empty (standard designs), calling collectTimingPathShapes with the empty chiplets vector will result in no timing path shapes being collected. We should fall back to using block when chiplets is empty.
if (!generator_->chiplets().empty()) {
collectTimingPathShapes(generator_->chiplets(), path, rects, lines);
} else {
collectTimingPathShapes(block, path, rects, lines);
}| if (!node.name.empty()) { | ||
| const std::string prefix = node.name + "/"; | ||
| if (pin_name.rfind(prefix, 0) == 0) { | ||
| const std::string local_name = pin_name.substr(prefix.length()); | ||
| if (odb::dbITerm* iterm = node.block->findITerm(local_name.c_str())) { | ||
| return {iterm, nullptr, &node}; | ||
| } | ||
| if (odb::dbBTerm* bterm = node.block->findBTerm(local_name.c_str())) { | ||
| return {nullptr, bterm, &node}; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
If the pin name starts with the chiplet's prefix but is not found within that chiplet's block, we can return early. Since chiplet names are unique, the pin cannot reside in any other chiplet, so continuing the loop or performing unscoped searches is redundant.
| if (!node.name.empty()) { | |
| const std::string prefix = node.name + "/"; | |
| if (pin_name.rfind(prefix, 0) == 0) { | |
| const std::string local_name = pin_name.substr(prefix.length()); | |
| if (odb::dbITerm* iterm = node.block->findITerm(local_name.c_str())) { | |
| return {iterm, nullptr, &node}; | |
| } | |
| if (odb::dbBTerm* bterm = node.block->findBTerm(local_name.c_str())) { | |
| return {nullptr, bterm, &node}; | |
| } | |
| } | |
| } | |
| if (!node.name.empty()) { | |
| const std::string prefix = node.name + "/"; | |
| if (pin_name.rfind(prefix, 0) == 0) { | |
| const std::string local_name = pin_name.substr(prefix.length()); | |
| if (odb::dbITerm* iterm = node.block->findITerm(local_name.c_str())) { | |
| return {iterm, nullptr, &node}; | |
| } | |
| if (odb::dbBTerm* bterm = node.block->findBTerm(local_name.c_str())) { | |
| return {nullptr, bterm, &node}; | |
| } | |
| return {nullptr, nullptr, nullptr}; | |
| } | |
| } |
| start_idx = 0; | ||
| } | ||
| summary.start_pin = summary.data_nodes[start_idx].pin_name; | ||
| if (!summary.data_nodes.empty()) { | ||
| summary.end_pin = summary.data_nodes.back().pin_name; | ||
| } | ||
| summary.end_pin = summary.data_nodes.back().pin_name; |
There was a problem hiding this comment.
If summary.data_nodes is empty, accessing summary.data_nodes[start_idx] and summary.data_nodes.back() will cause undefined behavior or crashes. It is safer to wrap both assignments in a check to ensure data_nodes is not empty.
start_idx = 0;
}
if (!summary.data_nodes.empty()) {
summary.start_pin = summary.data_nodes[start_idx].pin_name;
summary.end_pin = summary.data_nodes.back().pin_name;
}9fbb602 to
af50766
Compare
Signed-off-by: Jorge Ferreira <jorge.ferreira@precisioninno.com>
af50766 to
6728915
Compare
Fix #11169