diff --git a/src/gui/include/gui/gui.h b/src/gui/include/gui/gui.h index 50cacc1ed55..1848b863ad9 100644 --- a/src/gui/include/gui/gui.h +++ b/src/gui/include/gui/gui.h @@ -1023,7 +1023,7 @@ class Gui void registerHeatMap(HeatMapDataSource* heatmap); void unregisterHeatMap(HeatMapDataSource* heatmap); - const std::set& getHeatMaps() { return heat_maps_; } + const std::set& getHeatMaps(); HeatMapDataSource* getHeatMap(const std::string& name); // returns the Gui singleton @@ -1060,6 +1060,7 @@ class Gui private: Gui(); + void syncHeatMapChips(); void registerDescriptor(const std::type_info& type, const Descriptor* descriptor); diff --git a/src/gui/src/gui.cpp b/src/gui/src/gui.cpp index 89f49e336b2..8fafa360cf3 100644 --- a/src/gui/src/gui.cpp +++ b/src/gui/src/gui.cpp @@ -1016,8 +1016,32 @@ void Gui::unregisterHeatMap(HeatMapDataSource* heatmap) heat_maps_.erase(heatmap); } +void Gui::syncHeatMapChips() +{ + if (hasUI() || db_ == nullptr) { + return; + } + + // Console and headless sessions do not receive MainWindow::setBlock(). + auto* chip = db_->getChip(); + for (auto* heat_map : heat_maps_) { + if (heat_map->getChip() != chip) { + heat_map->setChip(chip); + heat_map->destroyMap(); + } + } +} + +const std::set& Gui::getHeatMaps() +{ + syncHeatMapChips(); + return heat_maps_; +} + HeatMapDataSource* Gui::getHeatMap(const std::string& name) { + syncHeatMapChips(); + HeatMapDataSource* source = nullptr; for (auto* heat_map : heat_maps_) { diff --git a/src/gui/test/BUILD b/src/gui/test/BUILD index d1285f47476..f5438d67ea8 100644 --- a/src/gui/test/BUILD +++ b/src/gui/test/BUILD @@ -1,3 +1,4 @@ +load("@rules_cc//cc:cc_test.bzl", "cc_test") load("@rules_python//python:defs.bzl", "py_test") # SPDX-License-Identifier: BSD-3-Clause @@ -11,6 +12,10 @@ ALL_TESTS = [ "supported", ] +# dump_heatmap_headless requires the full Qt GUI implementation. The default +# Bazel openroad binary links the stub GUI, so the equivalent Bazel regression is +# the C++ heatmap_sync_test against //src/gui:gui_qt_headless below. + filegroup( name = "test_resources", # overly broad glob, could be refined later, but @@ -54,3 +59,15 @@ doc_check_test( ], visibility = ["//visibility:public"], ) + +cc_test( + name = "heatmap_sync_test", + srcs = ["heatmap_sync_test.cpp"], + deps = [ + "//src/gui:gui_qt_headless", + "//src/odb", + "//src/utl", + "@googletest//:gtest", + "@googletest//:gtest_main", + ], +) diff --git a/src/gui/test/CMakeLists.txt b/src/gui/test/CMakeLists.txt index f6cfbaa34f1..d3ab3cd635f 100644 --- a/src/gui/test/CMakeLists.txt +++ b/src/gui/test/CMakeLists.txt @@ -1,6 +1,21 @@ +set(GUI_TESTS supported) + +if (Qt5_FOUND AND BUILD_GUI) + list(APPEND GUI_TESTS dump_heatmap_headless) + + add_executable(gui_heatmap_sync_test heatmap_sync_test.cpp) + target_link_libraries(gui_heatmap_sync_test + gui + odb + utl_lib + GTest::gtest + GTest::gtest_main + ) + gtest_discover_tests(gui_heatmap_sync_test) +endif() + or_integration_tests( "gui" TESTS - supported + ${GUI_TESTS} ) - diff --git a/src/gui/test/dump_heatmap_headless.ok b/src/gui/test/dump_heatmap_headless.ok new file mode 100644 index 00000000000..656dfc57d55 --- /dev/null +++ b/src/gui/test/dump_heatmap_headless.ok @@ -0,0 +1 @@ +Pass diff --git a/src/gui/test/dump_heatmap_headless.tcl b/src/gui/test/dump_heatmap_headless.tcl new file mode 100644 index 00000000000..5b4ed73dcdb --- /dev/null +++ b/src/gui/test/dump_heatmap_headless.tcl @@ -0,0 +1,35 @@ +set script_dir [file dirname [info script]] +set openroad_test_dir [file normalize [file join $script_dir .. .. .. test]] + +source [file join $openroad_test_dir helpers.tcl] + +if { ![gui::supported] } { + puts "Fail" + exit 1 +} + +suppress_message ODB 128 +suppress_message ODB 130 +suppress_message ODB 131 +suppress_message ODB 132 +suppress_message ODB 133 +suppress_message ODB 227 + +read_lef [file join $openroad_test_dir Nangate45 Nangate45.lef] +read_def [file join $openroad_test_dir gcd_nangate45.def] + +set heatmap_file [make_result_file dump_heatmap_headless.csv] +gui::dump_heatmap Placement $heatmap_file + +set heatmap [open $heatmap_file r] +set contents [read $heatmap] +close $heatmap + +if { + [string first "value (%)" $contents] >= 0 + && [regexp -line {^[0-9.-]+,[0-9.-]+,[0-9.-]+,[0-9.-]+,} $contents] +} { + puts "Pass" +} else { + puts "Fail" +} diff --git a/src/gui/test/heatmap_sync_test.cpp b/src/gui/test/heatmap_sync_test.cpp new file mode 100644 index 00000000000..48c79af6ea1 --- /dev/null +++ b/src/gui/test/heatmap_sync_test.cpp @@ -0,0 +1,43 @@ +// SPDX-License-Identifier: BSD-3-Clause +// Copyright (c) 2026, The OpenROAD Authors + +#include "gtest/gtest.h" +#include "gui/gui.h" +#include "gui/heatMap.h" +#include "odb/db.h" +#include "utl/Logger.h" + +namespace gui { +namespace { + +TEST(GuiHeatMapTest, HeadlessLookupSyncsSourcesToCurrentChip) +{ + utl::Logger logger; + odb::dbDatabase* db = odb::dbDatabase::create(); + ASSERT_NE(db, nullptr); + + Gui* gui = Gui::get(); + gui->init(db, nullptr, &logger); + + HeatMapDataSource* placement = gui->getHeatMap("Placement"); + ASSERT_NE(placement, nullptr); + EXPECT_EQ(nullptr, placement->getChip()); + + odb::dbTech* tech = odb::dbTech::create(db, "tech"); + ASSERT_NE(tech, nullptr); + odb::dbChip* chip = odb::dbChip::create(db, tech); + ASSERT_NE(chip, nullptr); + odb::dbBlock::create(chip, "top"); + + EXPECT_EQ(chip, db->getChip()); + EXPECT_EQ(chip, gui->getHeatMap("Placement")->getChip()); + + const auto& heat_maps = gui->getHeatMaps(); + ASSERT_FALSE(heat_maps.empty()); + for (auto* heat_map : heat_maps) { + EXPECT_EQ(chip, heat_map->getChip()); + } +} + +} // namespace +} // namespace gui