Merge pull request #499 from antmicro/mglb/StreamingOperator
Improve right-to-left streaming operator support.
diff --git a/systemverilog-plugin/UhdmAst.cc b/systemverilog-plugin/UhdmAst.cc
index 65bff57..94736fe 100644
--- a/systemverilog-plugin/UhdmAst.cc
+++ b/systemverilog-plugin/UhdmAst.cc
@@ -1,6 +1,8 @@
#include <algorithm>
+#include <cstdlib>
#include <cstring>
#include <functional>
+#include <iostream>
#include <limits>
#include <regex>
#include <string>
@@ -690,6 +692,51 @@
wire_node->children.insert(wire_node->children.end(), ranges.begin(), ranges.end());
}
+// Assert macro that prints location in C++ code and location of currently processed UHDM object.
+// Use only inside UhdmAst methods.
+#ifndef NDEBUG
+#if __GNUC__
+// gcc/clang's __builtin_trap() makes gdb stop on the line containing an assertion.
+#define uhdmast_assert(expr) \
+ if ((expr)) { \
+ } else { \
+ this->uhdmast_assert_log(#expr, __PRETTY_FUNCTION__, __FILE__, __LINE__); \
+ __builtin_trap(); \
+ }
+#else // #if __GNUC__
+// Just abort when using compiler other than gcc/clang.
+#define uhdmast_assert(expr) \
+ if ((expr)) { \
+ } else { \
+ this->uhdmast_assert_log(#expr, __func__, __FILE__, __LINE__); \
+ std::abort(); \
+ }
+#endif // #if __GNUC__
+#else // #ifndef NDEBUG
+#define uhdmast_assert(expr) \
+ if ((expr)) { \
+ } else { \
+ }
+#endif // #ifndef NDEBUG
+
+void UhdmAst::uhdmast_assert_log(const char *expr_str, const char *func, const char *file, int line) const
+{
+ std::cerr << file << ':' << line << ": error: Assertion failed: " << expr_str << std::endl;
+ std::cerr << file << ':' << line << ": note: In function: " << func << std::endl;
+ if (obj_h != 0) {
+ const char *const svfile = vpi_get_str(vpiFile, obj_h);
+ int svline = vpi_get(vpiLineNo, obj_h);
+ int svcolumn = vpi_get(vpiColumnNo, obj_h);
+ std::string obj_type_name = UHDM::VpiTypeName(obj_h);
+ const char *obj_name = vpi_get_str(vpiName, obj_h);
+ std::cerr << svfile << ':' << svline << svcolumn << ": note: When processing object of type '" << obj_type_name << '\'';
+ if (obj_name && obj_name[0] != '\0') {
+ std::cerr << " named '" << obj_name << '\'';
+ }
+ std::cerr << '.' << std::endl;
+ }
+}
+
static AST::AstNode *expand_dot(const AST::AstNode *current_struct, const AST::AstNode *search_node)
{
AST::AstNode *current_struct_elem = nullptr;
@@ -1620,40 +1667,81 @@
}
}
+void UhdmAst::apply_location_from_current_obj(AST::AstNode &target_node) const
+{
+ if (auto filename = vpi_get_str(vpiFile, obj_h)) {
+ target_node.filename = filename;
+ }
+ if (unsigned int first_line = vpi_get(vpiLineNo, obj_h)) {
+ target_node.location.first_line = first_line;
+ }
+ if (unsigned int last_line = vpi_get(vpiEndLineNo, obj_h)) {
+ target_node.location.last_line = last_line;
+ } else {
+ target_node.location.last_line = target_node.location.first_line;
+ }
+ if (unsigned int first_col = vpi_get(vpiColumnNo, obj_h)) {
+ target_node.location.first_column = first_col;
+ }
+ if (unsigned int last_col = vpi_get(vpiEndColumnNo, obj_h)) {
+ target_node.location.last_column = last_col;
+ } else {
+ target_node.location.last_column = target_node.location.first_column;
+ }
+}
+
+void UhdmAst::apply_name_from_current_obj(AST::AstNode &target_node, bool prefer_full_name) const
+{
+ target_node.str = get_name(obj_h, prefer_full_name);
+ auto it = node_renames.find(target_node.str);
+ if (it != node_renames.end())
+ target_node.str = it->second;
+}
+
+AstNodeBuilder UhdmAst::make_node(AST::AstNodeType type) const
+{
+ auto node = std::make_unique<AST::AstNode>(type);
+ apply_location_from_current_obj(*node);
+ return AstNodeBuilder(std::move(node));
+};
+
+AstNodeBuilder UhdmAst::make_named_node(AST::AstNodeType type, bool prefer_full_name) const
+{
+ auto node = std::make_unique<AST::AstNode>(type);
+ apply_location_from_current_obj(*node);
+ apply_name_from_current_obj(*node, prefer_full_name);
+ return AstNodeBuilder(std::move(node));
+};
+
+AstNodeBuilder UhdmAst::make_ident(std::string id) const { return make_node(::Yosys::AST::AST_IDENTIFIER).str(std::move(id)); };
+
+AstNodeBuilder UhdmAst::make_const(int32_t value, uint8_t width) const
+{
+ // Limited to width of the `value` argument.
+ log_assert(width <= 32);
+ return make_node(AST::AST_CONSTANT).value(value, true, width);
+};
+
+AstNodeBuilder UhdmAst::make_const(uint32_t value, uint8_t width) const
+{
+ // Limited to width of the `value` argument.
+ log_assert(width <= 32);
+ return make_node(AST::AST_CONSTANT).value(value, false, width);
+};
+
AST::AstNode *UhdmAst::make_ast_node(AST::AstNodeType type, std::vector<AST::AstNode *> children, bool prefer_full_name)
{
auto node = new AST::AstNode(type);
- node->str = get_name(obj_h, prefer_full_name);
- auto it = node_renames.find(node->str);
- if (it != node_renames.end())
- node->str = it->second;
- if (auto filename = vpi_get_str(vpiFile, obj_h)) {
- node->filename = filename;
- }
- if (unsigned int first_line = vpi_get(vpiLineNo, obj_h)) {
- node->location.first_line = first_line;
- }
- if (unsigned int last_line = vpi_get(vpiEndLineNo, obj_h)) {
- node->location.last_line = last_line;
- } else {
- node->location.last_line = node->location.first_line;
- }
- if (unsigned int first_col = vpi_get(vpiColumnNo, obj_h)) {
- node->location.first_column = first_col;
- }
- if (unsigned int last_col = vpi_get(vpiEndColumnNo, obj_h)) {
- node->location.last_column = last_col;
- } else {
- node->location.last_column = node->location.first_column;
- }
+ apply_name_from_current_obj(*node, prefer_full_name);
+ apply_location_from_current_obj(*node);
node->children = children;
return node;
}
-AST::AstNode *UhdmAst::make_identifier(const std::string &name)
+AST::AstNode *UhdmAst::make_identifier(std::string name)
{
auto *node = make_ast_node(AST::AST_IDENTIFIER);
- node->str = name;
+ node->str = std::move(name);
return node;
}
@@ -3058,16 +3146,29 @@
current_node = make_ast_node(AST::AST_ALWAYS);
visit_one_to_one({vpiStmt}, obj_h, [&](AST::AstNode *node) {
if (node) {
- AST::AstNode *block = nullptr;
if (node->type != AST::AST_BLOCK) {
- block = new AST::AstNode(AST::AST_BLOCK, node);
+ // Create implicit block.
+ AST::AstNode *block = make_ast_node(AST::AST_BLOCK);
+ // There are (at least) two cases where something could have been inserted into AST_ALWAYS node when `node` is not an AST_BLOCK:
+ // - stream_op inserts a block.
+ // - event_control inserts a non-block statement.
+ // Move the block inserted by a stream_op into an implicit group. Everything else stays where it is.
+ if (!current_node->children.empty() && current_node->children.back()->type == AST::AST_BLOCK) {
+ block->children.push_back(current_node->children.back());
+ current_node->children.pop_back();
+ }
+ block->children.push_back(node);
+ current_node->children.push_back(block);
} else {
- block = node;
+ // Child is an explicit block.
+ current_node->children.push_back(node);
}
- current_node->children.push_back(block);
} else {
- // create empty block
- current_node->children.push_back(new AST::AstNode(AST::AST_BLOCK));
+ // TODO (mglb): This branch is probably unreachable? Is it possible to have empty `always`?
+ // No children, so nothing should have been inserted into the always node during visitation.
+ log_assert(current_node->children.empty());
+ // Create implicit empty block.
+ current_node->children.push_back(make_ast_node(AST::AST_BLOCK));
}
});
switch (vpi_get(vpiAlwaysType, obj_h)) {
@@ -3109,15 +3210,30 @@
void UhdmAst::process_initial()
{
current_node = make_ast_node(AST::AST_INITIAL);
+ // TODO (mglb): handler below is identical as in `process_always`. Extract it to avoid duplication.
visit_one_to_one({vpiStmt}, obj_h, [&](AST::AstNode *node) {
if (node) {
if (node->type != AST::AST_BLOCK) {
- auto block_node = make_ast_node(AST::AST_BLOCK);
- block_node->children.push_back(node);
- node = block_node;
+ // Create an implicit block.
+ AST::AstNode *block = make_ast_node(AST::AST_BLOCK);
+ // There is (at least) one case where something could have been inserted into AST_INITIAL node when `node` is not an AST_BLOCK:
+ // - stream_op inserts a block.
+ // Move the block inserted by a stream_op into an implicit group.
+ if (!current_node->children.empty() && current_node->children.back()->type == AST::AST_BLOCK) {
+ block->children.push_back(current_node->children.back());
+ current_node->children.pop_back();
+ }
+ block->children.push_back(node);
+ current_node->children.push_back(block);
+ } else {
+ // Child is an explicit block.
+ current_node->children.push_back(node);
}
- current_node->children.push_back(node);
} else {
+ // TODO (mglb): This branch is probably unreachable? Is it possible to have empty `initial`?
+ // No children, so nothing should have been inserted into the always node during visitation.
+ log_assert(current_node->children.empty());
+ // Create implicit empty block.
current_node->children.push_back(make_ast_node(AST::AST_BLOCK));
}
});
@@ -3430,120 +3546,174 @@
void UhdmAst::process_stream_op()
{
- // Create a for loop that does what a streaming operator would do
- auto block_node = find_ancestor({AST::AST_BLOCK, AST::AST_ALWAYS, AST::AST_INITIAL});
- auto process_node = find_ancestor({AST::AST_ALWAYS, AST::AST_INITIAL});
- auto module_node = find_ancestor({AST::AST_MODULE, AST::AST_FUNCTION, AST::AST_PACKAGE});
- log_assert(module_node);
- if (!process_node) {
- if (module_node->type != AST::AST_FUNCTION) {
- // Create a @* always block
- process_node = make_ast_node(AST::AST_ALWAYS);
- module_node->children.push_back(process_node);
- block_node = make_ast_node(AST::AST_BLOCK);
- process_node->children.push_back(block_node);
- } else {
- // Create only block
- block_node = make_ast_node(AST::AST_BLOCK);
- module_node->children.push_back(block_node);
- }
- }
-
- auto loop_id = shared.next_loop_id();
- auto loop_counter =
- make_ast_node(AST::AST_WIRE, {make_ast_node(AST::AST_RANGE, {AST::AstNode::mkconst_int(31, false), AST::AstNode::mkconst_int(0, false)})});
- loop_counter->is_reg = true;
- loop_counter->is_signed = true;
- loop_counter->str = "\\loop" + std::to_string(loop_id) + "::i";
- module_node->children.insert(module_node->children.end() - 1, loop_counter);
- auto loop_counter_ident = make_ast_node(AST::AST_IDENTIFIER);
- loop_counter_ident->str = loop_counter->str;
-
- auto lhs_node = find_ancestor({AST::AST_ASSIGN, AST::AST_ASSIGN_EQ, AST::AST_ASSIGN_LE})->children[0];
- // Temp var to allow concatenation
- AST::AstNode *temp_var = nullptr;
- AST::AstNode *bits_call = nullptr;
- if (lhs_node->type == AST::AST_WIRE) {
- module_node->children.insert(module_node->children.begin(), lhs_node->clone());
- temp_var = lhs_node->clone(); // if we already have wire as lhs, we want to create the same wire for temp_var
- lhs_node->delete_children();
- lhs_node->type = AST::AST_IDENTIFIER;
- bits_call = make_ast_node(AST::AST_FCALL, {lhs_node->clone()});
- bits_call->str = "\\$bits";
- } else {
- // otherwise, we need to calculate size using bits fcall
- bits_call = make_ast_node(AST::AST_FCALL, {lhs_node->clone()});
- bits_call->str = "\\$bits";
- temp_var =
- make_ast_node(AST::AST_WIRE, {make_ast_node(AST::AST_RANGE, {make_ast_node(AST::AST_SUB, {bits_call, AST::AstNode::mkconst_int(1, false)}),
- AST::AstNode::mkconst_int(0, false)})});
- }
-
- temp_var->str = "\\loop" + std::to_string(loop_id) + "::temp";
- module_node->children.insert(module_node->children.end() - 1, temp_var);
- auto temp_var_ident = make_ast_node(AST::AST_IDENTIFIER);
- temp_var_ident->str = temp_var->str;
- auto temp_assign = make_ast_node(AST::AST_ASSIGN_EQ, {temp_var_ident});
- block_node->children.push_back(temp_assign);
-
- // Assignment in the loop's block
- auto assign_node = make_ast_node(AST::AST_ASSIGN_EQ, {lhs_node->clone(), temp_var_ident->clone()});
- AST::AstNode *slice_size = nullptr; // First argument in streaming op
- visit_one_to_many({vpiOperand}, obj_h, [&](AST::AstNode *node) {
- if (!slice_size && node->type == AST::AST_CONSTANT) {
- slice_size = node;
- } else {
- temp_assign->children.push_back(node);
- }
+ // Closest ancestor where new statements can be inserted.
+ AST::AstNode *stmt_list_node = find_ancestor({
+ AST::AST_MODULE,
+ AST::AST_PACKAGE,
+ AST::AST_BLOCK,
+ AST::AST_INITIAL,
+ AST::AST_ALWAYS,
+ AST::AST_FUNCTION,
});
- if (!slice_size) {
- slice_size = AST::AstNode::mkconst_int(1, true);
+ uhdmast_assert(stmt_list_node != nullptr);
+
+ // Detect whether we're in a procedural context. If yes, `for` loop will be generated, and `generate for` otherwise.
+ const AST::AstNode *const proc_ctx = find_ancestor({AST::AST_ALWAYS, AST::AST_INITIAL, AST::AST_FUNCTION});
+ const bool is_proc_ctx = (proc_ctx != nullptr);
+
+ // Get a prefix for internal identifiers.
+ const auto stream_op_id = shared.next_loop_id();
+ const auto make_id_str = [stream_op_id](const char *suffix) {
+ return std::string("$systemverilog_plugin$stream_op_") + std::to_string(stream_op_id) + "_" + suffix;
+ };
+
+ if (is_proc_ctx) {
+ // Put logic inside a sub-block to avoid issues with declarations not being at the beginning of a block.
+ AST::AstNode *block = make_node(Yosys::AST::AST_BLOCK).str(make_id_str("impl"));
+ stmt_list_node->children.push_back(block);
+ stmt_list_node = block;
}
- // Initialization of the loop counter to 0
- auto init_stmt = make_ast_node(AST::AST_ASSIGN_EQ, {loop_counter_ident, AST::AstNode::mkconst_int(0, true)});
+ // TODO (mglb): Only concat expression's size factors are supported as a slice size. Add support for other slice sizes as well.
+ AST::AstNode *slice_size_arg = nullptr;
+ AST::AstNode *stream_concat_arg = nullptr;
+ {
+ std::vector<AST::AstNode *> operands;
+ // Expected operands: [slice_size] stream_concatenation
+ visit_one_to_many({vpiOperand}, obj_h, [&](AST::AstNode *node) {
+ uhdmast_assert(node != nullptr);
+ uhdmast_assert(operands.size() < 2);
+ operands.push_back(node);
+ });
+ uhdmast_assert(operands.size() > 0);
- // Loop condition (loop counter < $bits(RHS))
- auto cond_stmt =
- make_ast_node(AST::AST_LE, {loop_counter_ident->clone(), make_ast_node(AST::AST_SUB, {bits_call->clone(), slice_size->clone()})});
+ if (operands.size() == 2) {
+ slice_size_arg = operands.at(0);
+ // SV spec says slice_size can be a constant or a type. However, Surelog converts type to its width, so we always expect a const.
+ uhdmast_assert(slice_size_arg->type == AST::AST_CONSTANT);
+ } else {
+ slice_size_arg = make_const(1u);
+ }
+ stream_concat_arg = operands.back();
+ }
- // Increment loop counter
- auto inc_stmt =
- make_ast_node(AST::AST_ASSIGN_EQ, {loop_counter_ident->clone(), make_ast_node(AST::AST_ADD, {loop_counter_ident->clone(), slice_size})});
+ AST::AstNode *const stream_concat_width_lp = //
+ (make_node(AST::AST_LOCALPARAM).str(make_id_str("width")))({
+ (make_node(AST::AST_FCALL).str("\\$bits"))({
+ (stream_concat_arg->clone()),
+ }),
+ (make_range(31, 0, true)),
+ });
- // Range on the LHS of the assignment
- auto lhs_range = make_ast_node(AST::AST_RANGE);
- auto lhs_selfsz = make_ast_node(
- AST::AST_SELFSZ, {make_ast_node(AST::AST_SUB, {make_ast_node(AST::AST_SUB, {bits_call->clone(), AST::AstNode::mkconst_int(1, true)}),
- loop_counter_ident->clone()})});
- lhs_range->children.push_back(make_ast_node(AST::AST_ADD, {lhs_selfsz, AST::AstNode::mkconst_int(0, true)}));
- lhs_range->children.push_back(
- make_ast_node(AST::AST_SUB, {make_ast_node(AST::AST_ADD, {lhs_selfsz->clone(), AST::AstNode::mkconst_int(1, true)}), slice_size->clone()}));
+ // TODO (mglb): src_wire and dst_wire should probably take argument signedness and logicness into account.
+ AST::AstNode *const src_wire = //
+ (make_node(AST::AST_WIRE).str(make_id_str("src")).is_reg(is_proc_ctx))({
+ (make_node(AST::AST_RANGE))({
+ (make_const(0)),
+ (make_node(AST::AST_SUB))({
+ (make_ident(stream_concat_width_lp->str)),
+ (make_const(1)),
+ }),
+ }),
+ });
- // Range on the RHS of the assignment
- auto rhs_range = make_ast_node(AST::AST_RANGE);
- auto rhs_selfsz = make_ast_node(AST::AST_SELFSZ, {loop_counter_ident->clone()});
- rhs_range->children.push_back(
- make_ast_node(AST::AST_SUB, {make_ast_node(AST::AST_ADD, {rhs_selfsz, slice_size->clone()}), AST::AstNode::mkconst_int(1, true)}));
- rhs_range->children.push_back(make_ast_node(AST::AST_ADD, {rhs_selfsz->clone(), AST::AstNode::mkconst_int(0, true)}));
+ AST::AstNode *const dst_wire = //
+ (make_node(AST::AST_WIRE).str(make_id_str("dst")).is_reg(is_proc_ctx))({
+ (make_node(AST::AST_RANGE))({
+ (make_node(AST::AST_SUB))({
+ (make_ident(stream_concat_width_lp->str)),
+ (make_const(1)),
+ }),
+ (make_const(0)),
+ }),
+ });
- // Put ranges on the sides of the assignment
- assign_node->children[0]->children.push_back(lhs_range);
- assign_node->children[1]->children.push_back(rhs_range);
+ AST::AstNode *const assign_stream_concat_to_src_wire = //
+ (make_node(is_proc_ctx ? AST::AST_ASSIGN_EQ : AST::AST_ASSIGN))({
+ (make_ident(src_wire->str)),
+ (stream_concat_arg),
+ });
- // Putting the loop together
- auto loop_node = make_ast_node(AST::AST_FOR);
- loop_node->str = "$loop" + std::to_string(loop_id);
- loop_node->children.push_back(init_stmt);
- loop_node->children.push_back(cond_stmt);
- loop_node->children.push_back(inc_stmt);
- loop_node->children.push_back(make_ast_node(AST::AST_BLOCK, {assign_node}));
- loop_node->children[3]->str = "\\stream_op_block" + std::to_string(loop_id);
+ AST::AstNode *const loop_counter = //
+ (make_node(is_proc_ctx ? AST::AST_WIRE : AST::AST_GENVAR).str(make_id_str("counter")).is_reg(true))({
+ (make_range(31, 0, true)),
+ });
- block_node->children.push_back(make_ast_node(AST::AST_BLOCK, {loop_node}));
+ AST::AstNode *const for_loop = //
+ (make_node(is_proc_ctx ? AST::AST_FOR : AST::AST_GENFOR))({
+ // init statement
+ (make_node(AST::AST_ASSIGN_EQ))({
+ (make_ident(loop_counter->str)),
+ (make_const(0)),
+ }),
+ // condition
+ (make_node(AST::AST_LT))({
+ (make_ident(loop_counter->str)),
+ (make_ident(stream_concat_width_lp->str)),
+ }),
+ // iteration expression
+ (make_node(AST::AST_ASSIGN_EQ))({
+ (make_ident(loop_counter->str)),
+ (make_node(Yosys::AST::AST_ADD))({
+ (make_ident(loop_counter->str)),
+ (slice_size_arg->clone()),
+ }),
+ }),
+ // loop body
+ (make_node(is_proc_ctx ? AST::AST_BLOCK : AST::AST_GENBLOCK).str(make_id_str("loop_body")))({
+ (make_node(is_proc_ctx ? AST::AST_ASSIGN_EQ : AST::AST_ASSIGN))({
+ (make_ident(dst_wire->str))({
+ (make_node(AST::AST_RANGE))({
+ (make_node(Yosys::AST::AST_SUB))({
+ (make_node(Yosys::AST::AST_ADD))({
+ (make_node(Yosys::AST::AST_SELFSZ))({
+ (make_ident(loop_counter->str)),
+ }),
+ (slice_size_arg->clone()),
+ }),
+ (make_const(1)),
+ }),
+ (make_node(Yosys::AST::AST_ADD))({
+ (make_node(Yosys::AST::AST_SELFSZ))({
+ (make_ident(loop_counter->str)),
+ }),
+ (make_const(0)),
+ }),
+ }),
+ }),
+ (make_ident(src_wire->str))({
+ (make_node(AST::AST_RANGE))({
+ (make_node(Yosys::AST::AST_SUB))({
+ (make_node(Yosys::AST::AST_ADD))({
+ (make_node(Yosys::AST::AST_SELFSZ))({
+ (make_ident(loop_counter->str)),
+ }),
+ (slice_size_arg),
+ }),
+ (make_const(1)),
+ }),
+ (make_node(Yosys::AST::AST_ADD))({
+ (make_node(Yosys::AST::AST_SELFSZ))({
+ (make_ident(loop_counter->str)),
+ }),
+ (make_const(0)),
+ }),
+ }),
+ }),
+ }),
+ }),
+ });
- // Do not create a node
- shared.report.mark_handled(obj_h);
+ stmt_list_node->children.insert(stmt_list_node->children.end(), {
+ stream_concat_width_lp,
+ src_wire,
+ dst_wire,
+ assign_stream_concat_to_src_wire,
+ loop_counter,
+ for_loop,
+ });
+
+ current_node = make_ident(is_proc_ctx ? (stmt_list_node->str + '.' + dst_wire->str) : dst_wire->str);
}
void UhdmAst::process_list_op()
diff --git a/systemverilog-plugin/UhdmAst.h b/systemverilog-plugin/UhdmAst.h
index e1931e9..adee39c 100644
--- a/systemverilog-plugin/UhdmAst.h
+++ b/systemverilog-plugin/UhdmAst.h
@@ -6,14 +6,20 @@
#undef cover
#include "uhdmastshared.h"
+#include <memory>
#include <uhdm/uhdm.h>
namespace systemverilog_plugin
{
+class AstNodeBuilder;
+
class UhdmAst
{
private:
+ // Logging method for exclusive use of `uhdmast_assert` macro.
+ void uhdmast_assert_log(const char *expr_str, const char *func, const char *file, int line) const;
+
// Walks through one-to-many relationships from given parent
// node through the VPI interface, visiting child nodes belonging to
// ChildrenNodeTypes that are present in the given object.
@@ -30,13 +36,31 @@
// Visit the default expression assigned to a variable.
void visit_default_expr(vpiHandle obj_h);
+ // Reads location info (start/end line/column numbers, file name) from `obj_h` and sets them on `target_node`.
+ void apply_location_from_current_obj(::Yosys::AST::AstNode &target_node) const;
+ // Reads object name from `obj_h` and assigns it to `target_node`.
+ void apply_name_from_current_obj(::Yosys::AST::AstNode &target_node, bool prefer_full_name = false) const;
+
+ // Creates node of specified `type` with location properties read from `obj_h`.
+ AstNodeBuilder make_node(::Yosys::AST::AstNodeType type) const;
+ // Creates node of specified `type` with location properties and name read from `obj_h`.
+ AstNodeBuilder make_named_node(::Yosys::AST::AstNodeType type, bool prefer_full_name = false) const;
+ // Creates AST_IDENTIFIER node with specified `id` and location properties read from `obj_h`.
+ AstNodeBuilder make_ident(std::string id) const;
+ // Creates signed AST_CONSTANT node with specified `value` and location properties read from `obj_h`.
+ AstNodeBuilder make_const(int32_t value, uint8_t width = 32) const;
+ // Creates unsigned AST_CONSTANT node with specified `value` and location properties read from `obj_h`.
+ AstNodeBuilder make_const(uint32_t value, uint8_t width = 32) const;
+
// Create an AstNode of the specified type with metadata extracted from
// the given vpiHandle.
+ // OBSOLETE: use `make_node` or `make_named_node` instead.
::Yosys::AST::AstNode *make_ast_node(::Yosys::AST::AstNodeType type, std::vector<::Yosys::AST::AstNode *> children = {},
bool prefer_full_name = false);
// Create an identifier AstNode
- ::Yosys::AST::AstNode *make_identifier(const std::string &name);
+ // OBSOLETE: use `make_ident` instead.
+ ::Yosys::AST::AstNode *make_identifier(std::string name);
// Makes the passed node a cell node of the specified type
void make_cell(vpiHandle obj_h, ::Yosys::AST::AstNode *node, ::Yosys::AST::AstNode *type);
@@ -179,6 +203,125 @@
static const ::Yosys::IdString &low_high_bound();
};
+// Utility for building AstNode trees.
+//
+// The object members that set AstNode properties return rvalue reference to *this (i.e. to the builder object), so they can be chained.
+// The children list is set using call operator (`builder_object({child0, child1, ...})`).
+// Build finalization is done through cast operator to either `AstNode*` or `std::unique_ptr<AstNode>`.
+//
+// Usage example:
+//
+// 1. Define one or more factory functions for creating base AstNode object:
+//
+// const auto make_node = [](AST::AstNodeType type) {
+// auto node = std::make_unique<AST::AstNode>(type);
+// // ...initialize the node if needed...
+// return AstNodeBuilder(std::move(node));
+// };
+//
+// 2. Use the factories to create a tree:
+//
+// // AST::AstNode *const variable_node = ...
+// // AST::AstNode *const value_node = ...
+// AST::AstNode *const assign = //
+// (make_node(AST::AST_ASSIGN_EQ))({
+// (make_node(AST::AST_IDENTIFIER).str(variable_node->str)),
+// (make_node(Yosys::AST::AST_ADD))({
+// (make_node(AST::AST_IDENTIFIER).str(value_node->str)),
+// (make_node(AST::AST_CONSTANT).value(4)),
+// }),
+// });
+//
+// In the real code instead of custom factories illustrated in point 1 above, you probably should use predefined methods from `UhdmAst` class.
+// The syntax above puts the factory call and all its method calls (but not the function call operator with the children list) in `()`. This is done
+// to make `clang-format` format the code as presented. Otherwise it is heavily wrapped and a lot less readable. `()` are technically not required
+// in leafs to make them format as expected, but its nice to use them for consistency.
+class AstNodeBuilder
+{
+ using AstNode = ::Yosys::AST::AstNode;
+ using AstNodeType = ::Yosys::AST::AstNodeType;
+
+ std::unique_ptr<AstNode> node;
+
+ public:
+ explicit AstNodeBuilder(AstNodeType node_type) : node(new AstNode(node_type)) {}
+ explicit AstNodeBuilder(std::unique_ptr<AstNode> node) : node(std::move(node)) {}
+ ~AstNodeBuilder() { log_assert(node == nullptr); }
+
+ AstNodeBuilder(AstNodeBuilder &&) = default;
+
+ AstNodeBuilder() = delete;
+ AstNodeBuilder(const AstNodeBuilder &) = delete;
+ AstNodeBuilder &operator=(const AstNodeBuilder &) = delete;
+ AstNodeBuilder &operator=(AstNodeBuilder &&) = delete;
+
+ // Property setters
+
+ // Sets `AstNode::children` vector
+ AstNodeBuilder &&operator()(std::vector<AstNode *> children) { return node->children = std::move(children), std::move(*this); }
+
+ // Sets `AstNode::str` value.
+ AstNodeBuilder &&str(std::string s) { return node->str = std::move(s), std::move(*this); }
+
+ // Sets `AstNode::integer` value.
+ AstNodeBuilder &&integer(uint32_t v) { return node->integer = v, std::move(*this); }
+
+ // Sets `AstNode::is_signed` value.
+ AstNodeBuilder &&is_signed(bool v) { return node->is_signed = v, std::move(*this); }
+
+ // Sets `AstNode::is_reg` value.
+ AstNodeBuilder &&is_reg(bool v) { return node->is_reg = v, std::move(*this); }
+
+ // Sets `AstNode::range_valid`.
+ AstNodeBuilder &&range_valid(bool v) { return node->range_valid = v, std::move(*this); }
+
+ // Convenience range setters
+
+ // Sets `AstNode::range_left`, `AstNode::range_right`, `AstNode::range_valid`.
+ AstNodeBuilder &&range(bool v, int left = -1, int right = 0)
+ {
+ node->range_valid = v;
+ node->range_left = left;
+ node->range_right = right;
+ return std::move(*this);
+ }
+
+ // Sets `AstNode::range_left`, `AstNode::range_right`, `AstNode::range_valid = true`.
+ AstNodeBuilder &&range(int left, int right) { return range(true, left, right); }
+
+ // Convenience value setters, mainly for constants.
+
+ // Sets node's value.
+ // Sets: `AstNode::integer`, `AstNode::is_signed`, `AstNode::bits`.
+ AstNodeBuilder &&value(uint32_t v, bool is_signed, int width = 32)
+ {
+ log_assert(width >= 0);
+ node->integer = v;
+ node->is_signed = is_signed;
+ // `AstNode::mkconst_int` does this too.
+ for (int i = 0; i < width; i++) {
+ node->bits.push_back((v & 1) ? Yosys::RTLIL::State::S1 : Yosys::RTLIL::State::S0);
+ v = v >> 1;
+ }
+ range(width - 1, 0);
+ return std::move(*this);
+ }
+
+ // Sets node's value to signed 32 bit integer.
+ // Sets: `AstNode::integer`, `AstNode::is_signed`, `AstNode::bits`.
+ AstNodeBuilder &&value(int32_t v) { return value(v, true); }
+
+ // Sets node's value to unsigned 32 bit integer.
+ // Sets: `AstNode::integer`, `AstNode::is_signed`, `AstNode::bits`.
+ AstNodeBuilder &&value(uint32_t v) { return value(v, false); }
+
+ // Type-cast operators used for building.
+
+ operator AstNode *() { return node.release(); }
+
+ operator std::unique_ptr<AstNode>() { return std::move(node); }
+};
+
} // namespace systemverilog_plugin
#endif