3
0
Fork 0
mirror of https://github.com/YosysHQ/yosys synced 2025-07-23 12:48:54 +00:00

cxxrtl: reorganize runtime component files.

In preparation for substantial expansion of CXXRTL's runtime, this commit
reorganizes the files used by the implementation. Only minimal changes are
required in a consumer.

First, change:
  -I$(yosys-config --datdir)/include
to:
  -I$(yosys-config --datdir)/include/backends/cxxrtl/runtime

Second, change:
  #include <backends/cxxrtl/cxxrtl.h>
to:
  #include <cxxrtl/cxxrtl.h>
(and do the same for cxxrtl_vcd.h, etc.)
This commit is contained in:
Catherine 2023-11-28 12:09:47 +00:00
parent 3dd5262355
commit 62bbd086b1
10 changed files with 43 additions and 24 deletions

View file

@ -0,0 +1,18 @@
This directory contains the runtime components of CXXRTL and should be placed on the include path
when building the simulation using the `-I${YOSYS}/backends/cxxrtl/runtime` option. These components
are not used in the Yosys binary; they are only built as a part of the simulation binary.
The interfaces declared in `cxxrtl_capi*.h` contain the stable C API. These interfaces will not be
changed in backward-incompatible ways unless no other option is available, and any breaking changes
will be made in a way that causes the downstream code to fail in a visible way. The ABI of these
interfaces is considered stable as well, and it will not use features complicating its use via
libraries such as libffi or ctypes.
The implementations in `cxxrtl_capi*.cc` are considered private; they are still placed in the include
path to enable build-system-less builds (where the CXXRTL runtime component is included in the C++
file of the simulation toplevel).
The interfaces declared in `cxxrtl*.h` (without `capi`) are unstable and may change without notice.
For clarity, all of the files in this directory and its subdirectories have unique names regardless
of the directory where they are placed.

View file

