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:
parent
f29b6a43c6
commit
7fdc304e12
|
|
@ -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
|
||||
----------
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
||||
################################################################
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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_;
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -169,6 +169,7 @@ record_public_tests {
|
|||
report_json2
|
||||
suppress_msg
|
||||
user_properties
|
||||
vcd_begin_end_time
|
||||
verilog_attribute
|
||||
verilog_well_supplies
|
||||
verilog_specify
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
Loading…
Reference in New Issue