aboutsummaryrefslogtreecommitdiffstats
path: root/backends
diff options
context:
space:
mode:
Diffstat (limited to 'backends')
-rw-r--r--backends/btor/btor.cc10
-rw-r--r--backends/cxxrtl/Makefile.inc2
-rw-r--r--backends/cxxrtl/cxxrtl.h160
-rw-r--r--backends/cxxrtl/cxxrtl_backend.cc (renamed from backends/cxxrtl/cxxrtl.cc)246
-rw-r--r--backends/cxxrtl/cxxrtl_capi.cc60
-rw-r--r--backends/cxxrtl/cxxrtl_capi.h152
-rw-r--r--backends/cxxrtl/cxxrtl_vcd.h224
-rw-r--r--backends/cxxrtl/cxxrtl_vcd_capi.cc83
-rw-r--r--backends/cxxrtl/cxxrtl_vcd_capi.h107
-rw-r--r--backends/smt2/smtio.py2
10 files changed, 958 insertions, 88 deletions
diff --git a/backends/btor/btor.cc b/backends/btor/btor.cc
index bbdcfb70a..9ac312480 100644
--- a/backends/btor/btor.cc
+++ b/backends/btor/btor.cc
@@ -1355,13 +1355,13 @@ struct BtorBackend : public Backend {
log(" -i <filename>\n");
log(" Create additional info file with auxiliary information\n");
log("\n");
- log(" -n\n");
- log(" Don't identify internal netnames\n");
+ log(" -x\n");
+ log(" Output symbols for internal netnames (starting with '$')\n");
log("\n");
}
void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE
{
- bool verbose = false, single_bad = false, cover_mode = false, print_internal_names = true;
+ bool verbose = false, single_bad = false, cover_mode = false, print_internal_names = false;
string info_filename;
log_header(design, "Executing BTOR backend.\n");
@@ -1385,8 +1385,8 @@ struct BtorBackend : public Backend {
info_filename = args[++argidx];
continue;
}
- if (args[argidx] == "-n") {
- print_internal_names = false;
+ if (args[argidx] == "-x") {
+ print_internal_names = true;
continue;
}
break;
diff --git a/backends/cxxrtl/Makefile.inc b/backends/cxxrtl/Makefile.inc
index f93e65f85..aaa304502 100644
--- a/backends/cxxrtl/Makefile.inc
+++ b/backends/cxxrtl/Makefile.inc
@@ -1,2 +1,2 @@
-OBJS += backends/cxxrtl/cxxrtl.o
+OBJS += backends/cxxrtl/cxxrtl_backend.o
diff --git a/backends/cxxrtl/cxxrtl.h b/backends/cxxrtl/cxxrtl.h
index 701510b7f..c988c9e80 100644
--- a/backends/cxxrtl/cxxrtl.h
+++ b/backends/cxxrtl/cxxrtl.h
@@ -33,13 +33,15 @@
#include <memory>
#include <sstream>
-// The cxxrtl support library implements compile time specialized arbitrary width arithmetics, as well as provides
+#include <backends/cxxrtl/cxxrtl_capi.h>
+
+// The CXXRTL support library implements compile time specialized arbitrary width arithmetics, as well as provides
// composite lvalues made out of bit slices and concatenations of lvalues. This allows the `write_cxxrtl` pass
// to perform a straightforward translation of RTLIL structures to readable C++, relying on the C++ compiler
// to unwrap the abstraction and generate efficient code.
namespace cxxrtl {
-// All arbitrary-width values in cxxrtl are backed by arrays of unsigned integers called chunks. The chunk size
+// All arbitrary-width values in CXXRTL are backed by arrays of unsigned integers called chunks. The chunk size
// is the same regardless of the value width to simplify manipulating values via FFI interfaces, e.g. driving
// and introspecting the simulation in Python.
//
@@ -49,6 +51,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 +69,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 +716,69 @@ 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++.
+//
+// To avoid violating strict aliasing rules, this structure has to be a subclass of the one used
+// in the C API, or it would not be possible to cast between the pointers to these.
+struct debug_item : ::cxxrtl_object {
+ enum : uint32_t {
+ VALUE = CXXRTL_VALUE,
+ WIRE = CXXRTL_WIRE,
+ MEMORY = CXXRTL_MEMORY,
+ };
+
+ debug_item(const ::cxxrtl_object &object) : cxxrtl_object(object) {}
+
+ template<size_t Bits>
+ debug_item(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 = 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>
+ debug_item(wire<Bits> &item) {
+ 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");
+ type = WIRE;
+ width = Bits;
+ depth = 1;
+ curr = item.curr.data;
+ next = item.next.data;
+ }
+
+ template<size_t Width>
+ debug_item(memory<Width> &item) {
+ static_assert(sizeof(item.data[0]) == value<Width>::chunks * sizeof(chunk_t),
+ "memory<Width> is not compatible with C layout");
+ type = MEMORY;
+ width = Width;
+ depth = item.data.size();
+ curr = item.data.empty() ? nullptr : item.data[0].data;
+ next = nullptr;
+ }
+};
+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,11 +798,18 @@ struct module {
} while (commit() && !converged);
return deltas;
}
+
+ virtual void debug_info(debug_items &items, std::string path = "") {}
};
} // namespace cxxrtl
-// Definitions of internal Yosys cells. Other than the functions in this namespace, cxxrtl is fully generic
+// Internal structure used to communicate with the implementation of the C interface.
+typedef struct _cxxrtl_toplevel {
+ std::unique_ptr<cxxrtl::module> module;
+} *cxxrtl_toplevel;
+
+// Definitions of internal Yosys cells. Other than the functions in this namespace, CXXRTL is fully generic
// and indepenent of Yosys implementation details.
//
// The `write_cxxrtl` pass translates internal cells (cells with names that start with `$`) to calls of these
@@ -755,73 +829,55 @@ constexpr T max(const T &a, const T &b) {
// Logic operations
template<size_t BitsY, size_t BitsA>
-value<BitsY> not_u(const value<BitsA> &a) {
- return a.template zcast<BitsY>().bit_not();
-}
-
-template<size_t BitsY, size_t BitsA>
-value<BitsY> not_s(const value<BitsA> &a) {
- return a.template scast<BitsY>().bit_not();
-}
-
-template<size_t BitsY, size_t BitsA>
-value<BitsY> logic_not_u(const value<BitsA> &a) {
+value<BitsY> logic_not(const value<BitsA> &a) {
return value<BitsY> { a ? 0u : 1u };
}
-template<size_t BitsY, size_t BitsA>
-value<BitsY> logic_not_s(const value<BitsA> &a) {
- return value<BitsY> { a ? 0u : 1u };
+template<size_t BitsY, size_t BitsA, size_t BitsB>
+value<BitsY> logic_and(const value<BitsA> &a, const value<BitsB> &b) {
+ return value<BitsY> { (bool(a) & bool(b)) ? 1u : 0u };
}
-template<size_t BitsY, size_t BitsA>
-value<BitsY> reduce_and_u(const value<BitsA> &a) {
- return value<BitsY> { a.bit_not().is_zero() ? 1u : 0u };
+template<size_t BitsY, size_t BitsA, size_t BitsB>
+value<BitsY> logic_or(const value<BitsA> &a, const value<BitsB> &b) {
+ return value<BitsY> { (bool(a) | bool(b)) ? 1u : 0u };
}
+// Reduction operations
template<size_t BitsY, size_t BitsA>
-value<BitsY> reduce_and_s(const value<BitsA> &a) {
+value<BitsY> reduce_and(const value<BitsA> &a) {
return value<BitsY> { a.bit_not().is_zero() ? 1u : 0u };
}
template<size_t BitsY, size_t BitsA>
-value<BitsY> reduce_or_u(const value<BitsA> &a) {
- return value<BitsY> { a ? 1u : 0u };
-}
-
-template<size_t BitsY, size_t BitsA>
-value<BitsY> reduce_or_s(const value<BitsA> &a) {
+value<BitsY> reduce_or(const value<BitsA> &a) {
return value<BitsY> { a ? 1u : 0u };
}
template<size_t BitsY, size_t BitsA>
-value<BitsY> reduce_xor_u(const value<BitsA> &a) {
- return value<BitsY> { (a.ctpop() % 2) ? 1u : 0u };
-}
-
-template<size_t BitsY, size_t BitsA>
-value<BitsY> reduce_xor_s(const value<BitsA> &a) {
+value<BitsY> reduce_xor(const value<BitsA> &a) {
return value<BitsY> { (a.ctpop() % 2) ? 1u : 0u };
}
template<size_t BitsY, size_t BitsA>
-value<BitsY> reduce_xnor_u(const value<BitsA> &a) {
+value<BitsY> reduce_xnor(const value<BitsA> &a) {
return value<BitsY> { (a.ctpop() % 2) ? 0u : 1u };
}
template<size_t BitsY, size_t BitsA>
-value<BitsY> reduce_xnor_s(const value<BitsA> &a) {
- return value<BitsY> { (a.ctpop() % 2) ? 0u : 1u };
+value<BitsY> reduce_bool(const value<BitsA> &a) {
+ return value<BitsY> { a ? 1u : 0u };
}
+// Bitwise operations
template<size_t BitsY, size_t BitsA>
-value<BitsY> reduce_bool_u(const value<BitsA> &a) {
- return value<BitsY> { a ? 1u : 0u };
+value<BitsY> not_u(const value<BitsA> &a) {
+ return a.template zcast<BitsY>().bit_not();
}
template<size_t BitsY, size_t BitsA>
-value<BitsY> reduce_bool_s(const value<BitsA> &a) {
- return value<BitsY> { a ? 1u : 0u };
+value<BitsY> not_s(const value<BitsA> &a) {
+ return a.template scast<BitsY>().bit_not();
}
template<size_t BitsY, size_t BitsA, size_t BitsB>
@@ -865,26 +921,6 @@ value<BitsY> xnor_ss(const value<BitsA> &a, const value<BitsB> &b) {
}
template<size_t BitsY, size_t BitsA, size_t BitsB>
-value<BitsY> logic_and_uu(const value<BitsA> &a, const value<BitsB> &b) {
- return value<BitsY> { (bool(a) & bool(b)) ? 1u : 0u };
-}
-
-template<size_t BitsY, size_t BitsA, size_t BitsB>
-value<BitsY> logic_and_ss(const value<BitsA> &a, const value<BitsB> &b) {
- return value<BitsY> { (bool(a) & bool(b)) ? 1u : 0u };
-}
-
-template<size_t BitsY, size_t BitsA, size_t BitsB>
-value<BitsY> logic_or_uu(const value<BitsA> &a, const value<BitsB> &b) {
- return value<BitsY> { (bool(a) | bool(b)) ? 1u : 0u };
-}
-
-template<size_t BitsY, size_t BitsA, size_t BitsB>
-value<BitsY> logic_or_ss(const value<BitsA> &a, const value<BitsB> &b) {
- return value<BitsY> { (bool(a) | bool(b)) ? 1u : 0u };
-}
-
-template<size_t BitsY, size_t BitsA, size_t BitsB>
value<BitsY> shl_uu(const value<BitsA> &a, const value<BitsB> &b) {
return a.template zcast<BitsY>().template shl(b);
}
@@ -921,7 +957,7 @@ value<BitsY> sshr_uu(const value<BitsA> &a, const value<BitsB> &b) {
template<size_t BitsY, size_t BitsA, size_t BitsB>
value<BitsY> sshr_su(const value<BitsA> &a, const value<BitsB> &b) {
- return a.template shr(b).template scast<BitsY>();
+ return a.template sshr(b).template scast<BitsY>();
}
template<size_t BitsY, size_t BitsA, size_t BitsB>
diff --git a/backends/cxxrtl/cxxrtl.cc b/backends/cxxrtl/cxxrtl_backend.cc
index 0cceecbba..4c04a2f14 100644
--- a/backends/cxxrtl/cxxrtl.cc
+++ b/backends/cxxrtl/cxxrtl_backend.cc
@@ -192,6 +192,13 @@ bool is_binary_cell(RTLIL::IdString type)
ID($add), ID($sub), ID($mul), ID($div), ID($mod));
}
+bool is_extending_cell(RTLIL::IdString type)
+{
+ return !type.in(
+ ID($logic_not), ID($logic_and), ID($logic_or),
+ ID($reduce_and), ID($reduce_or), ID($reduce_xor), ID($reduce_xnor), ID($reduce_bool));
+}
+
bool is_elidable_cell(RTLIL::IdString type)
{
return is_unary_cell(type) || is_binary_cell(type) || type.in(
@@ -359,10 +366,10 @@ struct FlowGraph {
//
// 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
- // as a wire in RTLIL. If it was expressible, then `\cell.\sync_output` would have a sync def,
- // and this node would be an ordinary CONNECT node, with `\lhs` having a comb def. Because it isn't,
- // a special node type is used, the right-hand side does not appear anywhere, and the left-hand
- // side has a comb def.
+ // expressible as a wire in RTLIL. If it was expressible, then `\cell.\sync_output` would have
+ // a sync def, and this node would be an ordinary CONNECT node, with `\lhs` having a comb def.
+ // Because it isn't, a special node type is used, the right-hand side does not appear anywhere,
+ // and the left-hand side has a comb def.
for (auto conn : cell->connections())
if (cell->output(conn.first))
if (is_cxxrtl_sync_port(cell, conn.first)) {
@@ -467,14 +474,16 @@ std::vector<std::string> split_by(const std::string &str, const std::string &sep
std::vector<std::string> result;
size_t prev = 0;
while (true) {
- size_t curr = str.find_first_of(sep, prev + 1);
- if (curr > str.size())
- curr = str.size();
- if (curr > prev + 1)
- result.push_back(str.substr(prev, curr - prev));
- if (curr == str.size())
+ size_t curr = str.find_first_of(sep, prev);
+ if (curr == std::string::npos) {
+ std::string part = str.substr(prev);
+ if (!part.empty()) result.push_back(part);
break;
- prev = curr;
+ } else {
+ std::string part = str.substr(prev, curr - prev);
+ if (!part.empty()) result.push_back(part);
+ prev = curr + 1;
+ }
}
return result;
}
@@ -502,6 +511,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().substr(1);
+}
+
struct CxxrtlWorker {
bool split_intf = false;
std::string intf_filename;
@@ -516,6 +534,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;
@@ -528,6 +548,8 @@ struct CxxrtlWorker {
dict<const RTLIL::Wire*, FlowGraph::Node> elided_wires;
dict<const RTLIL::Module*, std::vector<FlowGraph::Node>> schedule;
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*, bool> eval_converges;
@@ -894,17 +916,19 @@ struct CxxrtlWorker {
{
// Unary cells
if (is_unary_cell(cell->type)) {
- f << cell->type.substr(1) << '_' <<
- (cell->getParam(ID::A_SIGNED).as_bool() ? 's' : 'u') <<
- "<" << cell->getParam(ID::Y_WIDTH).as_int() << ">(";
+ f << cell->type.substr(1);
+ if (is_extending_cell(cell->type))
+ f << '_' << (cell->getParam(ID::A_SIGNED).as_bool() ? 's' : 'u');
+ f << "<" << cell->getParam(ID::Y_WIDTH).as_int() << ">(";
dump_sigspec_rhs(cell->getPort(ID::A));
f << ")";
// Binary cells
} else if (is_binary_cell(cell->type)) {
- f << cell->type.substr(1) << '_' <<
- (cell->getParam(ID::A_SIGNED).as_bool() ? 's' : 'u') <<
- (cell->getParam(ID::B_SIGNED).as_bool() ? 's' : 'u') <<
- "<" << cell->getParam(ID::Y_WIDTH).as_int() << ">(";
+ f << cell->type.substr(1);
+ if (is_extending_cell(cell->type))
+ f << '_' << (cell->getParam(ID::A_SIGNED).as_bool() ? 's' : 'u') <<
+ (cell->getParam(ID::B_SIGNED).as_bool() ? 's' : 'u');
+ f << "<" << cell->getParam(ID::Y_WIDTH).as_int() << ">(";
dump_sigspec_rhs(cell->getPort(ID::A));
f << ", ";
dump_sigspec_rhs(cell->getPort(ID::B));
@@ -1593,6 +1617,61 @@ struct CxxrtlWorker {
dec_indent();
}
+ 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();
+ f << indent << "assert(path.empty() || path[path.size() - 1] == ' ');\n";
+ for (auto wire : module->wires()) {
+ if (wire->name[0] != '\\')
+ continue;
+ if (debug_const_wires.count(wire)) {
+ // Wire tied to a constant
+ f << indent << "static const value<" << wire->width << "> const_" << mangle(wire) << " = ";
+ 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) {
+ 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();
+
+ 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)
{
if (metadata_map.empty()) {
@@ -1641,6 +1720,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 +1774,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 +1788,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,10 +1808,17 @@ 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)
{
+ RTLIL::Module *top_module = nullptr;
std::vector<RTLIL::Module*> modules;
TopoSort<RTLIL::Module*> topo_design;
for (auto module : design->modules()) {
@@ -1734,6 +1828,8 @@ struct CxxrtlWorker {
modules.push_back(module); // cxxrtl blackboxes first
if (module->get_blackbox_attribute() || module->get_bool_attribute(ID(cxxrtl_blackbox)))
continue;
+ if (module->get_bool_attribute(ID::top))
+ top_module = module;
topo_design.node(module);
for (auto cell : module->cells()) {
@@ -1755,6 +1851,25 @@ struct CxxrtlWorker {
f << "#ifndef " << include_guard << "\n";
f << "#define " << include_guard << "\n";
f << "\n";
+ if (top_module != nullptr && debug_info) {
+ f << "#include <backends/cxxrtl/cxxrtl_capi.h>\n";
+ f << "\n";
+ f << "#ifdef __cplusplus\n";
+ f << "extern \"C\" {\n";
+ f << "#endif\n";
+ f << "\n";
+ f << "cxxrtl_toplevel " << design_ns << "_create();\n";
+ f << "\n";
+ f << "#ifdef __cplusplus\n";
+ f << "}\n";
+ f << "#endif\n";
+ f << "\n";
+ } else {
+ f << "// The CXXRTL C API is not available because the design is built without debug information.\n";
+ f << "\n";
+ }
+ f << "#ifdef __cplusplus\n";
+ f << "\n";
f << "#include <backends/cxxrtl/cxxrtl.h>\n";
f << "\n";
f << "using namespace cxxrtl;\n";
@@ -1765,6 +1880,8 @@ struct CxxrtlWorker {
dump_module_intf(module);
f << "} // namespace " << design_ns << "\n";
f << "\n";
+ f << "#endif // __cplusplus\n";
+ f << "\n";
f << "#endif\n";
*intf_f << f.str(); f.str("");
}
@@ -1774,6 +1891,15 @@ struct CxxrtlWorker {
else
f << "#include <backends/cxxrtl/cxxrtl.h>\n";
f << "\n";
+ f << "#if defined(CXXRTL_INCLUDE_CAPI_IMPL) || \\\n";
+ f << " defined(CXXRTL_INCLUDE_VCD_CAPI_IMPL)\n";
+ f << "#include <backends/cxxrtl/cxxrtl_capi.cc>\n";
+ f << "#endif\n";
+ f << "\n";
+ f << "#if defined(CXXRTL_INCLUDE_VCD_CAPI_IMPL)\n";
+ f << "#include <backends/cxxrtl/cxxrtl_vcd_capi.cc>\n";
+ f << "#endif\n";
+ f << "\n";
f << "using namespace cxxrtl_yosys;\n";
f << "\n";
f << "namespace " << design_ns << " {\n";
@@ -1784,6 +1910,17 @@ struct CxxrtlWorker {
dump_module_impl(module);
}
f << "} // namespace " << design_ns << "\n";
+ f << "\n";
+ if (top_module != nullptr && debug_info) {
+ f << "cxxrtl_toplevel " << design_ns << "_create() {\n";
+ inc_indent();
+ f << indent << "return new _cxxrtl_toplevel { ";
+ f << "std::make_unique<" << design_ns << "::" << mangle(top_module) << ">()";
+ f << " };\n";
+ dec_indent();
+ f << "}\n";
+ }
+
*impl_f << f.str(); f.str("");
}
@@ -2044,6 +2181,44 @@ struct CxxrtlWorker {
}
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) {
// Although both non-feedback buffered combinatorial wires and apparent feedback wires may be eliminated
@@ -2120,6 +2295,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 +2489,23 @@ 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-optimized public wires. this also makes it\n");
+ log(" possible to use the C API.\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 +2521,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 +2565,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 debug information level %d.\n", debug_level);
+ }
+
std::ofstream intf_f;
if (worker.split_intf) {
if (filename == "<stdout>")
diff --git a/backends/cxxrtl/cxxrtl_capi.cc b/backends/cxxrtl/cxxrtl_capi.cc
new file mode 100644
index 000000000..489d72da5
--- /dev/null
+++ b/backends/cxxrtl/cxxrtl_capi.cc
@@ -0,0 +1,60 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2020 whitequark <whitequark@whitequark.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted.
+ *
+ * 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.
+ *
+ */
+
+// This file is a part of the CXXRTL C API. It should be used together with `cxxrtl_capi.h`.
+
+#include <backends/cxxrtl/cxxrtl.h>
+#include <backends/cxxrtl/cxxrtl_capi.h>
+
+struct _cxxrtl_handle {
+ std::unique_ptr<cxxrtl::module> module;
+ cxxrtl::debug_items objects;
+};
+
+// Private function for use by other units of the C API.
+const cxxrtl::debug_items &cxxrtl_debug_items_from_handle(cxxrtl_handle handle) {
+ return handle->objects;
+}
+
+cxxrtl_handle cxxrtl_create(cxxrtl_toplevel design) {
+ cxxrtl_handle handle = new _cxxrtl_handle;
+ handle->module = std::move(design->module);
+ handle->module->debug_info(handle->objects);
+ delete design;
+ return handle;
+}
+
+void cxxrtl_destroy(cxxrtl_handle handle) {
+ delete handle;
+}
+
+size_t cxxrtl_step(cxxrtl_handle handle) {
+ return handle->module->step();
+}
+
+cxxrtl_object *cxxrtl_get(cxxrtl_handle handle, const char *name) {
+ if (handle->objects.count(name) > 0)
+ return static_cast<cxxrtl_object*>(&handle->objects.at(name));
+ return nullptr;
+}
+
+void cxxrtl_enum(cxxrtl_handle handle, void *data,
+ void (*callback)(void *data, const char *name, cxxrtl_object *object)) {
+ for (auto &it : handle->objects)
+ callback(data, it.first.c_str(), static_cast<cxxrtl_object*>(&it.second));
+}
diff --git a/backends/cxxrtl/cxxrtl_capi.h b/backends/cxxrtl/cxxrtl_capi.h
new file mode 100644
index 000000000..46aa662b2
--- /dev/null
+++ b/backends/cxxrtl/cxxrtl_capi.h
@@ -0,0 +1,152 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2020 whitequark <whitequark@whitequark.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted.
+ *
+ * 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.
+ *
+ */
+
+#ifndef CXXRTL_CAPI_H
+#define CXXRTL_CAPI_H
+
+// This file is a part of the CXXRTL C API. It should be used together with `cxxrtl_capi.cc`.
+//
+// The CXXRTL C API makes it possible to drive CXXRTL designs using C or any other language that
+// supports the C ABI, for example, Python. It does not provide a way to implement black boxes.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Opaque reference to a design toplevel.
+//
+// A design toplevel can only be used to create a design handle.
+typedef struct _cxxrtl_toplevel *cxxrtl_toplevel;
+
+// The constructor for a design toplevel is provided as a part of generated code for that design.
+// Its prototype matches:
+//
+// cxxrtl_toplevel <design-name>_create();
+
+// Opaque reference to a design handle.
+//
+// A design handle is required by all operations in the C API.
+typedef struct _cxxrtl_handle *cxxrtl_handle;
+
+// Create a design handle from a design toplevel.
+//
+// The `design` is consumed by this operation and cannot be used afterwards.
+cxxrtl_handle cxxrtl_create(cxxrtl_toplevel design);
+
+// Release all resources used by a design and its handle.
+void cxxrtl_destroy(cxxrtl_handle handle);
+
+// Simulate the design to a fixed point.
+//
+// Returns the number of delta cycles.
+size_t cxxrtl_step(cxxrtl_handle handle);
+
+// Type of a simulated object.
+enum cxxrtl_type {
+ // Values correspond to singly buffered netlist nodes, i.e. nodes driven exclusively by
+ // combinatorial cells, or toplevel input nodes.
+ //
+ // Values can be inspected via the `curr` pointer. If the `next` pointer is NULL, the value is
+ // driven by a constant and can never be modified. Otherwise, the value can be modified through
+ // 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.
+ CXXRTL_VALUE = 0,
+
+ // Wires correspond to doubly buffered netlist nodes, i.e. nodes driven, at least in part, by
+ // 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
+ // distinct for wires). Note that changes to the bits driven by combinatorial cells will be
+ // ignored.
+ //
+ // Wires always have depth 1.
+ CXXRTL_WIRE = 1,
+
+ // Memories correspond to memory cells.
+ //
+ // Memories can be inspected and modified via the `curr` pointer. Due to a limitation of this
+ // API, memories cannot yet be modified in a guaranteed race-free way, and the `next` pointer is
+ // always NULL.
+ CXXRTL_MEMORY = 2,
+
+ // More object types will be added in the future, but the existing ones will never change.
+};
+
+// Description of a simulated object.
+//
+// The `data` array can be accessed directly to inspect and, if applicable, modify the bits
+// stored in the object.
+struct cxxrtl_object {
+ // Type of the object.
+ //
+ // All objects have the same memory layout determined by `width` and `depth`, but the type
+ // determines all other properties of the object.
+ uint32_t type; // actually `enum cxxrtl_type`
+
+ // Width of the object in bits.
+ size_t width;
+
+ // Depth of the object. Only meaningful for memories; for other objects, always 1.
+ size_t depth;
+
+ // Bits stored in the object, as 32-bit chunks, least significant bits first.
+ //
+ // The width is rounded up to a multiple of 32; the padding bits are always set to 0 by
+ // the simulation code, and must be always written as 0 when modified by user code.
+ // In memories, every element is stored contiguously. Therefore, the total number of chunks
+ // in any object is `((width + 31) / 32) * depth`.
+ //
+ // To allow the simulation to be partitioned into multiple independent units communicating
+ // through wires, the bits are double buffered. To avoid race conditions, user code should
+ // always read from `curr` and write to `next`. The `curr` pointer is always valid; for objects
+ // that cannot be modified, or cannot be modified in a race-free way, `next` is NULL.
+ uint32_t *curr;
+ uint32_t *next;
+
+ // More description fields will be added in the future, but the existing ones will never change.
+};
+
+// Retrieve description of a simulated object.
+//
+// The `name` is the full hierarchical name of the object in the Yosys notation, where public names
+// have a `\` prefix and hierarchy levels are separated by single spaces. For example, if
+// the top-level module instantiates a module `foo`, which in turn contains a wire `bar`, the full
+// hierarchical name is `\foo \bar`.
+//
+// Returns the object if it was found, NULL otherwise. The returned value is valid until the design
+// is destroyed.
+struct cxxrtl_object *cxxrtl_get(cxxrtl_handle handle, const char *name);
+
+// Enumerate simulated objects.
+//
+// For every object in the simulation, `callback` is called with the provided `data`, the full
+// hierarchical name of the object (see `cxxrtl_get` for details), and the object description.
+// The provided `name` and `object` values are valid until the design is destroyed.
+void cxxrtl_enum(cxxrtl_handle handle, void *data,
+ void (*callback)(void *data, const char *name, struct cxxrtl_object *object));
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/backends/cxxrtl/cxxrtl_vcd.h b/backends/cxxrtl/cxxrtl_vcd.h
new file mode 100644
index 000000000..f6b78bbf7
--- /dev/null
+++ b/backends/cxxrtl/cxxrtl_vcd.h
@@ -0,0 +1,224 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2020 whitequark <whitequark@whitequark.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted.
+ *
+ * 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.
+ *
+ */
+
+#ifndef CXXRTL_VCD_H
+#define CXXRTL_VCD_H
+
+#include <backends/cxxrtl/cxxrtl.h>
+
+namespace cxxrtl {
+
+class vcd_writer {
+ struct variable {
+ size_t ident;
+ size_t width;
+ chunk_t *curr;
+ size_t prev_off;
+ };
+
+ std::vector<std::string> current_scope;
+ std::vector<variable> variables;
+ std::vector<chunk_t> cache;
+ std::map<chunk_t*, size_t> aliases;
+ bool streaming = false;
+
+ void emit_timescale(unsigned number, const std::string &unit) {
+ assert(!streaming);
+ assert(number == 1 || number == 10 || number == 100);
+ assert(unit == "s" || unit == "ms" || unit == "us" ||
+ unit == "ns" || unit == "ps" || unit == "fs");
+ buffer += "$timescale " + std::to_string(number) + " " + unit + " $end\n";
+ }
+
+ void emit_scope(const std::vector<std::string> &scope) {
+ assert(!streaming);
+ while (current_scope.size() > scope.size() ||
+ (current_scope.size() > 0 &&
+ current_scope[current_scope.size() - 1] != scope[current_scope.size() - 1])) {
+ buffer += "$upscope $end\n";
+ current_scope.pop_back();
+ }
+ while (current_scope.size() < scope.size()) {
+ buffer += "$scope module " + scope[current_scope.size()] + " $end\n";
+ current_scope.push_back(scope[current_scope.size()]);
+ }
+ }
+
+ void emit_ident(size_t ident) {
+ do {
+ buffer += '!' + ident % 94; // "base94"
+ ident /= 94;
+ } while (ident != 0);
+ }
+
+ void emit_var(const variable &var, const std::string &type, const std::string &name) {
+ assert(!streaming);
+ buffer += "$var " + type + " " + std::to_string(var.width) + " ";
+ emit_ident(var.ident);
+ buffer += " " + name + " $end\n";
+ }
+
+ void emit_enddefinitions() {
+ assert(!streaming);
+ buffer += "$enddefinitions $end\n";
+ streaming = true;
+ }
+
+ void emit_time(uint64_t timestamp) {
+ assert(streaming);
+ buffer += "#" + std::to_string(timestamp) + "\n";
+ }
+
+ void emit_scalar(const variable &var) {
+ assert(streaming);
+ assert(var.width == 1);
+ buffer += (*var.curr ? '1' : '0');
+ emit_ident(var.ident);
+ buffer += '\n';
+ }
+
+ void emit_vector(const variable &var) {
+ assert(streaming);
+ buffer += 'b';
+ for (size_t bit = var.width - 1; bit != (size_t)-1; bit--) {
+ bool bit_curr = var.curr[bit / (8 * sizeof(chunk_t))] & (1 << (bit % (8 * sizeof(chunk_t))));
+ buffer += (bit_curr ? '1' : '0');
+ }
+ buffer += ' ';
+ emit_ident(var.ident);
+ buffer += '\n';
+ }
+
+ const variable &register_variable(size_t width, chunk_t *curr, bool immutable = false) {
+ if (aliases.count(curr)) {
+ return variables[aliases[curr]];
+ } 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) {
+ 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);
+ if (std::equal(&var.curr[0], &var.curr[chunks], &cache[var.prev_off])) {
+ return false;
+ } else {
+ std::copy(&var.curr[0], &var.curr[chunks], &cache[var.prev_off]);
+ return true;
+ }
+ }
+
+ static std::vector<std::string> split_hierarchy(const std::string &hier_name) {
+ std::vector<std::string> hierarchy;
+ size_t prev = 0;
+ while (true) {
+ size_t curr = hier_name.find_first_of(' ', prev);
+ if (curr == std::string::npos) {
+ hierarchy.push_back(hier_name.substr(prev));
+ break;
+ } else {
+ hierarchy.push_back(hier_name.substr(prev, curr - prev));
+ prev = curr + 1;
+ }
+ }
+ return hierarchy;
+ }
+
+public:
+ std::string buffer;
+
+ void timescale(unsigned number, const std::string &unit) {
+ emit_timescale(number, unit);
+ }
+
+ void add(const std::string &hier_name, const debug_item &item) {
+ std::vector<std::string> scope = split_hierarchy(hier_name);
+ std::string name = scope.back();
+ scope.pop_back();
+
+ emit_scope(scope);
+ switch (item.type) {
+ // Not the best naming but oh well...
+ case debug_item::VALUE:
+ emit_var(register_variable(item.width, item.curr, /*immutable=*/item.next == nullptr), "wire", name);
+ break;
+ case debug_item::WIRE:
+ emit_var(register_variable(item.width, item.curr), "reg", name);
+ break;
+ case debug_item::MEMORY: {
+ const size_t stride = (item.width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8);
+ for (size_t index = 0; index < item.depth; index++) {
+ chunk_t *nth_curr = &item.curr[stride * index];
+ std::string nth_name = name + '[' + std::to_string(index) + ']';
+ emit_var(register_variable(item.width, nth_curr), "reg", nth_name);
+ }
+ break;
+ }
+ }
+ }
+
+ template<class Filter>
+ void add(const debug_items &items, const Filter &filter) {
+ // `debug_items` is a map, so the items are already sorted in an order optimal for emitting
+ // VCD scope sections.
+ for (auto &it : items)
+ if (filter(it.first, it.second))
+ add(it.first, it.second);
+ }
+
+ void add(const debug_items &items) {
+ this->template add(items, [](const std::string &, const debug_item &) {
+ return true;
+ });
+ }
+
+ void add_without_memories(const debug_items &items) {
+ this->template add(items, [](const std::string &, const debug_item &item) {
+ return item.type == debug_item::VALUE || item.type == debug_item::WIRE;
+ });
+ }
+
+ void sample(uint64_t timestamp) {
+ bool first_sample = !streaming;
+ if (first_sample) {
+ emit_scope({});
+ emit_enddefinitions();
+ }
+ emit_time(timestamp);
+ for (auto var : variables)
+ if (test_variable(var) || first_sample) {
+ if (var.width == 1)
+ emit_scalar(var);
+ else
+ emit_vector(var);
+ }
+ }
+};
+
+}
+
+#endif
diff --git a/backends/cxxrtl/cxxrtl_vcd_capi.cc b/backends/cxxrtl/cxxrtl_vcd_capi.cc
new file mode 100644
index 000000000..52a9198b8
--- /dev/null
+++ b/backends/cxxrtl/cxxrtl_vcd_capi.cc
@@ -0,0 +1,83 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2020 whitequark <whitequark@whitequark.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted.
+ *
+ * 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.
+ *
+ */
+
+// This file is a part of the CXXRTL C API. It should be used together with `cxxrtl_vcd_capi.h`.
+
+#include <backends/cxxrtl/cxxrtl_vcd.h>
+#include <backends/cxxrtl/cxxrtl_vcd_capi.h>
+
+extern const cxxrtl::debug_items &cxxrtl_debug_items_from_handle(cxxrtl_handle handle);
+
+struct _cxxrtl_vcd {
+ cxxrtl::vcd_writer writer;
+ bool flush = false;
+};
+
+cxxrtl_vcd cxxrtl_vcd_create() {
+ return new _cxxrtl_vcd;
+}
+
+void cxxrtl_vcd_destroy(cxxrtl_vcd vcd) {
+ delete vcd;
+}
+
+void cxxrtl_vcd_timescale(cxxrtl_vcd vcd, int number, const char *unit) {
+ vcd->writer.timescale(number, unit);
+}
+
+void cxxrtl_vcd_add(cxxrtl_vcd vcd, const char *name, cxxrtl_object *object) {
+ // Note the copy. We don't know whether `object` came from a design (in which case it is
+ // an instance of `debug_item`), or from user code (in which case it is an instance of
+ // `cxxrtl_object`), so casting the pointer wouldn't be safe.
+ vcd->writer.add(name, cxxrtl::debug_item(*object));
+}
+
+void cxxrtl_vcd_add_from(cxxrtl_vcd vcd, cxxrtl_handle handle) {
+ vcd->writer.add(cxxrtl_debug_items_from_handle(handle));
+}
+
+void cxxrtl_vcd_add_from_if(cxxrtl_vcd vcd, cxxrtl_handle handle, void *data,
+ int (*filter)(void *data, const char *name,
+ const cxxrtl_object *object)) {
+ vcd->writer.add(cxxrtl_debug_items_from_handle(handle),
+ [=](const std::string &name, const cxxrtl::debug_item &item) {
+ return filter(data, name.c_str(), static_cast<const cxxrtl_object*>(&item));
+ });
+}
+
+void cxxrtl_vcd_add_from_without_memories(cxxrtl_vcd vcd, cxxrtl_handle handle) {
+ vcd->writer.add_without_memories(cxxrtl_debug_items_from_handle(handle));
+}
+
+void cxxrtl_vcd_sample(cxxrtl_vcd vcd, uint64_t time) {
+ if (vcd->flush) {
+ vcd->writer.buffer.clear();
+ vcd->flush = false;
+ }
+ vcd->writer.sample(time);
+}
+
+void cxxrtl_vcd_read(cxxrtl_vcd vcd, const char **data, size_t *size) {
+ if (vcd->flush) {
+ vcd->writer.buffer.clear();
+ vcd->flush = false;
+ }
+ *data = vcd->writer.buffer.c_str();
+ *size = vcd->writer.buffer.size();
+ vcd->flush = true;
+}
diff --git a/backends/cxxrtl/cxxrtl_vcd_capi.h b/backends/cxxrtl/cxxrtl_vcd_capi.h
new file mode 100644
index 000000000..6a7fb9f47
--- /dev/null
+++ b/backends/cxxrtl/cxxrtl_vcd_capi.h
@@ -0,0 +1,107 @@
+/*
+ * yosys -- Yosys Open SYnthesis Suite
+ *
+ * Copyright (C) 2020 whitequark <whitequark@whitequark.org>
+ *
+ * Permission to use, copy, modify, and/or distribute this software for any
+ * purpose with or without fee is hereby granted.
+ *
+ * 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.
+ *
+ */
+
+#ifndef CXXRTL_VCD_CAPI_H
+#define CXXRTL_VCD_CAPI_H
+
+// This file is a part of the CXXRTL C API. It should be used together with `cxxrtl_vcd_capi.cc`.
+//
+// The CXXRTL C API for VCD writing makes it possible to insert virtual probes into designs and
+// dump waveforms to Value Change Dump files.
+
+#include <stddef.h>
+#include <stdint.h>
+
+#include <backends/cxxrtl/cxxrtl_capi.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Opaque reference to a VCD writer.
+typedef struct _cxxrtl_vcd *cxxrtl_vcd;
+
+// Create a VCD writer.
+cxxrtl_vcd cxxrtl_vcd_create();
+
+// Release all resources used by a VCD writer.
+void cxxrtl_vcd_destroy(cxxrtl_vcd vcd);
+
+// Set VCD timescale.
+//
+// The `number` must be 1, 10, or 100, and the `unit` must be one of `"s"`, `"ms"`, `"us"`, `"ns"`,
+// `"ps"`, or `"fs"`.
+//
+// Timescale can only be set before the first call to `cxxrtl_vcd_sample`.
+void cxxrtl_vcd_timescale(cxxrtl_vcd vcd, int number, const char *unit);
+
+// Schedule a specific CXXRTL object to be sampled.
+//
+// The `name` is a full hierarchical name as described for `cxxrtl_get`; it does not need to match
+// the original name of `object`, if any. The `object` must outlive the VCD writer, but there are
+// no other requirements; if desired, it can be provided by user code, rather than come from
+// a design.
+//
+// Objects can only be scheduled before the first call to `cxxrtl_vcd_sample`.
+void cxxrtl_vcd_add(cxxrtl_vcd vcd, const char *name, struct cxxrtl_object *object);
+
+// Schedule all CXXRTL objects in a simulation.
+//
+// The design `handle` must outlive the VCD writer.
+//
+// Objects can only be scheduled before the first call to `cxxrtl_vcd_sample`.
+void cxxrtl_vcd_add_from(cxxrtl_vcd vcd, cxxrtl_handle handle);
+
+// Schedule CXXRTL objects in a simulation that match a given predicate.
+//
+// For every object in the simulation, `filter` is called with the provided `data`, the full
+// hierarchical name of the object (see `cxxrtl_get` for details), and the object description.
+// The object will be sampled if the predicate returns a non-zero value.
+//
+// Objects can only be scheduled before the first call to `cxxrtl_vcd_sample`.
+void cxxrtl_vcd_add_from_if(cxxrtl_vcd vcd, cxxrtl_handle handle, void *data,
+ int (*filter)(void *data, const char *name,
+ const struct cxxrtl_object *object));
+
+// Schedule all CXXRTL objects in a simulation except for memories.
+//
+// The design `handle` must outlive the VCD writer.
+//
+// Objects can only be scheduled before the first call to `cxxrtl_vcd_sample`.
+void cxxrtl_vcd_add_from_without_memories(cxxrtl_vcd vcd, cxxrtl_handle handle);
+
+// Sample all scheduled objects.
+//
+// First, `time` is written to the internal buffer. Second, the values of every signal changed since
+// the previous call to `cxxrtl_vcd_sample` (all values if this is the first call) are written to
+// the internal buffer. The contents of the buffer can be retrieved with `cxxrtl_vcd_read`.
+void cxxrtl_vcd_sample(cxxrtl_vcd vcd, uint64_t time);
+
+// Retrieve buffered VCD data.
+//
+// The pointer to the start of the next chunk of VCD data is assigned to `*data`, and the length
+// of that chunk is assigned to `*size`. The pointer to the data is valid until the next call to
+// `cxxrtl_vcd_sample` or `cxxrtl_vcd_read`. Once all of the buffered data has been retrieved,
+// this function will always return zero sized chunks.
+void cxxrtl_vcd_read(cxxrtl_vcd vcd, const char **data, size_t *size);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/backends/smt2/smtio.py b/backends/smt2/smtio.py
index 9f7c8c6d9..72ab39d39 100644
--- a/backends/smt2/smtio.py
+++ b/backends/smt2/smtio.py
@@ -39,7 +39,7 @@ if os.name == "posix":
smtio_stacksize = 128 * 1024 * 1024
if os.uname().sysname == "Darwin":
# MacOS has rather conservative stack limits
- smtio_stacksize = 16 * 1024 * 1024
+ smtio_stacksize = 8 * 1024 * 1024
if current_rlimit_stack[1] != resource.RLIM_INFINITY:
smtio_stacksize = min(smtio_stacksize, current_rlimit_stack[1])
if current_rlimit_stack[0] < smtio_stacksize: