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.
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
----------

View File

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

View File

@ -238,16 +238,18 @@ proc read_power_activities { args } {
set scope $keys(-scope)
}
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 } {
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
set filename [file nativename [lindex $args 0]]
@ -259,7 +261,15 @@ proc read_vcd { args } {
if { [info exists 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
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");
if (stream_) {
Stats stats(debug_, report_);
@ -51,6 +56,11 @@ VcdParse::read(const char *filename,
reader_ = reader;
file_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();
while (!token.empty()) {
if (token == "$date")
@ -87,7 +97,10 @@ VcdParse::read(const char *filename,
report_->fileError(806, filename_, file_line_, "time out of range {}",
token.substr(1));
}
reader_->setTimeMin(time_);
// Set time min to start time if it is not set at beginning
if (begin_time == vcd_null_time) {
reader_->setTimeMin(time_);
}
prev_time_ = time_;
}
else if (token[0] == '$')
@ -238,7 +251,13 @@ VcdParse::parseVarValues()
}
token = getToken();
}
reader_->setTimeMax(time_);
// 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_);
}
}
std::string

View File

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

View File

@ -52,48 +52,89 @@ public:
VcdTime highTime(VcdTime time_max) const;
void incrCounts(VcdTime time,
char value);
void incrCounts(VcdTime time,
int64_t value);
void addPin(const Pin *pin);
const PinSeq &pins() const { return pins_; }
static void setFilter(VcdTime begin,
VcdTime end);
private:
VcdTime clippedIntervalStart() const;
PinSeq pins_;
VcdTime prev_time_ = -1;
VcdTime prev_time_ = vcd_null_time;
char prev_value_ = '\0';
VcdTime high_time_ = 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
VcdCount::addPin(const Pin *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
VcdCount::incrCounts(VcdTime time,
char value)
{
// Initial value does not coontribute to transitions or high time.
if (prev_time_ != -1) {
if (prev_value_ == '1')
high_time_ += time - prev_time_;
// Determine if this time point is within the filter window
bool in_window = (begin_time_ == vcd_null_time || time >= begin_time_)
&& (end_time_ == vcd_null_time || time <= end_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_)
transition_count_ +=
(value == 'X' || value == 'Z' || prev_value_ == 'X' || prev_value_ == 'Z')
? .5
: 1.0;
}
prev_time_ = time;
prev_value_ = value;
// 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_value_ = value;
}
}
VcdTime
VcdCount::highTime(VcdTime time_max) const
{
if (prev_value_ == '1')
return high_time_ + time_max - prev_time_;
if (prev_value_ == '1') {
VcdTime interval_start = clippedIntervalStart();
if (time_max > interval_start)
return high_time_ + time_max - interval_start;
else
return high_time_;
}
else
return high_time_;
}
@ -180,12 +221,14 @@ VcdCountReader::setTimeUnit(std::string_view ,
void
VcdCountReader::setTimeMin(VcdTime time)
{
debugPrint(debug_, "read_vcd", 1, "setTimeMin called with time {}", time);
time_min_ = time;
}
void
VcdCountReader::setTimeMax(VcdTime time)
{
debugPrint(debug_, "read_vcd", 1, "setTimeMax called with time {}", time);
time_max_ = time;
}
@ -335,6 +378,8 @@ class ReadVcdActivities : public StaState
public:
ReadVcdActivities(std::string_view filename,
std::string_view scope,
VcdTime begin_time,
VcdTime end_time,
const Sdc *sdc,
Sta *sta);
void readActivities();
@ -345,6 +390,8 @@ private:
double transition_count);
const std::string filename_;
VcdTime begin_time_;
VcdTime end_time_;
std::set<const Pin *> annotated_pins_;
VcdCountReader vcd_reader_;
@ -359,20 +406,26 @@ void
readVcdActivities(std::string_view filename,
std::string_view scope,
std::string_view mode_name,
VcdTime begin_time,
VcdTime end_time,
Sta *sta)
{
const Mode *mode = sta->findMode(mode_name);
const Sdc *sdc = mode->sdc();
ReadVcdActivities reader(filename, scope, sdc, sta);
ReadVcdActivities reader(filename, scope, begin_time, end_time, sdc, sta);
reader.readActivities();
}
ReadVcdActivities::ReadVcdActivities(std::string_view filename,
std::string_view scope,
VcdTime begin_time,
VcdTime end_time,
const Sdc *sdc,
Sta *sta) :
StaState(sta),
filename_(filename),
begin_time_(begin_time),
end_time_(end_time),
vcd_reader_(scope,
sdc_network_,
report_,
@ -391,7 +444,9 @@ ReadVcdActivities::readActivities()
if (clks.empty())
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)
setActivities();

View File

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

View File

@ -169,6 +169,7 @@ record_public_tests {
report_json2
suppress_msg
user_properties
vcd_begin_end_time
verilog_attribute
verilog_well_supplies
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