@ -0,0 +1,135 @@
/*
* 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/cxxrtl_capi.h`.
#include <cxxrtl/capi/cxxrtl_capi.h>
#include <cxxrtl/cxxrtl.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) {
return cxxrtl_create_at(design, "");
}
cxxrtl_handle cxxrtl_create_at(cxxrtl_toplevel design, const char *root) {
std::string path = root;
if (!path.empty()) {
// module::debug_info() accepts either an empty path, or a path ending in space to simplify
// the logic in generated code. While this is sketchy at best to expose in the C++ API, this
// would be a lot worse in the C API, so don't expose it here.
assert(path.back() != ' ');
path += ' ';
}
cxxrtl_handle handle = new _cxxrtl_handle;
handle->module = std::move(design->module);
handle->module->debug_info(handle->objects, path);
delete design;
return handle;
}
void cxxrtl_destroy(cxxrtl_handle handle) {
delete handle;
}
void cxxrtl_reset(cxxrtl_handle handle) {
handle->module->reset();
}
int cxxrtl_eval(cxxrtl_handle handle) {
return handle->module->eval();
}
int cxxrtl_commit(cxxrtl_handle handle) {
return handle->module->commit();
}
size_t cxxrtl_step(cxxrtl_handle handle) {
return handle->module->step();
}
struct cxxrtl_object *cxxrtl_get_parts(cxxrtl_handle handle, const char *name, size_t *parts) {
auto it = handle->objects.table.find(name);
if (it == handle->objects.table.end())
return nullptr;
*parts = it->second.size();
return static_cast<cxxrtl_object*>(&it->second[0]);
}
void cxxrtl_enum(cxxrtl_handle handle, void *data,
void (*callback)(void *data, const char *name,
cxxrtl_object *object, size_t parts)) {
for (auto &it : handle->objects.table)
callback(data, it.first.c_str(), static_cast<cxxrtl_object*>(&it.second[0]), it.second.size());
}
void cxxrtl_outline_eval(cxxrtl_outline outline) {
outline->eval();
}
int cxxrtl_attr_type(cxxrtl_attr_set attrs_, const char *name) {
auto attrs = (cxxrtl::metadata_map*)attrs_;
if (!attrs->count(name))
return CXXRTL_ATTR_NONE;
switch (attrs->at(name).value_type) {
case cxxrtl::metadata::UINT:
return CXXRTL_ATTR_UNSIGNED_INT;
case cxxrtl::metadata::SINT:
return CXXRTL_ATTR_SIGNED_INT;
case cxxrtl::metadata::STRING:
return CXXRTL_ATTR_STRING;
case cxxrtl::metadata::DOUBLE:
return CXXRTL_ATTR_DOUBLE;
default:
// Present unsupported attribute type the same way as no attribute at all.
return CXXRTL_ATTR_NONE;
}
}
uint64_t cxxrtl_attr_get_unsigned_int(cxxrtl_attr_set attrs_, const char *name) {
auto &attrs = *(cxxrtl::metadata_map*)attrs_;
assert(attrs.count(name) && attrs.at(name).value_type == cxxrtl::metadata::UINT);
return attrs[name].as_uint();
}
int64_t cxxrtl_attr_get_signed_int(cxxrtl_attr_set attrs_, const char *name) {
auto &attrs = *(cxxrtl::metadata_map*)attrs_;
assert(attrs.count(name) && attrs.at(name).value_type == cxxrtl::metadata::SINT);
return attrs[name].as_sint();
}
const char *cxxrtl_attr_get_string(cxxrtl_attr_set attrs_, const char *name) {
auto &attrs = *(cxxrtl::metadata_map*)attrs_;
assert(attrs.count(name) && attrs.at(name).value_type == cxxrtl::metadata::STRING);
return attrs[name].as_string().c_str();
}
double cxxrtl_attr_get_double(cxxrtl_attr_set attrs_, const char *name) {
auto &attrs = *(cxxrtl::metadata_map*)attrs_;
assert(attrs.count(name) && attrs.at(name).value_type == cxxrtl::metadata::DOUBLE);
return attrs[name].as_double();
}

View file

@ -0,0 +1,376 @@
/*
* 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>
#include <assert.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);
// Create a design handle at a given hierarchy position from a design toplevel.
//
// This operation is similar to `cxxrtl_create`, except the full hierarchical name of every object
// is prepended with `root`.
cxxrtl_handle cxxrtl_create_at(cxxrtl_toplevel design, const char *root);
// Release all resources used by a design and its handle.
void cxxrtl_destroy(cxxrtl_handle handle);
// Reinitialize the design, replacing the internal state with the reset values while preserving
// black boxes.
//
// This operation is essentially equivalent to a power-on reset. Values, wires, and memories are
// returned to their reset state while preserving the state of black boxes and keeping all of
// the interior pointers obtained with e.g. `cxxrtl_get` valid.
void cxxrtl_reset(cxxrtl_handle handle);
// Evaluate the design, propagating changes on inputs to the `next` value of internal state and
// output wires.
//
// Returns 1 if the design is known to immediately converge, 0 otherwise.
int cxxrtl_eval(cxxrtl_handle handle);
// Commit the design, replacing the `curr` value of internal state and output wires with the `next`
// value.
//
// Return 1 if any of the `curr` values were updated, 0 otherwise.
int cxxrtl_commit(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.
//
// The type of a simulated object indicates the way it is stored and the operations that are legal
// to perform on it (i.e. won't crash the simulation). It says very little about object semantics,
// which is specified through flags.
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. They are also
// present in non-optimized builds.
//
// 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,
// Aliases correspond to netlist nodes driven by another node such that their value is always
// exactly equal.
//
// Aliases can be inspected via the `curr` pointer. They cannot be modified, and the `next`
// pointer is always NULL.
CXXRTL_ALIAS = 3,
// Outlines correspond to netlist nodes that were optimized in a way that makes them inaccessible
// outside of a module's `eval()` function. At the highest debug information level, every inlined
// node has a corresponding outline object.
//
// Outlines can be inspected via the `curr` pointer and can never be modified; the `next` pointer
// is always NULL. Unlike all other objects, the bits of an outline object are meaningful only
// after a call to `cxxrtl_outline_eval` and until any subsequent modification to the netlist.
// Observing this requirement is the responsibility of the caller; it is not enforced.
//
// Outlines always correspond to combinatorial netlist nodes that are not ports.
CXXRTL_OUTLINE = 4,
// More object types may be added in the future, but the existing ones will never change.
};
// Flags of a simulated object.
//
// The flags of a simulated object indicate its role in the netlist:
// * The flags `CXXRTL_INPUT` and `CXXRTL_OUTPUT` designate module ports.
// * The flags `CXXRTL_DRIVEN_SYNC`, `CXXRTL_DRIVEN_COMB`, and `CXXRTL_UNDRIVEN` specify
// the semantics of node state. An object with several of these flags set has different bits
// follow different semantics.
enum cxxrtl_flag {
// Node is a module input port.
//
// This flag can be set on objects of type `CXXRTL_VALUE` and `CXXRTL_WIRE`. It may be combined
// with `CXXRTL_OUTPUT`, as well as other flags.
CXXRTL_INPUT = 1 << 0,
// Node is a module output port.
//
// This flag can be set on objects of type `CXXRTL_WIRE`. It may be combined with `CXXRTL_INPUT`,
// as well as other flags.
CXXRTL_OUTPUT = 1 << 1,
// Node is a module inout port.
//
// This flag can be set on objects of type `CXXRTL_WIRE`. It may be combined with other flags.
CXXRTL_INOUT = (CXXRTL_INPUT|CXXRTL_OUTPUT),
// Node has bits that are driven by a storage cell.
//
// This flag can be set on objects of type `CXXRTL_WIRE`. It may be combined with
// `CXXRTL_DRIVEN_COMB` and `CXXRTL_UNDRIVEN`, as well as other flags.
//
// This flag is set on wires that have bits connected directly to the output of a flip-flop or
// a latch, and hold its state. Many `CXXRTL_WIRE` objects may not have the `CXXRTL_DRIVEN_SYNC`
// flag set; for example, output ports and feedback wires generally won't. Writing to the `next`
// pointer of these wires updates stored state, and for designs without combinatorial loops,
// capturing the value from every of these wires through the `curr` pointer creates a complete
// snapshot of the design state.
CXXRTL_DRIVEN_SYNC = 1 << 2,
// Node has bits that are driven by a combinatorial cell or another node.
//
// This flag can be set on objects of type `CXXRTL_VALUE`, `CXXRTL_WIRE`, and `CXXRTL_OUTLINE`.
// It may be combined with `CXXRTL_DRIVEN_SYNC` and `CXXRTL_UNDRIVEN`, as well as other flags.
//
// This flag is set on objects that have bits connected to the output of a combinatorial cell,
// or directly to another node. For designs without combinatorial loops, writing to such bits
// through the `next` pointer (if it is not NULL) has no effect.
CXXRTL_DRIVEN_COMB = 1 << 3,
// Node has bits that are not driven.
//
// This flag can be set on objects of type `CXXRTL_VALUE` and `CXXRTL_WIRE`. It may be combined
// with `CXXRTL_DRIVEN_SYNC` and `CXXRTL_DRIVEN_COMB`, as well as other flags.
//
// This flag is set on objects that have bits not driven by an output of any cell or by another
// node, such as inputs and dangling wires.
CXXRTL_UNDRIVEN = 1 << 4,
// More object flags may be added in the future, but the existing ones will never change.
};
// Description of a simulated object.
//
// The `curr` and `next` arrays 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`
// Flags of the object.
uint32_t flags; // actually bit mask of `enum cxxrtl_flags`
// Width of the object in bits.
size_t width;
// Index of the least significant bit.
size_t lsb_at;
// Depth of the object. Only meaningful for memories; for other objects, always 1.
size_t depth;
// Index of the first word. Only meaningful for memories; for other objects, always 0;
size_t zero_at;
// 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;
// Opaque reference to an outline. Only meaningful for outline objects.
//
// See the documentation of `cxxrtl_outline` for details. When creating a `cxxrtl_object`, set
// this field to NULL.
struct _cxxrtl_outline *outline;
// Opaque reference to an attribute set.
//
// See the documentation of `cxxrtl_attr_set` for details. When creating a `cxxrtl_object`, set
// this field to NULL.
//
// The lifetime of the pointers returned by `cxxrtl_attr_*` family of functions is the same as
// the lifetime of this structure.
struct _cxxrtl_attr_set *attrs;
// More description fields may 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`.
//
// The storage of a single abstract object may be split (usually with the `splitnets` pass) into
// many physical parts, all of which correspond to the same hierarchical name. To handle such cases,
// this function returns an array and writes its length to `parts`. The array is sorted by `lsb_at`.
//
// Returns the object parts if it was found, NULL otherwise. The returned parts are valid until
// the design is destroyed.
struct cxxrtl_object *cxxrtl_get_parts(cxxrtl_handle handle, const char *name, size_t *parts);
// Retrieve description of a single part simulated object.
//
// This function is a shortcut for the most common use of `cxxrtl_get_parts`. It asserts that,
// if the object exists, it consists of a single part. If assertions are disabled, it returns NULL
// for multi-part objects.
static inline struct cxxrtl_object *cxxrtl_get(cxxrtl_handle handle, const char *name) {
size_t parts = 0;
struct cxxrtl_object *object = cxxrtl_get_parts(handle, name, &parts);
assert(object == NULL || parts == 1);
if (object == NULL || parts == 1)
return object;
return NULL;
}
// 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 parts.
// 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, size_t parts));
// Opaque reference to an outline.
//
// An outline is a group of outline objects that are evaluated simultaneously. The identity of
// an outline can be compared to determine whether any two objects belong to the same outline.
typedef struct _cxxrtl_outline *cxxrtl_outline;
// Evaluate an outline.
//
// After evaluating an outline, the bits of every outline object contained in it are consistent
// with the current state of the netlist. In general, any further modification to the netlist
// causes every outline object to become stale, after which the corresponding outline must be
// re-evaluated, otherwise the bits read from that object are meaningless.
void cxxrtl_outline_eval(cxxrtl_outline outline);
// Opaque reference to an attribute set.
//
// An attribute set is a map between attribute names (always strings) and values (which may have
// several different types). To find out the type of an attribute, use `cxxrtl_attr_type`, and
// to retrieve the value of an attribute, use `cxxrtl_attr_as_string`.
typedef struct _cxxrtl_attr_set *cxxrtl_attr_set;
// Type of an attribute.
enum cxxrtl_attr_type {
// Attribute is not present.
CXXRTL_ATTR_NONE = 0,
// Attribute has an unsigned integer value.
CXXRTL_ATTR_UNSIGNED_INT = 1,
// Attribute has an unsigned integer value.
CXXRTL_ATTR_SIGNED_INT = 2,
// Attribute has a string value.
CXXRTL_ATTR_STRING = 3,
// Attribute has a double precision floating point value.
CXXRTL_ATTR_DOUBLE = 4,
// More attribute types may be defined in the future, but the existing values will never change.
};
// Determine the presence and type of an attribute in an attribute set.
//
// This function returns one of the possible `cxxrtl_attr_type` values.
int cxxrtl_attr_type(cxxrtl_attr_set attrs, const char *name);
// Retrieve an unsigned integer valued attribute from an attribute set.
//
// This function asserts that `cxxrtl_attr_type(attrs, name) == CXXRTL_ATTR_UNSIGNED_INT`.
// If assertions are disabled, returns 0 if the attribute is missing or has an incorrect type.
uint64_t cxxrtl_attr_get_unsigned_int(cxxrtl_attr_set attrs, const char *name);
// Retrieve a signed integer valued attribute from an attribute set.
//
// This function asserts that `cxxrtl_attr_type(attrs, name) == CXXRTL_ATTR_SIGNED_INT`.
// If assertions are disabled, returns 0 if the attribute is missing or has an incorrect type.
int64_t cxxrtl_attr_get_signed_int(cxxrtl_attr_set attrs, const char *name);
// Retrieve a string valued attribute from an attribute set. The returned string is zero-terminated.
//
// This function asserts that `cxxrtl_attr_type(attrs, name) == CXXRTL_ATTR_STRING`. If assertions
// are disabled, returns NULL if the attribute is missing or has an incorrect type.
const char *cxxrtl_attr_get_string(cxxrtl_attr_set attrs, const char *name);
// Retrieve a double precision floating point valued attribute from an attribute set.
//
// This function asserts that `cxxrtl_attr_type(attrs, name) == CXXRTL_ATTR_DOUBLE`. If assertions
// are disabled, returns NULL if the attribute is missing or has an incorrect type.
double cxxrtl_attr_get_double(cxxrtl_attr_set attrs, const char *name);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -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/capi/cxxrtl_capi_vcd.h`.
#include <cxxrtl/capi/cxxrtl_capi_vcd.h>
#include <cxxrtl/cxxrtl_vcd.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;
}

View file

@ -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_CAPI_VCD_H
#define CXXRTL_CAPI_VCD_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 <cxxrtl/capi/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

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,275 @@
/*
* 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 <cxxrtl/cxxrtl.h>
namespace cxxrtl {
class vcd_writer {
struct variable {
size_t ident;
size_t width;
chunk_t *curr;
size_t cache_offset;
debug_outline *outline;
bool *outline_warm;
};
std::vector<std::string> current_scope;
std::map<debug_outline*, bool> outlines;
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_name(const std::string &name) {
for (char c : name) {
if (c == ':') {
// Due to a bug, GTKWave cannot parse a colon in the variable name, causing the VCD file
// to be unreadable. It cannot be escaped either, so replace it with the sideways colon.
buffer += "..";
} else {
buffer += c;
}
}
}
void emit_var(const variable &var, const std::string &type, const std::string &name,
size_t lsb_at, bool multipart) {
assert(!streaming);
buffer += "$var " + type + " " + std::to_string(var.width) + " ";
emit_ident(var.ident);
buffer += " ";
emit_name(name);
if (multipart || name.back() == ']' || lsb_at != 0) {
if (var.width == 1)
buffer += " [" + std::to_string(lsb_at) + "]";
else
buffer += " [" + std::to_string(lsb_at + var.width - 1) + ":" + std::to_string(lsb_at) + "]";
}
buffer += " $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';
}
void reset_outlines() {
for (auto &outline_it : outlines)
outline_it.second = /*warm=*/(outline_it.first == nullptr);
}
variable &register_variable(size_t width, chunk_t *curr, bool constant = false, debug_outline *outline = nullptr) {
if (aliases.count(curr)) {
return variables[aliases[curr]];
} else {
auto outline_it = outlines.emplace(outline, /*warm=*/(outline == nullptr)).first;
const size_t chunks = (width + (sizeof(chunk_t) * 8 - 1)) / (sizeof(chunk_t) * 8);
aliases[curr] = variables.size();
if (constant) {
variables.emplace_back(variable { variables.size(), width, curr, (size_t)-1, outline_it->first, &outline_it->second });
} else {
variables.emplace_back(variable { variables.size(), width, curr, cache.size(), outline_it->first, &outline_it->second });
cache.insert(cache.end(), &curr[0], &curr[chunks]);
}
return variables.back();
}
}
bool test_variable(const variable &var) {
if (var.cache_offset == (size_t)-1)
return false; // constant
if (!*var.outline_warm) {
var.outline->eval();
*var.outline_warm = true;
}
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.cache_offset])) {
return false;
} else {
std::copy(&var.curr[0], &var.curr[chunks], &cache[var.cache_offset]);
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, bool multipart = false) {
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, /*constant=*/item.next == nullptr),
"wire", name, item.lsb_at, multipart);
break;
case debug_item::WIRE:
emit_var(register_variable(item.width, item.curr),
"reg", name, item.lsb_at, multipart);
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, item.lsb_at, multipart);
}
break;
}
case debug_item::ALIAS:
// Like VALUE, but, even though `item.next == nullptr` always holds, the underlying value
// can actually change, and must be tracked. In most cases the VCD identifier will be
// unified with the aliased reg, but we should handle the case where only the alias is
// added to the VCD writer, too.
emit_var(register_variable(item.width, item.curr),
"wire", name, item.lsb_at, multipart);
break;
case debug_item::OUTLINE:
emit_var(register_variable(item.width, item.curr, /*constant=*/false, item.outline),
"wire", name, item.lsb_at, multipart);
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.table)
for (auto &part : it.second)
if (filter(it.first, part))
add(it.first, part, it.second.size() > 1);
}
void add(const debug_items &items) {
this->add(items, [](const std::string &, const debug_item &) {
return true;
});
}
void add_without_memories(const debug_items &items) {
this->add(items, [](const std::string &, const debug_item &item) {
return item.type != debug_item::MEMORY;
});
}
void sample(uint64_t timestamp) {
bool first_sample = !streaming;
if (first_sample) {
emit_scope({});
emit_enddefinitions();
}
reset_outlines();
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