// 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 . #include #include #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 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 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 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 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 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 VcdReader::readStmtTokens() { stmt_line_ = file_line_; vector 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(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); } } } }