cxxrtl: add a C API for driving and introspecting designs.
authorwhitequark <whitequark@whitequark.org>
Fri, 5 Jun 2020 13:52:30 +0000 (13:52 +0000)
committerwhitequark <whitequark@whitequark.org>
Sat, 6 Jun 2020 21:12:55 +0000 (21:12 +0000)
Compared to the C++ API, the C API currently has two limitations:
  1. Memories cannot be updated in a race-free way.
  2. Black boxes cannot be implemented in C.

backends/cxxrtl/cxxrtl.cc
backends/cxxrtl/cxxrtl.h
backends/cxxrtl/cxxrtl_capi.cc [new file with mode: 0644]
backends/cxxrtl/cxxrtl_capi.h [new file with mode: 0644]

index edd606c1a6623868026469a499b6cdf0e82e3add..4dc534513fcf46bfd93358ef40363a6a4f6ca7c1 100644 (file)
@@ -1778,6 +1778,7 @@ struct CxxrtlWorker {
 
        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()) {
@@ -1787,6 +1788,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()) {
@@ -1808,6 +1811,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";
@@ -1818,6 +1840,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("");
                }
@@ -1827,6 +1851,10 @@ struct CxxrtlWorker {
                else
                        f << "#include <backends/cxxrtl/cxxrtl.h>\n";
                f << "\n";
+               f << "#ifdef CXXRTL_INCLUDE_CAPI_IMPL\n";
+               f << "#include <backends/cxxrtl/cxxrtl_capi.cc>\n";
+               f << "#endif\n";
+               f << "\n";
                f << "using namespace cxxrtl_yosys;\n";
                f << "\n";
                f << "namespace " << design_ns << " {\n";
@@ -1837,6 +1865,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("");
        }
 
index 14613afb0b59943639185b34ba14b51ce59865c5..aba2c77a1dd0ba764b813ecf003f740d7460e260 100644 (file)
 #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.
 //
@@ -716,39 +718,49 @@ 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 {
+//
+// 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  = 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`
+               VALUE  = CXXRTL_VALUE,
+               WIRE   = CXXRTL_WIRE,
+               MEMORY = CXXRTL_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");
-               }
+       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(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");
-               }
+       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) : 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");
-               }
+       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");
 
@@ -779,7 +791,12 @@ struct module {
 
 } // 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
diff --git a/backends/cxxrtl/cxxrtl_capi.cc b/backends/cxxrtl/cxxrtl_capi.cc
new file mode 100644 (file)
index 0000000..0dcd640
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ *  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;
+};
+
+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, struct 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 (file)
index 0000000..bee5a94
--- /dev/null
@@ -0,0 +1,151 @@
+/*
+ *  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 and modified via the `next` pointer (which are
+       // equal for values); however, 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); however, 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