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
|
|
|
|
2022-02-28 09:09:07 +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)
|
|
|
|
|
{
|
2022-02-28 09:09:07 +01:00
|
|
|
#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);
|
2025-09-11 07:25:26 +02:00
|
|
|
log("Exec: %s\n", cmd);
|
2022-02-28 09:09:07 +01:00
|
|
|
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)
|
2025-09-11 07:25:26 +02:00
|
|
|
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);
|
2022-02-28 09:09:07 +01:00
|
|
|
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); }
|
|
|
|
|
|
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) {
|
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
|
|
|
};
|
|
|
|
|
|
2022-05-04 10:41:04 +02: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;
|
2026-02-27 19:12:43 +01:00
|
|
|
|
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;
|
2022-03-16 14:35:39 +01:00
|
|
|
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;
|
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;
|
2024-09-30 14:08:35 +02:00
|
|
|
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];
|
2024-09-30 14:08:35 +02:00
|
|
|
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);
|
2024-09-30 14:08:35 +02:00
|
|
|
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);
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-05-04 10:41:04 +02:00
|
|
|
size_t pos = clean_name.find_last_of("<");
|
2022-11-07 11:55:22 +01:00
|
|
|
if (pos != std::string::npos && clean_name.back() == '>') {
|
2022-05-04 10:41:04 +02:00
|
|
|
std::string mem_cell = clean_name.substr(0, pos);
|
2022-11-07 11:55:22 +01:00
|
|
|
normalize_brackets(mem_cell);
|
2022-05-04 10:41:04 +02:00
|
|
|
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) {
|
2025-09-23 00:04:17 +02:00
|
|
|
log_debug("Error parsing memory address in : %s\n", clean_name);
|
2022-05-06 08:05:23 +02:00
|
|
|
} else {
|
|
|
|
|
memory_to_handle[var.scope+"."+mem_cell][mem_addr] = var.id;
|
2022-05-04 10:41:04 +02:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
pos = clean_name.find_last_of("[");
|
2022-11-07 11:55:22 +01:00
|
|
|
if (pos != std::string::npos && clean_name.back() == ']') {
|
2022-05-04 10:41:04 +02:00
|
|
|
std::string mem_cell = clean_name.substr(0, pos);
|
2022-11-07 11:55:22 +01:00
|
|
|
normalize_brackets(mem_cell);
|
2022-05-04 10:41:04 +02:00
|
|
|
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) {
|
2025-09-23 00:04:17 +02:00
|
|
|
log_debug("Error parsing memory address in : %s\n", clean_name);
|
2022-05-06 08:05:23 +02:00
|
|
|
} else {
|
|
|
|
|
memory_to_handle[var.scope+"."+mem_cell][mem_addr] = var.id;
|
2022-05-04 10:41:04 +02:00
|
|
|
}
|
|
|
|
|
}
|
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 */)
|
|
|
|
|
{
|
2023-01-17 12:58:08 +01:00
|
|
|
if (pnt_time > end_time || !pnt_value) return;
|
2025-05-12 02:48:01 +02:00
|
|
|
if (curr_cycle > last_cycle) return;
|
2022-01-26 10:23:38 +01:00
|
|
|
// if we are past the timestamp
|
2022-02-15 09:30:42 +01:00
|
|
|
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
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2022-02-15 09:30:42 +01:00
|
|
|
if (pnt_time > past_time) {
|
|
|
|
|
past_data = last_data;
|
|
|
|
|
past_time = pnt_time;
|
|
|
|
|
}
|
2022-01-26 10:23:38 +01:00
|
|
|
|
2022-02-15 09:30:42 +01:00
|
|
|
if (pnt_time > last_time) {
|
|
|
|
|
if (all_samples) {
|
|
|
|
|
callback(last_time);
|
2025-05-12 02:48:01 +02:00
|
|
|
curr_cycle++;
|
2022-02-15 09:30:42 +01:00
|
|
|
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);
|
2025-05-12 02:48:01 +02:00
|
|
|
curr_cycle++;
|
2022-02-15 09:30:42 +01:00
|
|
|
last_time = pnt_time;
|
|
|
|
|
}
|
|
|
|
|
}
|
2022-01-26 10:23:38 +01:00
|
|
|
}
|
|
|
|
|
}
|
2022-02-15 09:30:42 +01:00
|
|
|
// always update last_data
|
|
|
|
|
last_data[pnt_facidx] = std::string((const char *)pnt_value);
|
2022-01-26 10:23:38 +01:00
|
|
|
}
|
|
|
|
|
|
2025-05-12 02:48:01 +02: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
|
|
|
{
|
2022-02-15 09:30:42 +01:00
|
|
|
clk_signals = signal;
|
|
|
|
|
callback = cb;
|
|
|
|
|
start_time = start;
|
|
|
|
|
end_time = end;
|
2025-05-12 02:48:01 +02:00
|
|
|
curr_cycle = 0;
|
|
|
|
|
last_cycle = end_cycle;
|
2022-01-28 12:50:41 +01:00
|
|
|
last_data.clear();
|
2022-02-15 09:30:42 +01:00
|
|
|
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);
|
2025-05-12 03:18:19 +02:00
|
|
|
if (last_time!=end_time && curr_cycle <= last_cycle) {
|
2022-04-22 15:24:02 +02:00
|
|
|
past_data = last_data;
|
|
|
|
|
callback(last_time);
|
2025-05-12 02:48:01 +02:00
|
|
|
curr_cycle++;
|
|
|
|
|
}
|
2025-05-12 03:18:19 +02:00
|
|
|
if (curr_cycle <= last_cycle) {
|
2025-05-12 02:48:01 +02:00
|
|
|
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
|
|
|
}
|
|
|
|
|
|
2022-02-15 09:30:42 +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 19:12:43 +01:00
|
|
|
}
|
|
|
|
|
}
|
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:12:43 +01:00
|
|
|
}
|
2026-02-27 19:22:06 +01:00
|
|
|
return result;
|
2026-02-27 19:12:43 +01:00
|
|
|
}
|
|
|
|
|
|
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');
|
2024-10-02 11:18:19 +02:00
|
|
|
}
|
2022-02-15 09:30:42 +01:00
|
|
|
return past_data[signal];
|
2022-01-26 10:23:38 +01:00
|
|
|
}
|
2026-03-02 00:39:35 +01:00
|
|
|
|
|
|
|
|
// Auto-discover scope from FST by finding the top module
|
|
|
|
|
std::string FstData::autoScope(Module *topmod) {
|
|
|
|
|
|
|
|
|
|
log("Auto-discovering scope from file...\n");
|
|
|
|
|
std::string top = RTLIL::unescape_id(topmod->name);
|
|
|
|
|
|
2026-03-02 21:19:09 +01:00
|
|
|
log("Available scopes:\n");
|
|
|
|
|
std::set<std::string> unique_scopes;
|
|
|
|
|
for (const auto& var : vars) {
|
|
|
|
|
unique_scopes.insert(var.scope);
|
|
|
|
|
}
|
|
|
|
|
for (const auto& scope : unique_scopes) {
|
|
|
|
|
log(" %s\n", scope.c_str());
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-02 00:39:35 +01:00
|
|
|
// Option 1 - Instance based scope matching
|
|
|
|
|
// Will fail if the DUT instance name != the top module name
|
|
|
|
|
log("Trying instance-based scope matching...\n");
|
|
|
|
|
for (const auto& var : vars) {
|
|
|
|
|
// Check if this scope ends with our top module
|
|
|
|
|
log_debug("Checking scope: %s\n", var.scope.c_str());
|
|
|
|
|
if (var.scope == top ||
|
|
|
|
|
var.scope.find("." + top) != std::string::npos) {
|
|
|
|
|
// Extract the full path up to (and including) the top module
|
|
|
|
|
size_t pos = var.scope.find(top);
|
|
|
|
|
if (pos != std::string::npos) {
|
2026-03-02 20:05:44 +01:00
|
|
|
std::string scope = var.scope.substr(0, pos + top.length());
|
|
|
|
|
return scope;
|
2026-03-02 00:39:35 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-03-02 21:57:37 +01:00
|
|
|
// Option 2 - Port based scope matching
|
2026-03-02 00:39:35 +01:00
|
|
|
// Matches based on exact port name matching of the top module
|
|
|
|
|
log("Trying port-based scope matching...\n");
|
|
|
|
|
|
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;
|
2026-03-02 00:39:35 +01:00
|
|
|
for (const auto& var : vars) {
|
|
|
|
|
|
|
|
|
|
// Strip array '[]' notation from variable name
|
|
|
|
|
std::string var_name = var.name;
|
|
|
|
|
size_t bracket = var_name.find('[');
|
|
|
|
|
if (bracket != std::string::npos) {
|
|
|
|
|
var_name = var_name.substr(0, bracket);
|
|
|
|
|
}
|
2026-03-02 20:05:44 +01:00
|
|
|
|
|
|
|
|
// Check if this variable name matches one of our top module port names and width
|
|
|
|
|
if (top2widths.count(var_name) && top2widths[var_name] == var.width) {
|
|
|
|
|
scopes2matches[var.scope] += 1;
|
2026-03-02 00:39:35 +01:00
|
|
|
}
|
|
|
|
|
}
|
2026-03-02 20:05:44 +01:00
|
|
|
|
|
|
|
|
// Find scopes with exact matches
|
|
|
|
|
// If there is a tie, return the longest scope
|
|
|
|
|
std::string result = "";
|
|
|
|
|
for (const auto& entry : scopes2matches) {
|
|
|
|
|
int num_matches = entry.second;
|
|
|
|
|
if (num_matches == GetSize(top2widths)) {
|
|
|
|
|
std::string scope = entry.first;
|
|
|
|
|
if (result.empty() || scope.length() > result.length()) {
|
|
|
|
|
result = scope;
|
|
|
|
|
}
|
2026-03-02 00:39:35 +01:00
|
|
|
}
|
|
|
|
|
}
|
2026-03-02 20:05:44 +01:00
|
|
|
if (!result.empty()) {
|
|
|
|
|
return result;
|
|
|
|
|
}
|
2026-03-02 00:39:35 +01:00
|
|
|
|
|
|
|
|
// No match found
|
|
|
|
|
log_warning("Could not auto-discover scope for module '%s'...\n",
|
|
|
|
|
RTLIL::unescape_id(topmod->name).c_str());
|
|
|
|
|
return "";
|
|
|
|
|
}
|