diff --git a/include/sta/ReportFieldExtension.hh b/include/sta/ReportFieldExtension.hh
new file mode 100644
index 00000000..159315c2
--- /dev/null
+++ b/include/sta/ReportFieldExtension.hh
@@ -0,0 +1,57 @@
+// OpenSTA, Static Timing Analyzer
+// Copyright (c) 2026, Parallax Software, Inc.
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+//
+// You should have received a copy of the GNU General Public License
+// along with this program. If not, see .
+//
+// The origin of this software must not be misrepresented; you must not
+// claim that you wrote the original software.
+//
+// Altered source versions must be plainly marked as such, and must not be
+// misrepresented as being the original software.
+//
+// This notice may not be removed or altered from any source distribution.
+
+#pragma once
+
+#include
+#include
+#include
+
+#include "NetworkClass.hh"
+#include "SearchClass.hh"
+
+namespace sta {
+
+// External producers register extensions to add custom columns to
+// report_checks output. ReportPath takes ownership of the registered
+// pointer and deletes it at shutdown.
+class ReportFieldExtension
+{
+ public:
+ virtual ~ReportFieldExtension() = default;
+
+ // Identifier matched against `-fields` tokens.
+ virtual std::string_view name() const = 0;
+ // Column header.
+ virtual std::string_view title() const = 0;
+ // Column width in characters.
+ virtual size_t width() const = 0;
+
+ // Per-row value. Return "" to leave the cell blank.
+ virtual std::string value(const Path *path,
+ const Pin *pin,
+ const Instance *inst) const = 0;
+};
+
+} // namespace sta
diff --git a/include/sta/Sta.hh b/include/sta/Sta.hh
index 5237ec9a..a5f026e7 100644
--- a/include/sta/Sta.hh
+++ b/include/sta/Sta.hh
@@ -71,6 +71,7 @@ class SearchPred;
class Scene;
class ClkSkews;
class ReportField;
+class ReportFieldExtension;
class EquivCells;
class StaSimObserver;
class GraphLoop;
@@ -989,7 +990,8 @@ public:
bool report_slew,
bool report_fanout,
bool report_variation,
- bool report_src_attr);
+ bool report_src_attr,
+ const StringSeq &extension_names);
ReportField *findReportPathField(std::string_view name);
void setReportPathDigits(int digits);
void setReportPathNoSplit(bool no_split);
@@ -997,6 +999,9 @@ public:
void reportPathEnds(PathEndSeq *ends);
ReportPath *reportPath() { return report_path_; }
void reportPath(const Path *path);
+ // Register an extension that adds a custom column to report_checks.
+ // Sta takes ownership and deletes at destruction.
+ void registerReportFieldExtension(ReportFieldExtension *ext);
// Report clk skews for clks.
void reportClkSkew(ConstClockSeq &clks,
diff --git a/search/ReportPath.cc b/search/ReportPath.cc
index 50d05574..06513108 100644
--- a/search/ReportPath.cc
+++ b/search/ReportPath.cc
@@ -53,6 +53,7 @@
#include "PortDelay.hh"
#include "PortDirection.hh"
#include "Report.hh"
+#include "ReportFieldExtension.hh"
#include "Scene.hh"
#include "Sdc.hh"
#include "Search.hh"
@@ -96,7 +97,9 @@ ReportField::ReportField(std::string_view name,
}
ReportField::~ReportField()
-= default;
+{
+ delete extension_;
+}
void
ReportField::setProperties(std::string_view title,
@@ -128,7 +131,8 @@ ReportPath::ReportPath(StaState *sta) :
{
makeFields();
setDigits(2);
- setReportFields(false, false, false, false, false, false, false, false);
+ setReportFields(false, false, false, false, false, false, false, false,
+ StringSeq{});
}
ReportPath::~ReportPath()
@@ -226,7 +230,8 @@ ReportPath::setReportFields(bool report_input_pin,
bool report_slew,
bool report_fanout,
bool report_variation,
- bool report_src_attr)
+ bool report_src_attr,
+ const StringSeq &extension_names)
{
report_input_pin_ = report_input_pin;
report_hier_pins_ = report_hier_pins;
@@ -239,6 +244,62 @@ ReportPath::setReportFields(bool report_input_pin,
field_src_attr_->setEnabled(report_src_attr);
// for debug
field_case_->setEnabled(false);
+
+ // Reset extension-backed fields: disable, drop slot assignment.
+ for (ReportField *field : fields_) {
+ if (field->extension() != nullptr) {
+ field->setEnabled(false);
+ field->setExtensionIndex(-1);
+ }
+ }
+ // Enable each requested extension name and assign a buffer slot.
+ int slot = 0;
+ for (const std::string &name : extension_names) {
+ ReportField *field = findField(name);
+ if (field == nullptr || field->extension() == nullptr) {
+ report_->warn(168, "no field extension registered for '{}'.", name);
+ continue;
+ }
+ field->setEnabled(true);
+ field->setExtensionIndex(slot++);
+ }
+ enabled_extension_count_ = slot;
+}
+
+void
+ReportPath::registerFieldExtension(ReportFieldExtension *ext)
+{
+ if (findField(ext->name()) != nullptr) {
+ // Already registered (e.g. re-init in a long-lived Sta). Silently dedupe.
+ delete ext;
+ return;
+ }
+ ReportField *field = makeField(ext->name(),
+ ext->title(),
+ static_cast(ext->width()),
+ true,
+ nullptr,
+ false);
+ field->setExtension(ext);
+}
+
+void
+ReportPath::collectExtensionValues(const Path *path,
+ const Pin *pin,
+ const Instance *inst,
+ std::vector &values) const
+{
+ if (enabled_extension_count_ == 0) {
+ values.clear();
+ return;
+ }
+ values.resize(enabled_extension_count_);
+ for (const ReportField *field : fields_) {
+ ReportFieldExtension *ext = field->extension();
+ if (ext == nullptr || field->extensionIndex() < 0)
+ continue;
+ values[field->extensionIndex()] = ext->value(path, pin, inst);
+ }
}
void
@@ -2451,11 +2512,13 @@ ReportPath::reportPathLine(const Path *path,
std::string src_attr;
if (inst)
src_attr = network_->getAttribute(inst, "src");
+ collectExtensionValues(path, pin, inst, extension_values_buf_);
// Don't show capacitance field for input pins.
if (is_driver && field_capacitance_->enabled())
cap = graph_delay_calc_->loadCap(pin, rf, scene, min_max);
reportLine(what, cap, slew, field_blank_, incr, field_blank_,
- time, false, early_late, rf, src_attr, line_case);
+ time, false, early_late, rf, src_attr, line_case,
+ extension_values_buf_);
}
void
@@ -2733,6 +2796,7 @@ ReportPath::reportPath6(const Path *path,
std::string src_attr;
if (inst)
src_attr = network_->getAttribute(inst, "src");
+ collectExtensionValues(path1, pin, inst, extension_values_buf_);
// Always show the search start point (register clk pin).
// Skip reporting the clk tree unless it is requested.
if (is_clk_start
@@ -2825,7 +2889,7 @@ ReportPath::reportPath6(const Path *path,
const std::string what = descriptionField(vertex);
reportLine(what, cap, slew, fanout,
incr, field_blank_, time, false, min_max, rf, src_attr,
- line_case);
+ line_case, extension_values_buf_);
if (report_net_) {
const std::string what2 = descriptionNet(pin);
@@ -2844,7 +2908,7 @@ ReportPath::reportPath6(const Path *path,
const std::string what = descriptionField(vertex);
reportLine(what, field_blank_, slew, field_blank_,
incr, field_blank_, time, false, min_max, rf, src_attr,
- line_case);
+ line_case, extension_values_buf_);
prev_time = time;
}
}
@@ -3182,7 +3246,8 @@ ReportPath::reportLine(std::string_view what,
const EarlyLate *early_late,
const RiseFall *rf,
std::string_view src_attr,
- std::string_view line_case) const
+ std::string_view line_case,
+ std::span extension_values) const
{
std::string line;
size_t field_index = 0;
@@ -3234,6 +3299,13 @@ ReportPath::reportLine(std::string_view what,
}
else if (field == field_case_)
line += line_case;
+ else if (field->extensionIndex() >= 0) {
+ size_t idx = static_cast(field->extensionIndex());
+ if (idx < extension_values.size() && !extension_values[idx].empty())
+ reportField(extension_values[idx], field, line);
+ else
+ reportFieldBlank(field, line);
+ }
first_field = false;
}
diff --git a/search/ReportPath.hh b/search/ReportPath.hh
index b8ce652d..1acb60c7 100644
--- a/search/ReportPath.hh
+++ b/search/ReportPath.hh
@@ -25,6 +25,7 @@
#pragma once
#include
+#include
#include
#include
#include
@@ -50,6 +51,7 @@ namespace sta {
class Scene;
class PathExpanded;
class ReportField;
+class ReportFieldExtension;
using ReportFieldSeq = std::vector;
@@ -68,7 +70,13 @@ public:
bool report_slew,
bool report_fanout,
bool report_variation,
- bool report_src_attr);
+ bool report_src_attr,
+ const StringSeq &extension_names);
+
+ // Register an extension that adds a custom column to report_checks.
+ // ReportPath takes ownership and deletes at destruction.
+ void registerFieldExtension(ReportFieldExtension *ext);
+
int digits() const { return digits_; }
void setDigits(int digits);
void setNoSplit(bool no_split);
@@ -280,6 +288,10 @@ protected:
const Delay &incr,
const Arrival &time,
std::string_view line_case) const;
+ void collectExtensionValues(const Path *path,
+ const Pin *pin,
+ const Instance *inst,
+ std::vector &values) const;
void reportCommonClkPessimism(const PathEnd *end,
Arrival &clk_arrival) const ;
void reportClkUncertainty(const PathEnd *end,
@@ -380,7 +392,8 @@ protected:
const EarlyLate *early_late,
const RiseFall *rf,
std::string_view src_attr,
- std::string_view line_case) const;
+ std::string_view line_case,
+ std::span extension_values = {}) const;
void reportLineTotal(std::string_view what,
const Delay &incr,
const EarlyLate *early_late) const;
@@ -502,6 +515,8 @@ protected:
ReportField *field_src_attr_;
ReportField *field_edge_;
ReportField *field_case_;
+ mutable std::vector extension_values_buf_;
+ int enabled_extension_count_{0};
std::string plus_zero_;
std::string minus_zero_;
@@ -533,6 +548,11 @@ public:
const std::string &blank() const { return blank_; }
void setEnabled(bool enabled);
bool enabled() const { return enabled_; }
+ int extensionIndex() const { return extension_index_; }
+ void setExtensionIndex(int index) { extension_index_ = index; }
+ ReportFieldExtension *extension() const { return extension_; }
+ // ReportField takes ownership of the extension and deletes it.
+ void setExtension(ReportFieldExtension *ext) { extension_ = ext; }
protected:
std::string name_;
@@ -542,6 +562,8 @@ protected:
Unit *unit_;
bool enabled_;
std::string blank_;
+ int extension_index_{-1};
+ ReportFieldExtension *extension_{nullptr};
};
} // namespace sta
diff --git a/search/Search.i b/search/Search.i
index c378a5e8..df1b55c8 100644
--- a/search/Search.i
+++ b/search/Search.i
@@ -414,7 +414,8 @@ set_report_path_fields(bool report_input_pin,
bool report_slew,
bool report_fanout,
bool report_variation,
- bool report_src_attr)
+ bool report_src_attr,
+ StringSeq extension_names)
{
Sta::sta()->setReportPathFields(report_input_pin,
report_hier_pins,
@@ -423,7 +424,8 @@ set_report_path_fields(bool report_input_pin,
report_slew,
report_fanout,
report_variation,
- report_src_attr);
+ report_src_attr,
+ extension_names);
}
void
diff --git a/search/Search.tcl b/search/Search.tcl
index 11394292..e4dd296a 100644
--- a/search/Search.tcl
+++ b/search/Search.tcl
@@ -336,7 +336,7 @@ define_cmd_args "report_checks" \
[-sort_by_slack]\
[-path_group group_name]\
[-format full|full_clock|full_clock_expanded|short|end|slack_only|summary|json]\
- [-fields capacitance|slew|fanout|input_pin|net|src_attr]\
+ [-fields capacitance|slew|fanout|input_pin|net|src_attr|...]\
[-digits digits]\
[-no_line_splits]\
[> filename] [>> filename]}
@@ -724,6 +724,7 @@ proc parse_report_path_options { cmd args_var default_format
set report_fanout 0
set report_variation 0
set report_src_attr 0
+ set extension_names {}
if { [info exists path_options(-fields)] } {
foreach field $path_options(-fields) {
if { [string match "input*" $field] } {
@@ -743,13 +744,14 @@ proc parse_report_path_options { cmd args_var default_format
} elseif { [string match "src*" $field] } {
set report_src_attr 1
} else {
- sta_warn 168 "unknown field $field."
+ lappend extension_names $field
}
}
}
set_report_path_fields $report_input_pin $report_hier_pins $report_net \
- $report_cap $report_slew $report_fanout $report_variation $report_src_attr
+ $report_cap $report_slew $report_fanout $report_variation $report_src_attr \
+ $extension_names
set_report_path_no_split [info exists path_options(-no_line_splits)]
}
diff --git a/search/Sta.cc b/search/Sta.cc
index c5a40139..27798c26 100644
--- a/search/Sta.cc
+++ b/search/Sta.cc
@@ -2823,11 +2823,19 @@ Sta::setReportPathFields(bool report_input_pin,
bool report_slew,
bool report_fanout,
bool report_variation,
- bool report_src_attr)
+ bool report_src_attr,
+ const StringSeq &extension_names)
{
report_path_->setReportFields(report_input_pin, report_hier_pins, report_net,
report_cap, report_slew, report_fanout,
- report_variation, report_src_attr);
+ report_variation, report_src_attr,
+ extension_names);
+}
+
+void
+Sta::registerReportFieldExtension(ReportFieldExtension *ext)
+{
+ report_path_->registerFieldExtension(ext);
}
ReportField *