yosys/kernel/fstdata.cc

466 lines
14 KiB
C++
Raw Normal View History

2022-01-26 10:23:38 +01:00
/*
* yosys -- Yosys Open SYnthesis Suite
*
* Copyright (C) 2022 Miodrag Milanovic <micko@yosyshq.com>
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*
*/
#include "kernel/fstdata.h"
USING_YOSYS_NAMESPACE
2022-01-31 10:52:47 +01:00
static std::string file_base_name(std::string const & path)
{
return path.substr(path.find_last_of("/\\") + 1);
}
2022-01-26 10:23:38 +01:00
FstData::FstData(std::string filename) : ctx(nullptr)
{
#if !defined(YOSYS_DISABLE_SPAWN)
std::string filename_trim = file_base_name(filename);
2025-02-14 13:26:26 +01:00
if (filename_trim.size() > 4 && filename_trim.compare(filename_trim.size()-4, std::string::npos, ".vcd") == 0) {
filename_trim.erase(filename_trim.size()-4);
2025-09-09 14:50:48 +02:00
tmp_file = stringf("%s/converted_%s.fst", get_base_tmpdir(), filename_trim);
std::string cmd = stringf("vcd2fst %s %s", filename, tmp_file);
log("Exec: %s\n", cmd);
if (run_command(cmd) != 0)
log_cmd_error("Shell command failed!\n");
filename = tmp_file;
}
2025-02-14 13:26:26 +01:00
#endif
2022-01-31 10:52:47 +01:00
const std::vector<std::string> g_units = { "s", "ms", "us", "ns", "ps", "fs", "as", "zs" };
2022-01-26 10:23:38 +01:00
ctx = (fstReaderContext *)fstReaderOpen(filename.c_str());
2022-02-04 11:11:36 +01:00
if (!ctx)
log_error("Error opening '%s' as FST file\n", filename);
2022-01-31 10:52:47 +01:00
int scale = (int)fstReaderGetTimescale(ctx);
timescale = pow(10.0, scale);
timescale_str = "";
int unit = 0;
int zeros = 0;
if (scale > 0) {
zeros = scale;
} else {
if ((scale % 3) == 0) {
zeros = (-scale % 3);
unit = (-scale / 3);
} else {
zeros = 3 - (-scale % 3);
unit = (-scale / 3) + 1;
}
}
for (int i=0;i<zeros; i++) timescale_str += "0";
timescale_str += g_units[unit];
2022-01-26 10:23:38 +01:00
extractVarNames();
}
FstData::~FstData()
{
if (ctx)
fstReaderClose(ctx);
if (!tmp_file.empty())
remove(tmp_file.c_str());
2022-01-26 10:23:38 +01:00
}
uint64_t FstData::getStartTime() { return fstReaderGetStartTime(ctx); }
uint64_t FstData::getEndTime() { return fstReaderGetEndTime(ctx); }
fstdata: Handle square/angle bracket replacemnt, change memory handling When writing VCDs smtbmc replaces square brackets with angle brackets to avoid the issues with VCD readers misinterpreting such signal names. For memory addresses it also uses angle brackets and hexadecimal addresses, while other tools will use square brackets and decimal addresses. Previously the code handled both forms of memory addresses, assuming that any signal that looks like a memory address is a memory address. This is not the case when the user uses regular signals whose names include square brackets _or_ when the verific frontend generates such names to represent various constructs. With this change all angular brackets are turned into square brackets when reading the trace _and_ when performing a signal lookup. This means no matter which kind of brackets are used in the design or in the VCD signals will be matched. This will not handle multiple signals that are the same apart from replacing square/angle brackets, but this will cause issues during the VCD writing of smtbmc already. It still uses the distinction between square and angle brackets for memories to decide whether the address is hex or decimal, but even if something looks like a memory and is added to the `memory_to_handle` data, the plain signal added to `name_to_handle` is used as-is, without rewriting the address. This last change is needed to successfully match verific generated signal names that look like memory addresses while keeping memories working at the same time. It may cause regressions when VCD generation was done with a design that had memories but simulation is done with a design where the memories were mapped to registers. This seems like an unusual setup, but could be worked around with some further changes should this be required.
2022-11-07 11:55:22 +01:00
static void normalize_brackets(std::string &str)
{
for (auto &c : str) {
if (c == '<')
c = '[';
else if (c == '>')
c = ']';
}
}
2022-01-26 10:23:38 +01:00
fstHandle FstData::getHandle(std::string name) {
fstdata: Handle square/angle bracket replacemnt, change memory handling When writing VCDs smtbmc replaces square brackets with angle brackets to avoid the issues with VCD readers misinterpreting such signal names. For memory addresses it also uses angle brackets and hexadecimal addresses, while other tools will use square brackets and decimal addresses. Previously the code handled both forms of memory addresses, assuming that any signal that looks like a memory address is a memory address. This is not the case when the user uses regular signals whose names include square brackets _or_ when the verific frontend generates such names to represent various constructs. With this change all angular brackets are turned into square brackets when reading the trace _and_ when performing a signal lookup. This means no matter which kind of brackets are used in the design or in the VCD signals will be matched. This will not handle multiple signals that are the same apart from replacing square/angle brackets, but this will cause issues during the VCD writing of smtbmc already. It still uses the distinction between square and angle brackets for memories to decide whether the address is hex or decimal, but even if something looks like a memory and is added to the `memory_to_handle` data, the plain signal added to `name_to_handle` is used as-is, without rewriting the address. This last change is needed to successfully match verific generated signal names that look like memory addresses while keeping memories working at the same time. It may cause regressions when VCD generation was done with a design that had memories but simulation is done with a design where the memories were mapped to registers. This seems like an unusual setup, but could be worked around with some further changes should this be required.
2022-11-07 11:55:22 +01:00
normalize_brackets(name);
2022-01-26 10:23:38 +01:00
if (name_to_handle.find(name) != name_to_handle.end())
return name_to_handle[name];
else
2022-01-28 14:20:16 +01:00
return 0;
2022-01-26 10:23:38 +01:00
};
dict<int,fstHandle> FstData::getMemoryHandles(std::string name) {
if (memory_to_handle.find(name) != memory_to_handle.end())
return memory_to_handle[name];
else
return dict<int,fstHandle>();
};
2022-01-26 10:23:38 +01:00
static std::string remove_spaces(std::string str)
{
str.erase(std::remove(str.begin(), str.end(), ' '), str.end());
return str;
}
void FstData::extractVarNames()
{
struct fstHier *h;
2022-03-30 15:55:15 +02:00
std::string fst_scope_name;
2022-01-26 10:23:38 +01:00
2026-02-27 20:54:43 +01:00
// Track nested fork scopes using a stack to handle nested packed structs
// Begins with outmost scope and ends with innermost scope
2026-03-04 00:15:26 +01:00
// Scopes are not normalized on the stack
2026-02-27 20:54:43 +01:00
std::vector<std::string> fork_scope_stack;
2026-02-27 20:02:46 +01:00
// Start fork handles after the maximum real handle from FST file to avoid collisions
fstHandle next_fork_handle = fstReaderGetMaxHandle(ctx) + 1;
2026-03-04 00:15:26 +01:00
// Map of fork scopes to their members, which are all normalized
2026-02-27 19:22:06 +01:00
std::map<std::string, std::vector<fstHandle>> fork_scopes;
2022-01-26 10:23:38 +01:00
while ((h = fstReaderIterateHier(ctx))) {
switch (h->htyp) {
2026-02-27 20:27:37 +01:00
case FST_HT_SCOPE: {
fst_scope_name = fstReaderPushScope(ctx, h->u.scope.name, NULL);
2026-02-27 20:54:43 +01:00
// Fork scopes are identified by FST_ST_VCD_FORK and are pushed onto the stack
2026-02-27 20:27:37 +01:00
if (h->u.scope.typ == FST_ST_VCD_FORK) {
2026-02-27 20:54:43 +01:00
fork_scope_stack.push_back(fst_scope_name);
// Create new vector that contains struct members
2026-02-27 21:19:14 +01:00
normalize_brackets(fst_scope_name);
2026-02-27 20:54:43 +01:00
fork_scopes[fst_scope_name] = std::vector<fstHandle>();
2026-02-27 20:27:37 +01:00
}
break;
2022-01-26 10:23:38 +01:00
}
2026-02-27 20:27:37 +01:00
case FST_HT_UPSCOPE: {
2026-02-27 20:54:43 +01:00
if (!fork_scope_stack.empty() && fork_scope_stack.back() == fst_scope_name) {
// Assign a unique handle to this fork scope and increment for future forks
2026-02-27 20:27:37 +01:00
fstHandle fork_handle = next_fork_handle++;
// Map normalized scope name to the handle for future lookups via getHandle()
2026-02-27 20:54:43 +01:00
normalize_brackets(fst_scope_name);
name_to_handle[fst_scope_name] = fork_handle;
2026-02-27 20:27:37 +01:00
// Copy the extracted members of the fork scope to the fork scope members map
// for value lookups in valueOf()
2026-02-27 20:54:43 +01:00
fork_scope_members[fork_handle] = fork_scopes[fst_scope_name];
2026-02-27 20:27:37 +01:00
2026-02-27 20:54:43 +01:00
// If this is a nested fork scope, add its handle to the parent fork scope
if (fork_scope_stack.size() > 1) {
std::string parent_fork = fork_scope_stack[fork_scope_stack.size() - 2];
2026-03-04 00:15:26 +01:00
normalize_brackets(parent_fork);
2026-02-27 20:54:43 +01:00
fork_scopes[parent_fork].push_back(fork_handle);
}
// Pop this fork scope from the stack
fork_scope_stack.pop_back();
2026-02-27 20:27:37 +01:00
}
fst_scope_name = fstReaderPopScope(ctx);
break;
2022-01-26 10:23:38 +01:00
}
case FST_HT_VAR: {
FstVar var;
var.id = h->u.var.handle;
var.is_alias = h->u.var.is_alias;
var.is_reg = (fstVarType)h->u.var.typ == FST_VT_VCD_REG;
2022-01-26 10:23:38 +01:00
var.name = remove_spaces(h->u.var.name);
2022-03-30 15:55:15 +02:00
var.scope = fst_scope_name;
fstdata: Handle square/angle bracket replacemnt, change memory handling When writing VCDs smtbmc replaces square brackets with angle brackets to avoid the issues with VCD readers misinterpreting such signal names. For memory addresses it also uses angle brackets and hexadecimal addresses, while other tools will use square brackets and decimal addresses. Previously the code handled both forms of memory addresses, assuming that any signal that looks like a memory address is a memory address. This is not the case when the user uses regular signals whose names include square brackets _or_ when the verific frontend generates such names to represent various constructs. With this change all angular brackets are turned into square brackets when reading the trace _and_ when performing a signal lookup. This means no matter which kind of brackets are used in the design or in the VCD signals will be matched. This will not handle multiple signals that are the same apart from replacing square/angle brackets, but this will cause issues during the VCD writing of smtbmc already. It still uses the distinction between square and angle brackets for memories to decide whether the address is hex or decimal, but even if something looks like a memory and is added to the `memory_to_handle` data, the plain signal added to `name_to_handle` is used as-is, without rewriting the address. This last change is needed to successfully match verific generated signal names that look like memory addresses while keeping memories working at the same time. It may cause regressions when VCD generation was done with a design that had memories but simulation is done with a design where the memories were mapped to registers. This seems like an unusual setup, but could be worked around with some further changes should this be required.
2022-11-07 11:55:22 +01:00
normalize_brackets(var.scope);
2022-01-26 10:23:38 +01:00
var.width = h->u.var.length;
2026-02-27 20:27:37 +01:00
vars.push_back(var);
if (!var.is_alias)
handle_to_var[h->u.var.handle] = var;
2026-02-27 20:02:46 +01:00
2026-02-27 20:54:43 +01:00
// Add variable to the innermost fork scope in the fork scope stack
if (!fork_scope_stack.empty()) {
2026-03-04 00:15:26 +01:00
std::string current_fork = fork_scope_stack.back();
normalize_brackets(current_fork);
fork_scopes[current_fork].push_back(h->u.var.handle);
2026-02-27 20:27:37 +01:00
}
2026-02-27 20:02:46 +01:00
2026-02-27 20:29:31 +01:00
std::string clean_name;
bool has_space = false;
2022-01-26 10:23:38 +01:00
for(size_t i=0;i<strlen(h->u.var.name);i++)
{
char c = h->u.var.name[i];
if(c==' ') { has_space = true; break; }
2022-01-26 10:23:38 +01:00
clean_name += c;
}
2022-01-28 14:10:39 +01:00
if (clean_name[0]=='\\')
clean_name = clean_name.substr(1);
if (!has_space) {
size_t pos = clean_name.find_last_of("[");
std::string index_or_range = clean_name.substr(pos+1);
if (index_or_range.find(":") != std::string::npos) {
clean_name = clean_name.substr(0,pos);
}
}
size_t pos = clean_name.find_last_of("<");
fstdata: Handle square/angle bracket replacemnt, change memory handling When writing VCDs smtbmc replaces square brackets with angle brackets to avoid the issues with VCD readers misinterpreting such signal names. For memory addresses it also uses angle brackets and hexadecimal addresses, while other tools will use square brackets and decimal addresses. Previously the code handled both forms of memory addresses, assuming that any signal that looks like a memory address is a memory address. This is not the case when the user uses regular signals whose names include square brackets _or_ when the verific frontend generates such names to represent various constructs. With this change all angular brackets are turned into square brackets when reading the trace _and_ when performing a signal lookup. This means no matter which kind of brackets are used in the design or in the VCD signals will be matched. This will not handle multiple signals that are the same apart from replacing square/angle brackets, but this will cause issues during the VCD writing of smtbmc already. It still uses the distinction between square and angle brackets for memories to decide whether the address is hex or decimal, but even if something looks like a memory and is added to the `memory_to_handle` data, the plain signal added to `name_to_handle` is used as-is, without rewriting the address. This last change is needed to successfully match verific generated signal names that look like memory addresses while keeping memories working at the same time. It may cause regressions when VCD generation was done with a design that had memories but simulation is done with a design where the memories were mapped to registers. This seems like an unusual setup, but could be worked around with some further changes should this be required.
2022-11-07 11:55:22 +01:00
if (pos != std::string::npos && clean_name.back() == '>') {
std::string mem_cell = clean_name.substr(0, pos);
fstdata: Handle square/angle bracket replacemnt, change memory handling When writing VCDs smtbmc replaces square brackets with angle brackets to avoid the issues with VCD readers misinterpreting such signal names. For memory addresses it also uses angle brackets and hexadecimal addresses, while other tools will use square brackets and decimal addresses. Previously the code handled both forms of memory addresses, assuming that any signal that looks like a memory address is a memory address. This is not the case when the user uses regular signals whose names include square brackets _or_ when the verific frontend generates such names to represent various constructs. With this change all angular brackets are turned into square brackets when reading the trace _and_ when performing a signal lookup. This means no matter which kind of brackets are used in the design or in the VCD signals will be matched. This will not handle multiple signals that are the same apart from replacing square/angle brackets, but this will cause issues during the VCD writing of smtbmc already. It still uses the distinction between square and angle brackets for memories to decide whether the address is hex or decimal, but even if something looks like a memory and is added to the `memory_to_handle` data, the plain signal added to `name_to_handle` is used as-is, without rewriting the address. This last change is needed to successfully match verific generated signal names that look like memory addresses while keeping memories working at the same time. It may cause regressions when VCD generation was done with a design that had memories but simulation is done with a design where the memories were mapped to registers. This seems like an unusual setup, but could be worked around with some further changes should this be required.
2022-11-07 11:55:22 +01:00
normalize_brackets(mem_cell);
std::string addr = clean_name.substr(pos+1);
addr.pop_back(); // remove closing bracket
char *endptr;
int mem_addr = strtol(addr.c_str(), &endptr, 16);
if (*endptr) {
log_debug("Error parsing memory address in : %s\n", clean_name);
} else {
memory_to_handle[var.scope+"."+mem_cell][mem_addr] = var.id;
}
}
pos = clean_name.find_last_of("[");
fstdata: Handle square/angle bracket replacemnt, change memory handling When writing VCDs smtbmc replaces square brackets with angle brackets to avoid the issues with VCD readers misinterpreting such signal names. For memory addresses it also uses angle brackets and hexadecimal addresses, while other tools will use square brackets and decimal addresses. Previously the code handled both forms of memory addresses, assuming that any signal that looks like a memory address is a memory address. This is not the case when the user uses regular signals whose names include square brackets _or_ when the verific frontend generates such names to represent various constructs. With this change all angular brackets are turned into square brackets when reading the trace _and_ when performing a signal lookup. This means no matter which kind of brackets are used in the design or in the VCD signals will be matched. This will not handle multiple signals that are the same apart from replacing square/angle brackets, but this will cause issues during the VCD writing of smtbmc already. It still uses the distinction between square and angle brackets for memories to decide whether the address is hex or decimal, but even if something looks like a memory and is added to the `memory_to_handle` data, the plain signal added to `name_to_handle` is used as-is, without rewriting the address. This last change is needed to successfully match verific generated signal names that look like memory addresses while keeping memories working at the same time. It may cause regressions when VCD generation was done with a design that had memories but simulation is done with a design where the memories were mapped to registers. This seems like an unusual setup, but could be worked around with some further changes should this be required.
2022-11-07 11:55:22 +01:00
if (pos != std::string::npos && clean_name.back() == ']') {
std::string mem_cell = clean_name.substr(0, pos);
fstdata: Handle square/angle bracket replacemnt, change memory handling When writing VCDs smtbmc replaces square brackets with angle brackets to avoid the issues with VCD readers misinterpreting such signal names. For memory addresses it also uses angle brackets and hexadecimal addresses, while other tools will use square brackets and decimal addresses. Previously the code handled both forms of memory addresses, assuming that any signal that looks like a memory address is a memory address. This is not the case when the user uses regular signals whose names include square brackets _or_ when the verific frontend generates such names to represent various constructs. With this change all angular brackets are turned into square brackets when reading the trace _and_ when performing a signal lookup. This means no matter which kind of brackets are used in the design or in the VCD signals will be matched. This will not handle multiple signals that are the same apart from replacing square/angle brackets, but this will cause issues during the VCD writing of smtbmc already. It still uses the distinction between square and angle brackets for memories to decide whether the address is hex or decimal, but even if something looks like a memory and is added to the `memory_to_handle` data, the plain signal added to `name_to_handle` is used as-is, without rewriting the address. This last change is needed to successfully match verific generated signal names that look like memory addresses while keeping memories working at the same time. It may cause regressions when VCD generation was done with a design that had memories but simulation is done with a design where the memories were mapped to registers. This seems like an unusual setup, but could be worked around with some further changes should this be required.
2022-11-07 11:55:22 +01:00
normalize_brackets(mem_cell);
std::string addr = clean_name.substr(pos+1);
addr.pop_back(); // remove closing bracket
char *endptr;
int mem_addr = strtol(addr.c_str(), &endptr, 10);
if (*endptr) {
log_debug("Error parsing memory address in : %s\n", clean_name);
} else {
memory_to_handle[var.scope+"."+mem_cell][mem_addr] = var.id;
}
}
fstdata: Handle square/angle bracket replacemnt, change memory handling When writing VCDs smtbmc replaces square brackets with angle brackets to avoid the issues with VCD readers misinterpreting such signal names. For memory addresses it also uses angle brackets and hexadecimal addresses, while other tools will use square brackets and decimal addresses. Previously the code handled both forms of memory addresses, assuming that any signal that looks like a memory address is a memory address. This is not the case when the user uses regular signals whose names include square brackets _or_ when the verific frontend generates such names to represent various constructs. With this change all angular brackets are turned into square brackets when reading the trace _and_ when performing a signal lookup. This means no matter which kind of brackets are used in the design or in the VCD signals will be matched. This will not handle multiple signals that are the same apart from replacing square/angle brackets, but this will cause issues during the VCD writing of smtbmc already. It still uses the distinction between square and angle brackets for memories to decide whether the address is hex or decimal, but even if something looks like a memory and is added to the `memory_to_handle` data, the plain signal added to `name_to_handle` is used as-is, without rewriting the address. This last change is needed to successfully match verific generated signal names that look like memory addresses while keeping memories working at the same time. It may cause regressions when VCD generation was done with a design that had memories but simulation is done with a design where the memories were mapped to registers. This seems like an unusual setup, but could be worked around with some further changes should this be required.
2022-11-07 11:55:22 +01:00
normalize_brackets(clean_name);
2022-01-26 10:23:38 +01:00
name_to_handle[var.scope+"."+clean_name] = h->u.var.handle;
break;
}
}
}
}
static void reconstruct_clb_varlen_attimes(void *user_data, uint64_t pnt_time, fstHandle pnt_facidx, const unsigned char *pnt_value, uint32_t plen)
{
FstData *ptr = (FstData*)user_data;
ptr->reconstruct_callback_attimes(pnt_time, pnt_facidx, pnt_value, plen);
}
static void reconstruct_clb_attimes(void *user_data, uint64_t pnt_time, fstHandle pnt_facidx, const unsigned char *pnt_value)
{
FstData *ptr = (FstData*)user_data;
uint32_t plen = (pnt_value) ? strlen((const char *)pnt_value) : 0;
ptr->reconstruct_callback_attimes(pnt_time, pnt_facidx, pnt_value, plen);
}
void FstData::reconstruct_callback_attimes(uint64_t pnt_time, fstHandle pnt_facidx, const unsigned char *pnt_value, uint32_t /* plen */)
{
if (pnt_time > end_time || !pnt_value) return;
if (curr_cycle > last_cycle) return;
2022-01-26 10:23:38 +01:00
// if we are past the timestamp
bool is_clock = false;
if (!all_samples) {
for(auto &s : clk_signals) {
if (s==pnt_facidx) {
is_clock=true;
break;
}
2022-01-26 10:23:38 +01:00
}
}
if (pnt_time > past_time) {
past_data = last_data;
past_time = pnt_time;
}
2022-01-26 10:23:38 +01:00
if (pnt_time > last_time) {
if (all_samples) {
callback(last_time);
curr_cycle++;
last_time = pnt_time;
} else {
if (is_clock) {
std::string val = std::string((const char *)pnt_value);
std::string prev = past_data[pnt_facidx];
if ((prev!="1" && val=="1") || (prev!="0" && val=="0")) {
callback(last_time);
curr_cycle++;
last_time = pnt_time;
}
}
2022-01-26 10:23:38 +01:00
}
}
// always update last_data
last_data[pnt_facidx] = std::string((const char *)pnt_value);
2022-01-26 10:23:38 +01:00
}
void FstData::reconstructAllAtTimes(std::vector<fstHandle> &signal, uint64_t start, uint64_t end, unsigned int end_cycle, CallbackFunction cb)
2022-01-26 10:23:38 +01:00
{
clk_signals = signal;
callback = cb;
start_time = start;
end_time = end;
curr_cycle = 0;
last_cycle = end_cycle;
2022-01-28 12:50:41 +01:00
last_data.clear();
last_time = start_time;
past_data.clear();
past_time = start_time;
all_samples = clk_signals.empty();
2022-01-28 12:50:41 +01:00
fstReaderSetUnlimitedTimeRange(ctx);
2022-01-26 10:23:38 +01:00
fstReaderSetFacProcessMaskAll(ctx);
fstReaderIterBlocks2(ctx, reconstruct_clb_attimes, reconstruct_clb_varlen_attimes, this, nullptr);
if (last_time!=end_time && curr_cycle <= last_cycle) {
2022-04-22 15:24:02 +02:00
past_data = last_data;
callback(last_time);
curr_cycle++;
}
if (curr_cycle <= last_cycle) {
past_data = last_data;
callback(end_time);
curr_cycle++;
2022-04-22 15:24:02 +02:00
}
2022-01-26 10:23:38 +01:00
}
std::string FstData::valueOf(fstHandle signal)
2022-01-26 10:23:38 +01:00
{
2026-02-27 19:22:06 +01:00
// Check if this is a fork scope (struct)
auto it = fork_scope_members.find(signal);
if (it != fork_scope_members.end()) {
std::string result;
const std::vector<fstHandle>& members = it->second;
2026-02-27 20:11:05 +01:00
// Iterate over members of the struct to get concatenated value.
// The first declared member is MSB in SystemVerilog packed structs
2026-02-27 21:17:43 +01:00
for (auto m = members.begin(); m != members.end(); m++) {
2026-02-27 19:22:06 +01:00
fstHandle member = *m;
std::string member_val;
2026-02-27 20:54:43 +01:00
// Check if this member is itself a nested fork scope (struct)
if (fork_scope_members.find(member) != fork_scope_members.end()) {
// Recursively get the value of the nested struct
member_val = valueOf(member);
} else {
// Regular variable - look up in past_data
int expected_width = 0;
// Get the declared width of this member
if (handle_to_var.find(member) != handle_to_var.end()) {
expected_width = handle_to_var[member].width;
}
// Get the current value of the member
if (past_data.find(member) != past_data.end()) {
member_val = past_data[member];
// Pad with zeros to the expected width of the member
if (expected_width > 0 && (int)member_val.length() < expected_width) {
member_val = std::string(expected_width - member_val.length(), '0') + member_val;
}
} else if (expected_width > 0) {
// No value yet, use X to pad
member_val = std::string(expected_width, 'x');
} else { // fallback to X
member_val = "x";
}
}
2026-02-27 20:11:05 +01:00
// Concatenate the member value to the overall struct value
2026-02-27 19:22:06 +01:00
result += member_val;
}
2026-02-27 19:22:06 +01:00
return result;
}
2026-02-27 19:22:06 +01:00
// Normal signal handling
2026-02-27 20:40:13 +01:00
if (past_data.find(signal) == past_data.end()) {
2026-02-27 20:27:37 +01:00
return std::string(handle_to_var[signal].width, 'x');
}
return past_data[signal];
2022-01-26 10:23:38 +01:00
}
2026-03-02 00:39:35 +01:00
int FstData::getWidth(fstHandle signal)
{
// Check if signal is a fork scope (struct)
if (fork_scope_members.count(signal)) {
// Sum the widths of all members of the fork scope, which may be forks themselves
int width = 0;
for (fstHandle member : fork_scope_members[signal]) {
width += getWidth(member);
}
return width;
}
if (handle_to_var.count(signal)) {
return handle_to_var[signal].width;
}
// Signal not found
log_warning("Signal %d was not extracted from file...\n", signal);
return 0;
}
2026-03-02 00:39:35 +01:00
// Auto-discover scope from FST by finding the top module
2026-03-06 19:23:31 +01:00
std::string FstData::autoScope(Module *topmod) {
2026-03-02 00:39:35 +01:00
2026-03-06 18:51:45 +01:00
log("Auto-discovering scopes from file...\n");
2026-03-02 00:39:35 +01:00
std::string top = RTLIL::unescape_id(topmod->name);
2026-03-02 21:57:37 +01:00
// Map top module port name to their bit widths (RTL reference point)
2026-03-02 20:05:44 +01:00
dict<std::string, int> top2widths;
2026-03-02 00:39:35 +01:00
for (auto wire : topmod->wires()) {
if (wire->port_input || wire->port_output) {
2026-03-02 20:05:44 +01:00
top2widths[RTLIL::unescape_id(wire->name)] = wire->width;
2026-03-02 00:39:35 +01:00
}
}
2026-03-02 21:07:59 +01:00
log("Extracted %d ports from top module\n", GetSize(top2widths));
2026-03-02 00:39:35 +01:00
2026-03-02 20:05:44 +01:00
// For each scope, track the number of matching ports
dict<std::string, int> scopes2matches;
// Use name_to_handle to get all signals from the FST file
for (auto entry : name_to_handle) {
std::string name = entry.first;
fstHandle handle = entry.second;
// Extract signal name and scope using '.'
// Signal names of form '{scope}.signal_name' with scope potentially
// having zero to multiple '.'
size_t last_dot = name.find_last_of('.');
if (last_dot != std::string::npos) { // no '.' means no scope/signal extraction is possible
std::string scope = name.substr(0, last_dot);
std::string signal_name = name.substr(last_dot + 1);
2026-03-06 18:51:45 +01:00
// Check that signal is in the top module and width matches
if (top2widths.count(signal_name)) {
int signal_width = getWidth(handle);
if (signal_width == top2widths[signal_name]) {
scopes2matches[scope]++;
}
}
2026-03-02 00:39:35 +01:00
}
}
2026-03-02 20:05:44 +01:00
2026-03-06 18:51:45 +01:00
// Find scopes with exact matches and add to array
std::vector<std::string> results;
2026-03-02 20:05:44 +01:00
for (const auto& entry : scopes2matches) {
int num_matches = entry.second;
if (num_matches == GetSize(top2widths)) {
std::string scope = entry.first;
2026-03-06 18:51:45 +01:00
results.push_back(scope);
2026-03-02 00:39:35 +01:00
}
}
2026-03-06 18:51:45 +01:00
if (results.empty()) {
log_warning("Could not auto-discover scope for module '%s'...\n",
RTLIL::unescape_id(topmod->name).c_str());
2026-03-06 19:23:31 +01:00
return "";
2026-03-06 18:51:45 +01:00
} else {
log("Found %d scopes for module '%s':\n", GetSize(results), RTLIL::unescape_id(topmod->name).c_str());
for (const auto& scope : results) {
log(" %s\n", scope.c_str());
}
2026-03-06 19:23:31 +01:00
if (results.size() > 1) {
log_warning("Multiple scopes found for module '%s'. Using the first one.\n",
RTLIL::unescape_id(topmod->name).c_str());
}
std::string scope = results[0];
2026-03-02 20:05:44 +01:00
}
2026-03-06 19:23:31 +01:00
return scope;
2026-03-02 00:39:35 +01:00
}