Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/gui/include/gui/gui.h
Original file line number Diff line number Diff line change
Expand Up @@ -959,6 +959,8 @@ class Gui
Renderer::Setting getHeatMapSetting(const std::string& name,
const std::string& option);
void dumpHeatMap(const std::string& name, const std::string& file);
// Add an external heat map from a CSV file; returns short name for lookup.
std::string loadExternalHeatMap(const std::string& file_path);

void setMainWindowTitle(const std::string& title);
std::string getMainWindowTitle();
Expand Down
35 changes: 35 additions & 0 deletions src/gui/include/gui/heatMap.h
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#include "boost/multi_array.hpp"
#include "gui/gui.h"
#include "odb/db.h"
#include "odb/dbTransform.h"

namespace odb {
class dbBlock;
Expand Down Expand Up @@ -370,6 +371,40 @@ class PowerDensityDataSource : public RealValueHeatMapDataSource
sta::Scene* getScene() const;
};

// Data source that loads heatmap data from a CSV file.
class ExternalHeatMapDataSource : public HeatMapDataSource
{
public:
struct Entry
{
double x0, y0, x1, y1, value;
};

ExternalHeatMapDataSource(utl::Logger* logger,
const std::string& name,
const std::string& short_name,
std::vector<Entry> data);

void setTransform(const odb::dbTransform& transform)
{
transform_ = transform;
}

protected:
bool populateMap() override;
void combineMapData(bool base_has_value,
double& base,
double new_data,
double data_area,
double intersection_area,
double rect_area) override;
odb::Rect getBounds() const override;

private:
std::vector<Entry> data_entries_;
odb::dbTransform transform_;
};

class HeatMapSourceRegistration
{
public:
Expand Down
85 changes: 85 additions & 0 deletions src/gui/src/gui.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
#include "displayControls.h"
#include "drcWidget.h"
#include "gif.h"
#include "gui/heatMap.h"
#include "heatMapGui.h"
#include "helpWidget.h"
#include "inspector.h"
Expand All @@ -48,10 +49,12 @@
#include "odb/dbObject.h"
#include "odb/dbShape.h"
#include "odb/geom.h"
#include "odb/unfoldedModel.h"
#include "ord/OpenRoad.hh"
#include "ruler.h"
#include "scriptWidget.h"
#include "timingWidget.h"
#include "utl/CsvParser.h"
#include "utl/Logger.h"
#include "utl/decode.h"
#include "utl/exception.h"
Expand Down Expand Up @@ -1516,6 +1519,88 @@ void Gui::init(odb::dbDatabase* db, sta::dbSta* sta, utl::Logger* logger)
}
}

std::string Gui::loadExternalHeatMap(const std::string& file_path)
{
// Parse CSV:
// row 0 = chiplet_name, heatmap_name;
// rows 1+ = x0,y0,x1,y1,value.
const auto csv_rows = utl::readCsv(file_path, logger_);
if (csv_rows.empty()) {
logger_->error(utl::GUI, 113, "No data in CSV file: {}", file_path);
}
if (csv_rows[0].size() != 2) {
logger_->error(utl::GUI,
114,
"Invalid CSV file: {} - expected 2 columns in first row; "
"(chiplet_name, heatmap_name), got {}",
file_path,
csv_rows[0].size());
}
const std::string chiplet_name = csv_rows[0][0];
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

why is chiplet_name here? that seems somewhat specific

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

To support 3D designs. When you have multiple chiplets in a system, the chiplet name is needed to apply the correct transformation per chiplet.

const std::string heatmap_name = csv_rows[0][1];
std::vector<ExternalHeatMapDataSource::Entry> data;
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

Reserving space in the data vector can improve performance by avoiding multiple reallocations, especially for large CSV files.

  std::vector<ExternalHeatMapDataSource::Entry> data;
  data.reserve(csv_rows.size() - 1);

for (size_t i = 1; i < csv_rows.size(); ++i) {
const auto& row = csv_rows[i];
if (row.size() != 5) {
logger_->error(
utl::GUI,
115,
"Invalid CSV file: {} - expected 5 columns in row {}, got {}",
file_path,
i,
row.size());
}
try {
data.push_back({std::stod(row[0]),
std::stod(row[1]),
std::stod(row[2]),
std::stod(row[3]),
std::stod(row[4])});
} catch (const std::exception& e) {
logger_->error(utl::GUI,
116,
"Invalid CSV file: {} - exception in row {}: {}",
file_path,
i,
std::string(e.what()));
}
}
odb::dbChip* chip = nullptr;
odb::dbTransform transform;

if (db_->getUnfoldedModel() != nullptr) {
auto* unfolded_chip
= db_->getUnfoldedModel()->findUnfoldedChip(chiplet_name);
if (unfolded_chip != nullptr) {
chip = unfolded_chip->chip_inst_path.back()->getMasterChip();
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.

high

Accessing unfolded_chip->chip_inst_path.back() without checking if the path is empty can lead to a crash if chiplet_name refers to the top-level chip in an unfolded model. Hierarchical designs typically have an empty path for the root chip.

      if (unfolded_chip->chip_inst_path.empty()) {
        chip = db_->getChip();
      } else {
        chip = unfolded_chip->chip_inst_path.back()->getMasterChip();
      }

transform = unfolded_chip->transform;
} else {
logger_->error(utl::GUI,
117,
"Chiplet {} not found in the loaded design",
chiplet_name);
}
} else {
if (db_->getChip() == nullptr
|| db_->getChip()->getName() != chiplet_name) {
logger_->error(utl::GUI,
118,
"Chiplet {} not found in the loaded design",
chiplet_name);
}
chip = db_->getChip();
}
std::string short_name
= "External_" + std::to_string(owned_heat_maps_.size() + 1);
auto source = std::make_shared<ExternalHeatMapDataSource>(
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 "gui::ExternalHeatMapDataSource" is directly included [misc-include-cleaner]

  auto source = std::make_shared<ExternalHeatMapDataSource>(
                                 ^

logger_, heatmap_name, short_name, std::move(data));
source->setChip(chip);
source->setTransform(transform);
registerHeatMap(source.get());
owned_heat_maps_.push_back(std::move(source));
return short_name;
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: constness of 'short_name' prevents automatic move [performance-no-automatic-move]

  return short_name;
         ^

}

void Gui::selectHelp(const std::string& item)
{
if (!enabled()) {
Expand Down
9 changes: 9 additions & 0 deletions src/gui/src/gui.i
Original file line number Diff line number Diff line change
Expand Up @@ -725,6 +725,15 @@ void dump_heatmap(const std::string& name, const std::string& file)
gui->dumpHeatMap(name, file);
}

std::string load_external_heatmap(const std::string& file_path)
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

no tcl command, maybe call this load_heatmap (unless it's specific to the chiplet, in which case that should be clear from the name

{
if (!check_gui("load_external_heatmap")) {
return "";
}
auto gui = gui::Gui::get();
return gui->loadExternalHeatMap(file_path);
}

void timing_cone(odb::dbITerm* iterm, bool fanin, bool fanout)
{
if (!check_gui("timing_cone")) {
Expand Down
1 change: 1 addition & 0 deletions src/gui/src/heatMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ class HeatMapRenderer : public Renderer

if (first_paint_) {
first_paint_ = false;
datasource_.ensureMap();
datasource_.onShow();
}

Expand Down
49 changes: 49 additions & 0 deletions src/gui/src/heatMapCore.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
#include "heatMapPinDensity.h"
#include "heatMapPlacementDensity.h"
#include "odb/db.h"
#include "odb/dbTransform.h"
#include "odb/unfoldedModel.h"
#include "sta/PowerClass.hh"
#include "utl/Logger.h"

Expand Down Expand Up @@ -1221,6 +1223,53 @@ sta::Scene* PowerDensityDataSource::getScene() const
return nullptr;
}

ExternalHeatMapDataSource::ExternalHeatMapDataSource(
utl::Logger* logger,
const std::string& name,
const std::string& short_name,
std::vector<Entry> data)
: HeatMapDataSource(logger, name, short_name, "ExternalHeatMap"),
data_entries_(std::move(data))
{
}

bool ExternalHeatMapDataSource::populateMap()
{
if (getChip() == nullptr || data_entries_.empty()) {
return false;
}
const double dbu_per_micron = getDbuPerMicron();
for (const auto& entry : data_entries_) {
const int x0 = static_cast<int>(std::round(entry.x0 * dbu_per_micron));
const int y0 = static_cast<int>(std::round(entry.y0 * dbu_per_micron));
const int x1 = static_cast<int>(std::round(entry.x1 * dbu_per_micron));
const int y1 = static_cast<int>(std::round(entry.y1 * dbu_per_micron));
odb::Rect rect(
std::min(x0, x1), std::min(y0, y1), std::max(x0, x1), std::max(y0, y1));
transform_.apply(rect);
addToMap(rect, entry.value);
}
return true;
}

odb::Rect ExternalHeatMapDataSource::getBounds() const
{
odb::Rect bounds = HeatMapDataSource::getBounds();
transform_.apply(bounds);
return bounds;
}

void ExternalHeatMapDataSource::combineMapData(
bool base_has_value,
double& base,
const double new_data,
const double /* data_area */,
const double /* intersection_area */,
const double /* rect_area */)
{
base = base_has_value ? std::max(base, new_data) : new_data;
}

HeatMapSourceRegistration::HeatMapSourceRegistration(std::string name,
std::string short_name,
std::string settings_group,
Expand Down
5 changes: 5 additions & 0 deletions src/gui/src/stub.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,11 @@ void Gui::gifAddFrame(std::optional<int> key,
{
}

std::string Gui::loadExternalHeatMap(const std::string& /* file_path */)
{
return "";
}

void Gui::deleteLabel(const std::string& name)
{
}
Expand Down
31 changes: 31 additions & 0 deletions src/gui/src/stub_heatMap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -247,4 +247,35 @@ sta::Scene* PowerDensityDataSource::getScene() const
return nullptr;
}

//////////

ExternalHeatMapDataSource::ExternalHeatMapDataSource(
utl::Logger* /* logger */,
const std::string& /* name */,
const std::string& unique_short_name,
std::vector<Entry> /* data */)
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: the parameter #4 is copied for each invocation but only used as a const reference; consider making it a const reference [performance-unnecessary-value-param]

Suggested change
std::vector<Entry> /* data */)
const std::vector<Entry>& /* data */)

src/gui/include/gui/heatMap.h:385:

-                             std::vector<Entry> data);
+                             const std::vector<Entry>& data);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@maliberty I am not sure how I can bypass this. Clearly this suggestion is correct for the stub implementation because we don't utilize the vector in any way. However, it is better in the main implementation to pass by value as the constructor is intended to be the sink endpoint for that parameter.

: HeatMapDataSource(nullptr, "", unique_short_name, "")
{
}

bool ExternalHeatMapDataSource::populateMap()
{
return false;
}

odb::Rect ExternalHeatMapDataSource::getBounds() const
{
return HeatMapDataSource::getBounds();
}

void ExternalHeatMapDataSource::combineMapData(
bool /* base_has_value */,
double& /* base */,
const double /* new_data */,
const double /* data_area */,
const double /* intersection_area */,
const double /* rect_area */)
{
}

} // namespace gui
1 change: 1 addition & 0 deletions src/utl/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ add_library(utl_lib
src/CallBack.cpp
src/Metrics.cpp
src/CFileUtils.cpp
src/CsvParser.cpp
src/ScopedTemporaryFile.cpp
src/SuppressStdout.cpp
src/Logger.cpp
Expand Down
20 changes: 20 additions & 0 deletions src/utl/include/utl/CsvParser.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2026, The OpenROAD Authors

#pragma once

#include <string>
#include <vector>

namespace utl {

class Logger;

// Reads a delimiter-separated file and returns all non-empty rows.
// Each row is a vector of trimmed cell strings.
// Logs an error and returns an empty result if the file cannot be opened.
std::vector<std::vector<std::string>> readCsv(const std::string& file_path,
Logger* logger,
char delimiter = ',');

} // namespace utl
57 changes: 57 additions & 0 deletions src/utl/src/CsvParser.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2026, The OpenROAD Authors

#include "utl/CsvParser.h"

#include <fstream>
#include <sstream>
#include <string>
#include <utility>
#include <vector>

#include "utl/Logger.h"

namespace utl {

namespace {

static std::string trim(const std::string& s)
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 "std::string" is directly included [misc-include-cleaner]

src/utl/src/CsvParser.cpp:7:

+ #include <string>

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Use boost::trim ?

{
const auto start = s.find_first_not_of(" \t\r\n");
const auto end = s.find_last_not_of(" \t\r\n");
if (start == std::string::npos || end == std::string::npos) {
return "";
}
return s.substr(start, end - start + 1);
}

} // namespace

std::vector<std::vector<std::string>> readCsv(const std::string& file_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 "std::vector" is directly included [misc-include-cleaner]

src/utl/src/CsvParser.cpp:7:

+ #include <vector>

Logger* logger,
char delimiter)
{
std::ifstream in(file_path);
if (!in.is_open()) {
logger->error(utl::UTL, 201, "Unable to open {}", file_path);
return {};
}

std::vector<std::vector<std::string>> rows;
std::string line;
while (std::getline(in, line)) {
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 "std::getline" is directly included [misc-include-cleaner]

  while (std::getline(in, line)) {
              ^

if (line.empty()) {
continue;
}
std::vector<std::string> cells;
std::stringstream ss(line);
std::string cell;
while (std::getline(ss, cell, delimiter)) {
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 "std::getline" is directly included [misc-include-cleaner]

    while (std::getline(ss, cell, delimiter)) {
                ^

cells.push_back(trim(cell));
}
Comment on lines +49 to +51
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 CSV parsing logic using std::getline with a delimiter is very basic and does not handle standard CSV features like quoted fields (which may contain the delimiter or newlines) or escaped characters. While sufficient for simple numeric data, consider using a more robust parser if this utility is intended for general use within the utl namespace.

rows.push_back(std::move(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.

warning: no header providing "std::move" is directly included [misc-include-cleaner]

src/utl/src/CsvParser.cpp:7:

+ #include <utility>

}
return rows;
}

} // namespace utl
Loading