mirror of https://github.com/YosysHQ/yosys.git
Merge pull request #2121 from whitequark/cxxrtl-debug-aliases
cxxrtl: improve design visibility
This commit is contained in:
commit
4ef9ee3c42
5
Makefile
5
Makefile
|
@ -588,6 +588,11 @@ $(eval $(call add_include_file,passes/fsm/fsmdata.h))
|
||||||
$(eval $(call add_include_file,frontends/ast/ast.h))
|
$(eval $(call add_include_file,frontends/ast/ast.h))
|
||||||
$(eval $(call add_include_file,backends/ilang/ilang_backend.h))
|
$(eval $(call add_include_file,backends/ilang/ilang_backend.h))
|
||||||
$(eval $(call add_include_file,backends/cxxrtl/cxxrtl.h))
|
$(eval $(call add_include_file,backends/cxxrtl/cxxrtl.h))
|
||||||
|
$(eval $(call add_include_file,backends/cxxrtl/cxxrtl_vcd.h))
|
||||||
|
$(eval $(call add_include_file,backends/cxxrtl/cxxrtl_capi.cc))
|
||||||
|
$(eval $(call add_include_file,backends/cxxrtl/cxxrtl_capi.h))
|
||||||
|
$(eval $(call add_include_file,backends/cxxrtl/cxxrtl_vcd_capi.cc))
|
||||||
|
$(eval $(call add_include_file,backends/cxxrtl/cxxrtl_vcd_capi.h))
|
||||||
|
|
||||||
OBJS += kernel/driver.o kernel/register.o kernel/rtlil.o kernel/log.o kernel/calc.o kernel/yosys.o
|
OBJS += kernel/driver.o kernel/register.o kernel/rtlil.o kernel/log.o kernel/calc.o kernel/yosys.o
|
||||||
OBJS += kernel/cellaigs.o kernel/celledges.o
|
OBJS += kernel/cellaigs.o kernel/celledges.o
|
||||||
|
|
|
@ -741,6 +741,17 @@ struct debug_item : ::cxxrtl_object {
|
||||||
next = item.data;
|
next = item.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<size_t Bits>
|
||||||
|
debug_item(const value<Bits> &item) {
|
||||||
|
static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t),
|
||||||
|
"value<Bits> is not compatible with C layout");
|
||||||
|
type = VALUE;
|
||||||
|
width = Bits;
|
||||||
|
depth = 1;
|
||||||
|
curr = const_cast<uint32_t*>(item.data);
|
||||||
|
next = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
template<size_t Bits>
|
template<size_t Bits>
|
||||||
debug_item(wire<Bits> &item) {
|
debug_item(wire<Bits> &item) {
|
||||||
static_assert(sizeof(item.curr) == value<Bits>::chunks * sizeof(chunk_t) &&
|
static_assert(sizeof(item.curr) == value<Bits>::chunks * sizeof(chunk_t) &&
|
||||||
|
|
|
@ -359,10 +359,10 @@ struct FlowGraph {
|
||||||
//
|
//
|
||||||
// eliminating the unnecessary delta cycle. Conceptually, the CELL_SYNC node type is a series of
|
// eliminating the unnecessary delta cycle. Conceptually, the CELL_SYNC node type is a series of
|
||||||
// connections of the form `connect \lhs \cell.\sync_output`; the right-hand side of these is not
|
// connections of the form `connect \lhs \cell.\sync_output`; the right-hand side of these is not
|
||||||
// as a wire in RTLIL. If it was expressible, then `\cell.\sync_output` would have a sync def,
|
// expressible as a wire in RTLIL. If it was expressible, then `\cell.\sync_output` would have
|
||||||
// and this node would be an ordinary CONNECT node, with `\lhs` having a comb def. Because it isn't,
|
// a sync def, and this node would be an ordinary CONNECT node, with `\lhs` having a comb def.
|
||||||
// a special node type is used, the right-hand side does not appear anywhere, and the left-hand
|
// Because it isn't, a special node type is used, the right-hand side does not appear anywhere,
|
||||||
// side has a comb def.
|
// and the left-hand side has a comb def.
|
||||||
for (auto conn : cell->connections())
|
for (auto conn : cell->connections())
|
||||||
if (cell->output(conn.first))
|
if (cell->output(conn.first))
|
||||||
if (is_cxxrtl_sync_port(cell, conn.first)) {
|
if (is_cxxrtl_sync_port(cell, conn.first)) {
|
||||||
|
@ -539,6 +539,8 @@ struct CxxrtlWorker {
|
||||||
dict<const RTLIL::Wire*, FlowGraph::Node> elided_wires;
|
dict<const RTLIL::Wire*, FlowGraph::Node> elided_wires;
|
||||||
dict<const RTLIL::Module*, std::vector<FlowGraph::Node>> schedule;
|
dict<const RTLIL::Module*, std::vector<FlowGraph::Node>> schedule;
|
||||||
pool<const RTLIL::Wire*> localized_wires;
|
pool<const RTLIL::Wire*> localized_wires;
|
||||||
|
dict<const RTLIL::Wire*, const RTLIL::Wire*> debug_alias_wires;
|
||||||
|
dict<const RTLIL::Wire*, RTLIL::Const> debug_const_wires;
|
||||||
dict<const RTLIL::Module*, pool<std::string>> blackbox_specializations;
|
dict<const RTLIL::Module*, pool<std::string>> blackbox_specializations;
|
||||||
dict<const RTLIL::Module*, bool> eval_converges;
|
dict<const RTLIL::Module*, bool> eval_converges;
|
||||||
|
|
||||||
|
@ -1606,15 +1608,36 @@ struct CxxrtlWorker {
|
||||||
|
|
||||||
void dump_debug_info_method(RTLIL::Module *module)
|
void dump_debug_info_method(RTLIL::Module *module)
|
||||||
{
|
{
|
||||||
|
size_t count_const_wires = 0;
|
||||||
|
size_t count_alias_wires = 0;
|
||||||
|
size_t count_member_wires = 0;
|
||||||
|
size_t count_skipped_wires = 0;
|
||||||
inc_indent();
|
inc_indent();
|
||||||
f << indent << "assert(path.empty() || path[path.size() - 1] == ' ');\n";
|
f << indent << "assert(path.empty() || path[path.size() - 1] == ' ');\n";
|
||||||
for (auto wire : module->wires()) {
|
for (auto wire : module->wires()) {
|
||||||
if (wire->name[0] != '\\')
|
if (wire->name[0] != '\\')
|
||||||
continue;
|
continue;
|
||||||
if (localized_wires.count(wire))
|
if (debug_const_wires.count(wire)) {
|
||||||
continue;
|
// Wire tied to a constant
|
||||||
f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire));
|
f << indent << "static const value<" << wire->width << "> const_" << mangle(wire) << " = ";
|
||||||
f << ", debug_item(" << mangle(wire) << "));\n";
|
dump_const(debug_const_wires[wire]);
|
||||||
|
f << ";\n";
|
||||||
|
f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire));
|
||||||
|
f << ", debug_item(const_" << mangle(wire) << "));\n";
|
||||||
|
count_const_wires++;
|
||||||
|
} else if (debug_alias_wires.count(wire)) {
|
||||||
|
// Alias of a member wire
|
||||||
|
f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire));
|
||||||
|
f << ", debug_item(" << mangle(debug_alias_wires[wire]) << "));\n";
|
||||||
|
count_alias_wires++;
|
||||||
|
} else if (!localized_wires.count(wire)) {
|
||||||
|
// Member wire
|
||||||
|
f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire));
|
||||||
|
f << ", debug_item(" << mangle(wire) << "));\n";
|
||||||
|
count_member_wires++;
|
||||||
|
} else {
|
||||||
|
count_skipped_wires++;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
for (auto &memory_it : module->memories) {
|
for (auto &memory_it : module->memories) {
|
||||||
if (memory_it.first[0] != '\\')
|
if (memory_it.first[0] != '\\')
|
||||||
|
@ -1630,6 +1653,12 @@ struct CxxrtlWorker {
|
||||||
f << "path + " << escape_cxx_string(get_hdl_name(cell) + ' ') << ");\n";
|
f << "path + " << escape_cxx_string(get_hdl_name(cell) + ' ') << ");\n";
|
||||||
}
|
}
|
||||||
dec_indent();
|
dec_indent();
|
||||||
|
|
||||||
|
log_debug("Debug information statistics for module %s:\n", log_id(module));
|
||||||
|
log_debug(" Const wires: %zu\n", count_const_wires);
|
||||||
|
log_debug(" Alias wires: %zu\n", count_alias_wires);
|
||||||
|
log_debug(" Member wires: %zu\n", count_member_wires);
|
||||||
|
log_debug(" Other wires: %zu (no debug information)\n", count_skipped_wires);
|
||||||
}
|
}
|
||||||
|
|
||||||
void dump_metadata_map(const dict<RTLIL::IdString, RTLIL::Const> &metadata_map)
|
void dump_metadata_map(const dict<RTLIL::IdString, RTLIL::Const> &metadata_map)
|
||||||
|
@ -2141,6 +2170,44 @@ struct CxxrtlWorker {
|
||||||
}
|
}
|
||||||
|
|
||||||
eval_converges[module] = feedback_wires.empty() && buffered_wires.empty();
|
eval_converges[module] = feedback_wires.empty() && buffered_wires.empty();
|
||||||
|
|
||||||
|
if (debug_info) {
|
||||||
|
// Find wires that alias other wires or are tied to a constant; debug information can be enriched with these
|
||||||
|
// at essentially zero additional cost.
|
||||||
|
//
|
||||||
|
// Note that the information collected here can't be used for optimizing the netlist: debug information queries
|
||||||
|
// are pure and run on a design in a stable state, which allows assumptions that do not otherwise hold.
|
||||||
|
for (auto wire : module->wires()) {
|
||||||
|
if (wire->name[0] != '\\')
|
||||||
|
continue;
|
||||||
|
if (!localized_wires[wire])
|
||||||
|
continue;
|
||||||
|
const RTLIL::Wire *wire_it = wire;
|
||||||
|
while (1) {
|
||||||
|
if (!(flow.wire_def_elidable.count(wire_it) && flow.wire_def_elidable[wire_it]))
|
||||||
|
break; // not an alias: complex def
|
||||||
|
log_assert(flow.wire_comb_defs[wire_it].size() == 1);
|
||||||
|
FlowGraph::Node *node = *flow.wire_comb_defs[wire_it].begin();
|
||||||
|
if (node->type != FlowGraph::Node::Type::CONNECT)
|
||||||
|
break; // not an alias: def by cell
|
||||||
|
RTLIL::SigSpec rhs_sig = node->connect.second;
|
||||||
|
if (rhs_sig.is_wire()) {
|
||||||
|
RTLIL::Wire *rhs_wire = rhs_sig.as_wire();
|
||||||
|
if (localized_wires[rhs_wire]) {
|
||||||
|
wire_it = rhs_wire; // maybe an alias
|
||||||
|
} else {
|
||||||
|
debug_alias_wires[wire] = rhs_wire; // is an alias
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
} else if (rhs_sig.is_fully_const()) {
|
||||||
|
debug_const_wires[wire] = rhs_sig.as_const(); // is a const
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
break; // not an alias: complex rhs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (has_feedback_arcs || has_buffered_wires) {
|
if (has_feedback_arcs || has_buffered_wires) {
|
||||||
// Although both non-feedback buffered combinatorial wires and apparent feedback wires may be eliminated
|
// Although both non-feedback buffered combinatorial wires and apparent feedback wires may be eliminated
|
||||||
|
@ -2419,7 +2486,8 @@ struct CxxrtlBackend : public Backend {
|
||||||
log(" no debug information.\n");
|
log(" no debug information.\n");
|
||||||
log("\n");
|
log("\n");
|
||||||
log(" -g1\n");
|
log(" -g1\n");
|
||||||
log(" debug information for non-localized public wires.\n");
|
log(" debug information for non-optimized public wires. this also makes it\n");
|
||||||
|
log(" possible to use the C API.\n");
|
||||||
log("\n");
|
log("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2494,7 +2562,7 @@ struct CxxrtlBackend : public Backend {
|
||||||
case 0:
|
case 0:
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
log_cmd_error("Invalid optimization level %d.\n", opt_level);
|
log_cmd_error("Invalid debug information level %d.\n", debug_level);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::ofstream intf_f;
|
std::ofstream intf_f;
|
||||||
|
|
|
@ -64,9 +64,10 @@ enum cxxrtl_type {
|
||||||
// Values correspond to singly buffered netlist nodes, i.e. nodes driven exclusively by
|
// Values correspond to singly buffered netlist nodes, i.e. nodes driven exclusively by
|
||||||
// combinatorial cells, or toplevel input nodes.
|
// combinatorial cells, or toplevel input nodes.
|
||||||
//
|
//
|
||||||
// Values can be inspected via the `curr` pointer and modified via the `next` pointer (which are
|
// Values can be inspected via the `curr` pointer. If the `next` pointer is NULL, the value is
|
||||||
// equal for values); however, note that changes to the bits driven by combinatorial cells will
|
// driven by a constant and can never be modified. Otherwise, the value can be modified through
|
||||||
// be ignored.
|
// the `next` pointer (which is equal to `curr` if not NULL). Note that changes to the bits
|
||||||
|
// driven by combinatorial cells will be ignored.
|
||||||
//
|
//
|
||||||
// Values always have depth 1.
|
// Values always have depth 1.
|
||||||
CXXRTL_VALUE = 0,
|
CXXRTL_VALUE = 0,
|
||||||
|
@ -75,8 +76,8 @@ enum cxxrtl_type {
|
||||||
// storage cells, or by combinatorial cells that are a part of a feedback path.
|
// storage cells, or by combinatorial cells that are a part of a feedback path.
|
||||||
//
|
//
|
||||||
// Wires can be inspected via the `curr` pointer and modified via the `next` pointer (which are
|
// Wires can be inspected via the `curr` pointer and modified via the `next` pointer (which are
|
||||||
// distinct for wires); however, note that changes to the bits driven by combinatorial cells will
|
// distinct for wires). Note that changes to the bits driven by combinatorial cells will be
|
||||||
// be ignored.
|
// ignored.
|
||||||
//
|
//
|
||||||
// Wires always have depth 1.
|
// Wires always have depth 1.
|
||||||
CXXRTL_WIRE = 1,
|
CXXRTL_WIRE = 1,
|
||||||
|
|
|
@ -34,6 +34,7 @@ class vcd_writer {
|
||||||
std::vector<std::string> current_scope;
|
std::vector<std::string> current_scope;
|
||||||
std::vector<variable> variables;
|
std::vector<variable> variables;
|
||||||
std::vector<chunk_t> cache;
|
std::vector<chunk_t> cache;
|
||||||
|
std::map<chunk_t*, size_t> aliases;
|
||||||
bool streaming = false;
|
bool streaming = false;
|
||||||
|
|
||||||
void emit_timescale(unsigned number, const std::string &unit) {
|
void emit_timescale(unsigned number, const std::string &unit) {
|
||||||
|
@ -103,13 +104,25 @@ class vcd_writer {
|
||||||
buffer += '\n';
|
buffer += '\n';
|
||||||
}
|
}
|
||||||
|
|
||||||
void append_variable(size_t width, chunk_t *curr) {
|
const variable ®ister_variable(size_t width, chunk_t *curr, bool immutable = false) {
|
||||||
const size_t chunks = (width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8);
|
if (aliases.count(curr)) {
|
||||||
variables.emplace_back(variable { variables.size(), width, curr, cache.size() });
|
return variables[aliases[curr]];
|
||||||
cache.insert(cache.end(), &curr[0], &curr[chunks]);
|
} else {
|
||||||
|
const size_t chunks = (width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8);
|
||||||
|
aliases[curr] = variables.size();
|
||||||
|
if (immutable) {
|
||||||
|
variables.emplace_back(variable { variables.size(), width, curr, (size_t)-1 });
|
||||||
|
} else {
|
||||||
|
variables.emplace_back(variable { variables.size(), width, curr, cache.size() });
|
||||||
|
cache.insert(cache.end(), &curr[0], &curr[chunks]);
|
||||||
|
}
|
||||||
|
return variables.back();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool test_variable(const variable &var) {
|
bool test_variable(const variable &var) {
|
||||||
|
if (var.prev_off == (size_t)-1)
|
||||||
|
return false; // immutable
|
||||||
const size_t chunks = (var.width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8);
|
const size_t chunks = (var.width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8);
|
||||||
if (std::equal(&var.curr[0], &var.curr[chunks], &cache[var.prev_off])) {
|
if (std::equal(&var.curr[0], &var.curr[chunks], &cache[var.prev_off])) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -151,20 +164,17 @@ public:
|
||||||
switch (item.type) {
|
switch (item.type) {
|
||||||
// Not the best naming but oh well...
|
// Not the best naming but oh well...
|
||||||
case debug_item::VALUE:
|
case debug_item::VALUE:
|
||||||
append_variable(item.width, item.curr);
|
emit_var(register_variable(item.width, item.curr, /*immutable=*/item.next == nullptr), "wire", name);
|
||||||
emit_var(variables.back(), "wire", name);
|
|
||||||
break;
|
break;
|
||||||
case debug_item::WIRE:
|
case debug_item::WIRE:
|
||||||
append_variable(item.width, item.curr);
|
emit_var(register_variable(item.width, item.curr), "reg", name);
|
||||||
emit_var(variables.back(), "reg", name);
|
|
||||||
break;
|
break;
|
||||||
case debug_item::MEMORY: {
|
case debug_item::MEMORY: {
|
||||||
const size_t stride = (item.width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8);
|
const size_t stride = (item.width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8);
|
||||||
for (size_t index = 0; index < item.depth; index++) {
|
for (size_t index = 0; index < item.depth; index++) {
|
||||||
chunk_t *nth_curr = &item.curr[stride * index];
|
chunk_t *nth_curr = &item.curr[stride * index];
|
||||||
std::string nth_name = name + '[' + std::to_string(index) + ']';
|
std::string nth_name = name + '[' + std::to_string(index) + ']';
|
||||||
append_variable(item.width, nth_curr);
|
emit_var(register_variable(item.width, nth_curr), "reg", nth_name);
|
||||||
emit_var(variables.back(), "reg", nth_name);
|
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue