diff --git a/doc/ChangeLog.txt b/doc/ChangeLog.txt index c456d0e0..add039d0 100644 --- a/doc/ChangeLog.txt +++ b/doc/ChangeLog.txt @@ -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 ---------- diff --git a/power/Power.i b/power/Power.i index 8c2c42e3..8b60b98a 100644 --- a/power/Power.i +++ b/power/Power.i @@ -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 diff --git a/power/Power.tcl b/power/Power.tcl index 71cf563c..360a13d7 100644 --- a/power/Power.tcl +++ b/power/Power.tcl @@ -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 } ################################################################ diff --git a/power/VcdParse.cc b/power/VcdParse.cc index 2a841427..0243ac7b 100644 --- a/power/VcdParse.cc +++ b/power/VcdParse.cc @@ -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 diff --git a/power/VcdParse.hh b/power/VcdParse.hh index 66f5d28f..9c572680 100644 --- a/power/VcdParse.hh +++ b/power/VcdParse.hh @@ -36,6 +36,9 @@ namespace sta { using VcdTime = int64_t; using VcdScope = std::vector; +// 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_; diff --git a/power/VcdReader.cc b/power/VcdReader.cc index a300c90f..4b3948d2 100644 --- a/power/VcdReader.cc +++ b/power/VcdReader.cc @@ -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 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(); diff --git a/power/VcdReader.hh b/power/VcdReader.hh index e3b0bafc..f115d73c 100644 --- a/power/VcdReader.hh +++ b/power/VcdReader.hh @@ -26,6 +26,8 @@ #include +#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 diff --git a/test/regression_vars.tcl b/test/regression_vars.tcl index c9df3429..e9976ce6 100644 --- a/test/regression_vars.tcl +++ b/test/regression_vars.tcl @@ -169,6 +169,7 @@ record_public_tests { report_json2 suppress_msg user_properties + vcd_begin_end_time verilog_attribute verilog_well_supplies verilog_specify diff --git a/test/vcd_begin_end_time.ok b/test/vcd_begin_end_time.ok new file mode 100644 index 00000000..f6d6667f --- /dev/null +++ b/test/vcd_begin_end_time.ok @@ -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 + diff --git a/test/vcd_begin_end_time.tcl b/test/vcd_begin_end_time.tcl new file mode 100644 index 00000000..974f4be9 --- /dev/null +++ b/test/vcd_begin_end_time.tcl @@ -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 diff --git a/test/vcd_begin_end_time.v b/test/vcd_begin_end_time.v new file mode 100644 index 00000000..089d6545 --- /dev/null +++ b/test/vcd_begin_end_time.v @@ -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 diff --git a/test/vcd_begin_end_time.vcd b/test/vcd_begin_end_time.vcd new file mode 100644 index 00000000..0518ecc3 --- /dev/null +++ b/test/vcd_begin_end_time.vcd @@ -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