3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2025-06-06 06:03:23 +00:00

cxxrtl: expose scope information in the C++ API.

This commit adds a `debug_scopes` container, which can collect metadata
about scopes in a design. Currently the only scope is that of a module.
A module scope can be represented either by a module and cell pair, or
a `$scopeinfo` cell in a flattened netlist. The metadata produced by
the C++ API is identical between these two cases, so flattening remains
transparent to a netlist with CXXRTL.

The existing `debug_items` method is deprecated. This isn't strictly
necessary, but the user experience is better if the path is provided
as e.g. `"top "` (as some VCD viewers make it awkward to select topmost
anonymous scope), and the upgrade flow encourages that, which should
reduce frustration later.

While the new `debug_items` method could still be broken in the future
as the C++ API permits, this seems unlikely since the debug information
can now capture all common netlist aspects and includes several
extension points (via `debug_item`, `debug_scope` types).

Also, naming of scope paths was normalized to `path` or `top_path`,
as applicable.
This commit is contained in:
Catherine 2024-02-13 17:43:53 +00:00
parent d903f47d41
commit 1a44645aef
5 changed files with 247 additions and 162 deletions

View file

@ -25,6 +25,7 @@
#include "kernel/mem.h"
#include "kernel/log.h"
#include "kernel/fmt.h"
#include "kernel/scopeinfo.h"
USING_YOSYS_NAMESPACE
PRIVATE_NAMESPACE_BEGIN
@ -2311,11 +2312,14 @@ struct CxxrtlWorker {
dict<RTLIL::IdString, RTLIL::Const> attributes = object->attributes;
// Inherently necessary to get access to the object, so a waste of space to emit.
attributes.erase(ID::hdlname);
// Internal Yosys attribute that should be removed but isn't.
attributes.erase(ID::module_not_derived);
dump_metadata_map(attributes);
}
void dump_debug_info_method(RTLIL::Module *module)
{
size_t count_scopes = 0;
size_t count_public_wires = 0;
size_t count_member_wires = 0;
size_t count_undriven = 0;
@ -2328,153 +2332,188 @@ struct CxxrtlWorker {
size_t count_skipped_wires = 0;
inc_indent();
f << indent << "assert(path.empty() || path[path.size() - 1] == ' ');\n";
for (auto wire : module->wires()) {
const auto &debug_wire_type = debug_wire_types[wire];
if (!wire->name.isPublic())
continue;
count_public_wires++;
switch (debug_wire_type.type) {
case WireType::BUFFERED:
case WireType::MEMBER: {
// Member wire
std::vector<std::string> flags;
if (wire->port_input && wire->port_output)
flags.push_back("INOUT");
else if (wire->port_output)
flags.push_back("OUTPUT");
else if (wire->port_input)
flags.push_back("INPUT");
bool has_driven_sync = false;
bool has_driven_comb = false;
bool has_undriven = false;
if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) {
for (auto bit : SigSpec(wire))
if (!bit_has_state.count(bit))
has_undriven = true;
else if (bit_has_state[bit])
has_driven_sync = true;
else
has_driven_comb = true;
} else if (wire->port_output) {
switch (cxxrtl_port_type(module, wire->name)) {
case CxxrtlPortType::SYNC:
has_driven_sync = true;
break;
case CxxrtlPortType::COMB:
has_driven_comb = true;
break;
case CxxrtlPortType::UNKNOWN:
has_driven_sync = has_driven_comb = true;
break;
}
} else {
has_undriven = true;
}
if (has_undriven)
flags.push_back("UNDRIVEN");
if (!has_driven_sync && !has_driven_comb && has_undriven)
count_undriven++;
if (has_driven_sync)
flags.push_back("DRIVEN_SYNC");
if (has_driven_sync && !has_driven_comb && !has_undriven)
count_driven_sync++;
if (has_driven_comb)
flags.push_back("DRIVEN_COMB");
if (!has_driven_sync && has_driven_comb && !has_undriven)
count_driven_comb++;
if (has_driven_sync + has_driven_comb + has_undriven > 1)
count_mixed_driver++;
f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire));
f << ", debug_item(" << mangle(wire) << ", " << wire->start_offset;
bool first = true;
for (auto flag : flags) {
if (first) {
first = false;
f << ", ";
} else {
f << "|";
}
f << "debug_item::" << flag;
}
f << "), ";
dump_debug_attrs(wire);
f << indent << "if (scopes) {\n";
inc_indent();
// The module is responsible for adding its own scope.
f << indent << "scopes->add(path.empty() ? path : path.substr(0, path.size() - 1), ";
f << escape_cxx_string(get_hdl_name(module)) << ", ";
dump_debug_attrs(module);
f << ", std::move(cell_attrs));\n";
count_scopes++;
// If there were any submodules that were flattened, the module is also responsible for adding them.
for (auto cell : module->cells()) {
if (cell->type != ID($scopeinfo)) continue;
if (cell->getParam(ID::TYPE).decode_string() == "module") {
auto module_attrs = scopeinfo_attributes(cell, ScopeinfoAttrs::Module);
auto cell_attrs = scopeinfo_attributes(cell, ScopeinfoAttrs::Cell);
cell_attrs.erase(ID::module_not_derived);
f << indent << "scopes->add(path + " << escape_cxx_string(get_hdl_name(cell)) << ", ";
f << escape_cxx_string(cell->get_string_attribute(ID(module))) << ", ";
dump_metadata_map(module_attrs);
f << ", ";
dump_metadata_map(cell_attrs);
f << ");\n";
count_member_wires++;
break;
}
case WireType::ALIAS: {
// Alias of a member wire
const RTLIL::Wire *aliasee = debug_wire_type.sig_subst.as_wire();
f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire));
f << ", debug_item(";
// If the aliasee is an outline, then the alias must be an outline, too; otherwise downstream
// tooling has no way to find out about the outline.
if (debug_wire_types[aliasee].is_outline())
f << "debug_eval_outline";
else
f << "debug_alias()";
f << ", " << mangle(aliasee) << ", " << wire->start_offset << "), ";
dump_debug_attrs(aliasee);
f << ");\n";
count_alias_wires++;
break;
}
case WireType::CONST: {
// Wire tied to a constant
f << indent << "static const value<" << wire->width << "> const_" << mangle(wire) << " = ";
dump_const(debug_wire_type.sig_subst.as_const());
f << ";\n";
f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire));
f << ", debug_item(const_" << mangle(wire) << ", " << wire->start_offset << "), ";
dump_debug_attrs(wire);
f << ");\n";
count_const_wires++;
break;
}
case WireType::OUTLINE: {
// Localized or inlined, but rematerializable wire
f << indent << "items.add(path + " << escape_cxx_string(get_hdl_name(wire));
f << ", debug_item(debug_eval_outline, " << mangle(wire) << ", " << wire->start_offset << "), ";
dump_debug_attrs(wire);
f << ");\n";
count_inline_wires++;
break;
}
default: {
// Localized or inlined wire with no debug information
count_skipped_wires++;
break;
}
} else log_assert(false && "Unknown $scopeinfo type");
count_scopes++;
}
}
if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) {
for (auto &mem : mod_memories[module]) {
if (!mem.memid.isPublic())
dec_indent();
f << indent << "}\n";
f << indent << "if (items) {\n";
inc_indent();
for (auto wire : module->wires()) {
const auto &debug_wire_type = debug_wire_types[wire];
if (!wire->name.isPublic())
continue;
f << indent << "items.add(path + " << escape_cxx_string(mem.packed ? get_hdl_name(mem.cell) : get_hdl_name(mem.mem));
f << ", debug_item(" << mangle(&mem) << ", ";
f << mem.start_offset << "), ";
if (mem.packed) {
dump_debug_attrs(mem.cell);
} else {
dump_debug_attrs(mem.mem);
count_public_wires++;
switch (debug_wire_type.type) {
case WireType::BUFFERED:
case WireType::MEMBER: {
// Member wire
std::vector<std::string> flags;
if (wire->port_input && wire->port_output)
flags.push_back("INOUT");
else if (wire->port_output)
flags.push_back("OUTPUT");
else if (wire->port_input)
flags.push_back("INPUT");
bool has_driven_sync = false;
bool has_driven_comb = false;
bool has_undriven = false;
if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) {
for (auto bit : SigSpec(wire))
if (!bit_has_state.count(bit))
has_undriven = true;
else if (bit_has_state[bit])
has_driven_sync = true;
else
has_driven_comb = true;
} else if (wire->port_output) {
switch (cxxrtl_port_type(module, wire->name)) {
case CxxrtlPortType::SYNC:
has_driven_sync = true;
break;
case CxxrtlPortType::COMB:
has_driven_comb = true;
break;
case CxxrtlPortType::UNKNOWN:
has_driven_sync = has_driven_comb = true;
break;
}
} else {
has_undriven = true;
}
if (has_undriven)
flags.push_back("UNDRIVEN");
if (!has_driven_sync && !has_driven_comb && has_undriven)
count_undriven++;
if (has_driven_sync)
flags.push_back("DRIVEN_SYNC");
if (has_driven_sync && !has_driven_comb && !has_undriven)
count_driven_sync++;
if (has_driven_comb)
flags.push_back("DRIVEN_COMB");
if (!has_driven_sync && has_driven_comb && !has_undriven)
count_driven_comb++;
if (has_driven_sync + has_driven_comb + has_undriven > 1)
count_mixed_driver++;
f << indent << "items->add(path + " << escape_cxx_string(get_hdl_name(wire));
f << ", debug_item(" << mangle(wire) << ", " << wire->start_offset;
bool first = true;
for (auto flag : flags) {
if (first) {
first = false;
f << ", ";
} else {
f << "|";
}
f << "debug_item::" << flag;
}
f << "), ";
dump_debug_attrs(wire);
f << ");\n";
count_member_wires++;
break;
}
case WireType::ALIAS: {
// Alias of a member wire
const RTLIL::Wire *aliasee = debug_wire_type.sig_subst.as_wire();
f << indent << "items->add(path + " << escape_cxx_string(get_hdl_name(wire));
f << ", debug_item(";
// If the aliasee is an outline, then the alias must be an outline, too; otherwise downstream
// tooling has no way to find out about the outline.
if (debug_wire_types[aliasee].is_outline())
f << "debug_eval_outline";
else
f << "debug_alias()";
f << ", " << mangle(aliasee) << ", " << wire->start_offset << "), ";
dump_debug_attrs(aliasee);
f << ");\n";
count_alias_wires++;
break;
}
case WireType::CONST: {
// Wire tied to a constant
f << indent << "static const value<" << wire->width << "> const_" << mangle(wire) << " = ";
dump_const(debug_wire_type.sig_subst.as_const());
f << ";\n";
f << indent << "items->add(path + " << escape_cxx_string(get_hdl_name(wire));
f << ", debug_item(const_" << mangle(wire) << ", " << wire->start_offset << "), ";
dump_debug_attrs(wire);
f << ");\n";
count_const_wires++;
break;
}
case WireType::OUTLINE: {
// Localized or inlined, but rematerializable wire
f << indent << "items->add(path + " << escape_cxx_string(get_hdl_name(wire));
f << ", debug_item(debug_eval_outline, " << mangle(wire) << ", " << wire->start_offset << "), ";
dump_debug_attrs(wire);
f << ");\n";
count_inline_wires++;
break;
}
default: {
// Localized or inlined wire with no debug information
count_skipped_wires++;
break;
}
}
f << ");\n";
}
if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) {
for (auto &mem : mod_memories[module]) {
if (!mem.memid.isPublic())
continue;
f << indent << "items->add(path + " << escape_cxx_string(mem.packed ? get_hdl_name(mem.cell) : get_hdl_name(mem.mem));
f << ", debug_item(" << mangle(&mem) << ", ";
f << mem.start_offset << "), ";
if (mem.packed) {
dump_debug_attrs(mem.cell);
} else {
dump_debug_attrs(mem.mem);
}
f << ");\n";
}
}
dec_indent();
f << indent << "}\n";
if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) {
for (auto cell : module->cells()) {
if (is_internal_cell(cell->type))
continue;
const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : ".";
f << indent << mangle(cell) << access << "debug_info(items, ";
f << "path + " << escape_cxx_string(get_hdl_name(cell) + ' ') << ");\n";
f << indent << mangle(cell) << access;
f << "debug_info(items, scopes, path + " << escape_cxx_string(get_hdl_name(cell) + ' ') << ", ";
dump_debug_attrs(cell);
f << ");\n";
}
}
dec_indent();
log_debug("Debug information statistics for module `%s':\n", log_id(module));
log_debug(" Scopes: %zu", count_scopes);
log_debug(" Public wires: %zu, of which:\n", count_public_wires);
log_debug(" Member wires: %zu, of which:\n", count_member_wires);
log_debug(" Undriven: %zu (incl. inputs)\n", count_undriven);
@ -2522,7 +2561,8 @@ struct CxxrtlWorker {
f << indent << "}\n";
if (debug_info) {
f << "\n";
f << indent << "void debug_info(debug_items &items, std::string path = \"\") override {\n";
f << indent << "void debug_info(debug_items *items, debug_scopes *scopes, "
<< "std::string path, metadata_map &&cell_attrs = {}) override {\n";
dump_debug_info_method(module);
f << indent << "}\n";
}
@ -2631,7 +2671,8 @@ struct CxxrtlWorker {
}
}
f << "\n";
f << indent << "void debug_info(debug_items &items, std::string path = \"\") override;\n";
f << indent << "void debug_info(debug_items *items, debug_scopes *scopes, "
<< "std::string path, metadata_map &&cell_attrs = {}) override;\n";
}
dec_indent();
f << indent << "}; // struct " << mangle(module) << "\n";
@ -2659,7 +2700,8 @@ struct CxxrtlWorker {
}
f << "\n";
f << indent << "CXXRTL_EXTREMELY_COLD\n";
f << indent << "void " << mangle(module) << "::debug_info(debug_items &items, std::string path) {\n";
f << indent << "void " << mangle(module) << "::debug_info(debug_items *items, debug_scopes *scopes, "
<< "std::string path, metadata_map &&cell_attrs) {\n";
dump_debug_info_method(module);
f << indent << "}\n";
}