cxxrtl: add a C API for driving and introspecting designs.

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.
This commit is contained in:
whitequark 2020-06-05 13:52:30 +00:00
parent f6e16e7f4c
commit c399359ed6
4 changed files with 291 additions and 29 deletions

View File

@ -1778,6 +1778,7 @@ struct CxxrtlWorker {
void dump_design(RTLIL::Design *design) void dump_design(RTLIL::Design *design)
{ {
RTLIL::Module *top_module = nullptr;
std::vector<RTLIL::Module*> modules; std::vector<RTLIL::Module*> modules;
TopoSort<RTLIL::Module*> topo_design; TopoSort<RTLIL::Module*> topo_design;
for (auto module : design->modules()) { for (auto module : design->modules()) {
@ -1787,6 +1788,8 @@ struct CxxrtlWorker {
modules.push_back(module); // cxxrtl blackboxes first modules.push_back(module); // cxxrtl blackboxes first
if (module->get_blackbox_attribute() || module->get_bool_attribute(ID(cxxrtl_blackbox))) if (module->get_blackbox_attribute() || module->get_bool_attribute(ID(cxxrtl_blackbox)))
continue; continue;
if (module->get_bool_attribute(ID::top))
top_module = module;
topo_design.node(module); topo_design.node(module);
for (auto cell : module->cells()) { for (auto cell : module->cells()) {
@ -1808,6 +1811,25 @@ struct CxxrtlWorker {
f << "#ifndef " << include_guard << "\n"; f << "#ifndef " << include_guard << "\n";
f << "#define " << include_guard << "\n"; f << "#define " << include_guard << "\n";
f << "\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 << "#include <backends/cxxrtl/cxxrtl.h>\n";
f << "\n"; f << "\n";
f << "using namespace cxxrtl;\n"; f << "using namespace cxxrtl;\n";
@ -1818,6 +1840,8 @@ struct CxxrtlWorker {
dump_module_intf(module); dump_module_intf(module);
f << "} // namespace " << design_ns << "\n"; f << "} // namespace " << design_ns << "\n";
f << "\n"; f << "\n";
f << "#endif // __cplusplus\n";
f << "\n";
f << "#endif\n"; f << "#endif\n";
*intf_f << f.str(); f.str(""); *intf_f << f.str(); f.str("");
} }
@ -1827,6 +1851,10 @@ struct CxxrtlWorker {
else else
f << "#include <backends/cxxrtl/cxxrtl.h>\n"; f << "#include <backends/cxxrtl/cxxrtl.h>\n";
f << "\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 << "using namespace cxxrtl_yosys;\n";
f << "\n"; f << "\n";
f << "namespace " << design_ns << " {\n"; f << "namespace " << design_ns << " {\n";
@ -1837,6 +1865,17 @@ struct CxxrtlWorker {
dump_module_impl(module); dump_module_impl(module);
} }
f << "} // namespace " << design_ns << "\n"; 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(""); *impl_f << f.str(); f.str("");
} }

View File

@ -33,13 +33,15 @@
#include <memory> #include <memory>
#include <sstream> #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 // 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 perform a straightforward translation of RTLIL structures to readable C++, relying on the C++ compiler
// to unwrap the abstraction and generate efficient code. // to unwrap the abstraction and generate efficient code.
namespace cxxrtl { 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 // is the same regardless of the value width to simplify manipulating values via FFI interfaces, e.g. driving
// and introspecting the simulation in Python. // 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. // 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++. // 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 { enum : uint32_t {
VALUE = 0, VALUE = CXXRTL_VALUE,
WIRE = 1, WIRE = CXXRTL_WIRE,
MEMORY = 2, MEMORY = CXXRTL_MEMORY,
} 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> template<size_t Bits>
debug_item(value<Bits> &item) : type(VALUE), width(Bits), depth(1), debug_item(value<Bits> &item) {
curr(item.data), next(nullptr) { static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t),
static_assert(sizeof(item) == value<Bits>::chunks * sizeof(chunk_t), "value<Bits> is not compatible with C layout");
"value<Bits> is not compatible with C layout"); type = VALUE;
} width = Bits;
depth = 1;
curr = item.data;
next = item.data;
}
template<size_t Bits> template<size_t Bits>
debug_item(wire<Bits> &item) : type(WIRE), width(Bits), depth(1), debug_item(wire<Bits> &item) {
curr(item.curr.data), next(item.next.data) { static_assert(sizeof(item.curr) == value<Bits>::chunks * sizeof(chunk_t) &&
static_assert(sizeof(item.curr) == value<Bits>::chunks * sizeof(chunk_t) && sizeof(item.next) == value<Bits>::chunks * sizeof(chunk_t),
sizeof(item.next) == value<Bits>::chunks * sizeof(chunk_t), "wire<Bits> is not compatible with C layout");
"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> template<size_t Width>
debug_item(memory<Width> &item) : type(MEMORY), width(Width), depth(item.data.size()), debug_item(memory<Width> &item) {
curr(item.data.empty() ? nullptr : item.data[0].data), next(nullptr) { static_assert(sizeof(item.data[0]) == value<Width>::chunks * sizeof(chunk_t),
static_assert(sizeof(item.data[0]) == value<Width>::chunks * sizeof(chunk_t), "memory<Width> is not compatible with C layout");
"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"); 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 } // 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. // and indepenent of Yosys implementation details.
// //
// The `write_cxxrtl` pass translates internal cells (cells with names that start with `$`) to calls of these // The `write_cxxrtl` pass translates internal cells (cells with names that start with `$`) to calls of these

View File

@ -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));
}

View File

@ -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