mirror of https://github.com/YosysHQ/yosys.git
cxxrtl: generate debug information for non-localized public wires.
Debug information describes values, wires, and memories with a simple C-compatible layout. It can be emitted on demand into a map, which has no runtime cost when it is unused, and allows late bound designs. The `hdlname` attribute is used as the lookup key such that original names, as emitted by the frontend, can be used for debugging and introspection.
This commit is contained in:
parent
784bfec67c
commit
f6e16e7f4c
|
@ -502,6 +502,15 @@ std::string escape_cxx_string(const std::string &input)
|
|||
return output;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
std::string get_hdl_name(T *object)
|
||||
{
|
||||
if (object->has_attribute(ID::hdlname))
|
||||
return object->get_string_attribute(ID::hdlname);
|
||||
else
|
||||
return object->name.str();
|
||||
}
|
||||
|
||||
struct CxxrtlWorker {
|
||||
bool split_intf = false;
|
||||
std::string intf_filename;
|
||||
|
@ -516,6 +525,8 @@ struct CxxrtlWorker {
|
|||
bool run_proc_flatten = false;
|
||||
bool max_opt_level = false;
|
||||
|
||||
bool debug_info = false;
|
||||
|
||||
std::ostringstream f;
|
||||
std::string indent;
|
||||
int temporary = 0;
|
||||
|
@ -1593,6 +1604,34 @@ struct CxxrtlWorker {
|
|||
dec_indent();
|
||||
}
|
||||
|
||||
void dump_debug_info_method(RTLIL::Module *module)
|
||||
{
|
||||
inc_indent();
|
||||
f << indent << "assert(path.empty() || path[path.size() - 1] == ' ');\n";
|
||||
for (auto wire : module->wires()) {
|
||||
if (wire->name[0] != '\\')
|
||||
continue;
|
||||
if (localized_wires.count(wire))
|
||||
continue;
|
||||
f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(wire));
|
||||
f << ", debug_item(" << mangle(wire) << "));\n";
|
||||
}
|
||||
for (auto &memory_it : module->memories) {
|
||||
if (memory_it.first[0] != '\\')
|
||||
continue;
|
||||
f << indent << "items.emplace(path + " << escape_cxx_string(get_hdl_name(memory_it.second));
|
||||
f << ", debug_item(" << mangle(memory_it.second) << "));\n";
|
||||
}
|
||||
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";
|
||||
}
|
||||
dec_indent();
|
||||
}
|
||||
|
||||
void dump_metadata_map(const dict<RTLIL::IdString, RTLIL::Const> &metadata_map)
|
||||
{
|
||||
if (metadata_map.empty()) {
|
||||
|
@ -1641,6 +1680,12 @@ struct CxxrtlWorker {
|
|||
dump_commit_method(module);
|
||||
f << indent << "}\n";
|
||||
f << "\n";
|
||||
if (debug_info) {
|
||||
f << indent << "void debug_info(debug_items &items, std::string path = \"\") override {\n";
|
||||
dump_debug_info_method(module);
|
||||
f << indent << "}\n";
|
||||
f << "\n";
|
||||
}
|
||||
f << indent << "static std::unique_ptr<" << mangle(module);
|
||||
f << template_params(module, /*is_decl=*/false) << "> ";
|
||||
f << "create(std::string name, metadata_map parameters, metadata_map attributes);\n";
|
||||
|
@ -1689,7 +1734,7 @@ struct CxxrtlWorker {
|
|||
if (cell_module->get_bool_attribute(ID(cxxrtl_blackbox))) {
|
||||
f << indent << "std::unique_ptr<" << mangle(cell_module) << template_args(cell) << "> ";
|
||||
f << mangle(cell) << " = " << mangle(cell_module) << template_args(cell);
|
||||
f << "::create(" << escape_cxx_string(cell->name.str()) << ", ";
|
||||
f << "::create(" << escape_cxx_string(get_hdl_name(cell)) << ", ";
|
||||
dump_metadata_map(cell->parameters);
|
||||
f << ", ";
|
||||
dump_metadata_map(cell->attributes);
|
||||
|
@ -1703,6 +1748,8 @@ struct CxxrtlWorker {
|
|||
f << "\n";
|
||||
f << indent << "bool eval() override;\n";
|
||||
f << indent << "bool commit() override;\n";
|
||||
if (debug_info)
|
||||
f << indent << "void debug_info(debug_items &items, std::string path = \"\") override;\n";
|
||||
dec_indent();
|
||||
f << indent << "}; // struct " << mangle(module) << "\n";
|
||||
f << "\n";
|
||||
|
@ -1721,6 +1768,12 @@ struct CxxrtlWorker {
|
|||
dump_commit_method(module);
|
||||
f << indent << "}\n";
|
||||
f << "\n";
|
||||
if (debug_info) {
|
||||
f << indent << "void " << mangle(module) << "::debug_info(debug_items &items, std::string path) {\n";
|
||||
dump_debug_info_method(module);
|
||||
f << indent << "}\n";
|
||||
f << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
void dump_design(RTLIL::Design *design)
|
||||
|
@ -2120,6 +2173,7 @@ struct CxxrtlWorker {
|
|||
|
||||
struct CxxrtlBackend : public Backend {
|
||||
static const int DEFAULT_OPT_LEVEL = 5;
|
||||
static const int DEFAULT_DEBUG_LEVEL = 1;
|
||||
|
||||
CxxrtlBackend() : Backend("cxxrtl", "convert design to C++ RTL simulation") { }
|
||||
void help() YS_OVERRIDE
|
||||
|
@ -2313,10 +2367,22 @@ struct CxxrtlBackend : public Backend {
|
|||
log(" -O5\n");
|
||||
log(" like -O4, and run `proc; flatten` first.\n");
|
||||
log("\n");
|
||||
log(" -g <level>\n");
|
||||
log(" set the debug level. the default is -g%d. higher debug levels provide\n", DEFAULT_DEBUG_LEVEL);
|
||||
log(" more visibility and generate more code, but do not pessimize evaluation.\n");
|
||||
log("\n");
|
||||
log(" -g0\n");
|
||||
log(" no debug information.\n");
|
||||
log("\n");
|
||||
log(" -g1\n");
|
||||
log(" debug information for non-localized public wires.\n");
|
||||
log("\n");
|
||||
}
|
||||
|
||||
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
|
||||
{
|
||||
int opt_level = DEFAULT_OPT_LEVEL;
|
||||
int debug_level = DEFAULT_DEBUG_LEVEL;
|
||||
CxxrtlWorker worker;
|
||||
|
||||
log_header(design, "Executing CXXRTL backend.\n");
|
||||
|
@ -2332,6 +2398,14 @@ struct CxxrtlBackend : public Backend {
|
|||
opt_level = std::stoi(args[argidx].substr(2));
|
||||
continue;
|
||||
}
|
||||
if (args[argidx] == "-g" && argidx+1 < args.size()) {
|
||||
debug_level = std::stoi(args[++argidx]);
|
||||
continue;
|
||||
}
|
||||
if (args[argidx].substr(0, 2) == "-g" && args[argidx].size() == 3 && isdigit(args[argidx][2])) {
|
||||
debug_level = std::stoi(args[argidx].substr(2));
|
||||
continue;
|
||||
}
|
||||
if (args[argidx] == "-header") {
|
||||
worker.split_intf = true;
|
||||
continue;
|
||||
|
@ -2368,6 +2442,17 @@ struct CxxrtlBackend : public Backend {
|
|||
log_cmd_error("Invalid optimization level %d.\n", opt_level);
|
||||
}
|
||||
|
||||
switch (debug_level) {
|
||||
// the highest level here must match DEFAULT_DEBUG_LEVEL
|
||||
case 1:
|
||||
worker.debug_info = true;
|
||||
YS_FALLTHROUGH
|
||||
case 0:
|
||||
break;
|
||||
default:
|
||||
log_cmd_error("Invalid optimization level %d.\n", opt_level);
|
||||
}
|
||||
|
||||
std::ofstream intf_f;
|
||||
if (worker.split_intf) {
|
||||
if (filename == "<stdout>")
|
||||
|
|
|
@ -49,6 +49,8 @@ namespace cxxrtl {
|
|||
// invisible to the compiler, (b) we often operate on non-power-of-2 values and have to clear the high bits anyway.
|
||||
// Therefore, using relatively wide chunks and clearing the high bits explicitly and only when we know they may be
|
||||
// clobbered results in simpler generated code.
|
||||
typedef uint32_t chunk_t;
|
||||
|
||||
template<typename T>
|
||||
struct chunk_traits {
|
||||
static_assert(std::is_integral<T>::value && std::is_unsigned<T>::value,
|
||||
|
@ -65,7 +67,7 @@ template<size_t Bits>
|
|||
struct value : public expr_base<value<Bits>> {
|
||||
static constexpr size_t bits = Bits;
|
||||
|
||||
using chunk = chunk_traits<uint32_t>;
|
||||
using chunk = chunk_traits<chunk_t>;
|
||||
static constexpr chunk::type msb_mask = (Bits % chunk::bits == 0) ? chunk::mask
|
||||
: chunk::mask >> (chunk::bits - (Bits % chunk::bits));
|
||||
|
||||
|
@ -712,6 +714,46 @@ struct metadata {
|
|||
|
||||
typedef std::map<std::string, metadata> metadata_map;
|
||||
|
||||
// This structure is intended for consumption via foreign function interfaces, like Python's ctypes.
|
||||
// Because of this it uses a C-style layout that is easy to parse rather than more idiomatic C++.
|
||||
struct debug_item {
|
||||
enum : uint32_t {
|
||||
VALUE = 0,
|
||||
WIRE = 1,
|
||||
MEMORY = 2,
|
||||
} type;
|
||||
|
||||
size_t width; // in bits
|
||||
size_t depth; // 1 if `type != MEMORY`
|
||||
chunk_t *curr;
|
||||
chunk_t *next; // nullptr if `type == VALUE || type == MEMORY`
|
||||
|
||||
template<size_t Bits>
|
||||
debug_item(value<Bits> &item) : type(VALUE), width(Bits), depth(1),
|
||||
curr(item.data), next(nullptr) {
|
||||
static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t),
|
||||
"value<Bits> is not compatible with C layout");
|
||||
}
|
||||
|
||||
template<size_t Bits>
|
||||
debug_item(wire<Bits> &item) : type(WIRE), width(Bits), depth(1),
|
||||
curr(item.curr.data), next(item.next.data) {
|
||||
static_assert(sizeof(item.curr) == value<Bits>::chunks * sizeof(chunk_t) &&
|
||||
sizeof(item.next) == value<Bits>::chunks * sizeof(chunk_t),
|
||||
"wire<Bits> is not compatible with C layout");
|
||||
}
|
||||
|
||||
template<size_t Width>
|
||||
debug_item(memory<Width> &item) : type(MEMORY), width(Width), depth(item.data.size()),
|
||||
curr(item.data.empty() ? nullptr : item.data[0].data), next(nullptr) {
|
||||
static_assert(sizeof(item.data[0]) == value<Width>::chunks * sizeof(chunk_t),
|
||||
"memory<Width> is not compatible with C layout");
|
||||
}
|
||||
};
|
||||
static_assert(std::is_standard_layout<debug_item>::value, "debug_item is not compatible with C layout");
|
||||
|
||||
typedef std::map<std::string, debug_item> debug_items;
|
||||
|
||||
struct module {
|
||||
module() {}
|
||||
virtual ~module() {}
|
||||
|
@ -731,6 +773,8 @@ struct module {
|
|||
} while (commit() && !converged);
|
||||
return deltas;
|
||||
}
|
||||
|
||||
virtual void debug_info(debug_items &items, std::string path = "") {}
|
||||
};
|
||||
|
||||
} // namespace cxxrtl
|
||||
|
|
Loading…
Reference in New Issue