FEATURE: Add `read_vcd -begin_time`/`-end_time` activity windowing (#466)

* Add read_vcd -begin_time/-end_time activity windowing.

Limit VCD transition and duty counting to an optional time window so
activity annotation can ignore regions outside the interval of interest.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Format VcdCount::setFilter parameters one per line.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address VCD begin/end review: VcdTime, sentinel, rename.

Use VcdTime and vcd_null_time instead of int64_t/-1, rename
VcdCount filter bounds to begin/end_time, rename the regression to
vcd_begin_end_time, and document read_vcd -begin_time/-end_time in
ChangeLog.txt.

Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: James Cherry <34749589+jjcherry56@users.noreply.github.com>
This commit is contained in:
Akash Levy 2026-07-29 08:44:51 -07:00 committed by GitHub
parent f29b6a43c6
commit 7fdc304e12
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
12 changed files with 317 additions and 23 deletions

View File

@ -4,6 +4,15 @@ OpenSTA Timing Analyzer Release Notes
This file summarizes user visible changes for each release. This file summarizes user visible changes for each release.
See ApiChangeLog.txt for changes to the STA api. See ApiChangeLog.txt for changes to the STA api.
2026/07/20
----------
The read_vcd command supports -begin_time / -end_time to limit
activity annotation to a VCD time window.
read_vcd [-scope scope] [-mode mode_name]
[-begin_time begin_time] [-end_time end_time] filename
2026/05/01 2026/05/01
---------- ----------

View File

@ -22,6 +22,8 @@
// //
// This notice may not be removed or altered from any source distribution. // This notice may not be removed or altered from any source distribution.
%include "stdint.i"
%{ %{
#include "power/Power.hh" #include "power/Power.hh"
@ -29,12 +31,16 @@
#include "Sdc.hh" #include "Sdc.hh"
#include "Sta.hh" #include "Sta.hh"
#include "power/SaifReader.hh" #include "power/SaifReader.hh"
#include "power/VcdParse.hh"
#include "power/VcdReader.hh" #include "power/VcdReader.hh"
using namespace sta; using namespace sta;
%} %}
// Match power/VcdParse.hh vcd_null_time for Tcl defaults.
%constant int64_t vcd_null_time = -1;
%inline %{ %inline %{
void void
@ -201,11 +207,13 @@ clock_min_period(const char *mode_name)
void void
read_vcd_file(const char *filename, read_vcd_file(const char *filename,
const char *scope, const char *scope,
const char *mode_name) const char *mode_name,
int64_t begin_time,
int64_t end_time)
{ {
Sta *sta = Sta::sta(); Sta *sta = Sta::sta();
sta->ensureLibLinked(); sta->ensureLibLinked();
readVcdActivities(filename, scope, mode_name, sta); readVcdActivities(filename, scope, mode_name, begin_time, end_time, sta);
} }
//////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////
@ -228,4 +236,12 @@ report_activity_annotation_cmd(bool report_unannotated,
report_annotated); report_annotated);
} }
void
clear_power()
{
Power *power = Sta::sta()->power();
power->clear();
}
%} // inline %} // inline

View File

@ -238,16 +238,18 @@ proc read_power_activities { args } {
set scope $keys(-scope) set scope $keys(-scope)
} }
sta_warn 305 "read_power_activities is deprecated. Use read_vcd." sta_warn 305 "read_power_activities is deprecated. Use read_vcd."
read_vcd_file $filename $scope read_vcd_file $filename $scope [cmd_mode_name] \
$sta::vcd_null_time $sta::vcd_null_time
} }
################################################################ ################################################################
define_cmd_args "read_vcd" { [-scope scope] [-mode mode_name] filename } define_cmd_args "read_vcd" \
{[-scope scope] [-mode mode_name] [-begin_time begin_time] [-end_time end_time] filename}
proc read_vcd { args } { proc read_vcd { args } {
parse_key_args "read_vcd" args \ parse_key_args "read_vcd" args \
keys {-scope -mode_name} flags {} keys {-scope -mode -begin_time -end_time} flags {}
check_argc_eq1 "read_vcd" $args check_argc_eq1 "read_vcd" $args
set filename [file nativename [lindex $args 0]] set filename [file nativename [lindex $args 0]]
@ -259,7 +261,15 @@ proc read_vcd { args } {
if { [info exists keys(-mode)] } { if { [info exists keys(-mode)] } {
set mode_name $keys(-mode) set mode_name $keys(-mode)
} }
read_vcd_file $filename $scope $mode_name set begin_time $sta::vcd_null_time
if { [info exists keys(-begin_time)] } {
set begin_time $keys(-begin_time)
}
set end_time $sta::vcd_null_time
if { [info exists keys(-end_time)] } {
set end_time $keys(-end_time)
}
read_vcd_file $filename $scope $mode_name $begin_time $end_time
} }
################################################################ ################################################################

View File

@ -42,8 +42,13 @@ namespace sta {
void void
VcdParse::read(const char *filename, VcdParse::read(const char *filename,
VcdReader *reader) VcdReader *reader,
VcdTime begin_time,
VcdTime end_time)
{ {
begin_time_ = begin_time;
end_time_ = end_time;
stream_ = gzopen(filename, "r"); stream_ = gzopen(filename, "r");
if (stream_) { if (stream_) {
Stats stats(debug_, report_); Stats stats(debug_, report_);
@ -51,6 +56,11 @@ VcdParse::read(const char *filename,
reader_ = reader; reader_ = reader;
file_line_ = 0; file_line_ = 0;
stmt_line_ = 0; stmt_line_ = 0;
// If user specified a start time, set it now.
if (begin_time != vcd_null_time) {
reader_->setTimeMin(begin_time);
}
std::string token = getToken(); std::string token = getToken();
while (!token.empty()) { while (!token.empty()) {
if (token == "$date") if (token == "$date")
@ -87,7 +97,10 @@ VcdParse::read(const char *filename,
report_->fileError(806, filename_, file_line_, "time out of range {}", report_->fileError(806, filename_, file_line_, "time out of range {}",
token.substr(1)); token.substr(1));
} }
// Set time min to start time if it is not set at beginning
if (begin_time == vcd_null_time) {
reader_->setTimeMin(time_); reader_->setTimeMin(time_);
}
prev_time_ = time_; prev_time_ = time_;
} }
else if (token[0] == '$') else if (token[0] == '$')
@ -238,7 +251,13 @@ VcdParse::parseVarValues()
} }
token = getToken(); token = getToken();
} }
// Set time_max to end_time if specified, otherwise use actual parsed time
if (end_time_ != vcd_null_time) {
reader_->setTimeMax(end_time_);
} else {
reader_->setTimeMax(time_); reader_->setTimeMax(time_);
}
} }
std::string std::string

View File

