OpenSTA/power/VcdReader.cc

453 lines
12 KiB
C++
Raw Normal View History

// 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 <cctype>
#include <cinttypes>
#include "VcdReader.hh"
#include "Zlib.hh"
#include "Stats.hh"
#include "Report.hh"
#include "Error.hh"
#include "StringUtil.hh"
#include "EnumNameMap.hh"
namespace sta {
using std::isspace;
// Very imprecise syntax definition
// https://en.wikipedia.org/wiki/Value_change_dump#Structure.2FSyntax
// Much better syntax definition
// https://web.archive.org/web/20120323132708/http://www.beyondttl.com/vcd.php
class VcdReader : public StaState
{
public:
VcdReader(StaState *sta);
Vcd read(const char *filename);
private:
void parseTimescale();
void setTimeUnit(const string &time_unit);
void parseVar();
void parseScope();
void parseUpscope();
void parseVarValues();
string getToken();
string readStmtString();
vector<string> readStmtTokens();
gzFile stream_;
string token_;
const char *filename_;
int file_line_;
int stmt_line_;
Vcd *vcd_;
VcdTime time_;
VcdTime prev_time_;
VcdScope scope_;
};
Vcd
readVcdFile(const char *filename,
StaState *sta)
{
VcdReader reader(sta);
return reader.read(filename);
}
Vcd
VcdReader::read(const char *filename)
{
Vcd vcd(this);
vcd_ = &vcd;
stream_ = gzopen(filename, "r");
if (stream_) {
Stats stats(debug_, report_);
filename_ = filename;
file_line_ = 1;
stmt_line_ = 1;
string token = getToken();
while (!token.empty()) {
if (token == "$date")
vcd_->setDate(readStmtString());
else if (token == "$comment")
vcd_->setComment(readStmtString());
else if (token == "$version")
vcd_->setVersion(readStmtString());
else if (token == "$timescale")
parseTimescale();
else if (token == "$var")
parseVar();
else if (token == "$scope")
parseScope();
else if (token == "$upscope")
parseUpscope();
else if (token == "$enddefinitions")
// empty body
readStmtString();
else if (token == "$dumpall")
parseVarValues();
else if (token == "$dumpvars")
// Initial values.
parseVarValues();
else if (token[0] == '$')
report_->fileError(800, filename_, stmt_line_, "unhandled vcd command.");
else
parseVarValues();
token = getToken();
}
gzclose(stream_);
stats.report("Read VCD");
}
else
throw FileNotReadable(filename);
return vcd;
}
VcdReader::VcdReader(StaState *sta) :
StaState(sta),
file_line_(0),
stmt_line_(0),
vcd_(nullptr),
time_(0),
prev_time_(0)
{
}
void
VcdReader::parseTimescale()
{
vector<string> tokens = readStmtTokens();
if (tokens.size() == 1) {
size_t last;
double time_scale = std::stod(tokens[0], &last);
setTimeUnit(tokens[0].substr(last));
vcd_->setTimeScale(time_scale * vcd_->timeUnitScale());
}
else if (tokens.size() == 2) {
setTimeUnit(tokens[1]);
double time_scale = std::stod(tokens[0]);
vcd_->setTimeScale(time_scale * vcd_->timeUnitScale());
}
else
report_->fileError(801, filename_, stmt_line_, "timescale syntax error.");
}
void
VcdReader::setTimeUnit(const string &time_unit)
{
double time_unit_scale = 1.0;
if (time_unit == "fs")
time_unit_scale = 1e-15;
else if (time_unit == "ps")
time_unit_scale = 1e-12;
else if (time_unit == "ns")
time_unit_scale = 1e-9;
else
report_->fileError(802, filename_, stmt_line_, "Unknown timescale unit.");
vcd_->setTimeUnit(time_unit, time_unit_scale);;
}
static EnumNameMap<VcdVarType> vcd_var_type_map =
{{VcdVarType::wire, "wire"},
{VcdVarType::reg, "reg"},
{VcdVarType::parameter, "parameter"},
{VcdVarType::integer, "integer"},
{VcdVarType::real, "real"},
{VcdVarType::supply0, "supply0"},
{VcdVarType::supply1, "supply1"},
{VcdVarType::time, "time"},
{VcdVarType::tri, "tri"},
{VcdVarType::triand, "triand"},
{VcdVarType::trior, "trior"},
{VcdVarType::trireg, "trireg"},
{VcdVarType::tri0, "tri0"},
{VcdVarType::tri1, "tri1"},
{VcdVarType::wand, "wand"},
{VcdVarType::wor, "wor"}
};
void
VcdReader::parseVar()
{
vector<string> tokens = readStmtTokens();
if (tokens.size() == 4
|| tokens.size() == 5) {
string type_name = tokens[0];
VcdVarType type = vcd_var_type_map.find(type_name, VcdVarType::unknown);
if (type == VcdVarType::unknown)
report_->fileWarn(1370, filename_, stmt_line_,
"Unknown variable type %s.",
type_name.c_str());
else {
int width = stoi(tokens[1]);
string &id = tokens[2];
string name;
for (string &context : scope_) {
name += context;
name += '/';
}
name += tokens[3];
// iverilog separates bus base name from bit range.
if (tokens.size() == 5) {
// Preserve space after esacaped name.
if (name[0] == '\\')
name += ' ';
name += tokens[4];
}
vcd_->makeVar(name, type, width, id);
}
}
else
report_->fileError(804, filename_, stmt_line_, "Variable syntax error.");
}
void
VcdReader::parseScope()
{
vector<string> tokens = readStmtTokens();
string &scope = tokens[1];
scope_.push_back(scope);
}
void
VcdReader::parseUpscope()
{
readStmtTokens();
scope_.pop_back();
}
void
VcdReader::parseVarValues()
{
string token = getToken();
while (!token.empty()) {
char char0 = toupper(token[0]);
if (char0 == '#' && token.size() > 1) {
prev_time_ = time_;
time_ = stoll(token.substr(1));
if (time_ > prev_time_)
vcd_->setMinDeltaTime(min(time_ - prev_time_, vcd_->minDeltaTime()));
}
else if (char0 == '0'
|| char0 == '1'
|| char0 == 'X'
|| char0 == 'U'
|| char0 == 'Z') {
string id = token.substr(1);
if (!vcd_->varIdValid(id))
report_->fileError(805, filename_, stmt_line_,
"unknown variable %s", id.c_str());
vcd_->varAppendValue(id, time_, char0);
}
else if (char0 == 'B') {
char char1 = toupper(token[1]);
if (char1 == 'X'
|| char1 == 'U'
|| char1 == 'Z') {
string id = getToken();
if (!vcd_->varIdValid(id))
report_->fileError(806, filename_, stmt_line_,
"unknown variable %s", id.c_str());
// Bus mixed 0/1/X/U not supported.
vcd_->varAppendValue(id, time_, char1);
}
else {
string bin = token.substr(1);
char *end;
int64_t bus_value = strtol(bin.c_str(), &end, 2);
string id = getToken();
if (!vcd_->varIdValid(id))
report_->fileError(807, filename_, stmt_line_,
"unknown variable %s", id.c_str());
else
vcd_->varAppendBusValue(id, time_, bus_value);
}
}
token = getToken();
}
vcd_->setTimeMax(time_);
}
string
VcdReader::readStmtString()
{
stmt_line_ = file_line_;
string line;
string token = getToken();
while (!token.empty() && token != "$end") {
if (!line.empty())
line += " ";
line += token;
token = getToken();
}
return line;
}
vector<string>
VcdReader::readStmtTokens()
{
stmt_line_ = file_line_;
vector<string> tokens;
string token = getToken();
while (!token.empty() && token != "$end") {
tokens.push_back(token);
token = getToken();
}
return tokens;
}
string
VcdReader::getToken()
{
string token;
int ch = gzgetc(stream_);
if (ch == '\n')
file_line_++;
// skip whitespace
while (ch != EOF && isspace(ch)) {
ch = gzgetc(stream_);
if (ch == '\n')
file_line_++;
}
while (ch != EOF && !isspace(ch)) {
token.push_back(ch);
ch = gzgetc(stream_);
if (ch == '\n')
file_line_++;
}
if (ch == EOF)
return "";
else
return token;
}
////////////////////////////////////////////////////////////////
static void
reportWaveforms(Vcd &vcd,
Report *report);
void
reportVcdWaveforms(const char *filename,
StaState *sta)
{
Vcd vcd = readVcdFile(filename, sta);
reportWaveforms(vcd, sta->report());
}
static void
reportWaveforms(Vcd &vcd,
Report *report)
{
report->reportLine("Date: %s", vcd.date().c_str());
report->reportLine("Timescale: %.2f%s", vcd.timeScale(), vcd.timeUnit().c_str());
// Characters per time sample.
int zoom = (vcd.maxVarWidth() + 7) / 4;
int time_delta = vcd.minDeltaTime();
int max_var_name_length = vcd.maxVarNameLength();
for (VcdVar *var : vcd.vars()) {
string line;
stringPrint(line, " %-*s",
static_cast<int>(max_var_name_length),
var->name().c_str());
const VcdValues &var_values = vcd.values(var);
if (!var_values.empty()) {
size_t value_index = 0;
VcdValue var_value = var_values[value_index];
VcdValue prev_var_value = var_values[value_index];
VcdTime next_value_time = var_values[value_index + 1].time();
for (double time = 0.0; time < vcd.timeMax(); time += time_delta) {
if (time >= next_value_time) {
if (value_index < var_values.size() - 1)
value_index++;
var_value = var_values[value_index];
if (value_index < var_values.size())
next_value_time = var_values[value_index + 1].time();
}
if (var_value.value()) {
// 01UZX
char value = var_value.value();
char prev_value = prev_var_value.value();
if (var->width() == 1) {
if (value == '0' || value == '1') {
for (int z = 0; z < zoom; z++) {
if (z == 0
&& value != prev_value
&& (prev_value == '0'
|| prev_value == '1'))
line += (prev_value == '1') ? "" : "";
else
line += (value == '1') ? "" : "";
}
}
else {
string field;
stringPrint(field, "%-*c", zoom, value);
line += field;
}
}
else {
string field;
stringPrint(field, "%-*c", zoom, value);
line += field;
}
}
else {
// bus
string field;
stringPrint(field, "%-*" PRIX64, zoom, var_value.busValue());
line += field;
}
prev_var_value = var_value;
}
}
report->reportLineString(line);
}
}
void
reportVcdVarValues(const char *filename,
const char *var_name,
StaState *sta)
{
Vcd vcd = readVcdFile(filename, sta);
VcdVar *var = vcd.var(var_name);
if (var) {
Report *report = sta->report();
for (const VcdValue &var_value : vcd.values(var)) {
double time = var_value.time() * vcd.timeUnitScale();
char value = var_value.value();
if (value == '\0')
report->reportLine("%.2e %" PRIu64,
time, var_value.busValue());
else
report->reportLine("%.2e %c", time, value);
}
}
}
}