444 lines
12 KiB
C++
444 lines
12 KiB
C++
// OpenSTA, Static Timing Analyzer
|
|
// Copyright (c) 2024, 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 <https://www.gnu.org/licenses/>.
|
|
|
|
#include "VcdReader.hh"
|
|
|
|
#include <inttypes.h>
|
|
#include <set>
|
|
|
|
#include "VcdParse.hh"
|
|
#include "Debug.hh"
|
|
#include "Network.hh"
|
|
#include "PortDirection.hh"
|
|
#include "VerilogNamespace.hh"
|
|
#include "ParseBus.hh"
|
|
#include "Sdc.hh"
|
|
#include "Power.hh"
|
|
#include "Sta.hh"
|
|
|
|
namespace sta {
|
|
|
|
using std::abs;
|
|
using std::min;
|
|
using std::to_string;
|
|
using std::vector;
|
|
using std::map;
|
|
|
|
// Transition count and high time for duty cycle for a group of pins
|
|
// for one bit of vcd ID.
|
|
class VcdCount
|
|
{
|
|
public:
|
|
VcdCount();
|
|
double transitionCount() const { return transition_count_; }
|
|
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_; }
|
|
|
|
private:
|
|
PinSeq pins_;
|
|
VcdTime prev_time_;
|
|
char prev_value_;
|
|
VcdTime high_time_;
|
|
double transition_count_;
|
|
};
|
|
|
|
VcdCount::VcdCount() :
|
|
prev_time_(-1),
|
|
prev_value_('\0'),
|
|
high_time_(0),
|
|
transition_count_(0)
|
|
{
|
|
}
|
|
|
|
void
|
|
VcdCount::addPin(const Pin *pin)
|
|
{
|
|
pins_.push_back(pin);
|
|
}
|
|
|
|
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_;
|
|
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;
|
|
}
|
|
|
|
VcdTime
|
|
VcdCount::highTime(VcdTime time_max) const
|
|
{
|
|
if (prev_value_ == '1')
|
|
return high_time_ + time_max - prev_time_;
|
|
else
|
|
return high_time_;
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
// VcdCount[bit]
|
|
typedef vector<VcdCount> VcdCounts;
|
|
// ID -> VcdCount[bit]
|
|
typedef map<string, VcdCounts> VcdIdCountsMap;
|
|
|
|
class VcdCountReader : public VcdReader
|
|
{
|
|
public:
|
|
VcdCountReader(const char *scope,
|
|
Network *sdc_network,
|
|
Report *report,
|
|
Debug *debug);
|
|
VcdTime timeMax() const { return time_max_; }
|
|
const VcdIdCountsMap &countMap() const { return vcd_count_map_; }
|
|
double timeScale() const { return time_scale_; }
|
|
|
|
// VcdParse callbacks.
|
|
void setDate(const string &) override {}
|
|
void setComment(const string &) override {}
|
|
void setVersion(const string &) override {}
|
|
void setTimeUnit(const string &time_unit,
|
|
double time_unit_scale,
|
|
double time_scale) override;
|
|
void setTimeMax(VcdTime time_max) override;
|
|
void varMinDeltaTime(VcdTime) override {}
|
|
bool varIdValid(const string &id) override;
|
|
void makeVar(const VcdScope &scope,
|
|
const string &name,
|
|
VcdVarType type,
|
|
size_t width,
|
|
const string &id) override;
|
|
void varAppendValue(const string &id,
|
|
VcdTime time,
|
|
char value) override;
|
|
void varAppendBusValue(const string &id,
|
|
VcdTime time,
|
|
int64_t bus_value) override;
|
|
|
|
private:
|
|
void addVarPin(const string &pin_name,
|
|
const string &id,
|
|
size_t width,
|
|
size_t bit_idx);
|
|
|
|
const char *scope_;
|
|
Network *sdc_network_;
|
|
Report *report_;
|
|
Debug *debug_;
|
|
|
|
double time_scale_;
|
|
VcdTime time_max_;
|
|
VcdIdCountsMap vcd_count_map_;
|
|
};
|
|
|
|
VcdCountReader::VcdCountReader(const char *scope,
|
|
Network *sdc_network,
|
|
Report *report,
|
|
Debug *debug) :
|
|
scope_(scope),
|
|
sdc_network_(sdc_network),
|
|
report_(report),
|
|
debug_(debug),
|
|
time_scale_(1.0),
|
|
time_max_(0.0)
|
|
{
|
|
}
|
|
|
|
void
|
|
VcdCountReader::setTimeUnit(const string &,
|
|
double time_unit_scale,
|
|
double time_scale)
|
|
{
|
|
time_scale_ = time_scale * time_unit_scale;
|
|
}
|
|
|
|
void
|
|
VcdCountReader::setTimeMax(VcdTime time_max)
|
|
{
|
|
time_max_ = time_max;
|
|
}
|
|
|
|
bool
|
|
VcdCountReader::varIdValid(const string &)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void
|
|
VcdCountReader::makeVar(const VcdScope &scope,
|
|
const string &name,
|
|
VcdVarType type,
|
|
size_t width,
|
|
const string &id)
|
|
{
|
|
if (type == VcdVarType::wire
|
|
|| type == VcdVarType::reg) {
|
|
string path_name;
|
|
bool first = true;
|
|
for (const string &context : scope) {
|
|
if (!first)
|
|
path_name += '/';
|
|
path_name += context;
|
|
first = false;
|
|
}
|
|
size_t scope_length = strlen(scope_);
|
|
// string::starts_with in c++20
|
|
if (scope_length == 0
|
|
|| path_name.substr(0, scope_length) == scope_) {
|
|
path_name += '/';
|
|
path_name += name;
|
|
// Strip the scope from the name.
|
|
string var_scoped = path_name.substr(scope_length + 1);
|
|
if (width == 1) {
|
|
string pin_name = netVerilogToSta(var_scoped.c_str());
|
|
addVarPin(pin_name, id, width, 0);
|
|
}
|
|
else {
|
|
bool is_bus, is_range, subscript_wild;
|
|
string bus_name;
|
|
int from, to;
|
|
parseBusName(var_scoped.c_str(), '[', ']', '\\',
|
|
is_bus, is_range, bus_name, from, to, subscript_wild);
|
|
if (is_bus) {
|
|
string sta_bus_name = netVerilogToSta(bus_name.c_str());
|
|
int bit_idx = 0;
|
|
if (to < from) {
|
|
for (int bus_bit = to; bus_bit <= from; bus_bit++) {
|
|
string pin_name = sta_bus_name;
|
|
pin_name += '[';
|
|
pin_name += to_string(bus_bit);
|
|
pin_name += ']';
|
|
addVarPin(pin_name, id, width, bit_idx);
|
|
bit_idx++;
|
|
}
|
|
}
|
|
else {
|
|
for (int bus_bit = to; bus_bit >= from; bus_bit--) {
|
|
string pin_name = sta_bus_name;
|
|
pin_name += '[';
|
|
pin_name += to_string(bus_bit);
|
|
pin_name += ']';
|
|
addVarPin(pin_name, id, width, bit_idx);
|
|
bit_idx++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
report_->warn(1451, "problem parsing bus %s.", var_scoped.c_str());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
VcdCountReader::addVarPin(const string &pin_name,
|
|
const string &id,
|
|
size_t width,
|
|
size_t bit_idx)
|
|
{
|
|
const Pin *pin = sdc_network_->findPin(pin_name.c_str());
|
|
if (pin
|
|
&& !sdc_network_->isHierarchical(pin)
|
|
&& !sdc_network_->direction(pin)->isInternal()) {
|
|
VcdCounts &vcd_counts = vcd_count_map_[id];
|
|
vcd_counts.resize(width);
|
|
vcd_counts[bit_idx].addPin(pin);
|
|
debugPrint(debug_, "read_vcd_activities", 2, "id %s pin %s",
|
|
id.c_str(),
|
|
pin_name.c_str());
|
|
}
|
|
}
|
|
|
|
void
|
|
VcdCountReader::varAppendValue(const string &id,
|
|
VcdTime time,
|
|
char value)
|
|
{
|
|
auto itr = vcd_count_map_.find(id);
|
|
if (itr != vcd_count_map_.end()) {
|
|
VcdCounts &vcd_counts = itr->second;
|
|
if (debug_->check("read_vcd_activities", 3)) {
|
|
for (size_t bit_idx = 0; bit_idx < vcd_counts.size(); bit_idx++) {
|
|
VcdCount &vcd_count = vcd_counts[bit_idx];
|
|
for (const Pin *pin : vcd_count.pins()) {
|
|
debugPrint(debug_, "read_vcd_activities", 3, "%s time %" PRIu64 " value %c",
|
|
sdc_network_->pathName(pin),
|
|
time,
|
|
value);
|
|
}
|
|
}
|
|
}
|
|
for (size_t bit_idx = 0; bit_idx < vcd_counts.size(); bit_idx++) {
|
|
VcdCount &vcd_count = vcd_counts[bit_idx];
|
|
vcd_count.incrCounts(time, value);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
VcdCountReader::varAppendBusValue(const string &id,
|
|
VcdTime time,
|
|
int64_t bus_value)
|
|
{
|
|
auto itr = vcd_count_map_.find(id);
|
|
if (itr != vcd_count_map_.end()) {
|
|
VcdCounts &vcd_counts = itr->second;
|
|
for (size_t bit_idx = 0; bit_idx < vcd_counts.size(); bit_idx++) {
|
|
char bit_value = ((bus_value >> bit_idx) & 0x1) ? '1' : '0';
|
|
VcdCount &vcd_count = vcd_counts[bit_idx];
|
|
if (debug_->check("read_vcd_activities", 3)) {
|
|
for (const Pin *pin : vcd_count.pins()) {
|
|
debugPrint(debug_, "read_vcd_activities", 3, "%s time %" PRIu64 " value %c",
|
|
sdc_network_->pathName(pin),
|
|
time,
|
|
bit_value);
|
|
}
|
|
}
|
|
vcd_count.incrCounts(time, bit_value);
|
|
}
|
|
}
|
|
}
|
|
|
|
////////////////////////////////////////////////////////////////
|
|
|
|
class ReadVcdActivities : public StaState
|
|
{
|
|
public:
|
|
ReadVcdActivities(const char *filename,
|
|
const char *scope,
|
|
Sta *sta);
|
|
void readActivities();
|
|
|
|
private:
|
|
void setActivities();
|
|
void checkClkPeriod(const Pin *pin,
|
|
double transition_count);
|
|
|
|
const char *filename_;
|
|
VcdCountReader vcd_reader_;
|
|
VcdParse vcd_parse_;
|
|
|
|
Power *power_;
|
|
std::set<const Pin*> annotated_pins_;
|
|
|
|
static constexpr double sim_clk_period_tolerance_ = .1;
|
|
};
|
|
|
|
void
|
|
readVcdActivities(const char *filename,
|
|
const char *scope,
|
|
Sta *sta)
|
|
{
|
|
ReadVcdActivities reader(filename, scope, sta);
|
|
reader.readActivities();
|
|
}
|
|
|
|
ReadVcdActivities::ReadVcdActivities(const char *filename,
|
|
const char *scope,
|
|
Sta *sta) :
|
|
StaState(sta),
|
|
filename_(filename),
|
|
vcd_reader_(scope, sdc_network_, report_, debug_),
|
|
vcd_parse_(report_, debug_),
|
|
power_(sta->power())
|
|
{
|
|
}
|
|
|
|
void
|
|
ReadVcdActivities::readActivities()
|
|
{
|
|
ClockSeq *clks = sdc_->clocks();
|
|
if (clks->empty())
|
|
report_->error(805, "No clocks have been defined.");
|
|
|
|
vcd_parse_.read(filename_, &vcd_reader_);
|
|
|
|
if (vcd_reader_.timeMax() > 0)
|
|
setActivities();
|
|
else
|
|
report_->warn(1450, "VCD max time is zero.");
|
|
report_->reportLine("Annotated %zu pin activities.", annotated_pins_.size());
|
|
}
|
|
|
|
void
|
|
ReadVcdActivities::setActivities()
|
|
{
|
|
VcdTime time_max = vcd_reader_.timeMax();
|
|
double time_scale = vcd_reader_.timeScale();
|
|
for (auto& [id, vcd_counts] : vcd_reader_.countMap()) {
|
|
for (const VcdCount &vcd_count : vcd_counts) {
|
|
double transition_count = vcd_count.transitionCount();
|
|
VcdTime high_time = vcd_count.highTime(time_max);
|
|
float duty = static_cast<double>(high_time) / time_max;
|
|
float density = transition_count / (time_max * time_scale);
|
|
if (debug_->check("read_vcd_activities", 1)) {
|
|
for (const Pin *pin : vcd_count.pins()) {
|
|
debugPrint(debug_, "read_vcd_activities", 1,
|
|
"%s transitions %.1f activity %.2f duty %.2f",
|
|
sdc_network_->pathName(pin),
|
|
transition_count,
|
|
density,
|
|
duty);
|
|
}
|
|
}
|
|
for (const Pin *pin : vcd_count.pins()) {
|
|
power_->setUserActivity(pin, density, duty, PwrActivityOrigin::vcd);
|
|
if (sdc_->isLeafPinClock(pin))
|
|
checkClkPeriod(pin, transition_count);
|
|
annotated_pins_.insert(pin);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
ReadVcdActivities::checkClkPeriod(const Pin *pin,
|
|
double transition_count)
|
|
{
|
|
VcdTime time_max = vcd_reader_.timeMax();
|
|
double time_scale = vcd_reader_.timeScale();
|
|
double sim_period = time_max * time_scale / (transition_count / 2.0);
|
|
ClockSet *clks = sdc_->findLeafPinClocks(pin);
|
|
if (clks) {
|
|
for (Clock *clk : *clks) {
|
|
double clk_period = clk->period();
|
|
if (abs((clk_period - sim_period) / clk_period) > sim_clk_period_tolerance_)
|
|
// Warn if sim clock period differs from SDC by more than 10%.
|
|
report_->warn(1452, "clock %s vcd period %s differs from SDC clock period %s",
|
|
clk->name(),
|
|
delayAsString(sim_period, this),
|
|
delayAsString(clk_period, this));
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|