@ -36,6 +36,9 @@ namespace sta {
using VcdTime = int64_t; using VcdTime = int64_t;
using VcdScope = std::vector<std::string>; using VcdScope = std::vector<std::string>;
// Sentinel for an unset begin/end time window bound.
constexpr VcdTime vcd_null_time = -1;
enum class VcdVarType { enum class VcdVarType {
wire, wire,
reg, reg,
@ -64,7 +67,9 @@ public:
VcdParse(Report *report, VcdParse(Report *report,
Debug *debug); Debug *debug);
void read(const char *filename, void read(const char *filename,
VcdReader *reader); VcdReader *reader,
VcdTime begin_time,
VcdTime end_time);
private: private:
void parseTimescale(); void parseTimescale();
@ -87,6 +92,11 @@ private:
VcdTime time_ = 0; VcdTime time_ = 0;
VcdTime prev_time_ = 0; VcdTime prev_time_ = 0;
// Arguments to VcdParse
VcdTime begin_time_ = vcd_null_time;
VcdTime end_time_ = vcd_null_time;
VcdScope scope_; VcdScope scope_;
Report *report_; Report *report_;

View File

@ -52,48 +52,89 @@ public:
VcdTime highTime(VcdTime time_max) const; VcdTime highTime(VcdTime time_max) const;
void incrCounts(VcdTime time, void incrCounts(VcdTime time,
char value); char value);
void incrCounts(VcdTime time,
int64_t value);
void addPin(const Pin *pin); void addPin(const Pin *pin);
const PinSeq &pins() const { return pins_; } const PinSeq &pins() const { return pins_; }
static void setFilter(VcdTime begin,
VcdTime end);
private: private:
VcdTime clippedIntervalStart() const;
PinSeq pins_; PinSeq pins_;
VcdTime prev_time_ = -1; VcdTime prev_time_ = vcd_null_time;
char prev_value_ = '\0'; char prev_value_ = '\0';
VcdTime high_time_ = 0; VcdTime high_time_ = 0;
double transition_count_ = 0; double transition_count_ = 0;
static VcdTime begin_time_;
static VcdTime end_time_;
}; };
// Define static members
VcdTime VcdCount::begin_time_ = vcd_null_time;
VcdTime VcdCount::end_time_ = vcd_null_time;
void void
VcdCount::addPin(const Pin *pin) VcdCount::addPin(const Pin *pin)
{ {
pins_.push_back(pin); pins_.push_back(pin);
} }
VcdTime
VcdCount::clippedIntervalStart() const
{
// Clip prev_time_ to begin_time if signal went high before the window.
return (begin_time_ != vcd_null_time && prev_time_ < begin_time_)
? begin_time_ : prev_time_;
}
void
VcdCount::setFilter(VcdTime begin,
VcdTime end)
{
begin_time_ = begin;
end_time_ = end;
}
void void
VcdCount::incrCounts(VcdTime time, VcdCount::incrCounts(VcdTime time,
char value) char value)
{ {
// Initial value does not coontribute to transitions or high time. // Determine if this time point is within the filter window
if (prev_time_ != -1) { bool in_window = (begin_time_ == vcd_null_time || time >= begin_time_)
if (prev_value_ == '1') && (end_time_ == vcd_null_time || time <= end_time_);
high_time_ += time - prev_time_;
// Initial value does not contribute to transitions or high time.
if (prev_time_ != vcd_null_time && in_window) {
if (prev_value_ == '1') {
VcdTime interval_start = clippedIntervalStart();
if (time > interval_start)
high_time_ += time - interval_start;
}
if (value != prev_value_) if (value != prev_value_)
transition_count_ += transition_count_ +=
(value == 'X' || value == 'Z' || prev_value_ == 'X' || prev_value_ == 'Z') (value == 'X' || value == 'Z' || prev_value_ == 'X' || prev_value_ == 'Z')
? .5 ? .5
: 1.0; : 1.0;
} }
// Update state for transitions before or within the window.
// This prevents values after window boundaries corrupting high time.
if (end_time_ == vcd_null_time || time <= end_time_) {
prev_time_ = time; prev_time_ = time;
prev_value_ = value; prev_value_ = value;
}
} }
VcdTime VcdTime
VcdCount::highTime(VcdTime time_max) const VcdCount::highTime(VcdTime time_max) const
{ {
if (prev_value_ == '1') if (prev_value_ == '1') {
return high_time_ + time_max - prev_time_; VcdTime interval_start = clippedIntervalStart();
if (time_max > interval_start)
return high_time_ + time_max - interval_start;
else
return high_time_;
}
else else
return high_time_; return high_time_;
} }
@ -180,12 +221,14 @@ VcdCountReader::setTimeUnit(std::string_view ,
void void
VcdCountReader::setTimeMin(VcdTime time) VcdCountReader::setTimeMin(VcdTime time)
{ {
debugPrint(debug_, "read_vcd", 1, "setTimeMin called with time {}", time);
time_min_ = time; time_min_ = time;
} }
void void
VcdCountReader::setTimeMax(VcdTime time) VcdCountReader::setTimeMax(VcdTime time)
{ {
debugPrint(debug_, "read_vcd", 1, "setTimeMax called with time {}", time);
time_max_ = time; time_max_ = time;
} }
@ -335,6 +378,8 @@ class ReadVcdActivities : public StaState
public: public:
ReadVcdActivities(std::string_view filename, ReadVcdActivities(std::string_view filename,
std::string_view scope, std::string_view scope,
VcdTime begin_time,
VcdTime end_time,
const Sdc *sdc, const Sdc *sdc,
Sta *sta); Sta *sta);
void readActivities(); void readActivities();
@ -345,6 +390,8 @@ private:
double transition_count); double transition_count);
const std::string filename_; const std::string filename_;
VcdTime begin_time_;
VcdTime end_time_;
std::set<const Pin *> annotated_pins_; std::set<const Pin *> annotated_pins_;
VcdCountReader vcd_reader_; VcdCountReader vcd_reader_;
@ -359,20 +406,26 @@ void
readVcdActivities(std::string_view filename, readVcdActivities(std::string_view filename,
std::string_view scope, std::string_view scope,
std::string_view mode_name, std::string_view mode_name,
VcdTime begin_time,
VcdTime end_time,
Sta *sta) Sta *sta)
{ {
const Mode *mode = sta->findMode(mode_name); const Mode *mode = sta->findMode(mode_name);
const Sdc *sdc = mode->sdc(); const Sdc *sdc = mode->sdc();
ReadVcdActivities reader(filename, scope, sdc, sta); ReadVcdActivities reader(filename, scope, begin_time, end_time, sdc, sta);
reader.readActivities(); reader.readActivities();
} }
ReadVcdActivities::ReadVcdActivities(std::string_view filename, ReadVcdActivities::ReadVcdActivities(std::string_view filename,
std::string_view scope, std::string_view scope,
VcdTime begin_time,
VcdTime end_time,
const Sdc *sdc, const Sdc *sdc,
Sta *sta) : Sta *sta) :
StaState(sta), StaState(sta),
filename_(filename), filename_(filename),
begin_time_(begin_time),
end_time_(end_time),
vcd_reader_(scope, vcd_reader_(scope,
sdc_network_, sdc_network_,
report_, report_,
@ -391,7 +444,9 @@ ReadVcdActivities::readActivities()
if (clks.empty()) if (clks.empty())
report_->error(820, "No clocks have been defined."); report_->error(820, "No clocks have been defined.");
vcd_parse_.read(filename_.c_str(), &vcd_reader_); // Set the time window filter once globally
VcdCount::setFilter(begin_time_, end_time_);
vcd_parse_.read(filename_.c_str(), &vcd_reader_, begin_time_, end_time_);
if (vcd_reader_.timeMax() > 0) if (vcd_reader_.timeMax() > 0)
setActivities(); setActivities();

View File

@ -26,6 +26,8 @@
#include <string> #include <string>
#include "VcdParse.hh"
namespace sta { namespace sta {
class Sta; class Sta;
@ -34,6 +36,8 @@ void
readVcdActivities(std::string_view filename, readVcdActivities(std::string_view filename,
std::string_view scope, std::string_view scope,
std::string_view mode_name, std::string_view mode_name,
VcdTime begin_time,
VcdTime end_time,
Sta *sta); Sta *sta);
} // namespace sta } // namespace sta

View File

@ -169,6 +169,7 @@ record_public_tests {
report_json2 report_json2
suppress_msg suppress_msg
user_properties user_properties
vcd_begin_end_time
verilog_attribute verilog_attribute
verilog_well_supplies verilog_well_supplies
verilog_specify verilog_specify

View File

@ -0,0 +1,60 @@
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.0666665 0.667
u_inv/A 0.0666665 0.333
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.1 1.000
u_inv/A 0.1 0.000
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.2 0.000
u_inv/A 0.2 1.000
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.1 1.000
u_inv/A 0.1 0.000
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.25 0.500
u_inv/A 0.25 0.500
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.125 0.750
u_inv/A 0.125 0.250
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.125 0.250
u_inv/A 0.125 0.750
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.25 0.500
u_inv/A 0.25 0.500
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.125 0.250
u_inv/A 0.125 0.750
Annotated 2 pin activities.
Pin Name Activity Duty Cycle
--------------------------------------------------------
u_inv/Y 0.125 0.750
u_inv/A 0.125 0.250

View File

@ -0,0 +1,70 @@
# Report pin activities
proc report_activities { } {
set pins [get_pins -hierarchical *]
set clk_freq [expr 1.0 / (10 * 1e-12)]
puts "Pin Name Activity Duty Cycle"
puts "--------------------------------------------------------"
foreach pin $pins {
set prop [get_property $pin activity]
set transitions_per_sec [lindex $prop 0]
set duty [lindex $prop 1]
set activity [expr double($transitions_per_sec) / [expr $clk_freq * 2]]
puts "[get_full_name $pin] $activity $duty"
}
puts ""
}
# Setup
read_liberty asap7_invbuf.lib.gz
read_verilog vcd_begin_end_time.v
link_design top
# Define clock period in ps
create_clock -name vclk -period 10
# Full VCD reading works (normal behavior)
# VCD changes at time 50 and 100 (inverter)
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top
report_activities
# Read VCD from start to first transition point
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top -end_time 50
report_activities
# Read VCD from first transition point to second transition point
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 50 -end_time 100
report_activities
# Read VCD from second transition point to end
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 100
report_activities
# Read VCD around the first transition point
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 40 -end_time 60
report_activities
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 20 -end_time 60
report_activities
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 40 -end_time 80
report_activities
# Read VCD around the second transition point (should mirror the first)
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 90 -end_time 110
report_activities
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 70 -end_time 110
report_activities
sta::clear_power
read_vcd vcd_begin_end_time.vcd -scope top -begin_time 90 -end_time 130
report_activities

14
test/vcd_begin_end_time.v Normal file
View File

@ -0,0 +1,14 @@
`timescale 1ps/1ps
module top (
input wire A,
input wire clk,
output wire Y
);
INVx2_ASAP7_75t_R u_inv (
.A(A),
.Y(Y)
);
endmodule

View File

@ -0,0 +1,26 @@
$date
Mon Mar 16 2026
$end
$version
VCD Test File
$end
$timescale
1ps
$end
$scope module top $end
$var wire 1 ! A $end
$var wire 1 " Y $end
$upscope $end
$enddefinitions $end
#0
$dumpvars
0!
1"
$end
#50
1!
0"
#100
0!
1"
#150