| #include <algorithm> |
| #include <cstring> |
| #include <functional> |
| #include <limits> |
| #include <regex> |
| #include <string> |
| #include <utility> |
| #include <vector> |
| |
| #include "UhdmAst.h" |
| #include "frontends/ast/ast.h" |
| #include "libs/sha1/sha1.h" |
| |
| // UHDM |
| #include <uhdm/ExprEval.h> |
| #include <uhdm/uhdm.h> |
| #include <uhdm/vpi_user.h> |
| |
| #include "third_party/yosys/const2ast.h" |
| |
| YOSYS_NAMESPACE_BEGIN |
| namespace VERILOG_FRONTEND |
| { |
| extern bool sv_mode; |
| } |
| YOSYS_NAMESPACE_END |
| |
| namespace systemverilog_plugin |
| { |
| |
| using namespace ::Yosys; |
| |
| namespace AST |
| { |
| using namespace ::Yosys::AST; |
| |
| namespace Extended |
| { |
| enum AstNodeTypeExtended { |
| AST_DOT = ::Yosys::AST::AST_BIND + 1, // here we always want to point to the last element of yosys' AstNodeType |
| AST_BREAK, |
| AST_CONTINUE |
| }; |
| } |
| } // namespace AST |
| |
| namespace attr_id |
| { |
| static IdString partial; |
| static IdString packed_ranges; |
| static IdString unpacked_ranges; |
| static IdString force_convert; |
| static IdString is_imported; |
| static IdString is_simplified_wire; |
| static IdString low_high_bound; |
| }; // namespace attr_id |
| |
| // TODO(mglb): use attr_id::* directly everywhere and remove those methods. |
| /*static*/ const IdString &UhdmAst::partial() { return attr_id::partial; } |
| /*static*/ const IdString &UhdmAst::packed_ranges() { return attr_id::packed_ranges; } |
| /*static*/ const IdString &UhdmAst::unpacked_ranges() { return attr_id::unpacked_ranges; } |
| /*static*/ const IdString &UhdmAst::force_convert() { return attr_id::force_convert; } |
| /*static*/ const IdString &UhdmAst::is_imported() { return attr_id::is_imported; } |
| /*static*/ const IdString &UhdmAst::is_simplified_wire() { return attr_id::is_simplified_wire; } |
| /*static*/ const IdString &UhdmAst::low_high_bound() { return attr_id::low_high_bound; } |
| |
| void attr_id_init() |
| { |
| // Initialize only once |
| static bool already_initialized = false; |
| if (already_initialized) |
| return; |
| already_initialized = true; |
| |
| // Actual initialization |
| |
| // Register IdStrings. Can't be done statically, as the IdString class uses resources created during Yosys initialization which happens after |
| // static initialization of the plugin when everything is statically linked. |
| attr_id::partial = IdString("$systemverilog_plugin$partial"); |
| attr_id::packed_ranges = IdString("$systemverilog_plugin$packed_ranges"); |
| attr_id::unpacked_ranges = IdString("$systemverilog_plugin$unpacked_ranges"); |
| attr_id::force_convert = IdString("$systemverilog_plugin$force_convert"); |
| attr_id::is_imported = IdString("$systemverilog_plugin$is_imported"); |
| attr_id::is_simplified_wire = IdString("$systemverilog_plugin$is_simplified_wire"); |
| attr_id::low_high_bound = IdString("$systemverilog_plugin$low_high_bound"); |
| } |
| |
| void attr_id_cleanup() |
| { |
| // Release static copies of private IdStrings. |
| attr_id::low_high_bound = IdString(); |
| attr_id::is_simplified_wire = IdString(); |
| attr_id::is_imported = IdString(); |
| attr_id::force_convert = IdString(); |
| attr_id::unpacked_ranges = IdString(); |
| attr_id::packed_ranges = IdString(); |
| attr_id::partial = IdString(); |
| } |
| |
| // Delete the selected attribute if it exists. |
| // Does nothing if the node doesn't exist, or the attribute doesn't exists. |
| static void delete_attribute(AST::AstNode *node, const IdString &attribute) |
| { |
| if (!node) |
| return; |
| |
| if (node->attributes.count(attribute)) { |
| delete node->attributes[attribute]; |
| node->attributes.erase(attribute); |
| } |
| } |
| |
| // Delete all attributes that belong to the SV plugin. |
| // The attributes beloning to Yosys are *not* deleted here. |
| static void delete_internal_attributes(AST::AstNode *node) |
| { |
| if (!node) |
| return; |
| |
| for (auto &attr : {UhdmAst::partial(), UhdmAst::packed_ranges(), UhdmAst::unpacked_ranges(), UhdmAst::force_convert(), UhdmAst::is_imported(), |
| UhdmAst::is_simplified_wire(), UhdmAst::low_high_bound()}) { |
| delete_attribute(node, attr); |
| } |
| } |
| |
| // Delete all children nodes. |
| // Does *not* delete attributes. |
| // This function exists as Yosys's function node->delete_children() does remove all children and attributes. |
| static void delete_children(AST::AstNode *node) |
| { |
| if (!node) |
| return; |
| |
| for (auto *child : node->children) { |
| delete child; |
| } |
| node->children.clear(); |
| } |
| |
| static void sanitize_symbol_name(std::string &name) |
| { |
| if (!name.empty()) { |
| auto pos = name.find_last_of('@'); |
| name = name.substr(pos + 1); |
| // symbol names must begin with '\' |
| name.insert(0, "\\"); |
| } |
| } |
| |
| static std::string get_name(vpiHandle obj_h, bool prefer_full_name = false) |
| { |
| auto first_check = prefer_full_name ? vpiFullName : vpiName; |
| auto last_check = prefer_full_name ? vpiName : vpiFullName; |
| std::string name; |
| if (auto s = vpi_get_str(first_check, obj_h)) { |
| name = s; |
| } else if (auto s = vpi_get_str(vpiDefName, obj_h)) { |
| name = s; |
| } else if (auto s = vpi_get_str(last_check, obj_h)) { |
| name = s; |
| } |
| if (name.rfind('.') != std::string::npos) { |
| name = name.substr(name.rfind('.') + 1); |
| } |
| sanitize_symbol_name(name); |
| return name; |
| } |
| |
| static std::string strip_package_name(std::string name) |
| { |
| auto sep_index = name.find("::"); |
| if (sep_index != string::npos) { |
| name = name.substr(sep_index + 1); |
| name[0] = '\\'; |
| } |
| return name; |
| } |
| |
| static std::string get_object_name(vpiHandle obj_h, const std::vector<int> &name_fields = {vpiName}) |
| { |
| std::string objectName; |
| for (auto name : name_fields) { |
| if (auto s = vpi_get_str(name, obj_h)) { |
| objectName = s; |
| sanitize_symbol_name(objectName); |
| break; |
| } |
| } |
| return objectName; |
| } |
| |
| static AST::AstNode *mkconst_real(double d) |
| { |
| AST::AstNode *node = new AST::AstNode(AST::AST_REALVALUE); |
| node->realvalue = d; |
| return node; |
| } |
| |
| static AST::AstNode *make_range(int left, int right, bool is_signed = false) |
| { |
| // generate a pre-validated range node for a fixed signal range. |
| auto range = new AST::AstNode(AST::AST_RANGE); |
| range->range_left = left; |
| range->range_right = right; |
| range->range_valid = true; |
| range->children.push_back(AST::AstNode::mkconst_int(left, true)); |
| range->children.push_back(AST::AstNode::mkconst_int(right, true)); |
| range->is_signed = is_signed; |
| return range; |
| } |
| |
| static void copy_packed_unpacked_attribute(AST::AstNode *from, AST::AstNode *to) |
| { |
| if (!to->attributes.count(UhdmAst::packed_ranges())) |
| to->attributes[UhdmAst::packed_ranges()] = AST::AstNode::mkconst_int(1, false, 1); |
| if (!to->attributes.count(UhdmAst::unpacked_ranges())) |
| to->attributes[UhdmAst::unpacked_ranges()] = AST::AstNode::mkconst_int(1, false, 1); |
| if (from->attributes.count(UhdmAst::packed_ranges())) { |
| for (auto r : from->attributes[UhdmAst::packed_ranges()]->children) { |
| to->attributes[UhdmAst::packed_ranges()]->children.push_back(r->clone()); |
| } |
| } |
| if (from->attributes.count(UhdmAst::unpacked_ranges())) { |
| for (auto r : from->attributes[UhdmAst::unpacked_ranges()]->children) { |
| to->attributes[UhdmAst::unpacked_ranges()]->children.push_back(r->clone()); |
| } |
| } |
| } |
| |
| static int get_max_offset_struct(AST::AstNode *node) |
| { |
| // get the width from the MS member in the struct |
| // as members are laid out from left to right in the packed wire |
| log_assert(node->type == AST::AST_STRUCT || node->type == AST::AST_UNION); |
| while (node->range_left < 0) { |
| node = node->children[0]; |
| } |
| return node->range_left; |
| } |
| |
| static void visitEachDescendant(AST::AstNode *node, const std::function<void(AST::AstNode *)> &f) |
| { |
| for (auto child : node->children) { |
| f(child); |
| visitEachDescendant(child, f); |
| } |
| } |
| |
| static void add_multirange_wire(AST::AstNode *node, std::vector<AST::AstNode *> packed_ranges, std::vector<AST::AstNode *> unpacked_ranges, |
| bool reverse = true) |
| { |
| delete_attribute(node, UhdmAst::packed_ranges()); |
| node->attributes[UhdmAst::packed_ranges()] = AST::AstNode::mkconst_int(1, false, 1); |
| if (!packed_ranges.empty()) { |
| if (reverse) |
| std::reverse(packed_ranges.begin(), packed_ranges.end()); |
| node->attributes[UhdmAst::packed_ranges()]->children = std::move(packed_ranges); |
| } |
| |
| delete_attribute(node, UhdmAst::unpacked_ranges()); |
| node->attributes[UhdmAst::unpacked_ranges()] = AST::AstNode::mkconst_int(1, false, 1); |
| if (!unpacked_ranges.empty()) { |
| if (reverse) |
| std::reverse(unpacked_ranges.begin(), unpacked_ranges.end()); |
| node->attributes[UhdmAst::unpacked_ranges()]->children = std::move(unpacked_ranges); |
| } |
| } |
| |
| static size_t add_multirange_attribute(AST::AstNode *wire_node, const std::vector<AST::AstNode *> ranges) |
| { |
| size_t size = 1; |
| for (size_t i = 0; i < ranges.size(); i++) { |
| log_assert(AST_INTERNAL::current_ast_mod); |
| if (ranges[i]->children.size() == 1) { |
| ranges[i]->children.push_back(ranges[i]->children[0]->clone()); |
| } |
| while (ranges[i]->simplify(true, false, false, 1, -1, false, false)) { |
| } |
| // this workaround case, where yosys doesn't follow id2ast and simplifies it to resolve constant |
| if (ranges[i]->children[0]->id2ast) { |
| while (ranges[i]->children[0]->id2ast->simplify(true, false, false, 1, -1, false, false)) { |
| } |
| } |
| if (ranges[i]->children[1]->id2ast) { |
| while (ranges[i]->children[1]->id2ast->simplify(true, false, false, 1, -1, false, false)) { |
| } |
| } |
| while (ranges[i]->simplify(true, false, false, 1, -1, false, false)) { |
| } |
| log_assert(ranges[i]->children[0]->type == AST::AST_CONSTANT); |
| log_assert(ranges[i]->children[1]->type == AST::AST_CONSTANT); |
| if (wire_node->type != AST::AST_STRUCT_ITEM) { |
| wire_node->multirange_dimensions.push_back(min(ranges[i]->children[0]->integer, ranges[i]->children[1]->integer)); |
| wire_node->multirange_swapped.push_back(ranges[i]->range_swapped); |
| } |
| auto elem_size = max(ranges[i]->children[0]->integer, ranges[i]->children[1]->integer) - |
| min(ranges[i]->children[0]->integer, ranges[i]->children[1]->integer) + 1; |
| if (wire_node->type != AST::AST_STRUCT_ITEM || (wire_node->type == AST::AST_STRUCT_ITEM && i == 0)) { |
| wire_node->multirange_dimensions.push_back(elem_size); |
| } |
| size *= elem_size; |
| } |
| return size; |
| } |
| |
| static AST::AstNode *convert_range(AST::AstNode *id, int packed_ranges_size, int unpacked_ranges_size, int i) |
| { |
| log_assert(AST_INTERNAL::current_ast_mod); |
| log_assert(AST_INTERNAL::current_scope.count(id->str)); |
| AST::AstNode *wire_node = AST_INTERNAL::current_scope[id->str]; |
| log_assert(!wire_node->multirange_dimensions.empty()); |
| int elem_size = 1; |
| std::vector<int> single_elem_size; |
| single_elem_size.push_back(elem_size); |
| for (size_t j = 0; (j + 1) < wire_node->multirange_dimensions.size(); j = j + 2) { |
| elem_size *= wire_node->multirange_dimensions[j + 1] - wire_node->multirange_dimensions[j]; |
| single_elem_size.push_back(elem_size); |
| } |
| std::reverse(single_elem_size.begin(), single_elem_size.end()); |
| log_assert(i < (unpacked_ranges_size + packed_ranges_size)); |
| log_assert(!id->children.empty()); |
| AST::AstNode *result = nullptr; |
| // we want to start converting from the end |
| if (i < static_cast<int>(id->children.size()) - 1) { |
| result = convert_range(id, packed_ranges_size, unpacked_ranges_size, i + 1); |
| } |
| // special case, we want to select whole wire |
| if (id->children.size() == 0 && i == 0) { |
| result = make_range(single_elem_size[i] - 1, 0); |
| } else { |
| AST::AstNode *range_left = nullptr; |
| AST::AstNode *range_right = nullptr; |
| if (id->children[i]->children.size() == 2) { |
| range_left = id->children[i]->children[0]->clone(); |
| range_right = id->children[i]->children[1]->clone(); |
| } else { |
| range_left = id->children[i]->children[0]->clone(); |
| range_right = id->children[i]->children[0]->clone(); |
| } |
| if (!wire_node->multirange_swapped.empty()) { |
| bool is_swapped = wire_node->multirange_swapped[wire_node->multirange_swapped.size() - i - 1]; |
| auto right_idx = wire_node->multirange_dimensions.size() - (i * 2) - 2; |
| if (is_swapped) { |
| auto left_idx = wire_node->multirange_dimensions.size() - (i * 2) - 1; |
| auto elem_size = wire_node->multirange_dimensions[left_idx] - wire_node->multirange_dimensions[right_idx]; |
| range_left = new AST::AstNode(AST::AST_SUB, AST::AstNode::mkconst_int(elem_size - 1, false), range_left); |
| range_right = new AST::AstNode(AST::AST_SUB, AST::AstNode::mkconst_int(elem_size - 1, false), range_right); |
| } else if (wire_node->multirange_dimensions[right_idx] != 0) { |
| range_left = |
| new AST::AstNode(AST::AST_SUB, range_left, AST::AstNode::mkconst_int(wire_node->multirange_dimensions[right_idx], false)); |
| range_right = |
| new AST::AstNode(AST::AST_SUB, range_right, AST::AstNode::mkconst_int(wire_node->multirange_dimensions[right_idx], false)); |
| } |
| } |
| range_left = new AST::AstNode(AST::AST_SUB, |
| new AST::AstNode(AST::AST_MUL, new AST::AstNode(AST::AST_ADD, range_left, AST::AstNode::mkconst_int(1, false)), |
| AST::AstNode::mkconst_int(single_elem_size[i + 1], false)), |
| AST::AstNode::mkconst_int(1, false)); |
| range_right = new AST::AstNode(AST::AST_MUL, range_right, AST::AstNode::mkconst_int(single_elem_size[i + 1], false)); |
| if (result) { |
| range_right = new AST::AstNode(AST::AST_ADD, range_right, result->children[1]->clone()); |
| range_left = new AST::AstNode(AST::AST_SUB, new AST::AstNode(AST::AST_ADD, range_right->clone(), result->children[0]->clone()), |
| result->children[1]->clone()); |
| delete result; |
| result = nullptr; |
| } |
| result = new AST::AstNode(AST::AST_RANGE, range_left, range_right); |
| } |
| // return range from *current* selected range |
| // in the end, it results in whole selected range |
| id->basic_prep = true; |
| return result; |
| } |
| |
| static void resolve_wiretype(AST::AstNode *wire_node) |
| { |
| AST::AstNode *wiretype_node = nullptr; |
| if (!wire_node->children.empty()) { |
| if (wire_node->children[0]->type == AST::AST_WIRETYPE) { |
| wiretype_node = wire_node->children[0]; |
| } |
| } |
| if (wire_node->children.size() > 1) { |
| if (wire_node->children[1]->type == AST::AST_WIRETYPE) { |
| wiretype_node = wire_node->children[1]; |
| } |
| } |
| if (wiretype_node == nullptr) |
| return; |
| std::vector<AST::AstNode *> packed_ranges; |
| std::vector<AST::AstNode *> unpacked_ranges; |
| // First check if it has already defined ranges |
| if (wire_node->attributes.count(UhdmAst::packed_ranges())) { |
| for (auto r : wire_node->attributes[UhdmAst::packed_ranges()]->children) { |
| packed_ranges.push_back(r->clone()); |
| } |
| } |
| if (wire_node->attributes.count(UhdmAst::unpacked_ranges())) { |
| for (auto r : wire_node->attributes[UhdmAst::unpacked_ranges()]->children) { |
| unpacked_ranges.push_back(r->clone()); |
| } |
| } |
| AST::AstNode *wiretype_ast = nullptr; |
| log_assert(AST_INTERNAL::current_scope.count(wiretype_node->str)); |
| wiretype_ast = AST_INTERNAL::current_scope[wiretype_node->str]; |
| // we need to setup current top ast as this simplify |
| // needs to have access to all already defined ids |
| while (wire_node->simplify(true, false, false, 1, -1, false, false)) { |
| } |
| if (wiretype_ast->children[0]->type == AST::AST_STRUCT && wire_node->type == AST::AST_WIRE) { |
| auto struct_width = get_max_offset_struct(wiretype_ast->children[0]); |
| wire_node->range_left = struct_width; |
| wire_node->children[0]->range_left = struct_width; |
| wire_node->children[0]->children[0]->integer = struct_width; |
| } |
| if (wiretype_ast && wire_node->attributes.count(ID::wiretype)) { |
| log_assert(wiretype_ast->type == AST::AST_TYPEDEF); |
| wire_node->attributes[ID::wiretype]->id2ast = wiretype_ast->children[0]; |
| } |
| if ((wire_node->children[0]->type == AST::AST_RANGE || (wire_node->children.size() > 1 && wire_node->children[1]->type == AST::AST_RANGE)) && |
| wire_node->multirange_dimensions.empty()) { |
| if (wiretype_ast && !wiretype_ast->children.empty() && wiretype_ast->children[0]->attributes.count(UhdmAst::packed_ranges()) && |
| wiretype_ast->children[0]->attributes.count(UhdmAst::unpacked_ranges())) { |
| for (auto r : wiretype_ast->children[0]->attributes[UhdmAst::packed_ranges()]->children) { |
| packed_ranges.push_back(r->clone()); |
| } |
| for (auto r : wiretype_ast->children[0]->attributes[UhdmAst::unpacked_ranges()]->children) { |
| unpacked_ranges.push_back(r->clone()); |
| } |
| } else { |
| if (wire_node->children[0]->type == AST::AST_RANGE) |
| packed_ranges.push_back(wire_node->children[0]->clone()); |
| else if (wire_node->children[1]->type == AST::AST_RANGE) |
| packed_ranges.push_back(wire_node->children[1]->clone()); |
| else |
| log_error("Unhandled case in resolve_wiretype!\n"); |
| } |
| AST::AstNode *value = nullptr; |
| if (wire_node->children[0]->type != AST::AST_RANGE) { |
| value = wire_node->children[0]->clone(); |
| } |
| delete_children(wire_node); |
| if (value) |
| wire_node->children.push_back(value); |
| wire_node->attributes[UhdmAst::packed_ranges()] = AST::AstNode::mkconst_int(1, false, 1); |
| if (!packed_ranges.empty()) { |
| std::reverse(packed_ranges.begin(), packed_ranges.end()); |
| wire_node->attributes[UhdmAst::packed_ranges()]->children.insert(wire_node->attributes[UhdmAst::packed_ranges()]->children.end(), |
| packed_ranges.begin(), packed_ranges.end()); |
| packed_ranges.clear(); |
| } |
| |
| wire_node->attributes[UhdmAst::unpacked_ranges()] = AST::AstNode::mkconst_int(1, false, 1); |
| if (!unpacked_ranges.empty()) { |
| wire_node->attributes[UhdmAst::unpacked_ranges()]->children.insert(wire_node->attributes[UhdmAst::unpacked_ranges()]->children.end(), |
| unpacked_ranges.begin(), unpacked_ranges.end()); |
| unpacked_ranges.clear(); |
| } |
| } |
| for (auto *range : packed_ranges) { |
| delete range; |
| } |
| for (auto *range : unpacked_ranges) { |
| delete range; |
| } |
| } |
| |
| static void add_force_convert_attribute(AST::AstNode *wire_node, uint32_t val = 1) |
| { |
| AST::AstNode *&attr = wire_node->attributes[UhdmAst::force_convert()]; |
| if (!attr) { |
| attr = AST::AstNode::mkconst_int(val, true); |
| } else if (attr->integer != val) { |
| attr->integer = val; |
| } |
| } |
| |
| static void check_memories(AST::AstNode *node, std::string scope, std::map<std::string, AST::AstNode *> &memories) |
| { |
| for (auto *child : node->children) { |
| check_memories(child, node->type == AST::AST_GENBLOCK ? scope + "." + node->str : scope, memories); |
| } |
| |
| if (node->str == "\\$readmemh") { |
| if (node->children.size() != 2 || node->children[1]->str.empty() || node->children[1]->type != AST::AST_IDENTIFIER) { |
| log_error("%s:%d: Wrong usage of '\\$readmemh'\n", node->filename.c_str(), node->location.first_line); |
| } |
| // TODO: Look for the memory in all other scope levels, like we do in case of AST::AST_IDENTIFIER, |
| // as here the memory can also be defined before before the current scope. |
| std::string name = scope + "." + node->children[1]->str; |
| const auto iter = memories.find(name); |
| if (iter != memories.end()) { |
| add_force_convert_attribute(iter->second, 0); |
| } |
| } |
| |
| if (node->type == AST::AST_WIRE) { |
| const std::size_t packed_ranges_count = |
| node->attributes.count(UhdmAst::packed_ranges()) ? node->attributes[UhdmAst::packed_ranges()]->children.size() : 0; |
| const std::size_t unpacked_ranges_count = |
| node->attributes.count(UhdmAst::unpacked_ranges()) ? node->attributes[UhdmAst::unpacked_ranges()]->children.size() : 0; |
| |
| if (packed_ranges_count == 1 && unpacked_ranges_count == 1) { |
| std::string name = scope + "." + node->str; |
| auto [iter, did_insert] = memories.insert_or_assign(std::move(name), node); |
| log_assert(did_insert); |
| } |
| return; |
| } |
| |
| if (node->type == AST::AST_IDENTIFIER) { |
| std::string full_id = scope; |
| std::size_t scope_end_pos = scope.size(); |
| |
| for (;;) { |
| full_id += "." + node->str; |
| const auto iter = memories.find(full_id); |
| if (iter != memories.end()) { |
| // Memory node found! |
| if (!iter->second->attributes.count(UhdmAst::force_convert())) { |
| const bool is_full_memory_access = (node->children.size() == 0); |
| const bool is_slice_memory_access = (node->children.size() == 1 && node->children[0]->children.size() != 1); |
| // convert memory to list of registers |
| // in case of access to whole memory |
| // or slice of memory |
| // e.g. |
| // logic [3:0] mem [8:0]; |
| // always_ff @ (posedge clk) begin |
| // mem <= '{default:0}; |
| // mem[7:1] <= mem[6:0]; |
| // end |
| // don't convert in case of accessing |
| // memory using address, e.g. |
| // mem[0] <= '{default:0} |
| if (is_full_memory_access || is_slice_memory_access) { |
| add_force_convert_attribute(iter->second); |
| } |
| } |
| break; |
| } else { |
| if (scope_end_pos == 0) { |
| // We reached the top scope and the memory node wasn't found. |
| break; |
| } else { |
| // Memory node wasn't found. |
| // Erase node name and last segment of the scope to check the previous scope. |
| // FIXME: This doesn't work with escaped identifiers containing a dot. |
| scope_end_pos = full_id.find_last_of('.', scope_end_pos - 1); |
| if (scope_end_pos == std::string::npos) { |
| scope_end_pos = 0; |
| } |
| full_id.erase(scope_end_pos); |
| } |
| } |
| } |
| } |
| } |
| |
| static void check_memories(AST::AstNode *node) |
| { |
| std::map<std::string, AST::AstNode *> memories; |
| check_memories(node, "", memories); |
| } |
| |
| static void warn_start_range(const std::vector<AST::AstNode *> ranges) |
| { |
| for (size_t i = 0; i < ranges.size(); i++) { |
| auto start_elem = min(ranges[i]->children[0]->integer, ranges[i]->children[1]->integer); |
| if (start_elem != 0) { |
| log_file_warning(ranges[i]->filename, ranges[i]->location.first_line, "Limited support for multirange wires that don't start from 0\n"); |
| } |
| } |
| } |
| |
| // This function is workaround missing support for multirange (with n-ranges) packed/unpacked nodes |
| // It converts multirange node to single-range node and translates access to this node |
| // to correct range |
| static void convert_packed_unpacked_range(AST::AstNode *wire_node) |
| { |
| resolve_wiretype(wire_node); |
| const std::vector<AST::AstNode *> packed_ranges = wire_node->attributes.count(UhdmAst::packed_ranges()) |
| ? wire_node->attributes[UhdmAst::packed_ranges()]->children |
| : std::vector<AST::AstNode *>(); |
| const std::vector<AST::AstNode *> unpacked_ranges = wire_node->attributes.count(UhdmAst::unpacked_ranges()) |
| ? wire_node->attributes[UhdmAst::unpacked_ranges()]->children |
| : std::vector<AST::AstNode *>(); |
| if (packed_ranges.empty() && unpacked_ranges.empty()) { |
| delete_attribute(wire_node, UhdmAst::packed_ranges()); |
| delete_attribute(wire_node, UhdmAst::unpacked_ranges()); |
| wire_node->range_left = 0; |
| wire_node->range_right = 0; |
| wire_node->range_valid = true; |
| return; |
| } |
| std::vector<AST::AstNode *> ranges; |
| |
| // Convert only when node is not a memory and at least 1 of the ranges has more than 1 range |
| const bool convert_node = [&]() { |
| if (wire_node->type == AST::AST_MEMORY) |
| return false; |
| if (packed_ranges.size() > 1) |
| return true; |
| if (unpacked_ranges.size() > 1) |
| return true; |
| if (wire_node->attributes.count(ID::wiretype)) |
| return true; |
| if (wire_node->type == AST::AST_PARAMETER) |
| return true; |
| if (wire_node->type == AST::AST_LOCALPARAM) |
| return true; |
| if ((wire_node->is_input || wire_node->is_output) && (packed_ranges.size() > 0 || unpacked_ranges.size() > 0)) |
| return true; |
| if (wire_node->attributes.count(UhdmAst::force_convert()) && wire_node->attributes[UhdmAst::force_convert()]->integer == 1) |
| return true; |
| return false; |
| }(); |
| if (convert_node) { |
| // if not already converted |
| if (wire_node->multirange_dimensions.empty()) { |
| const size_t packed_size = add_multirange_attribute(wire_node, packed_ranges); |
| const size_t unpacked_size = add_multirange_attribute(wire_node, unpacked_ranges); |
| if (packed_ranges.size() == 1 && unpacked_ranges.empty()) { |
| ranges.push_back(packed_ranges[0]->clone()); |
| } else if (unpacked_ranges.size() == 1 && packed_ranges.empty()) { |
| ranges.push_back(unpacked_ranges[0]->clone()); |
| } else { |
| // currently we have limited support |
| // for multirange wires that doesn't start from 0 |
| warn_start_range(packed_ranges); |
| warn_start_range(unpacked_ranges); |
| const size_t size = packed_size * unpacked_size; |
| log_assert(size >= 1); |
| ranges.push_back(make_range(size - 1, 0)); |
| } |
| } |
| } else { |
| for (auto r : packed_ranges) { |
| ranges.push_back(r->clone()); |
| } |
| for (auto r : unpacked_ranges) { |
| ranges.push_back(r->clone()); |
| } |
| // if there is only one packed and one unpacked range, |
| // and wire is not port wire, change type to AST_MEMORY |
| if (wire_node->type == AST::AST_WIRE && packed_ranges.size() == 1 && unpacked_ranges.size() == 1 && !wire_node->is_input && |
| !wire_node->is_output) { |
| wire_node->type = AST::AST_MEMORY; |
| wire_node->is_logic = true; |
| } |
| } |
| |
| if (wire_node->type == AST::AST_STRUCT_ITEM || wire_node->type == AST::AST_STRUCT) { |
| delete_attribute(wire_node, UhdmAst::packed_ranges()); |
| delete_attribute(wire_node, UhdmAst::unpacked_ranges()); |
| } |
| |
| // Insert new range |
| wire_node->children.insert(wire_node->children.end(), ranges.begin(), ranges.end()); |
| } |
| |
| static AST::AstNode *expand_dot(const AST::AstNode *current_struct, const AST::AstNode *search_node) |
| { |
| AST::AstNode *current_struct_elem = nullptr; |
| auto search_str = search_node->str.find("\\") == 0 ? search_node->str.substr(1) : search_node->str; |
| auto struct_elem_it = |
| std::find_if(current_struct->children.begin(), current_struct->children.end(), [&](AST::AstNode *node) { return node->str == search_str; }); |
| if (struct_elem_it == current_struct->children.end()) { |
| current_struct->dumpAst(NULL, "struct >"); |
| log_error("Couldn't find search elem: %s in struct\n", search_str.c_str()); |
| } |
| current_struct_elem = *struct_elem_it; |
| |
| AST::AstNode *left = nullptr, *right = nullptr; |
| if (current_struct_elem->type == AST::AST_STRUCT_ITEM) { |
| left = AST::AstNode::mkconst_int(current_struct_elem->range_left, true); |
| right = AST::AstNode::mkconst_int(current_struct_elem->range_right, true); |
| } else if (current_struct_elem->type == AST::AST_STRUCT) { |
| // Struct can have multiple range, so to get size of 1 struct, |
| // we get left range for first children, and right range for last children |
| left = AST::AstNode::mkconst_int(current_struct_elem->children.front()->range_left, true); |
| right = AST::AstNode::mkconst_int(current_struct_elem->children.back()->range_right, true); |
| } else if (current_struct_elem->type == AST::AST_UNION) { |
| left = AST::AstNode::mkconst_int(current_struct_elem->range_left, true); |
| right = AST::AstNode::mkconst_int(current_struct_elem->range_right, true); |
| } else { |
| // Structs currently can only have AST_STRUCT, AST_STRUCT_ITEM, or AST_UNION. |
| log_file_error(current_struct_elem->filename, current_struct_elem->location.first_line, |
| "Accessing struct member of type %s is unsupported.\n", type2str(current_struct_elem->type).c_str()); |
| } |
| |
| auto elem_size = |
| new AST::AstNode(AST::AST_ADD, new AST::AstNode(AST::AST_SUB, left->clone(), right->clone()), AST::AstNode::mkconst_int(1, true)); |
| AST::AstNode *sub_dot = nullptr; |
| AST::AstNode *struct_range = nullptr; |
| |
| for (auto c : search_node->children) { |
| if (c->type == static_cast<int>(AST::Extended::AST_DOT)) { |
| // There should be only 1 AST_DOT node children |
| log_assert(!sub_dot); |
| sub_dot = expand_dot(current_struct_elem, c); |
| } |
| if (c->type == AST::AST_RANGE) { |
| // Currently supporting only 1 range |
| log_assert(!struct_range); |
| struct_range = c; |
| } |
| } |
| if (sub_dot) { |
| // First select correct element in first struct |
| std::swap(left, sub_dot->children[0]); |
| std::swap(right, sub_dot->children[1]); |
| delete sub_dot; |
| } |
| if (struct_range) { |
| // now we have correct element set, |
| // but we still need to set correct struct |
| log_assert(!struct_range->children.empty()); |
| if (current_struct_elem->type == AST::AST_STRUCT_ITEM) { |
| // if we selecting range of struct item, just add this range |
| // to our current select |
| if (struct_range->children.size() == 2) { |
| auto range_size = new AST::AstNode( |
| AST::AST_ADD, new AST::AstNode(AST::AST_SUB, struct_range->children[0]->clone(), struct_range->children[1]->clone()), |
| AST::AstNode::mkconst_int(1, true)); |
| right = new AST::AstNode(AST::AST_ADD, right, struct_range->children[1]->clone()); |
| left = new AST::AstNode( |
| AST::AST_ADD, left, |
| new AST::AstNode(AST::AST_ADD, struct_range->children[1]->clone(), new AST::AstNode(AST::AST_SUB, range_size, elem_size->clone()))); |
| } else if (struct_range->children.size() == 1) { |
| if (!current_struct_elem->multirange_dimensions.empty()) { |
| right = new AST::AstNode(AST::AST_ADD, right, |
| new AST::AstNode(AST::AST_MUL, struct_range->children[0]->clone(), |
| AST::AstNode::mkconst_int(current_struct_elem->multirange_dimensions.back(), true))); |
| delete left; |
| left = new AST::AstNode(AST::AST_ADD, right->clone(), |
| AST::AstNode::mkconst_int(current_struct_elem->multirange_dimensions.back() - 1, true)); |
| } else { |
| right = new AST::AstNode(AST::AST_ADD, right, struct_range->children[0]->clone()); |
| delete left; |
| left = right->clone(); |
| } |
| } else { |
| struct_range->dumpAst(NULL, "range >"); |
| log_error("Unhandled range select (AST_STRUCT_ITEM) in AST_DOT!\n"); |
| } |
| } else if (current_struct_elem->type == AST::AST_STRUCT) { |
| if (struct_range->children.size() == 2) { |
| right = new AST::AstNode(AST::AST_ADD, right, struct_range->children[1]->clone()); |
| auto range_size = new AST::AstNode( |
| AST::AST_ADD, new AST::AstNode(AST::AST_SUB, struct_range->children[0]->clone(), struct_range->children[1]->clone()), |
| AST::AstNode::mkconst_int(1, true)); |
| left = new AST::AstNode(AST::AST_ADD, left, new AST::AstNode(AST::AST_SUB, range_size, elem_size->clone())); |
| } else if (struct_range->children.size() == 1) { |
| AST::AstNode *mul = new AST::AstNode(AST::AST_MUL, elem_size->clone(), struct_range->children[0]->clone()); |
| |
| left = new AST::AstNode(AST::AST_ADD, left, mul); |
| right = new AST::AstNode(AST::AST_ADD, right, mul->clone()); |
| } else { |
| struct_range->dumpAst(NULL, "range >"); |
| log_error("Unhandled range select (AST_STRUCT) in AST_DOT!\n"); |
| } |
| } else { |
| log_file_error(current_struct_elem->filename, current_struct_elem->location.first_line, |
| "Accessing member of a slice of type %s is unsupported.\n", type2str(current_struct_elem->type).c_str()); |
| } |
| } |
| delete elem_size; |
| // Return range from the begining of *current* struct |
| // When all AST_DOT are expanded it will return range |
| // from original wire |
| return new AST::AstNode(AST::AST_RANGE, left, right); |
| } |
| |
| static AST::AstNode *convert_dot(AST::AstNode *wire_node, AST::AstNode *node, AST::AstNode *dot) |
| { |
| AST::AstNode *struct_node = nullptr; |
| if (wire_node->type == AST::AST_STRUCT || wire_node->type == AST::AST_UNION) { |
| struct_node = wire_node; |
| } else if (wire_node->attributes.count(ID::wiretype)) { |
| log_assert(wire_node->attributes[ID::wiretype]->id2ast); |
| struct_node = wire_node->attributes[ID::wiretype]->id2ast; |
| } else { |
| log_file_error(wire_node->filename, wire_node->location.first_line, "Unsupported node type: %s\n", type2str(wire_node->type).c_str()); |
| } |
| log_assert(struct_node); |
| auto expanded = expand_dot(struct_node, dot); |
| if (node->children[0]->type == AST::AST_RANGE) { |
| int struct_size_int = get_max_offset_struct(struct_node) + 1; |
| log_assert(!wire_node->multirange_dimensions.empty()); |
| int range = wire_node->multirange_dimensions.back() - 1; |
| if (!wire_node->attributes[UhdmAst::unpacked_ranges()]->children.empty() && |
| wire_node->attributes[UhdmAst::unpacked_ranges()]->children.back()->range_left == range) { |
| expanded->children[1] = new AST::AstNode( |
| AST::AST_ADD, expanded->children[1], |
| new AST::AstNode(AST::AST_MUL, AST::AstNode::mkconst_int(struct_size_int, true, 32), |
| new AST::AstNode(AST::AST_SUB, AST::AstNode::mkconst_int(range, true, 32), node->children[0]->children[0]->clone()))); |
| expanded->children[0] = new AST::AstNode( |
| AST::AST_ADD, expanded->children[0], |
| new AST::AstNode(AST::AST_MUL, AST::AstNode::mkconst_int(struct_size_int, true, 32), |
| new AST::AstNode(AST::AST_SUB, AST::AstNode::mkconst_int(range, true, 32), node->children[0]->children[0]->clone()))); |
| } else { |
| expanded->children[1] = new AST::AstNode( |
| AST::AST_ADD, expanded->children[1], |
| new AST::AstNode(AST::AST_MUL, AST::AstNode::mkconst_int(struct_size_int, true, 32), node->children[0]->children[0]->clone())); |
| expanded->children[0] = new AST::AstNode( |
| AST::AST_ADD, expanded->children[0], |
| new AST::AstNode(AST::AST_MUL, AST::AstNode::mkconst_int(struct_size_int, true, 32), node->children[0]->children[0]->clone())); |
| } |
| } |
| return expanded; |
| } |
| |
| static void setup_current_scope(std::unordered_map<std::string, AST::AstNode *> top_nodes, AST::AstNode *current_top_node) |
| { |
| for (auto it = top_nodes.begin(); it != top_nodes.end(); it++) { |
| if (!it->second) |
| continue; |
| if (it->second->type == AST::AST_PACKAGE) { |
| for (auto &o : it->second->children) { |
| // import only parameters |
| if (o->type == AST::AST_TYPEDEF || o->type == AST::AST_PARAMETER || o->type == AST::AST_LOCALPARAM) { |
| // add imported nodes to current scope |
| AST_INTERNAL::current_scope[it->second->str + std::string("::") + o->str.substr(1)] = o; |
| AST_INTERNAL::current_scope[o->str] = o; |
| } else if (o->type == AST::AST_ENUM) { |
| AST_INTERNAL::current_scope[o->str] = o; |
| for (auto c : o->children) { |
| AST_INTERNAL::current_scope[c->str] = c; |
| } |
| } |
| } |
| } |
| } |
| for (auto &o : current_top_node->children) { |
| if (o->type == AST::AST_TYPEDEF || o->type == AST::AST_PARAMETER || o->type == AST::AST_LOCALPARAM) { |
| AST_INTERNAL::current_scope[o->str] = o; |
| } else if (o->type == AST::AST_ENUM) { |
| AST_INTERNAL::current_scope[o->str] = o; |
| for (auto c : o->children) { |
| AST_INTERNAL::current_scope[c->str] = c; |
| } |
| } |
| } |
| // hackish way of setting current_ast_mod as it is required |
| // for simplify to get references for already defined ids |
| AST_INTERNAL::current_ast_mod = current_top_node; |
| log_assert(AST_INTERNAL::current_ast_mod != nullptr); |
| } |
| |
| static int range_width_local(AST::AstNode *node, AST::AstNode *rnode) |
| { |
| log_assert(rnode->type == AST::AST_RANGE); |
| if (!rnode->range_valid) { |
| log_file_error(node->filename, node->location.first_line, "Size must be constant in packed struct/union member %s\n", node->str.c_str()); |
| } |
| // note: range swapping has already been checked for |
| return rnode->range_left - rnode->range_right + 1; |
| } |
| |
| static void save_struct_array_width_local(AST::AstNode *node, int width) |
| { |
| // stash the stride for the array |
| node->multirange_dimensions.push_back(width); |
| } |
| |
| static int simplify_struct(AST::AstNode *snode, int base_offset, AST::AstNode *parent_node) |
| { |
| // Struct members will be laid out in the structure contiguously from left to right. |
| // Union members all have zero offset from the start of the union. |
| // Determine total packed size and assign offsets. Store these in the member node. |
| bool is_union = (snode->type == AST::AST_UNION); |
| int offset = 0; |
| int packed_width = -1; |
| for (auto s : snode->children) { |
| if (s->type == AST::AST_RANGE) { |
| while (s->simplify(true, false, false, 1, -1, false, false)) { |
| }; |
| } |
| } |
| // embeded struct or union with range? |
| auto it = std::remove_if(snode->children.begin(), snode->children.end(), [](AST::AstNode *node) { return node->type == AST::AST_RANGE; }); |
| std::vector<AST::AstNode *> ranges(it, snode->children.end()); |
| snode->children.erase(it, snode->children.end()); |
| if (!ranges.empty()) { |
| if (ranges.size() > 1) { |
| log_file_error(ranges[1]->filename, ranges[1]->location.first_line, |
| "Currently support for custom-type with range is limited to single range\n"); |
| } |
| for (auto range : ranges) { |
| snode->multirange_dimensions.push_back(min(range->range_left, range->range_right)); |
| snode->multirange_dimensions.push_back(max(range->range_left, range->range_right) - min(range->range_left, range->range_right) + 1); |
| snode->multirange_swapped.push_back(range->range_swapped); |
| delete range; |
| } |
| } |
| // examine members from last to first |
| for (auto it = snode->children.rbegin(); it != snode->children.rend(); ++it) { |
| auto node = *it; |
| int width; |
| if (node->type == AST::AST_STRUCT || node->type == AST::AST_UNION) { |
| // embedded struct or union |
| width = simplify_struct(node, base_offset + offset, parent_node); |
| if (!node->multirange_dimensions.empty()) { |
| int number_of_structs = 1; |
| number_of_structs = node->multirange_dimensions.back(); |
| width *= number_of_structs; |
| } |
| // set range of struct |
| node->range_right = base_offset + offset; |
| node->range_left = base_offset + offset + width - 1; |
| node->range_valid = true; |
| } else { |
| log_assert(node->type == AST::AST_STRUCT_ITEM); |
| if (node->children.size() > 0 && node->children[0]->type == AST::AST_RANGE) { |
| // member width e.g. bit [7:0] a |
| width = range_width_local(node, node->children[0]); |
| if (node->children.size() == 2) { |
| if (node->children[1]->type == AST::AST_RANGE) { |
| // unpacked array e.g. bit [63:0] a [0:3] |
| auto rnode = node->children[1]; |
| int array_count = range_width_local(node, rnode); |
| if (array_count == 1) { |
| // C-type array size e.g. bit [63:0] a [4] |
| array_count = rnode->range_left; |
| } |
| save_struct_array_width_local(node, width); |
| width *= array_count; |
| } else { |
| // array element must be single bit for a packed array |
| log_file_error(node->filename, node->location.first_line, "Unpacked array in packed struct/union member %s\n", |
| node->str.c_str()); |
| } |
| } |
| // range nodes are now redundant |
| for (AST::AstNode *child : node->children) |
| delete child; |
| node->children.clear(); |
| } else if (node->children.size() == 1 && node->children[0]->type == AST::AST_MULTIRANGE) { |
| // packed 2D array, e.g. bit [3:0][63:0] a |
| auto rnode = node->children[0]; |
| if (rnode->children.size() != 2) { |
| // packed arrays can only be 2D |
| log_file_error(node->filename, node->location.first_line, "Unpacked array in packed struct/union member %s\n", node->str.c_str()); |
| } |
| int array_count = range_width_local(node, rnode->children[0]); |
| width = range_width_local(node, rnode->children[1]); |
| save_struct_array_width_local(node, width); |
| width *= array_count; |
| // range nodes are now redundant |
| for (AST::AstNode *child : node->children) |
| delete child; |
| node->children.clear(); |
| } else if (node->range_left < 0) { |
| // 1 bit signal: bit, logic or reg |
| width = 1; |
| } else { |
| // already resolved and compacted |
| width = node->range_left - node->range_right + 1; |
| } |
| if (is_union) { |
| node->range_right = base_offset; |
| node->range_left = base_offset + width - 1; |
| } else { |
| node->range_right = base_offset + offset; |
| node->range_left = base_offset + offset + width - 1; |
| } |
| node->range_valid = true; |
| } |
| if (is_union) { |
| // check that all members have the same size |
| if (packed_width == -1) { |
| // first member |
| packed_width = width; |
| } else { |
| if (packed_width != width) { |
| |
| log_file_error(node->filename, node->location.first_line, "member %s of a packed union has %d bits, expecting %d\n", |
| node->str.c_str(), width, packed_width); |
| } |
| } |
| } else { |
| offset += width; |
| } |
| } |
| if (!snode->str.empty() && parent_node && parent_node->type != AST::AST_TYPEDEF && parent_node->type != AST::AST_STRUCT && |
| AST_INTERNAL::current_scope.count(snode->str) != 0) { |
| AST_INTERNAL::current_scope[snode->str]->attributes[ID::wiretype] = AST::AstNode::mkconst_str(snode->str); |
| AST_INTERNAL::current_scope[snode->str]->attributes[ID::wiretype]->id2ast = snode; |
| } |
| return (is_union ? packed_width : offset); |
| } |
| |
| static void add_members_to_scope_local(AST::AstNode *snode, std::string name) |
| { |
| // add all the members in a struct or union to local scope |
| // in case later referenced in assignments |
| log_assert(snode->type == AST::AST_STRUCT || snode->type == AST::AST_UNION); |
| for (auto *node : snode->children) { |
| auto member_name = name + "." + node->str; |
| AST_INTERNAL::current_scope[member_name] = node; |
| if (node->type != AST::AST_STRUCT_ITEM) { |
| // embedded struct or union |
| add_members_to_scope_local(node, name + "." + node->str); |
| } |
| } |
| } |
| |
| static AST::AstNode *make_packed_struct_local(AST::AstNode *template_node, std::string &name) |
| { |
| // create a wire for the packed struct |
| auto wnode = new AST::AstNode(AST::AST_WIRE); |
| wnode->str = name; |
| wnode->is_logic = true; |
| wnode->range_valid = true; |
| wnode->is_signed = template_node->is_signed; |
| int offset = get_max_offset_struct(template_node); |
| auto range = make_range(offset, 0); |
| copy_packed_unpacked_attribute(template_node, wnode); |
| wnode->attributes[UhdmAst::packed_ranges()]->children.insert(wnode->attributes[UhdmAst::packed_ranges()]->children.begin(), range); |
| // make sure this node is the one in scope for this name |
| AST_INTERNAL::current_scope[name] = wnode; |
| // add all the struct members to scope under the wire's name |
| add_members_to_scope_local(template_node, name); |
| return wnode; |
| } |
| |
| static void simplify_format_string(AST::AstNode *current_node) |
| { |
| std::string sformat = current_node->children[0]->str; |
| std::string preformatted_string = ""; |
| int next_arg = 1; |
| for (size_t i = 0; i < sformat.length(); i++) { |
| if (sformat[i] == '%') { |
| AST::AstNode *node_arg = current_node->children[next_arg]; |
| char cformat = sformat[++i]; |
| if (cformat == 'b' or cformat == 'B') { |
| node_arg->simplify(true, false, false, 1, -1, false, false); |
| if (node_arg->type != AST::AST_CONSTANT) |
| log_file_error(current_node->filename, current_node->location.first_line, |
| "Failed to evaluate system task `%s' with non-constant argument.\n", current_node->str.c_str()); |
| |
| RTLIL::Const val = node_arg->bitsAsConst(); |
| for (int j = val.size() - 1; j >= 0; j--) { |
| // We add ACII value of 0 to convert number to character |
| preformatted_string += ('0' + val[j]); |
| } |
| delete current_node->children[next_arg]; |
| current_node->children.erase(current_node->children.begin() + next_arg); |
| } else { |
| next_arg++; |
| preformatted_string += std::string("%") + cformat; |
| } |
| } else { |
| preformatted_string += sformat[i]; |
| } |
| } |
| delete current_node->children[0]; |
| current_node->children[0] = AST::AstNode::mkconst_str(preformatted_string); |
| } |
| |
| static void simplify(AST::AstNode *current_node, AST::AstNode *parent_node) |
| { |
| auto dot_it = std::find_if(current_node->children.begin(), current_node->children.end(), |
| [](auto c) { return c->type == static_cast<int>(AST::Extended::AST_DOT); }); |
| AST::AstNode *dot = (dot_it != current_node->children.end()) ? *dot_it : nullptr; |
| |
| AST::AstNode *expanded = nullptr; |
| if (dot) { |
| if (!AST_INTERNAL::current_scope.count(current_node->str)) { |
| // for accessing elements currently unsupported with AST_DOT |
| // fallback to "." notation |
| AST::AstNode *prefix_node = nullptr; |
| AST::AstNode *parent_node = current_node; |
| while (dot && !dot->str.empty()) { |
| // it is not possible for AST_RANGE to be after AST::DOT (see process_hier_path function) |
| if (parent_node->children[0]->type == AST::AST_RANGE) { |
| if (parent_node->children[1]->type == AST::AST_RANGE) |
| log_error("Multirange in AST_DOT is currently unsupported\n"); |
| |
| dot->type = AST::AST_IDENTIFIER; |
| simplify(dot, nullptr); |
| AST::AstNode *range_const = parent_node->children[0]->children[0]; |
| prefix_node = new AST::AstNode(AST::AST_PREFIX, range_const->clone(), dot->clone()); |
| break; |
| } else { |
| current_node->str += "." + dot->str.substr(1); |
| dot_it = std::find_if(dot->children.begin(), dot->children.end(), |
| [](auto c) { return c->type == static_cast<int>(AST::Extended::AST_DOT); }); |
| parent_node = dot; |
| dot = (dot_it != dot->children.end()) ? *dot_it : nullptr; |
| } |
| } |
| delete_children(current_node); |
| if (prefix_node != nullptr) { |
| current_node->type = AST::AST_PREFIX; |
| current_node->children = prefix_node->children; |
| |
| prefix_node->children.clear(); |
| delete prefix_node; |
| } |
| } else { |
| auto wire_node = AST_INTERNAL::current_scope[current_node->str]; |
| // make sure wire_node is already simplified |
| simplify(wire_node, nullptr); |
| expanded = convert_dot(wire_node, current_node, dot); |
| } |
| } |
| if (expanded) { |
| delete_children(current_node); |
| current_node->children.push_back(expanded->clone()); |
| current_node->basic_prep = true; |
| delete expanded; |
| expanded = nullptr; |
| } |
| // First simplify children |
| for (size_t i = 0; i < current_node->children.size(); i++) { |
| simplify(current_node->children[i], current_node); |
| } |
| switch (current_node->type) { |
| case AST::AST_TYPEDEF: |
| case AST::AST_ENUM: |
| case AST::AST_FUNCTION: |
| AST_INTERNAL::current_scope[current_node->str] = current_node; |
| break; |
| case AST::AST_WIRE: |
| delete_attribute(current_node, UhdmAst::is_simplified_wire()); |
| current_node->attributes[UhdmAst::is_simplified_wire()] = AST::AstNode::mkconst_int(1, true); |
| [[fallthrough]]; |
| case AST::AST_PARAMETER: |
| case AST::AST_LOCALPARAM: |
| AST_INTERNAL::current_scope[current_node->str] = current_node; |
| convert_packed_unpacked_range(current_node); |
| break; |
| case AST::AST_IDENTIFIER: |
| if (!current_node->children.empty() && !current_node->basic_prep) { |
| log_assert(AST_INTERNAL::current_ast_mod); |
| if (!AST_INTERNAL::current_scope.count(current_node->str)) { |
| break; |
| } |
| AST::AstNode *wire_node = AST_INTERNAL::current_scope[current_node->str]; |
| |
| // if a wire is simplified multiple times, its ranges may be added multiple times and be redundant as a result |
| if (!wire_node->attributes.count(UhdmAst::is_simplified_wire())) { |
| simplify(wire_node, nullptr); |
| } |
| const int packed_ranges_size = |
| wire_node->attributes.count(UhdmAst::packed_ranges()) ? wire_node->attributes[UhdmAst::packed_ranges()]->children.size() : 0; |
| const int unpacked_ranges_size = |
| wire_node->attributes.count(UhdmAst::unpacked_ranges()) ? wire_node->attributes[UhdmAst::unpacked_ranges()]->children.size() : 0; |
| if ((wire_node->type == AST::AST_WIRE || wire_node->type == AST::AST_PARAMETER || wire_node->type == AST::AST_LOCALPARAM) && |
| (packed_ranges_size + unpacked_ranges_size > 1)) { |
| auto *result = convert_range(current_node, packed_ranges_size, unpacked_ranges_size, 0); |
| delete_children(current_node); |
| current_node->children.push_back(result); |
| } |
| } |
| break; |
| case AST::AST_STRUCT: |
| case AST::AST_UNION: |
| simplify_struct(current_node, 0, parent_node); |
| // instance rather than just a type in a typedef or outer struct? |
| if (!current_node->str.empty() && current_node->str[0] == '\\') { |
| // instance so add a wire for the packed structure |
| auto wnode = make_packed_struct_local(current_node, current_node->str); |
| convert_packed_unpacked_range(wnode); |
| log_assert(AST_INTERNAL::current_ast_mod); |
| AST_INTERNAL::current_ast_mod->children.push_back(wnode); |
| AST_INTERNAL::current_scope[wnode->str]->attributes[ID::wiretype] = AST::AstNode::mkconst_str(current_node->str); |
| AST_INTERNAL::current_scope[wnode->str]->attributes[ID::wiretype]->id2ast = current_node; |
| } |
| |
| current_node->basic_prep = true; |
| break; |
| case AST::AST_STRUCT_ITEM: |
| AST_INTERNAL::current_scope[current_node->str] = current_node; |
| convert_packed_unpacked_range(current_node); |
| while (current_node->simplify(true, false, false, 1, -1, false, false)) { |
| }; |
| break; |
| case AST::AST_TCALL: |
| if (current_node->str == "$display" || current_node->str == "$write") |
| simplify_format_string(current_node); |
| break; |
| case AST::AST_COND: |
| case AST::AST_CONDX: |
| case AST::AST_CONDZ: |
| // handle custom low high bound |
| if (current_node->attributes.count(UhdmAst::low_high_bound())) { |
| log_assert(!current_node->children.empty()); |
| log_assert(current_node->children[0]->type == AST::AST_BLOCK); |
| log_assert(current_node->children[0]->children.size() == 2); |
| auto low_high_bound = current_node->children[0]; |
| // this is executed when condition is met |
| // save pointer that will be added later again |
| // as conditions needs to go before this block |
| auto result = current_node->children[1]; |
| |
| current_node->children[0] = nullptr; |
| current_node->children[1] = nullptr; |
| delete_children(current_node); |
| while (low_high_bound->children[0]->simplify(true, false, false, 1, -1, false, false)) { |
| }; |
| while (low_high_bound->children[1]->simplify(true, false, false, 1, -1, false, false)) { |
| }; |
| log_assert(low_high_bound->children[0]->type == AST::AST_CONSTANT); |
| log_assert(low_high_bound->children[1]->type == AST::AST_CONSTANT); |
| const int low = low_high_bound->children[0]->integer; |
| const int high = low_high_bound->children[1]->integer; |
| const int range = low_high_bound->children[1]->range_valid |
| ? low_high_bound->children[1]->range_left |
| : low_high_bound->children[0]->range_valid ? low_high_bound->children[0]->range_left : 32; |
| delete low_high_bound; |
| // According to standard: |
| // If the bound to the left of the colon is greater than the |
| // bound to the right, the range is empty and contains no values. |
| for (int i = low; i >= low && i <= high; i++) { |
| current_node->children.push_back(AST::AstNode::mkconst_int(i, false, range)); |
| } |
| current_node->children.push_back(result); |
| delete_attribute(current_node, UhdmAst::low_high_bound()); |
| } |
| break; |
| default: |
| break; |
| } |
| } |
| |
| static void clear_current_scope() |
| { |
| // Remove clear current_scope from package nodes |
| AST_INTERNAL::current_scope.clear(); |
| // unset current_ast_mod |
| AST_INTERNAL::current_ast_mod = nullptr; |
| } |
| |
| void UhdmAst::visit_one_to_many(const std::vector<int> child_node_types, vpiHandle parent_handle, const std::function<void(AST::AstNode *)> &f) |
| { |
| for (auto child : child_node_types) { |
| vpiHandle itr = vpi_iterate(child, parent_handle); |
| while (vpiHandle vpi_child_obj = vpi_scan(itr)) { |
| UhdmAst uhdm_ast(this, shared, indent + " "); |
| auto *child_node = uhdm_ast.process_object(vpi_child_obj); |
| f(child_node); |
| vpi_release_handle(vpi_child_obj); |
| } |
| vpi_release_handle(itr); |
| } |
| } |
| |
| void UhdmAst::visit_one_to_one(const std::vector<int> child_node_types, vpiHandle parent_handle, const std::function<void(AST::AstNode *)> &f) |
| { |
| for (auto child : child_node_types) { |
| vpiHandle itr = vpi_handle(child, parent_handle); |
| if (itr) { |
| UhdmAst uhdm_ast(this, shared, indent + " "); |
| auto *child_node = uhdm_ast.process_object(itr); |
| f(child_node); |
| } |
| vpi_release_handle(itr); |
| } |
| } |
| |
| void UhdmAst::visit_range(vpiHandle obj_h, const std::function<void(AST::AstNode *)> &f) |
| { |
| std::vector<AST::AstNode *> range_nodes; |
| visit_one_to_many({vpiRange}, obj_h, [&](AST::AstNode *node) { range_nodes.push_back(node); }); |
| if (range_nodes.size() > 1) { |
| auto multirange_node = new AST::AstNode(AST::AST_MULTIRANGE); |
| multirange_node->children = range_nodes; |
| f(multirange_node); |
| } else if (!range_nodes.empty()) { |
| f(range_nodes[0]); |
| } |
| } |
| |
| void UhdmAst::visit_default_expr(vpiHandle obj_h) |
| { |
| UhdmAst initial_ast(parent, shared, indent); |
| UhdmAst block_ast(&initial_ast, shared, indent); |
| block_ast.visit_one_to_one({vpiExpr}, obj_h, [&](AST::AstNode *expr_node) { |
| auto mod = find_ancestor({AST::AST_MODULE}); |
| AST::AstNode *initial_node = nullptr; |
| AST::AstNode *block_node = nullptr; |
| auto assign_node = new AST::AstNode(AST::AST_ASSIGN_EQ); |
| auto id_node = new AST::AstNode(AST::AST_IDENTIFIER); |
| id_node->str = current_node->str; |
| |
| for (auto child : mod->children) { |
| if (child->type == AST::AST_INITIAL) { |
| initial_node = child; |
| break; |
| } |
| } |
| // Ensure single AST_INITIAL node is located in AST_MODULE |
| // before any AST_ALWAYS |
| if (initial_node == nullptr) { |
| initial_node = new AST::AstNode(AST::AST_INITIAL); |
| auto insert_it = find_if(mod->children.begin(), mod->children.end(), [](AST::AstNode *node) { return (node->type == AST::AST_ALWAYS); }); |
| mod->children.insert(insert_it, initial_node); |
| } |
| // Ensure single AST_BLOCK node in AST_INITIAL |
| if (!initial_node->children.empty() && initial_node->children[0]) { |
| block_node = initial_node->children[0]; |
| } else { |
| block_node = new AST::AstNode(AST::AST_BLOCK); |
| initial_node->children.push_back(block_node); |
| } |
| auto block_child = |
| find_if(block_node->children.begin(), block_node->children.end(), [](AST::AstNode *node) { return (node->type == AST::AST_ASSIGN_EQ); }); |
| // Insert AST_ASSIGN_EQ nodes that came from |
| // custom_var or int_var before any other AST_ASSIGN_EQ |
| // Especially before ones explicitly placed in initial block in source code |
| block_node->children.insert(block_child, assign_node); |
| assign_node->children.push_back(id_node); |
| initial_ast.current_node = initial_node; |
| block_ast.current_node = block_node; |
| assign_node->children.push_back(expr_node); |
| }); |
| } |
| |
| AST::AstNode *UhdmAst::process_value(vpiHandle obj_h) |
| { |
| s_vpi_value val; |
| vpi_get_value(obj_h, &val); |
| std::string strValType = "'"; |
| bool is_signed = false; |
| if (vpiHandle typespec_h = vpi_handle(vpiTypespec, obj_h)) { |
| is_signed = vpi_get(vpiSigned, typespec_h); |
| if (is_signed) { |
| strValType += "s"; |
| } |
| } |
| if (val.format) { // Needed to handle parameter nodes without typespecs and constants |
| switch (val.format) { |
| case vpiScalarVal: |
| return AST::AstNode::mkconst_int(val.value.scalar, false, 1); |
| case vpiBinStrVal: { |
| strValType += "b"; |
| break; |
| } |
| case vpiDecStrVal: { |
| strValType += "d"; |
| break; |
| } |
| case vpiHexStrVal: { |
| strValType += "h"; |
| break; |
| } |
| case vpiOctStrVal: { |
| strValType += "o"; |
| break; |
| } |
| // Surelog reports constant integers as a unsigned, but by default int is signed |
| // so we are treating here UInt in the same way as if they would be Int |
| case vpiUIntVal: |
| if (val.value.uint > std::numeric_limits<std::uint32_t>::max()) { |
| // an integer is by default signed, so use 'sd despite the variant vpiUIntVal |
| strValType = "'sd"; |
| string str_value = std::to_string(val.value.uint); |
| val.value.str = strdup(str_value.c_str()); |
| break; |
| } |
| [[fallthrough]]; |
| case vpiIntVal: { |
| if (val.value.integer > std::numeric_limits<std::int32_t>::max()) { |
| strValType = "'sd"; |
| string str_value = std::to_string(val.value.integer); |
| val.value.str = strdup(str_value.c_str()); |
| break; |
| } |
| |
| int size = -1; |
| // Surelog sometimes report size as part of vpiTypespec (e.g. int_typespec) |
| // if it is the case, we need to set size to the left_range of first packed range |
| visit_one_to_one({vpiTypespec}, obj_h, [&](AST::AstNode *node) { |
| if (node && node->attributes.count(UhdmAst::packed_ranges()) && node->attributes[UhdmAst::packed_ranges()]->children.size() && |
| node->attributes[UhdmAst::packed_ranges()]->children[0]->children.size()) { |
| size = node->attributes[UhdmAst::packed_ranges()]->children[0]->children[0]->integer + 1; |
| } |
| delete node; |
| }); |
| if (size == -1) { |
| size = vpi_get(vpiSize, obj_h); |
| } |
| // Surelog by default returns 64 bit numbers and stardard says that they shall be at least 32bits |
| // yosys is assuming that int/uint is 32 bit, so we are setting here correct size |
| // NOTE: it *shouldn't* break on explicite 64 bit const values, as they *should* be handled |
| // above by vpi*StrVal |
| if (size == 64) { |
| size = 32; |
| is_signed = true; |
| } |
| auto c = AST::AstNode::mkconst_int(val.format == vpiUIntVal ? val.value.uint : val.value.integer, is_signed, size > 0 ? size : 32); |
| if (size == 0 || size == -1) |
| c->is_unsized = true; |
| return c; |
| } |
| case vpiRealVal: |
| return mkconst_real(val.value.real); |
| case vpiStringVal: |
| return AST::AstNode::mkconst_str(val.value.str); |
| default: { |
| const uhdm_handle *const handle = (const uhdm_handle *)obj_h; |
| const UHDM::BaseClass *const object = (const UHDM::BaseClass *)handle->object; |
| report_error("%.*s:%d: Encountered unhandled constant format %d\n", (int)object->VpiFile().length(), object->VpiFile().data(), |
| object->VpiLineNo(), val.format); |
| } |
| } |
| // if this constant is under case/casex/casez |
| // get current case type |
| char caseType = ' '; |
| if (vpiHandle caseItem_h = vpi_handle(vpiParent, obj_h)) { |
| if (vpiHandle case_h = vpi_handle(vpiParent, caseItem_h)) { |
| switch (vpi_get(vpiCaseType, case_h)) { |
| case vpiCaseExact: |
| caseType = ' '; |
| break; |
| case vpiCaseX: |
| caseType = 'x'; |
| break; |
| case vpiCaseZ: |
| caseType = 'z'; |
| break; |
| default: { |
| caseType = ' '; |
| break; |
| } |
| } |
| vpi_release_handle(case_h); |
| } |
| vpi_release_handle(caseItem_h); |
| } |
| // handle vpiBinStrVal, vpiDecStrVal and vpiHexStrVal |
| if (std::strchr(val.value.str, '\'')) { |
| return ::systemverilog_plugin::const2ast(val.value.str, caseType, false); |
| } else { |
| auto size = vpi_get(vpiSize, obj_h); |
| if (size == 0) { |
| auto c = AST::AstNode::mkconst_int(atoi(val.value.str), true, 32); |
| c->is_unsized = true; |
| return c; |
| } else { |
| return ::systemverilog_plugin::const2ast(std::to_string(size) + strValType + val.value.str, caseType, false); |
| } |
| } |
| } |
| return nullptr; |
| } |
| |
| void UhdmAst::transform_breaks_continues(AST::AstNode *loop, AST::AstNode *decl_block) |
| { |
| AST::AstNode *break_wire = nullptr; |
| AST::AstNode *continue_wire = nullptr; |
| // Creates a 1-bit wire with the given name |
| const auto make_cond_var = [this](const std::string &var_name) { |
| auto cond_var = |
| make_ast_node(AST::AST_WIRE, {make_ast_node(AST::AST_RANGE, {AST::AstNode::mkconst_int(0, false), AST::AstNode::mkconst_int(0, false)}), |
| AST::AstNode::mkconst_int(0, false)}); |
| cond_var->str = var_name; |
| cond_var->is_reg = true; |
| return cond_var; |
| }; |
| // Creates a conditional like 'if (!casevar) block' |
| auto make_case = [this](AST::AstNode *block, const std::string &casevar_name) { |
| auto *case_node = make_ast_node(AST::AST_CASE); |
| auto *id = make_identifier(casevar_name); |
| case_node->children.push_back(id); |
| auto *constant = AST::AstNode::mkconst_int(0, false, 1); |
| auto *cond_node = make_ast_node(AST::AST_COND); |
| cond_node->children.push_back(constant); |
| cond_node->children.push_back(block); |
| case_node->children.push_back(cond_node); |
| return case_node; |
| }; |
| // Pre-declare this function to be able to call it recursively |
| std::function<bool(AST::AstNode *)> transform_block; |
| // Transforms the given block if it has a break or continue; recurses into child blocks; return true if a break/continue was encountered |
| transform_block = [&](AST::AstNode *block) { |
| auto wrap_and_transform = [&](decltype(block->children)::iterator it) { |
| // Move the (it, end()) statements into a new block under 'if (!continue) {...}' |
| auto *new_block = make_ast_node(AST::AST_BLOCK, {it, block->children.end()}); |
| block->children.erase(it, block->children.end()); |
| auto *case_node = make_case(new_block, continue_wire->str); |
| block->children.push_back(case_node); |
| transform_block(new_block); |
| }; |
| |
| for (auto it = block->children.begin(); it != block->children.end(); it++) { |
| auto type = static_cast<int>((*it)->type); |
| switch (type) { |
| case AST::AST_BLOCK: { |
| if (transform_block(*it)) { |
| // If there was a break/continue, we need to wrap the rest of the block in an if |
| wrap_and_transform(it + 1); |
| return true; |
| } |
| break; |
| } |
| case AST::AST_CASE: { |
| // Go over each block in a case |
| bool has_jump = false; |
| for (auto *node : (*it)->children) { |
| if (node->type == AST::AST_COND) |
| has_jump = has_jump || transform_block(node->children.back()); |
| } |
| if (has_jump) { |
| // If there was a break/continue, we need to wrap the rest of the block in an if |
| wrap_and_transform(it + 1); |
| return true; |
| } |
| break; |
| } |
| case AST::Extended::AST_BREAK: |
| case AST::Extended::AST_CONTINUE: { |
| std::for_each(it, block->children.end(), [](auto *node) { delete node; }); |
| block->children.erase(it, block->children.end()); |
| if (!continue_wire) |
| continue_wire = make_cond_var("$continue"); |
| auto *continue_id = make_identifier(continue_wire->str); |
| block->children.push_back(make_ast_node(AST::AST_ASSIGN_EQ, {continue_id, AST::AstNode::mkconst_int(1, false)})); |
| if (type == AST::Extended::AST_BREAK) { |
| if (!break_wire) |
| break_wire = make_cond_var("$break"); |
| auto *break_id = make_identifier(break_wire->str); |
| block->children.push_back(make_ast_node(AST::AST_ASSIGN_EQ, {break_id, AST::AstNode::mkconst_int(1, false)})); |
| } |
| return true; |
| } |
| } |
| } |
| return false; |
| }; |
| |
| // Actual transformation starts here |
| transform_block(loop->children.back()); |
| if (continue_wire) { |
| auto *continue_id = make_identifier(continue_wire->str); |
| // Reset $continue each iteration |
| auto *continue_assign = make_ast_node(AST::AST_ASSIGN_EQ, {continue_id, AST::AstNode::mkconst_int(0, false)}); |
| decl_block->children.insert(decl_block->children.begin(), continue_wire); |
| loop->children.back()->children.insert(loop->children.back()->children.begin(), continue_assign); |
| } |
| if (break_wire) { |
| auto *break_id = make_identifier(break_wire->str); |
| // Reset $break before the loop |
| auto *break_assign = make_ast_node(AST::AST_ASSIGN_EQ, {break_id, AST::AstNode::mkconst_int(0, false)}); |
| decl_block->children.insert(decl_block->children.begin(), break_assign); |
| decl_block->children.insert(decl_block->children.begin(), break_wire); |
| if (loop->type == AST::AST_REPEAT || loop->type == AST::AST_FOR) { |
| // Wrap loop body in 'if (!break) {...}' |
| // Changing the for loop condition won't work here, |
| // as then simplify fails with error "2nd expression of procedural for-loop is not constant!" |
| auto *case_node = make_case(loop->children.back(), break_wire->str); |
| auto *new_block = make_ast_node(AST::AST_BLOCK); |
| new_block->children.push_back(case_node); |
| new_block->str = loop->children.back()->str; |
| loop->children.back() = new_block; |
| } else if (loop->type == AST::AST_WHILE) { |
| // Add the break var to the loop condition |
| auto *break_id = make_identifier(break_wire->str); |
| AST::AstNode *&loop_cond = loop->children[0]; |
| loop_cond = make_ast_node(AST::AST_LOGIC_AND, {make_ast_node(AST::AST_LOGIC_NOT, {break_id}), loop_cond}); |
| } else { |
| log_error("break unsupported for this loop type"); |
| } |
| } |
| } |
| |
| 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; |
| } |
| node->children = children; |
| return node; |
| } |
| |
| AST::AstNode *UhdmAst::make_identifier(const std::string &name) |
| { |
| auto *node = make_ast_node(AST::AST_IDENTIFIER); |
| node->str = name; |
| return node; |
| } |
| |
| void UhdmAst::process_packed_array_typespec() |
| { |
| std::vector<AST::AstNode *> packed_ranges; |
| std::vector<AST::AstNode *> unpacked_ranges; |
| current_node = make_ast_node(AST::AST_WIRE); |
| visit_one_to_many({vpiRange}, obj_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); }); |
| visit_one_to_one({vpiElemTypespec}, obj_h, [&](AST::AstNode *node) { |
| if (node && node->type == AST::AST_STRUCT) { |
| auto str = current_node->str; |
| node->cloneInto(current_node); |
| current_node->str = str; |
| delete node; |
| } else if (node) { |
| current_node->str = node->str; |
| if (node->type == AST::AST_ENUM && !node->children.empty()) { |
| for (auto *c : node->children[0]->children) { |
| if (c->type == AST::AST_RANGE && c->str.empty()) |
| packed_ranges.push_back(c); |
| else |
| delete c; |
| } |
| node->children[0]->children.clear(); |
| } |
| delete node; |
| } |
| }); |
| add_multirange_wire(current_node, std::move(packed_ranges), std::move(unpacked_ranges)); |
| } |
| |
| static void add_or_replace_child(AST::AstNode *parent, AST::AstNode *child) |
| { |
| if (!child->str.empty()) { |
| auto it = std::find_if(parent->children.begin(), parent->children.end(), |
| [child](AST::AstNode *existing_child) { return existing_child->str == child->str; }); |
| if (it != parent->children.end()) { |
| // If port direction is already set, copy it to replaced child node |
| if ((*it)->is_input || (*it)->is_output) { |
| child->is_input = (*it)->is_input; |
| child->is_output = (*it)->is_output; |
| child->port_id = (*it)->port_id; |
| if (child->type == AST::AST_MEMORY) |
| child->type = AST::AST_WIRE; |
| } |
| child->is_signed = child->is_signed || (*it)->is_signed; |
| if (!(*it)->children.empty() && child->children.empty()) { |
| // This is a bit ugly, but if the child we're replacing has children and |
| // our node doesn't, we copy its children to not lose any information |
| for (auto grandchild : (*it)->children) { |
| child->children.push_back(grandchild->clone()); |
| if (child->type == AST::AST_WIRE && grandchild->type == AST::AST_WIRETYPE) |
| child->is_custom_type = true; |
| } |
| } |
| if ((*it)->attributes.count(UhdmAst::packed_ranges()) && child->attributes.count(UhdmAst::packed_ranges())) { |
| if ((!(*it)->attributes[UhdmAst::packed_ranges()]->children.empty() && |
| child->attributes[UhdmAst::packed_ranges()]->children.empty())) { |
| |
| delete_attribute(child, UhdmAst::packed_ranges()); |
| child->attributes[UhdmAst::packed_ranges()] = (*it)->attributes[UhdmAst::packed_ranges()]->clone(); |
| } |
| } |
| if ((*it)->attributes.count(UhdmAst::unpacked_ranges()) && child->attributes.count(UhdmAst::unpacked_ranges())) { |
| if ((!(*it)->attributes[UhdmAst::unpacked_ranges()]->children.empty() && |
| child->attributes[UhdmAst::unpacked_ranges()]->children.empty())) { |
| |
| delete_attribute(child, UhdmAst::unpacked_ranges()); |
| child->attributes[UhdmAst::unpacked_ranges()] = (*it)->attributes[UhdmAst::unpacked_ranges()]->clone(); |
| } |
| } |
| // Surelog doesn't report correct sign value for param_assign nodes |
| // and only default vpiParameter node have correct sign value, so |
| // if we are overriding parameter, copy sign value from current node to the new node |
| if (((*it)->type == AST::AST_PARAMETER || (*it)->type == AST::AST_LOCALPARAM) && child->children.size() && (*it)->children.size()) { |
| child->children[0]->is_signed = (*it)->children[0]->is_signed; |
| } |
| delete *it; |
| *it = child; |
| return; |
| } |
| parent->children.push_back(child); |
| } else if (child->type == AST::AST_INITIAL) { |
| // Special case for initials |
| // Ensure that there is only one AST_INITIAL in the design |
| // And there is only one AST_BLOCK inside that initial |
| // Copy nodes from child initial to parent initial |
| auto initial_node_it = |
| find_if(parent->children.begin(), parent->children.end(), [](AST::AstNode *node) { return (node->type == AST::AST_INITIAL); }); |
| if (initial_node_it != parent->children.end()) { |
| AST::AstNode *initial_node = *initial_node_it; |
| |
| // simplify assumes that initial has a block under it |
| // In case we don't have one (there were no statements under the initial), let's add it |
| if (initial_node->children.empty()) { |
| initial_node->children.push_back(new AST::AstNode(AST::AST_BLOCK)); |
| } |
| |
| log_assert(initial_node->children[0]->type == AST::AST_BLOCK); |
| log_assert(!(child->children.empty())); |
| log_assert(child->children[0]->type == AST::AST_BLOCK); |
| |
| AST::AstNode *block_node = initial_node->children[0]; |
| AST::AstNode *child_block_node = child->children[0]; |
| |
| // Place the contents of child block node inside parent block |
| for (auto child_block_child : child_block_node->children) |
| block_node->children.push_back(child_block_child->clone()); |
| // Place the remaining contents of child initial node inside the parent initial |
| for (auto initial_child = child->children.begin() + 1; initial_child != child->children.end(); ++initial_child) { |
| initial_node->children.push_back((*initial_child)->clone()); |
| } |
| delete child; |
| } else { |
| // Parent AST_INITIAL does not exist |
| // Place child AST_INITIAL before AST_ALWAYS if found |
| auto insert_it = |
| find_if(parent->children.begin(), parent->children.end(), [](AST::AstNode *node) { return (node->type == AST::AST_ALWAYS); }); |
| parent->children.insert(insert_it, 1, child); |
| } |
| } else { |
| parent->children.push_back(child); |
| } |
| } |
| |
| void UhdmAst::make_cell(vpiHandle obj_h, AST::AstNode *cell_node, AST::AstNode *type_node) |
| { |
| if (cell_node->children.empty() || (!cell_node->children.empty() && cell_node->children[0]->type != AST::AST_CELLTYPE)) { |
| auto typeNode = new AST::AstNode(AST::AST_CELLTYPE); |
| typeNode->str = type_node->str; |
| cell_node->children.insert(cell_node->children.begin(), typeNode); |
| } |
| // Add port connections as arguments |
| vpiHandle port_itr = vpi_iterate(vpiPort, obj_h); |
| while (vpiHandle port_h = vpi_scan(port_itr)) { |
| std::string arg_name; |
| if (auto s = vpi_get_str(vpiName, port_h)) { |
| arg_name = s; |
| sanitize_symbol_name(arg_name); |
| } |
| auto arg_node = new AST::AstNode(AST::AST_ARGUMENT); |
| arg_node->str = arg_name; |
| arg_node->filename = cell_node->filename; |
| arg_node->location = cell_node->location; |
| visit_one_to_one({vpiHighConn}, port_h, [&](AST::AstNode *node) { |
| if (node) { |
| if (node->type == AST::AST_PARAMETER || node->type == AST::AST_LOCALPARAM) { |
| node->type = AST::AST_IDENTIFIER; |
| } |
| arg_node->children.push_back(node); |
| } |
| }); |
| cell_node->children.push_back(arg_node); |
| shared.report.mark_handled(port_h); |
| vpi_release_handle(port_h); |
| } |
| vpi_release_handle(port_itr); |
| } |
| |
| void UhdmAst::move_type_to_new_typedef(AST::AstNode *current_node, AST::AstNode *type_node) |
| { |
| auto typedef_node = new AST::AstNode(AST::AST_TYPEDEF); |
| typedef_node->location = type_node->location; |
| typedef_node->filename = type_node->filename; |
| typedef_node->str = strip_package_name(type_node->str); |
| for (auto c : current_node->children) { |
| if (c->str == typedef_node->str) { |
| return; |
| } |
| } |
| if (type_node->type == AST::AST_STRUCT) { |
| type_node->str.clear(); |
| typedef_node->children.push_back(type_node); |
| current_node->children.push_back(typedef_node); |
| } else if (type_node->type == AST::AST_ENUM) { |
| if (type_node->attributes.count("\\enum_base_type")) { |
| auto base_type = type_node->attributes["\\enum_base_type"]; |
| auto wire_node = new AST::AstNode(AST::AST_WIRE); |
| wire_node->is_reg = true; |
| for (auto c : base_type->children) { |
| std::string enum_item_str = "\\enum_value_"; |
| log_assert(!c->children.empty()); |
| log_assert(c->children[0]->type == AST::AST_CONSTANT); |
| int width = 1; |
| bool is_signed = c->children[0]->is_signed; |
| if (c->children.size() == 2) { |
| width = c->children[1]->children[0]->integer + 1; |
| } |
| RTLIL::Const val = c->children[0]->bitsAsConst(width, is_signed); |
| enum_item_str.append(val.as_string()); |
| wire_node->attributes[enum_item_str.c_str()] = AST::AstNode::mkconst_str(c->str); |
| } |
| typedef_node->children.push_back(wire_node); |
| current_node->children.push_back(typedef_node); |
| delete type_node; |
| } else { |
| type_node->str = "$enum" + std::to_string(shared.next_enum_id()); |
| auto wire_node = new AST::AstNode(AST::AST_WIRE); |
| wire_node->is_reg = true; |
| wire_node->attributes["\\enum_type"] = AST::AstNode::mkconst_str(type_node->str); |
| if (!type_node->children.empty() && type_node->children[0]->children.size() > 1) { |
| wire_node->children.push_back(type_node->children[0]->children[1]->clone()); |
| } else { |
| // Add default range |
| wire_node->children.push_back(make_range(31, 0)); |
| } |
| typedef_node->children.push_back(wire_node); |
| current_node->children.push_back(type_node); |
| current_node->children.push_back(typedef_node); |
| } |
| } else { |
| type_node->str.clear(); |
| typedef_node->children.push_back(type_node); |
| current_node->children.push_back(typedef_node); |
| } |
| } |
| |
| AST::AstNode *UhdmAst::find_ancestor(const std::unordered_set<AST::AstNodeType> &types) |
| { |
| auto searched_node = this; |
| while (searched_node) { |
| if (searched_node->current_node) { |
| if (types.find(searched_node->current_node->type) != types.end()) { |
| return searched_node->current_node; |
| } |
| } |
| searched_node = searched_node->parent; |
| } |
| return nullptr; |
| } |
| |
| void UhdmAst::process_design() |
| { |
| current_node = make_ast_node(AST::AST_DESIGN); |
| visit_one_to_many({UHDM::uhdmallInterfaces, UHDM::uhdmtopPackages, UHDM::uhdmallModules, UHDM::uhdmtopModules, vpiTaskFunc}, obj_h, |
| [&](AST::AstNode *node) { |
| if (node) { |
| shared.top_nodes[node->str] = node; |
| } |
| }); |
| visit_one_to_many({vpiParameter, vpiParamAssign}, obj_h, [&](AST::AstNode *node) {}); |
| visit_one_to_many({vpiTypedef}, obj_h, [&](AST::AstNode *node) { |
| if (node) |
| move_type_to_new_typedef(current_node, node); |
| }); |
| // Add top level typedefs and params to scope |
| setup_current_scope(shared.top_nodes, current_node); |
| for (auto pair : shared.top_nodes) { |
| if (!pair.second) |
| continue; |
| if (pair.second->type == AST::AST_PACKAGE) { |
| check_memories(pair.second); |
| setup_current_scope(shared.top_nodes, pair.second); |
| simplify(pair.second, nullptr); |
| clear_current_scope(); |
| } |
| } |
| setup_current_scope(shared.top_nodes, current_node); |
| // Once we walked everything, unroll that as children of this node |
| for (auto &pair : shared.top_nodes) { |
| if (!pair.second) |
| continue; |
| if (!pair.second->get_bool_attribute(UhdmAst::partial())) { |
| if (pair.second->type == AST::AST_PACKAGE) |
| current_node->children.insert(current_node->children.begin(), pair.second); |
| else { |
| check_memories(pair.second); |
| setup_current_scope(shared.top_nodes, pair.second); |
| simplify(pair.second, nullptr); |
| clear_current_scope(); |
| current_node->children.push_back(pair.second); |
| } |
| } else { |
| log_warning("Removing unused module: %s from the design.\n", pair.second->str.c_str()); |
| // TODO: This should be properly erased from the module, but it seems that it's |
| // needed to resolve scope |
| delete pair.second; |
| pair.second = nullptr; |
| } |
| } |
| } |
| |
| void UhdmAst::simplify_parameter(AST::AstNode *parameter, AST::AstNode *module_node) |
| { |
| setup_current_scope(shared.top_nodes, shared.current_top_node); |
| visitEachDescendant(shared.current_top_node, [&](AST::AstNode *current_scope_node) { |
| if (current_scope_node->type == AST::AST_TYPEDEF || current_scope_node->type == AST::AST_PARAMETER || |
| current_scope_node->type == AST::AST_LOCALPARAM) { |
| AST_INTERNAL::current_scope[current_scope_node->str] = current_scope_node; |
| } |
| }); |
| if (module_node) { |
| visitEachDescendant(module_node, [&](AST::AstNode *current_scope_node) { |
| if (current_scope_node->type == AST::AST_TYPEDEF || current_scope_node->type == AST::AST_PARAMETER || |
| current_scope_node->type == AST::AST_LOCALPARAM) { |
| AST_INTERNAL::current_scope[current_scope_node->str] = current_scope_node; |
| } |
| }); |
| } |
| // first apply custom simplification step if needed |
| simplify(parameter, nullptr); |
| // then simplify parameter to AST_CONSTANT or AST_REALVALUE |
| while (parameter->simplify(true, false, false, 1, -1, false, false)) { |
| } |
| clear_current_scope(); |
| } |
| |
| void UhdmAst::process_module() |
| { |
| std::string type = vpi_get_str(vpiDefName, obj_h); |
| std::string name = vpi_get_str(vpiName, obj_h) ? vpi_get_str(vpiName, obj_h) : type; |
| bool is_module_instance = type != name; |
| sanitize_symbol_name(type); |
| sanitize_symbol_name(name); |
| type = strip_package_name(type); |
| name = strip_package_name(name); |
| if (!is_module_instance) { |
| if (shared.top_nodes.find(type) != shared.top_nodes.end()) { |
| current_node = shared.top_nodes[type]; |
| shared.current_top_node = current_node; |
| auto process_it = std::find_if(current_node->children.begin(), current_node->children.end(), |
| [](auto node) { return node->type == AST::AST_INITIAL || node->type == AST::AST_ALWAYS; }); |
| auto children_after_process = std::vector<AST::AstNode *>(process_it, current_node->children.end()); |
| current_node->children.erase(process_it, current_node->children.end()); |
| visit_one_to_many({vpiModule, vpiInterface, vpiParameter, vpiParamAssign, vpiPort, vpiNet, vpiArrayNet, vpiTaskFunc, vpiGenScopeArray, |
| vpiContAssign, vpiVariables}, |
| obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| add_or_replace_child(current_node, node); |
| } |
| }); |
| // Primitives will have the same names (like "and"), so we need to make sure we don't replace them |
| visit_one_to_many({vpiPrimitive}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| current_node->children.push_back(node); |
| } |
| }); |
| current_node->children.insert(current_node->children.end(), children_after_process.begin(), children_after_process.end()); |
| |
| delete_attribute(current_node, UhdmAst::partial()); |
| } else { |
| current_node = make_ast_node(AST::AST_MODULE); |
| current_node->str = type; |
| shared.top_nodes[current_node->str] = current_node; |
| shared.current_top_node = current_node; |
| current_node->attributes[UhdmAst::partial()] = AST::AstNode::mkconst_int(1, false, 1); |
| visit_one_to_many({vpiTypedef}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| move_type_to_new_typedef(current_node, node); |
| } |
| }); |
| visit_one_to_many({vpiModule, vpiParameter, vpiParamAssign, vpiNet, vpiArrayNet, vpiProcess}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| if (node->type == AST::AST_ASSIGN && node->children.size() < 2) { |
| delete node; |
| return; |
| } |
| add_or_replace_child(current_node, node); |
| } |
| }); |
| } |
| } else { |
| // Not a top module, create instance |
| current_node = make_ast_node(AST::AST_CELL); |
| std::vector<std::pair<RTLIL::IdString, RTLIL::Const>> parameters; |
| visit_one_to_many({vpiParamAssign}, obj_h, [&](AST::AstNode *node) { |
| if (node && node->type == AST::AST_PARAMETER) { |
| log_assert(!node->children.empty()); |
| if (node->children[0]->type != AST::AST_CONSTANT) { |
| if (shared.top_nodes.count(type)) { |
| simplify_parameter(node, shared.top_nodes[type]); |
| } else { |
| simplify_parameter(node, nullptr); |
| } |
| } |
| log_assert(node->children[0]->type == AST::AST_CONSTANT || node->children[0]->type == AST::AST_REALVALUE); |
| parameters.push_back(std::make_pair(node->str, node->children[0]->asParaConst())); |
| } |
| delete node; |
| }); |
| // We need to rename module to prevent name collision with the same module, but with different parameters |
| std::string module_name = !parameters.empty() ? AST::derived_module_name(type, parameters).c_str() : type; |
| auto module_node = shared.top_nodes[module_name]; |
| // true, when Surelog don't have definition of module while parsing design |
| // if so, we leaving module parameters to yosys and don't rename module |
| // as it will be done by yosys |
| bool isPrimitive = false; |
| if (!module_node) { |
| module_node = shared.top_nodes[type]; |
| if (!module_node) { |
| module_node = new AST::AstNode(AST::AST_MODULE); |
| module_node->str = type; |
| module_node->attributes[UhdmAst::partial()] = AST::AstNode::mkconst_int(2, false, 1); |
| module_node->attributes[ID::whitebox] = AST::AstNode::mkconst_int(1, false, 1); |
| } |
| isPrimitive = module_node->attributes.count(UhdmAst::partial()) && module_node->attributes[UhdmAst::partial()]->integer == 2; |
| if (!parameters.empty() && !isPrimitive) { |
| module_node = module_node->clone(); |
| module_node->str = module_name; |
| } |
| } |
| shared.top_nodes[module_node->str] = module_node; |
| visit_one_to_many({vpiParamAssign}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| if (node->children[0]->type != AST::AST_CONSTANT) { |
| if (shared.top_nodes[type]) { |
| simplify_parameter(node, module_node); |
| log_assert(node->children[0]->type == AST::AST_CONSTANT || node->children[0]->type == AST::AST_REALVALUE); |
| } |
| } |
| // if module is primitive |
| // Surelog doesn't have definition of this module, |
| // so we need to left setting of parameters to yosys |
| if (isPrimitive) { |
| node->type = AST::AST_PARASET; |
| current_node->children.push_back(node); |
| } else { |
| add_or_replace_child(module_node, node); |
| } |
| } |
| }); |
| if (module_node->attributes.count(UhdmAst::partial())) { |
| AST::AstNode *attr = module_node->attributes.at(UhdmAst::partial()); |
| if (attr->type == AST::AST_CONSTANT) |
| if (attr->integer == 1) { |
| delete_attribute(module_node, UhdmAst::partial()); |
| } |
| } |
| auto typeNode = new AST::AstNode(AST::AST_CELLTYPE); |
| typeNode->str = module_node->str; |
| current_node->children.insert(current_node->children.begin(), typeNode); |
| auto old_top = shared.current_top_node; |
| shared.current_top_node = module_node; |
| visit_one_to_many({vpiVariables, vpiNet, vpiArrayNet, vpiInterface, vpiModule, vpiPort, vpiGenScopeArray, vpiContAssign, vpiTaskFunc}, obj_h, |
| [&](AST::AstNode *node) { |
| if (node) { |
| add_or_replace_child(module_node, node); |
| } |
| }); |
| make_cell(obj_h, current_node, module_node); |
| shared.current_top_node = old_top; |
| } |
| } |
| |
| void UhdmAst::process_struct_typespec() |
| { |
| current_node = make_ast_node(AST::AST_STRUCT); |
| visit_one_to_many({vpiTypespecMember}, obj_h, [&](AST::AstNode *node) { |
| if (node->children.size() > 0 && node->children[0]->type == AST::AST_ENUM) { |
| log_assert(node->children.size() == 1); |
| log_assert(!node->children[0]->children.empty()); |
| log_assert(!node->children[0]->children[0]->children.empty()); |
| // TODO: add missing enum_type attribute |
| AST::AstNode *range = nullptr; |
| // check if single enum element is larger than 1 bit |
| if (node->children[0]->children[0]->children.size() == 2) { |
| range = node->children[0]->children[0]->children[1]->clone(); |
| } else { |
| range = make_range(0, 0); |
| } |
| delete_children(node); |
| node->children.push_back(range); |
| } |
| current_node->children.push_back(node); |
| }); |
| } |
| |
| void UhdmAst::process_union_typespec() |
| { |
| current_node = make_ast_node(AST::AST_UNION); |
| visit_one_to_many({vpiTypespecMember}, obj_h, [&](AST::AstNode *node) { |
| if (node->children.size() > 0 && node->children[0]->type == AST::AST_ENUM) { |
| log_assert(node->children.size() == 1); |
| log_assert(!node->children[0]->children.empty()); |
| log_assert(!node->children[0]->children[0]->children.empty()); |
| // TODO: add missing enum_type attribute |
| AST::AstNode *range = nullptr; |
| // check if single enum element is larger than 1 bit |
| if (node->children[0]->children[0]->children.size() == 2) { |
| range = node->children[0]->children[0]->children[1]->clone(); |
| } else { |
| range = make_range(0, 0); |
| } |
| delete_children(node); |
| node->children.push_back(range); |
| } |
| current_node->children.push_back(node); |
| }); |
| } |
| |
| void UhdmAst::process_array_typespec() |
| { |
| current_node = make_ast_node(AST::AST_WIRE); |
| std::vector<AST::AstNode *> packed_ranges; |
| std::vector<AST::AstNode *> unpacked_ranges; |
| visit_one_to_one({vpiElemTypespec}, obj_h, [&](AST::AstNode *node) { |
| if (node && node->type == AST::AST_STRUCT) { |
| auto str = current_node->str; |
| node->cloneInto(current_node); |
| current_node->str = str; |
| delete node; |
| } |
| }); |
| if (auto elemtypespec_h = vpi_handle(vpiElemTypespec, obj_h)) { |
| visit_one_to_many({vpiRange}, elemtypespec_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); }); |
| vpi_release_handle(elemtypespec_h); |
| } |
| visit_one_to_many({vpiRange}, obj_h, [&](AST::AstNode *node) { unpacked_ranges.push_back(node); }); |
| add_multirange_wire(current_node, packed_ranges, unpacked_ranges); |
| } |
| |
| void UhdmAst::process_typespec_member() |
| { |
| std::vector<AST::AstNode *> packed_ranges; |
| std::vector<AST::AstNode *> unpacked_ranges; |
| current_node = make_ast_node(AST::AST_STRUCT_ITEM); |
| current_node->str = current_node->str.substr(1); |
| vpiHandle typespec_h = vpi_handle(vpiTypespec, obj_h); |
| int typespec_type = vpi_get(vpiType, typespec_h); |
| const uhdm_handle *const handle = (const uhdm_handle *)typespec_h; |
| const UHDM::BaseClass *const object = (const UHDM::BaseClass *)handle->object; |
| switch (typespec_type) { |
| case vpiBitTypespec: |
| case vpiLogicTypespec: { |
| current_node->is_logic = true; |
| visit_one_to_many({vpiRange}, typespec_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); }); |
| shared.report.mark_handled(typespec_h); |
| break; |
| } |
| case vpiByteTypespec: { |
| current_node->is_signed = vpi_get(vpiSigned, typespec_h); |
| packed_ranges.push_back(make_range(7, 0)); |
| shared.report.mark_handled(typespec_h); |
| break; |
| } |
| case vpiShortIntTypespec: { |
| current_node->is_signed = vpi_get(vpiSigned, typespec_h); |
| packed_ranges.push_back(make_range(15, 0)); |
| shared.report.mark_handled(typespec_h); |
| break; |
| } |
| case vpiIntTypespec: |
| case vpiIntegerTypespec: { |
| current_node->is_signed = vpi_get(vpiSigned, typespec_h); |
| packed_ranges.push_back(make_range(31, 0)); |
| shared.report.mark_handled(typespec_h); |
| break; |
| } |
| case vpiTimeTypespec: |
| case vpiLongIntTypespec: { |
| current_node->is_signed = vpi_get(vpiSigned, typespec_h); |
| packed_ranges.push_back(make_range(63, 0)); |
| shared.report.mark_handled(typespec_h); |
| break; |
| } |
| case vpiStructTypespec: |
| case vpiUnionTypespec: |
| case vpiEnumTypespec: { |
| visit_one_to_one({vpiTypespec}, obj_h, [&](AST::AstNode *node) { |
| if (typespec_type == vpiStructTypespec || typespec_type == vpiUnionTypespec) { |
| auto str = current_node->str; |
| node->cloneInto(current_node); |
| current_node->str = str; |
| delete node; |
| } else if (typespec_type == vpiEnumTypespec) { |
| current_node->children.push_back(node); |
| } else { |
| delete node; |
| } |
| }); |
| break; |
| } |
| case vpiPackedArrayTypespec: |
| visit_one_to_one({vpiTypespec}, obj_h, [&](AST::AstNode *node) { |
| if (node && node->type == AST::AST_STRUCT) { |
| auto str = current_node->str; |
| if (node->attributes.count(UhdmAst::packed_ranges())) { |
| for (auto r : node->attributes[UhdmAst::packed_ranges()]->children) { |
| packed_ranges.push_back(r->clone()); |
| } |
| std::reverse(packed_ranges.begin(), packed_ranges.end()); |
| delete_attribute(node, UhdmAst::packed_ranges()); |
| } |
| if (node->attributes.count(UhdmAst::unpacked_ranges())) { |
| for (auto r : node->attributes[UhdmAst::unpacked_ranges()]->children) { |
| unpacked_ranges.push_back(r->clone()); |
| } |
| delete_attribute(node, UhdmAst::unpacked_ranges()); |
| } |
| node->cloneInto(current_node); |
| current_node->str = str; |
| current_node->children.insert(current_node->children.end(), packed_ranges.begin(), packed_ranges.end()); |
| packed_ranges.clear(); |
| delete node; |
| } else if (node) { |
| auto str = current_node->str; |
| if (node->attributes.count(UhdmAst::packed_ranges())) { |
| for (auto r : node->attributes[UhdmAst::packed_ranges()]->children) { |
| packed_ranges.push_back(r->clone()); |
| } |
| std::reverse(packed_ranges.begin(), packed_ranges.end()); |
| delete_attribute(node, UhdmAst::packed_ranges()); |
| } |
| if (node->attributes.count(UhdmAst::unpacked_ranges())) { |
| for (auto r : node->attributes[UhdmAst::unpacked_ranges()]->children) { |
| unpacked_ranges.push_back(r->clone()); |
| } |
| delete_attribute(node, UhdmAst::unpacked_ranges()); |
| } |
| node->cloneInto(current_node); |
| current_node->str = str; |
| current_node->type = AST::AST_STRUCT_ITEM; |
| delete node; |
| } |
| }); |
| break; |
| case vpiVoidTypespec: { |
| report_error("%.*s:%d: Void typespecs are currently unsupported", (int)object->VpiFile().length(), object->VpiFile().data(), |
| object->VpiLineNo()); |
| break; |
| } |
| case vpiClassTypespec: { |
| report_error("%.*s:%d: Class typespecs are unsupported", (int)object->VpiFile().length(), object->VpiFile().data(), object->VpiLineNo()); |
| break; |
| } |
| default: { |
| report_error("%.*s:%d: Encountered unhandled typespec in process_typespec_member: '%.*s' of type '%s'\n", (int)object->VpiFile().length(), |
| object->VpiFile().data(), object->VpiLineNo(), (int)object->VpiName().length(), object->VpiName().data(), |
| UHDM::VpiTypeName(typespec_h).c_str()); |
| break; |
| } |
| } |
| vpi_release_handle(typespec_h); |
| add_multirange_wire(current_node, packed_ranges, unpacked_ranges); |
| } |
| |
| static UHDM::expr *reduce_expression(const UHDM::any *expr, const UHDM::any *inst, const UHDM::any *pexpr) |
| { |
| log_assert(expr); |
| log_assert(inst); |
| log_assert(pexpr); |
| |
| bool invalidvalue = false; |
| UHDM::ExprEval eval; |
| UHDM::expr *resolved_operation = eval.reduceExpr(expr, invalidvalue, inst, pexpr); |
| log_assert(!invalidvalue); |
| return resolved_operation; |
| } |
| |
| void UhdmAst::process_enum_typespec() |
| { |
| // BaseTypespec specifies underlying type of the enum. |
| // The BaseTypespec has at most one explicit packed dimension (range). |
| // When base type is not specified in SystemVerilog code, it is assumed to be an int. |
| // Type of enum items (constants) is the same as the enum type. |
| current_node = make_ast_node(AST::AST_ENUM); |
| |
| const uhdm_handle *const handle = (const uhdm_handle *)obj_h; |
| const auto *enum_object = (const UHDM::enum_typespec *)handle->object; |
| const auto *typespec = enum_object->Base_typespec(); |
| |
| if (typespec && typespec->UhdmType() == UHDM::uhdmlogic_typespec) { |
| // If it's a logic_typespec, try to reduce expressions inside of it. |
| // The `reduceExpr` function needs the whole context of the enum typespec |
| // so it's called here instead of `process_operation` or any other more specific function. |
| |
| const UHDM::logic_typespec *logic_typespec_obj = enum_object->Base_typespec()->Cast<const UHDM::logic_typespec *>(); |
| std::vector<UHDM::range *> ranges; |
| // Check if ranges exist, as Ranges() returns a pointer to std::vector. |
| if (logic_typespec_obj->Ranges()) { |
| ranges = *(logic_typespec_obj->Ranges()); |
| } |
| for (UHDM::range *range_obj : ranges) { |
| // For each range, take both left and right and reduce them if they're of type uhdmoperation. |
| const auto *leftrange_obj = range_obj->Left_expr(); |
| const auto *rightrange_obj = range_obj->Right_expr(); |
| log_assert(leftrange_obj); |
| log_assert(rightrange_obj); |
| |
| if (leftrange_obj->UhdmType() == UHDM::uhdmoperation) { |
| // Substitute the previous leftrange with the resolved operation result. |
| range_obj->Left_expr(reduce_expression(leftrange_obj, enum_object->Instance() ? enum_object->Instance() : enum_object->VpiParent(), |
| enum_object->VpiParent())); |
| } |
| if (rightrange_obj->UhdmType() == UHDM::uhdmoperation) { |
| // Substitute the previous rightrange with the resolved operation result. |
| range_obj->Right_expr(reduce_expression(rightrange_obj, enum_object->Instance() ? enum_object->Instance() : enum_object->VpiParent(), |
| enum_object->VpiParent())); |
| } |
| } |
| } |
| |
| bool has_base_type = false; |
| visit_one_to_one({vpiBaseTypespec}, obj_h, [&](AST::AstNode *node) { |
| has_base_type = true; |
| current_node->children = std::move(node->children); |
| current_node->attributes = std::move(node->attributes); |
| current_node->is_signed = node->is_signed; |
| current_node->is_logic = node->is_logic; |
| delete node; |
| }); |
| if (!has_base_type) { |
| // Base typespec is `int` by default |
| // TODO (mglb): This is almost the same code as in `process_int_typespec()`. Put common code in dedicated function. |
| std::vector<AST::AstNode *> packed_ranges; |
| packed_ranges.push_back(make_range(31, 0)); |
| add_multirange_wire(current_node, std::move(packed_ranges), {}); |
| current_node->is_signed = true; |
| } |
| // We have to restore node's range_* properties if there's no range. |
| const auto range_left = current_node->range_left; |
| const auto range_right = current_node->range_right; |
| const auto range_valid = current_node->range_valid; |
| // Create a range from the typespec just for the purpose of copying it to consts. |
| convert_packed_unpacked_range(current_node); |
| const auto range_it = std::find_if(current_node->children.cbegin(), current_node->children.cend(), |
| [](const AST::AstNode *n) { return n->type == AST::AST_RANGE || n->type == AST::AST_MULTIRANGE; }); |
| const auto *const range = range_it != current_node->children.cend() ? *range_it : nullptr; |
| if (range) { |
| current_node->children.erase(range_it); |
| } else { |
| current_node->range_left = range_left; |
| current_node->range_right = range_right; |
| current_node->range_valid = range_valid; |
| } |
| |
| visit_one_to_one({vpiTypedefAlias}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| current_node->attributes["\\enum_base_type"] = node; |
| } |
| }); |
| visit_one_to_many({vpiEnumConst}, obj_h, [&](AST::AstNode *node) { |
| // Enum const must have the same type and ranges as the enum. |
| node->is_logic = current_node->is_logic; |
| node->is_signed = current_node->is_signed; |
| if (range) { |
| node->children.push_back(range->clone()); |
| node->range_valid = true; |
| } else { |
| node->range_left = range_left; |
| node->range_right = range_right; |
| node->range_valid = range_valid; |
| } |
| // IMPORTANT: invalidates `range_it`! |
| current_node->children.push_back(node); |
| }); |
| if (range) { |
| delete range; |
| } |
| } |
| |
| void UhdmAst::process_enum_const() |
| { |
| current_node = make_ast_node(AST::AST_ENUM_ITEM); |
| AST::AstNode *constant_node = process_value(obj_h); |
| if (constant_node) { |
| constant_node->filename = current_node->filename; |
| constant_node->location = current_node->location; |
| current_node->children.push_back(constant_node); |
| } |
| } |
| |
| void UhdmAst::process_custom_var() |
| { |
| current_node = make_ast_node(AST::AST_WIRE); |
| visit_one_to_one({vpiTypespec}, obj_h, [&](AST::AstNode *node) { |
| if (node->str.empty()) { |
| // anonymous typespec, move the children to variable |
| current_node->type = node->type; |
| copy_packed_unpacked_attribute(node, current_node); |
| current_node->children = std::move(node->children); |
| } else { |
| auto wiretype_node = new AST::AstNode(AST::AST_WIRETYPE); |
| wiretype_node->str = node->str; |
| current_node->children.push_back(wiretype_node); |
| } |
| delete node; |
| }); |
| auto type = vpi_get(vpiType, obj_h); |
| if (type == vpiEnumVar || type == vpiStructVar || type == vpiUnionVar) { |
| visit_default_expr(obj_h); |
| } |
| current_node->is_custom_type = true; |
| } |
| |
| void UhdmAst::process_int_var() |
| { |
| current_node = make_ast_node(AST::AST_WIRE); |
| auto left_const = AST::AstNode::mkconst_int(31, true); |
| auto right_const = AST::AstNode::mkconst_int(0, true); |
| auto range = new AST::AstNode(AST::AST_RANGE, left_const, right_const); |
| current_node->children.push_back(range); |
| current_node->is_signed = vpi_get(vpiSigned, obj_h); |
| visit_default_expr(obj_h); |
| } |
| |
| void UhdmAst::process_real_var() |
| { |
| auto module_node = find_ancestor({AST::AST_MODULE}); |
| auto wire_node = make_ast_node(AST::AST_WIRE); |
| auto left_const = AST::AstNode::mkconst_int(63, true); |
| auto right_const = AST::AstNode::mkconst_int(0, true); |
| auto range = new AST::AstNode(AST::AST_RANGE, left_const, right_const); |
| wire_node->children.push_back(range); |
| wire_node->is_signed = true; |
| module_node->children.push_back(wire_node); |
| current_node = make_ast_node(AST::AST_IDENTIFIER); |
| visit_default_expr(obj_h); |
| } |
| |
| void UhdmAst::process_array_var() |
| { |
| current_node = make_ast_node(AST::AST_WIRE); |
| std::vector<AST::AstNode *> packed_ranges; |
| std::vector<AST::AstNode *> unpacked_ranges; |
| visit_one_to_one({vpiTypespec}, obj_h, [&](AST::AstNode *node) { |
| if (node->str.empty()) { |
| // anonymous typespec, move the children to variable |
| current_node->type = node->type; |
| current_node->children = std::move(node->children); |
| } else { |
| auto wiretype_node = new AST::AstNode(AST::AST_WIRETYPE); |
| wiretype_node->str = node->str; |
| current_node->children.push_back(wiretype_node); |
| current_node->is_custom_type = true; |
| } |
| delete node; |
| }); |
| vpiHandle itr = vpi_iterate(vpi_get(vpiType, obj_h) == vpiArrayVar ? vpiReg : vpiElement, obj_h); |
| while (vpiHandle reg_h = vpi_scan(itr)) { |
| if (vpi_get(vpiType, reg_h) == vpiStructVar || vpi_get(vpiType, reg_h) == vpiEnumVar) { |
| visit_one_to_one({vpiTypespec}, reg_h, [&](AST::AstNode *node) { |
| if (node->str.empty()) { |
| // anonymous typespec, move the children to variable |
| current_node->type = node->type; |
| current_node->children = std::move(node->children); |
| } else { |
| auto wiretype_node = new AST::AstNode(AST::AST_WIRETYPE); |
| wiretype_node->str = node->str; |
| current_node->children.push_back(wiretype_node); |
| current_node->is_custom_type = true; |
| } |
| delete node; |
| }); |
| } else if (vpi_get(vpiType, reg_h) == vpiLogicVar) { |
| current_node->is_logic = true; |
| visit_one_to_one({vpiTypespec}, reg_h, [&](AST::AstNode *node) { |
| if (node->str.empty()) { |
| // anonymous typespec, move the children to variable |
| current_node->type = node->type; |
| current_node->children = std::move(node->children); |
| } else { |
| auto wiretype_node = new AST::AstNode(AST::AST_WIRETYPE); |
| wiretype_node->str = node->str; |
| current_node->children.push_back(wiretype_node); |
| current_node->is_custom_type = true; |
| } |
| delete node; |
| }); |
| visit_one_to_many({vpiRange}, reg_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); }); |
| } else if (vpi_get(vpiType, reg_h) == vpiIntVar) { |
| packed_ranges.push_back(make_range(31, 0)); |
| visit_default_expr(reg_h); |
| } |
| vpi_release_handle(reg_h); |
| } |
| vpi_release_handle(itr); |
| visit_one_to_many({vpiRange}, obj_h, [&](AST::AstNode *node) { unpacked_ranges.push_back(node); }); |
| add_multirange_wire(current_node, packed_ranges, unpacked_ranges); |
| visit_default_expr(obj_h); |
| } |
| |
| void UhdmAst::process_packed_array_var() |
| { |
| current_node = make_ast_node(AST::AST_WIRE); |
| std::vector<AST::AstNode *> packed_ranges; |
| std::vector<AST::AstNode *> unpacked_ranges; |
| visit_one_to_one({vpiTypespec}, obj_h, [&](AST::AstNode *node) { |
| if (node->str.empty()) { |
| // anonymous typespec, move the children to variable |
| current_node->type = node->type; |
| current_node->children = std::move(node->children); |
| } else { |
| auto wiretype_node = new AST::AstNode(AST::AST_WIRETYPE); |
| wiretype_node->str = node->str; |
| current_node->children.push_back(wiretype_node); |
| current_node->is_custom_type = true; |
| } |
| delete node; |
| }); |
| vpiHandle itr = vpi_iterate(vpi_get(vpiType, obj_h) == vpiArrayVar ? vpiReg : vpiElement, obj_h); |
| while (vpiHandle reg_h = vpi_scan(itr)) { |
| if (vpi_get(vpiType, reg_h) == vpiStructVar || vpi_get(vpiType, reg_h) == vpiEnumVar) { |
| visit_one_to_one({vpiTypespec}, reg_h, [&](AST::AstNode *node) { |
| if (node->str.empty()) { |
| // anonymous typespec, move the children to variable |
| current_node->type = node->type; |
| current_node->children = std::move(node->children); |
| } else { |
| auto wiretype_node = new AST::AstNode(AST::AST_WIRETYPE); |
| wiretype_node->str = node->str; |
| current_node->children.push_back(wiretype_node); |
| current_node->is_custom_type = true; |
| } |
| delete node; |
| }); |
| } else if (vpi_get(vpiType, reg_h) == vpiLogicVar) { |
| current_node->is_logic = true; |
| visit_one_to_one({vpiTypespec}, reg_h, [&](AST::AstNode *node) { |
| if (node->str.empty()) { |
| // anonymous typespec, move the children to variable |
| current_node->type = node->type; |
| current_node->children = std::move(node->children); |
| } else { |
| auto wiretype_node = new AST::AstNode(AST::AST_WIRETYPE); |
| wiretype_node->str = node->str; |
| current_node->children.push_back(wiretype_node); |
| current_node->is_custom_type = true; |
| } |
| delete node; |
| }); |
| visit_one_to_many({vpiRange}, reg_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); }); |
| } else if (vpi_get(vpiType, reg_h) == vpiIntVar) { |
| packed_ranges.push_back(make_range(31, 0)); |
| visit_default_expr(reg_h); |
| } |
| vpi_release_handle(reg_h); |
| } |
| vpi_release_handle(itr); |
| visit_one_to_many({vpiRange}, obj_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); }); |
| add_multirange_wire(current_node, packed_ranges, unpacked_ranges); |
| visit_default_expr(obj_h); |
| } |
| |
| void UhdmAst::process_param_assign() |
| { |
| current_node = make_ast_node(AST::AST_PARAMETER); |
| visit_one_to_one({vpiLhs}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| current_node->type = node->type; |
| current_node->str = node->str; |
| // Here we need to copy any ranges that is already present in lhs, |
| // but we want to skip actual value, as it is set in rhs |
| for (auto *c : node->children) { |
| if (c->type != AST::AST_CONSTANT) { |
| current_node->children.push_back(c->clone()); |
| } |
| } |
| delete_children(node); |
| copy_packed_unpacked_attribute(node, current_node); |
| current_node->is_custom_type = node->is_custom_type; |
| auto it = shared.param_types.find(current_node->str); |
| if (it == shared.param_types.end()) |
| shared.param_types[current_node->str] = shared.param_types[node->str]; |
| delete node; |
| } |
| }); |
| visit_one_to_one({vpiRhs}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| if (node->children.size() > 1 && (node->children[1]->type == AST::AST_PARAMETER || node->children[1]->type == AST::AST_LOCALPARAM)) { |
| node->children[1]->type = AST::AST_IDENTIFIER; |
| } |
| current_node->children.insert(current_node->children.begin(), node); |
| } |
| }); |
| } |
| |
| void UhdmAst::process_cont_assign_var_init() |
| { |
| current_node = make_ast_node(AST::AST_INITIAL); |
| auto block_node = make_ast_node(AST::AST_BLOCK); |
| auto assign_node = make_ast_node(AST::AST_ASSIGN_LE); |
| block_node->children.push_back(assign_node); |
| current_node->children.push_back(block_node); |
| |
| visit_one_to_one({vpiLhs, vpiRhs}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| if (node->type == AST::AST_WIRE || node->type == AST::AST_PARAMETER || node->type == AST::AST_LOCALPARAM) { |
| assign_node->children.push_back(new AST::AstNode(AST::AST_IDENTIFIER)); |
| assign_node->children.back()->str = node->str; |
| delete node; |
| } else { |
| assign_node->children.push_back(node); |
| } |
| } |
| }); |
| } |
| |
| void UhdmAst::process_cont_assign_net() |
| { |
| current_node = make_ast_node(AST::AST_ASSIGN); |
| |
| visit_one_to_one({vpiLhs, vpiRhs}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| if (node->type == AST::AST_WIRE || node->type == AST::AST_PARAMETER || node->type == AST::AST_LOCALPARAM) { |
| current_node->children.push_back(new AST::AstNode(AST::AST_IDENTIFIER)); |
| current_node->children.back()->str = node->str; |
| } else { |
| current_node->children.push_back(node->clone()); |
| } |
| delete node; |
| } |
| }); |
| } |
| |
| void UhdmAst::process_cont_assign() |
| { |
| auto net_decl_assign = vpi_get(vpiNetDeclAssign, obj_h); |
| vpiHandle node_lhs_h = vpi_handle(vpiLhs, obj_h); |
| auto lhs_net_type = vpi_get(vpiNetType, node_lhs_h); |
| vpi_release_handle(node_lhs_h); |
| |
| // Check if lhs is a subtype of a net |
| bool isNet; |
| if (lhs_net_type >= vpiWire && lhs_net_type <= vpiUwire) |
| isNet = true; |
| else |
| // lhs is a variable |
| isNet = false; |
| if (net_decl_assign && !isNet) |
| process_cont_assign_var_init(); |
| else |
| process_cont_assign_net(); |
| } |
| |
| void UhdmAst::process_assignment(const UHDM::BaseClass *object) |
| { |
| auto type = vpi_get(vpiBlocking, obj_h) == 1 ? AST::AST_ASSIGN_EQ : AST::AST_ASSIGN_LE; |
| bool shift_unsigned = false; |
| int op_type = vpi_get(vpiOpType, obj_h); |
| AST::AstNodeType node_type; |
| current_node = make_ast_node(type); |
| |
| visit_one_to_one({vpiLhs, vpiRhs}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| // fix node types for some assignments |
| // yosys requires that declaration of variable |
| // and assignment are separated |
| switch (node->type) { |
| case AST::AST_WIRE: |
| // wires can be declarated inside initialization block of for block |
| if (AST::AstNode *for_block = find_ancestor({AST::AST_BLOCK})) { |
| if (for_block->str.find("$fordecl_block") != std::string::npos) |
| break; |
| } |
| [[fallthrough]]; |
| case AST::AST_PARAMETER: |
| case AST::AST_LOCALPARAM: |
| node->type = AST::AST_IDENTIFIER; |
| delete_children(node); |
| delete_attribute(node, UhdmAst::packed_ranges()); |
| delete_attribute(node, UhdmAst::unpacked_ranges()); |
| break; |
| default: |
| break; |
| }; |
| current_node->children.push_back(node); |
| } |
| }); |
| if (op_type && op_type != vpiAssignmentOp) { |
| switch (op_type) { |
| case vpiSubOp: |
| node_type = AST::AST_SUB; |
| break; |
| case vpiDivOp: |
| node_type = AST::AST_DIV; |
| break; |
| case vpiModOp: |
| node_type = AST::AST_MOD; |
| break; |
| case vpiLShiftOp: |
| node_type = AST::AST_SHIFT_LEFT; |
| shift_unsigned = true; |
| break; |
| case vpiRShiftOp: |
| node_type = AST::AST_SHIFT_RIGHT; |
| shift_unsigned = true; |
| break; |
| case vpiAddOp: |
| node_type = AST::AST_ADD; |
| break; |
| case vpiMultOp: |
| node_type = AST::AST_MUL; |
| break; |
| case vpiBitAndOp: |
| node_type = AST::AST_BIT_AND; |
| break; |
| case vpiBitOrOp: |
| node_type = AST::AST_BIT_OR; |
| break; |
| case vpiBitXorOp: |
| node_type = AST::AST_BIT_XOR; |
| break; |
| case vpiArithLShiftOp: |
| node_type = AST::AST_SHIFT_SLEFT; |
| shift_unsigned = true; |
| break; |
| case vpiArithRShiftOp: |
| node_type = AST::AST_SHIFT_SRIGHT; |
| shift_unsigned = true; |
| break; |
| default: |
| delete current_node; |
| current_node = nullptr; |
| report_error("%.*s:%d: Encountered unhandled compound assignment with operation type %d\n", (int)object->VpiFile().length(), |
| object->VpiFile().data(), object->VpiLineNo(), op_type); |
| return; |
| } |
| log_assert(current_node->children.size() == 2); |
| auto child_node = new AST::AstNode(node_type, current_node->children[0]->clone(), current_node->children[1]); |
| current_node->children[1] = child_node; |
| if (shift_unsigned) { |
| log_assert(current_node->children[1]->children.size() == 2); |
| auto unsigned_node = new AST::AstNode(AST::AST_TO_UNSIGNED, current_node->children[1]->children[1]); |
| current_node->children[1]->children[1] = unsigned_node; |
| } |
| } |
| if (current_node->children.size() == 1 && current_node->children[0]->type == AST::AST_WIRE) { |
| auto top_node = find_ancestor({AST::AST_MODULE}); |
| if (!top_node) |
| return; |
| top_node->children.push_back(current_node->children[0]->clone()); |
| current_node = nullptr; |
| } |
| } |
| |
| void UhdmAst::process_packed_array_net() |
| { |
| std::vector<AST::AstNode *> packed_ranges; |
| std::vector<AST::AstNode *> unpacked_ranges; |
| current_node = make_ast_node(AST::AST_WIRE); |
| visit_one_to_many({vpiElement}, obj_h, [&](AST::AstNode *node) { |
| if (node && GetSize(node->children) == 1) |
| current_node->children.push_back(node->children[0]->clone()); |
| current_node->is_custom_type = node->is_custom_type; |
| delete node; |
| }); |
| visit_one_to_many({vpiRange}, obj_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); }); |
| add_multirange_wire(current_node, packed_ranges, unpacked_ranges); |
| } |
| |
| void UhdmAst::process_array_net(const UHDM::BaseClass *object) |
| { |
| current_node = make_ast_node(AST::AST_WIRE); |
| vpiHandle itr = vpi_iterate(vpiNet, obj_h); |
| std::vector<AST::AstNode *> packed_ranges; |
| std::vector<AST::AstNode *> unpacked_ranges; |
| while (vpiHandle net_h = vpi_scan(itr)) { |
| auto net_type = vpi_get(vpiType, net_h); |
| if (net_type == vpiLogicNet) { |
| current_node->is_logic = true; |
| current_node->is_signed = vpi_get(vpiSigned, net_h); |
| vpiHandle typespec_h = vpi_handle(vpiTypespec, net_h); |
| if (!typespec_h) { |
| typespec_h = vpi_handle(vpiTypespec, obj_h); |
| } |
| if (typespec_h) { |
| visit_one_to_many({vpiRange}, typespec_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); }); |
| vpi_release_handle(typespec_h); |
| } else { |
| visit_one_to_many({vpiRange}, net_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); }); |
| } |
| shared.report.mark_handled(net_h); |
| } else if (net_type == vpiStructNet) { |
| visit_one_to_one({vpiTypespec}, net_h, [&](AST::AstNode *node) { |
| if (node->str.empty()) { |
| // anonymous typespec, move the children to variable |
| current_node->type = node->type; |
| current_node->children = std::move(node->children); |
| } else { |
| auto wiretype_node = new AST::AstNode(AST::AST_WIRETYPE); |
| wiretype_node->str = node->str; |
| current_node->children.push_back(wiretype_node); |
| current_node->is_custom_type = true; |
| } |
| delete node; |
| }); |
| } |
| vpi_release_handle(net_h); |
| } |
| vpi_release_handle(itr); |
| visit_one_to_many({vpiRange}, obj_h, [&](AST::AstNode *node) { unpacked_ranges.push_back(node); }); |
| add_multirange_wire(current_node, packed_ranges, unpacked_ranges); |
| } |
| |
| void UhdmAst::process_package() |
| { |
| current_node = make_ast_node(AST::AST_PACKAGE); |
| shared.current_top_node = current_node; |
| visit_one_to_many({vpiTypedef}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| move_type_to_new_typedef(current_node, node); |
| } |
| }); |
| visit_one_to_many({vpiParameter, vpiParamAssign}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| node->str = strip_package_name(node->str); |
| for (auto c : node->children) { |
| c->str = strip_package_name(c->str); |
| } |
| add_or_replace_child(current_node, node); |
| } |
| }); |
| visit_one_to_many({vpiTaskFunc}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| current_node->children.push_back(node); |
| } |
| }); |
| } |
| |
| void UhdmAst::process_interface() |
| { |
| std::string type = vpi_get_str(vpiDefName, obj_h); |
| std::string name = vpi_get_str(vpiName, obj_h) ? vpi_get_str(vpiName, obj_h) : type; |
| sanitize_symbol_name(type); |
| sanitize_symbol_name(name); |
| AST::AstNode *elaboratedInterface; |
| // Check if we have encountered this object before |
| if (shared.top_nodes.find(type) != shared.top_nodes.end()) { |
| // Was created before, fill missing |
| elaboratedInterface = shared.top_nodes[type]; |
| visit_one_to_many({vpiPort, vpiVariables}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| add_or_replace_child(elaboratedInterface, node); |
| } |
| }); |
| } else { |
| // Encountered for the first time |
| elaboratedInterface = new AST::AstNode(AST::AST_INTERFACE); |
| elaboratedInterface->str = name; |
| visit_one_to_many({vpiNet, vpiPort, vpiModport}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| add_or_replace_child(elaboratedInterface, node); |
| } |
| }); |
| } |
| shared.top_nodes[elaboratedInterface->str] = elaboratedInterface; |
| if (name != type) { |
| // Not a top module, create instance |
| current_node = make_ast_node(AST::AST_CELL); |
| make_cell(obj_h, current_node, elaboratedInterface); |
| } else { |
| current_node = elaboratedInterface; |
| } |
| } |
| |
| void UhdmAst::process_modport() |
| { |
| current_node = make_ast_node(AST::AST_MODPORT); |
| visit_one_to_many({vpiIODecl}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| current_node->children.push_back(node); |
| } |
| }); |
| } |
| |
| void UhdmAst::process_io_decl() |
| { |
| current_node = nullptr; |
| std::vector<AST::AstNode *> packed_ranges; // comes before wire name |
| std::vector<AST::AstNode *> unpacked_ranges; // comes after wire name |
| visit_one_to_one({vpiExpr}, obj_h, [&](AST::AstNode *node) { current_node = node; }); |
| if (current_node == nullptr) { |
| current_node = make_ast_node(AST::AST_MODPORTMEMBER); |
| visit_one_to_many({vpiRange}, obj_h, [&](AST::AstNode *node) { unpacked_ranges.push_back(node); }); |
| } |
| std::reverse(unpacked_ranges.begin(), unpacked_ranges.end()); |
| |
| visit_one_to_one({vpiTypedef}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| if (!node->str.empty()) { |
| auto wiretype_node = new AST::AstNode(AST::AST_WIRETYPE); |
| wiretype_node->str = node->str; |
| // wiretype needs to be 1st node (if port have also another range nodes) |
| current_node->children.insert(current_node->children.begin(), wiretype_node); |
| current_node->is_custom_type = true; |
| } else { |
| // anonymous typedef, just move children |
| for (auto child : node->children) { |
| current_node->children.push_back(child->clone()); |
| } |
| if (node->attributes.count(UhdmAst::packed_ranges())) { |
| for (auto r : node->attributes[UhdmAst::packed_ranges()]->children) { |
| packed_ranges.push_back(r->clone()); |
| } |
| } |
| if (node->attributes.count(UhdmAst::unpacked_ranges())) { |
| for (auto r : node->attributes[UhdmAst::unpacked_ranges()]->children) { |
| unpacked_ranges.push_back(r->clone()); |
| } |
| } |
| current_node->is_logic = node->is_logic; |
| current_node->is_reg = node->is_reg; |
| } |
| current_node->is_signed = node->is_signed; |
| delete node; |
| } |
| }); |
| if (const int n = vpi_get(vpiDirection, obj_h)) { |
| if (n == vpiInput) { |
| current_node->is_input = true; |
| } else if (n == vpiOutput) { |
| current_node->is_output = true; |
| } else if (n == vpiInout) { |
| current_node->is_input = true; |
| current_node->is_output = true; |
| } |
| } |
| add_multirange_wire(current_node, packed_ranges, unpacked_ranges, false); |
| } |
| |
| void UhdmAst::process_always() |
| { |
| 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); |
| } else { |
| block = node; |
| } |
| current_node->children.push_back(block); |
| } else { |
| // create empty block |
| current_node->children.push_back(new AST::AstNode(AST::AST_BLOCK)); |
| } |
| }); |
| switch (vpi_get(vpiAlwaysType, obj_h)) { |
| case vpiAlwaysComb: |
| current_node->attributes[ID::always_comb] = AST::AstNode::mkconst_int(1, false); |
| break; |
| case vpiAlwaysFF: |
| current_node->attributes[ID::always_ff] = AST::AstNode::mkconst_int(1, false); |
| break; |
| case vpiAlwaysLatch: |
| current_node->attributes[ID::always_latch] = AST::AstNode::mkconst_int(1, false); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void UhdmAst::process_event_control(const UHDM::BaseClass *object) |
| { |
| current_node = make_ast_node(AST::AST_BLOCK); |
| visit_one_to_one({vpiCondition}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| auto process_node = find_ancestor({AST::AST_ALWAYS}); |
| if (!process_node) { |
| log_error("%.*s:%d: Currently supports only event control stmts inside 'always'\n", (int)object->VpiFile().length(), |
| object->VpiFile().data(), object->VpiLineNo()); |
| } |
| process_node->children.push_back(node); |
| } |
| // is added inside vpiOperation |
| }); |
| visit_one_to_one({vpiStmt}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| current_node->children.push_back(node); |
| } |
| }); |
| } |
| |
| void UhdmAst::process_initial() |
| { |
| current_node = make_ast_node(AST::AST_INITIAL); |
| 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; |
| } |
| current_node->children.push_back(node); |
| } else { |
| current_node->children.push_back(make_ast_node(AST::AST_BLOCK)); |
| } |
| }); |
| } |
| |
| void UhdmAst::process_begin(bool is_named) |
| { |
| current_node = make_ast_node(AST::AST_BLOCK); |
| if (!is_named) { |
| // for unnamed block, reset block name |
| current_node->str = ""; |
| } |
| AST::AstNode *hierarchy_node = nullptr; |
| static int unnamed_block_idx = 0; |
| visit_one_to_many({vpiVariables}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| if (!is_named) { |
| if (!hierarchy_node) { |
| // Create an implicit hierarchy scope |
| // simplify checks if sv_mode is set to true when wire is declared inside unnamed block |
| VERILOG_FRONTEND::sv_mode = true; |
| hierarchy_node = make_ast_node(AST::AST_BLOCK); |
| hierarchy_node->str = "$unnamed_block$" + std::to_string(unnamed_block_idx++); |
| } |
| hierarchy_node->children.push_back(node); |
| } else { |
| current_node->children.push_back(node); |
| } |
| } |
| }); |
| visit_one_to_many({vpiStmt}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| if ((node->type == AST::AST_ASSIGN_EQ || node->type == AST::AST_ASSIGN_LE) && node->children.size() == 1) { |
| auto func_node = find_ancestor({AST::AST_FUNCTION, AST::AST_TASK}); |
| if (!func_node) { |
| delete node; |
| return; |
| } |
| auto wire_node = new AST::AstNode(AST::AST_WIRE); |
| wire_node->type = AST::AST_WIRE; |
| wire_node->str = node->children[0]->str; |
| func_node->children.push_back(wire_node); |
| delete node; |
| } else { |
| if (hierarchy_node) |
| hierarchy_node->children.push_back(node); |
| else |
| current_node->children.push_back(node); |
| } |
| } |
| }); |
| if (hierarchy_node) |
| current_node->children.push_back(hierarchy_node); |
| } |
| |
| void UhdmAst::process_operation(const UHDM::BaseClass *object) |
| { |
| auto operation = vpi_get(vpiOpType, obj_h); |
| switch (operation) { |
| case vpiStreamRLOp: |
| process_stream_op(); |
| break; |
| case vpiEventOrOp: |
| case vpiListOp: |
| process_list_op(); |
| break; |
| case vpiCastOp: |
| process_cast_op(); |
| break; |
| case vpiInsideOp: |
| process_inside_op(); |
| break; |
| case vpiAssignmentPatternOp: |
| process_assignment_pattern_op(); |
| break; |
| case vpiWildEqOp: |
| case vpiWildNeqOp: { |
| report_error("%.*s:%d: Wildcard operators are not supported yet\n", (int)object->VpiFile().length(), object->VpiFile().data(), |
| object->VpiLineNo()); |
| break; |
| } |
| default: { |
| current_node = make_ast_node(AST::AST_NONE); |
| visit_one_to_many({vpiOperand}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| current_node->children.push_back(node); |
| } |
| }); |
| switch (operation) { |
| case vpiMinusOp: |
| current_node->type = AST::AST_NEG; |
| break; |
| case vpiPlusOp: |
| current_node->type = AST::AST_POS; |
| break; |
| case vpiPosedgeOp: |
| current_node->type = AST::AST_POSEDGE; |
| break; |
| case vpiNegedgeOp: |
| current_node->type = AST::AST_NEGEDGE; |
| break; |
| case vpiUnaryAndOp: |
| current_node->type = AST::AST_REDUCE_AND; |
| break; |
| case vpiUnaryOrOp: |
| current_node->type = AST::AST_REDUCE_OR; |
| break; |
| case vpiUnaryXorOp: |
| current_node->type = AST::AST_REDUCE_XOR; |
| break; |
| case vpiUnaryXNorOp: |
| current_node->type = AST::AST_REDUCE_XNOR; |
| break; |
| case vpiUnaryNandOp: { |
| auto not_node = new AST::AstNode(AST::AST_NONE, current_node); |
| if (current_node->children.size() == 2) { |
| current_node->type = AST::AST_BIT_AND; |
| not_node->type = AST::AST_BIT_NOT; |
| } else { |
| current_node->type = AST::AST_REDUCE_AND; |
| not_node->type = AST::AST_LOGIC_NOT; |
| } |
| current_node = not_node; |
| break; |
| } |
| case vpiUnaryNorOp: { |
| auto not_node = new AST::AstNode(AST::AST_NONE, current_node); |
| if (current_node->children.size() == 2) { |
| current_node->type = AST::AST_BIT_OR; |
| not_node->type = AST::AST_BIT_NOT; |
| } else { |
| current_node->type = AST::AST_REDUCE_OR; |
| not_node->type = AST::AST_LOGIC_NOT; |
| } |
| current_node = not_node; |
| break; |
| } |
| case vpiBitNegOp: |
| current_node->type = AST::AST_BIT_NOT; |
| break; |
| case vpiBitAndOp: |
| current_node->type = AST::AST_BIT_AND; |
| break; |
| case vpiBitOrOp: |
| current_node->type = AST::AST_BIT_OR; |
| break; |
| case vpiBitXorOp: |
| current_node->type = AST::AST_BIT_XOR; |
| break; |
| case vpiBitXnorOp: |
| current_node->type = AST::AST_BIT_XNOR; |
| break; |
| case vpiLShiftOp: { |
| current_node->type = AST::AST_SHIFT_LEFT; |
| log_assert(current_node->children.size() == 2); |
| auto unsigned_node = new AST::AstNode(AST::AST_TO_UNSIGNED, current_node->children[1]); |
| current_node->children[1] = unsigned_node; |
| break; |
| } |
| case vpiRShiftOp: { |
| current_node->type = AST::AST_SHIFT_RIGHT; |
| log_assert(current_node->children.size() == 2); |
| auto unsigned_node = new AST::AstNode(AST::AST_TO_UNSIGNED, current_node->children[1]); |
| current_node->children[1] = unsigned_node; |
| break; |
| } |
| case vpiNotOp: |
| current_node->type = AST::AST_LOGIC_NOT; |
| break; |
| case vpiLogAndOp: |
| current_node->type = AST::AST_LOGIC_AND; |
| break; |
| case vpiLogOrOp: |
| current_node->type = AST::AST_LOGIC_OR; |
| break; |
| case vpiEqOp: |
| current_node->type = AST::AST_EQ; |
| break; |
| case vpiNeqOp: |
| current_node->type = AST::AST_NE; |
| break; |
| case vpiCaseEqOp: |
| current_node->type = AST::AST_EQX; |
| break; |
| case vpiCaseNeqOp: |
| current_node->type = AST::AST_NEX; |
| break; |
| case vpiGtOp: |
| current_node->type = AST::AST_GT; |
| break; |
| case vpiGeOp: |
| current_node->type = AST::AST_GE; |
| break; |
| case vpiLtOp: |
| current_node->type = AST::AST_LT; |
| break; |
| case vpiLeOp: |
| current_node->type = AST::AST_LE; |
| break; |
| case vpiSubOp: |
| current_node->type = AST::AST_SUB; |
| if (!current_node->children.empty() && current_node->children[0]->type == AST::AST_LOCALPARAM) { |
| current_node->children[0]->type = AST::AST_IDENTIFIER; |
| } |
| break; |
| case vpiAddOp: |
| current_node->type = AST::AST_ADD; |
| break; |
| case vpiMultOp: |
| current_node->type = AST::AST_MUL; |
| break; |
| case vpiDivOp: |
| current_node->type = AST::AST_DIV; |
| break; |
| case vpiModOp: |
| current_node->type = AST::AST_MOD; |
| break; |
| case vpiArithLShiftOp: { |
| current_node->type = AST::AST_SHIFT_SLEFT; |
| log_assert(current_node->children.size() == 2); |
| auto unsigned_node = new AST::AstNode(AST::AST_TO_UNSIGNED, current_node->children[1]); |
| current_node->children[1] = unsigned_node; |
| break; |
| } |
| case vpiArithRShiftOp: { |
| current_node->type = AST::AST_SHIFT_SRIGHT; |
| log_assert(current_node->children.size() == 2); |
| auto unsigned_node = new AST::AstNode(AST::AST_TO_UNSIGNED, current_node->children[1]); |
| current_node->children[1] = unsigned_node; |
| break; |
| } |
| case vpiPowerOp: |
| current_node->type = AST::AST_POW; |
| break; |
| case vpiPostIncOp: { |
| // TODO: Make this an actual post-increment op (currently it's a pre-increment) |
| log_warning("%.*s:%d: Post-incrementation operations are handled as pre-incrementation.\n", (int)object->VpiFile().length(), |
| object->VpiFile().data(), object->VpiLineNo()); |
| [[fallthrough]]; |
| } |
| case vpiPreIncOp: { |
| current_node->type = AST::AST_ASSIGN_EQ; |
| auto id = current_node->children[0]->clone(); |
| auto add_node = new AST::AstNode(AST::AST_ADD, id, AST::AstNode::mkconst_int(1, true)); |
| add_node->filename = current_node->filename; |
| add_node->location = current_node->location; |
| current_node->children.push_back(add_node); |
| break; |
| } |
| case vpiPostDecOp: { |
| // TODO: Make this an actual post-decrement op (currently it's a pre-decrement) |
| log_warning("%.*s:%d: Post-decrementation operations are handled as pre-decrementation.\n", (int)object->VpiFile().length(), |
| object->VpiFile().data(), object->VpiLineNo()); |
| [[fallthrough]]; |
| } |
| case vpiPreDecOp: { |
| current_node->type = AST::AST_ASSIGN_EQ; |
| auto id = current_node->children[0]->clone(); |
| auto add_node = new AST::AstNode(AST::AST_SUB, id, AST::AstNode::mkconst_int(1, true)); |
| add_node->filename = current_node->filename; |
| add_node->location = current_node->location; |
| current_node->children.push_back(add_node); |
| break; |
| } |
| case vpiConditionOp: |
| current_node->type = AST::AST_TERNARY; |
| break; |
| case vpiConcatOp: { |
| current_node->type = AST::AST_CONCAT; |
| std::reverse(current_node->children.begin(), current_node->children.end()); |
| break; |
| } |
| case vpiMultiConcatOp: |
| case vpiMultiAssignmentPatternOp: |
| current_node->type = AST::AST_REPLICATE; |
| break; |
| case vpiAssignmentOp: |
| current_node->type = AST::AST_ASSIGN_EQ; |
| break; |
| case vpiStreamLROp: { |
| auto concat_node = current_node->children.back(); |
| current_node->children.pop_back(); |
| delete current_node; |
| current_node = concat_node; |
| break; |
| } |
| case vpiNullOp: { |
| delete current_node; |
| current_node = nullptr; |
| break; |
| } |
| case vpiMinTypMaxOp: { |
| // ignore min and max and set only typ |
| log_assert(current_node->children.size() == 3); |
| auto tmp = current_node->children[1]->clone(); |
| delete current_node; |
| current_node = tmp; |
| break; |
| } |
| default: { |
| delete current_node; |
| current_node = nullptr; |
| report_error("%.*s:%d: Encountered unhandled operation type %d\n", (int)object->VpiFile().length(), object->VpiFile().data(), |
| object->VpiLineNo(), operation); |
| } |
| } |
| } |
| } |
| } |
| |
| 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); |
| } |
| }); |
| if (!slice_size) { |
| slice_size = AST::AstNode::mkconst_int(1, true); |
| } |
| |
| // 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)}); |
| |
| // 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()})}); |
| |
| // 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})}); |
| |
| // 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()})); |
| |
| // 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)})); |
| |
| // 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); |
| |
| // 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); |
| |
| block_node->children.push_back(make_ast_node(AST::AST_BLOCK, {loop_node})); |
| |
| // Do not create a node |
| shared.report.mark_handled(obj_h); |
| } |
| |
| void UhdmAst::process_list_op() |
| { |
| // Add all operands as children of process node |
| if (auto parent_node = find_ancestor({AST::AST_ALWAYS, AST::AST_COND})) { |
| visit_one_to_many({vpiOperand}, obj_h, [&](AST::AstNode *node) { |
| // add directly to process/cond node |
| if (node) { |
| parent_node->children.push_back(node); |
| } |
| }); |
| } else { |
| log_error("Unhandled list op, couldn't find parent node."); |
| } |
| // Do not create a node |
| shared.report.mark_handled(obj_h); |
| } |
| |
| void UhdmAst::process_cast_op() |
| { |
| current_node = make_ast_node(AST::AST_NONE); |
| visit_one_to_many({vpiOperand}, obj_h, [&](AST::AstNode *node) { |
| node->cloneInto(current_node); |
| delete node; |
| }); |
| vpiHandle typespec_h = vpi_handle(vpiTypespec, obj_h); |
| shared.report.mark_handled(typespec_h); |
| vpi_release_handle(typespec_h); |
| } |
| |
| void UhdmAst::process_inside_op() |
| { |
| current_node = make_ast_node(AST::AST_EQ); |
| AST::AstNode *lhs = nullptr; |
| visit_one_to_many({vpiOperand}, obj_h, [&](AST::AstNode *node) { |
| if (!lhs) { |
| lhs = node; |
| } |
| if (current_node->children.size() < 2) { |
| current_node->children.push_back(node); |
| } else { |
| auto or_node = new AST::AstNode(AST::AST_LOGIC_OR); |
| or_node->filename = current_node->filename; |
| or_node->location = current_node->location; |
| auto eq_node = new AST::AstNode(AST::AST_EQ); |
| eq_node->filename = current_node->filename; |
| eq_node->location = current_node->location; |
| or_node->children.push_back(current_node); |
| or_node->children.push_back(eq_node); |
| eq_node->children.push_back(lhs->clone()); |
| eq_node->children.push_back(node); |
| current_node = or_node; |
| } |
| }); |
| } |
| |
| void UhdmAst::process_assignment_pattern_op() |
| { |
| current_node = make_ast_node(AST::AST_CONCAT); |
| if (auto param_node = find_ancestor({AST::AST_PARAMETER, AST::AST_LOCALPARAM})) { |
| std::map<size_t, AST::AstNode *> ordered_children; |
| visit_one_to_many({vpiOperand}, obj_h, [&](AST::AstNode *node) { |
| if (node->type == AST::AST_ASSIGN || node->type == AST::AST_ASSIGN_EQ || node->type == AST::AST_ASSIGN_LE) { |
| // Find at what position in the concat should we place this node |
| auto key = node->children[0]->str; |
| key = key.substr(key.find('.') + 1); |
| auto param_type = shared.param_types[param_node->str]; |
| if (!param_type) { |
| log_error("Couldn't find parameter type for node: %s\n", param_node->str.c_str()); |
| } |
| size_t pos = |
| std::find_if(param_type->children.begin(), param_type->children.end(), [key](AST::AstNode *child) { return child->str == key; }) - |
| param_type->children.begin(); |
| ordered_children.insert(std::make_pair(pos, node->children[1]->clone())); |
| delete node; |
| } else { |
| current_node->children.push_back(node); |
| } |
| }); |
| for (auto p : ordered_children) { |
| current_node->children.push_back(p.second); |
| } |
| std::reverse(current_node->children.begin(), current_node->children.end()); |
| return; |
| } |
| auto assign_node = find_ancestor({AST::AST_ASSIGN, AST::AST_ASSIGN_EQ, AST::AST_ASSIGN_LE}); |
| |
| auto proc_node = |
| find_ancestor({AST::AST_BLOCK, AST::AST_GENBLOCK, AST::AST_ALWAYS, AST::AST_INITIAL, AST::AST_MODULE, AST::AST_PACKAGE, AST::AST_CELL}); |
| if (proc_node && proc_node->type == AST::AST_CELL && shared.top_nodes.count(proc_node->children[0]->str)) { |
| proc_node = shared.top_nodes[proc_node->children[0]->str]; |
| } |
| std::vector<AST::AstNode *> assignments; |
| visit_one_to_many({vpiOperand}, obj_h, [&](AST::AstNode *node) { |
| if (node->type == AST::AST_ASSIGN || node->type == AST::AST_ASSIGN_EQ || node->type == AST::AST_ASSIGN_LE) { |
| assignments.push_back(node); |
| } else { |
| current_node->children.push_back(node); |
| } |
| }); |
| std::reverse(current_node->children.begin(), current_node->children.end()); |
| if (!assignments.empty()) { |
| if (current_node->children.empty()) { |
| delete assign_node->children[0]; |
| assign_node->children[0] = assignments[0]->children[0]; |
| current_node = assignments[0]->children[1]; |
| assignments[0]->children.clear(); |
| delete assignments[0]; |
| proc_node->children.insert(proc_node->children.end(), assignments.begin() + 1, assignments.end()); |
| } else { |
| proc_node->children.insert(proc_node->children.end(), assignments.begin(), assignments.end()); |
| } |
| } |
| } |
| |
| void UhdmAst::process_bit_select() |
| { |
| current_node = make_ast_node(AST::AST_IDENTIFIER); |
| visit_one_to_one({vpiIndex}, obj_h, [&](AST::AstNode *node) { |
| auto range_node = new AST::AstNode(AST::AST_RANGE, node); |
| range_node->filename = current_node->filename; |
| range_node->location = current_node->location; |
| current_node->children.push_back(range_node); |
| }); |
| } |
| |
| void UhdmAst::process_part_select() |
| { |
| current_node = make_ast_node(AST::AST_IDENTIFIER); |
| vpiHandle parent_h = vpi_handle(vpiParent, obj_h); |
| current_node->str = get_name(parent_h); |
| vpi_release_handle(parent_h); |
| auto range_node = new AST::AstNode(AST::AST_RANGE); |
| range_node->filename = current_node->filename; |
| range_node->location = current_node->location; |
| visit_one_to_one({vpiLeftRange, vpiRightRange}, obj_h, [&](AST::AstNode *node) { range_node->children.push_back(node); }); |
| current_node->children.push_back(range_node); |
| } |
| |
| void UhdmAst::process_indexed_part_select() |
| { |
| current_node = make_ast_node(AST::AST_IDENTIFIER); |
| vpiHandle parent_h = vpi_handle(vpiParent, obj_h); |
| current_node->str = get_name(parent_h); |
| vpi_release_handle(parent_h); |
| // TODO: check if there are other types, for now only handle 1 and 2 (+: and -:) |
| auto indexed_part_select_type = vpi_get(vpiIndexedPartSelectType, obj_h) == 1 ? AST::AST_ADD : AST::AST_SUB; |
| auto range_node = new AST::AstNode(AST::AST_RANGE); |
| range_node->filename = current_node->filename; |
| range_node->location = current_node->location; |
| visit_one_to_one({vpiBaseExpr}, obj_h, [&](AST::AstNode *node) { range_node->children.push_back(node); }); |
| visit_one_to_one({vpiWidthExpr}, obj_h, [&](AST::AstNode *node) { |
| auto right_range_node = new AST::AstNode(indexed_part_select_type); |
| right_range_node->children.push_back(range_node->children[0]->clone()); |
| right_range_node->children.push_back(node); |
| auto sub = new AST::AstNode(indexed_part_select_type == AST::AST_ADD ? AST::AST_SUB : AST::AST_ADD); |
| sub->children.push_back(right_range_node); |
| sub->children.push_back(AST::AstNode::mkconst_int(1, false, 1)); |
| range_node->children.push_back(sub); |
| // range_node->children.push_back(right_range_node); |
| }); |
| if (indexed_part_select_type == AST::AST_ADD) { |
| std::reverse(range_node->children.begin(), range_node->children.end()); |
| } |
| current_node->children.push_back(range_node); |
| } |
| |
| void UhdmAst::process_if_else() |
| { |
| current_node = make_ast_node(AST::AST_CASE); |
| visit_one_to_one({vpiCondition}, obj_h, [&](AST::AstNode *node) { |
| if (!node) { |
| log_error("Couldn't find node in if stmt. This can happend if unsupported '$value$plusargs' function is used inside if.\n"); |
| } |
| auto reduce_node = new AST::AstNode(AST::AST_REDUCE_BOOL, node); |
| current_node->children.push_back(reduce_node); |
| }); |
| // If true: |
| auto *condition = new AST::AstNode(AST::AST_COND); |
| auto *constant = AST::AstNode::mkconst_int(1, false, 1); |
| condition->children.push_back(constant); |
| visit_one_to_one({vpiStmt}, obj_h, [&](AST::AstNode *node) { |
| auto *statements = new AST::AstNode(AST::AST_BLOCK); |
| if (node) |
| statements->children.push_back(node); |
| condition->children.push_back(statements); |
| }); |
| current_node->children.push_back(condition); |
| // Else: |
| if (vpi_get(vpiType, obj_h) == vpiIfElse) { |
| auto *condition = new AST::AstNode(AST::AST_COND); |
| auto *elseBlock = new AST::AstNode(AST::AST_DEFAULT); |
| condition->children.push_back(elseBlock); |
| visit_one_to_one({vpiElseStmt}, obj_h, [&](AST::AstNode *node) { |
| auto *statements = new AST::AstNode(AST::AST_BLOCK); |
| if (node) |
| statements->children.push_back(node); |
| condition->children.push_back(statements); |
| }); |
| current_node->children.push_back(condition); |
| } |
| } |
| |
| void UhdmAst::process_for() |
| { |
| current_node = make_ast_node(AST::AST_BLOCK); |
| auto loop_id = shared.next_loop_id(); |
| current_node->str = "$fordecl_block" + std::to_string(loop_id); |
| auto loop = make_ast_node(AST::AST_FOR); |
| loop->str = "$loop" + std::to_string(loop_id); |
| visit_one_to_many({vpiForInitStmt}, obj_h, [&](AST::AstNode *node) { |
| if (node->type == AST::AST_ASSIGN_LE) |
| node->type = AST::AST_ASSIGN_EQ; |
| auto lhs = node->children[0]; |
| if (lhs->type == AST::AST_WIRE) { |
| auto *wire = lhs->clone(); |
| wire->is_logic = true; |
| current_node->children.push_back(wire); |
| lhs->type = AST::AST_IDENTIFIER; |
| lhs->is_signed = false; |
| lhs->delete_children(); |
| } |
| loop->children.push_back(node); |
| }); |
| visit_one_to_one({vpiCondition}, obj_h, [&](AST::AstNode *node) { loop->children.push_back(node); }); |
| visit_one_to_many({vpiForIncStmt}, obj_h, [&](AST::AstNode *node) { |
| if (node->type == AST::AST_ASSIGN_LE) |
| node->type = AST::AST_ASSIGN_EQ; |
| loop->children.push_back(node); |
| }); |
| visit_one_to_one({vpiStmt}, obj_h, [&](AST::AstNode *node) { |
| if (node->type != AST::AST_BLOCK) { |
| auto *statements = make_ast_node(AST::AST_BLOCK); |
| statements->str = current_node->str; // Needed in simplify step |
| statements->children.push_back(node); |
| loop->children.push_back(statements); |
| } else { |
| if (node->str == "") { |
| node->str = loop->str; |
| } |
| loop->children.push_back(node); |
| } |
| }); |
| current_node->children.push_back(loop); |
| transform_breaks_continues(loop, current_node); |
| } |
| |
| void UhdmAst::process_gen_scope() |
| { |
| current_node = make_ast_node(AST::AST_GENBLOCK); |
| visit_one_to_many({vpiTypedef}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| move_type_to_new_typedef(current_node, node); |
| } |
| }); |
| |
| visit_one_to_many( |
| {vpiParamAssign, vpiParameter, vpiNet, vpiArrayNet, vpiVariables, vpiContAssign, vpiProcess, vpiModule, vpiGenScopeArray, vpiTaskFunc}, obj_h, |
| [&](AST::AstNode *node) { |
| if (node) { |
| if ((node->type == AST::AST_PARAMETER || node->type == AST::AST_LOCALPARAM) && node->children.empty()) { |
| delete node; // skip parameters without any children |
| } else { |
| current_node->children.push_back(node); |
| } |
| } |
| }); |
| } |
| |
| void UhdmAst::process_case() |
| { |
| current_node = make_ast_node(AST::AST_CASE); |
| visit_one_to_one({vpiCondition}, obj_h, [&](AST::AstNode *node) { current_node->children.push_back(node); }); |
| visit_one_to_many({vpiCaseItem}, obj_h, [&](AST::AstNode *node) { current_node->children.push_back(node); }); |
| } |
| |
| void UhdmAst::process_case_item() |
| { |
| auto cond_type = AST::AST_COND; |
| if (vpiHandle parent_h = vpi_handle(vpiParent, obj_h)) { |
| switch (vpi_get(vpiCaseType, parent_h)) { |
| case vpiCaseExact: |
| cond_type = AST::AST_COND; |
| break; |
| case vpiCaseX: |
| cond_type = AST::AST_CONDX; |
| break; |
| case vpiCaseZ: |
| cond_type = AST::AST_CONDZ; |
| break; |
| default: { |
| const uhdm_handle *const handle = (const uhdm_handle *)obj_h; |
| const UHDM::BaseClass *const object = (const UHDM::BaseClass *)handle->object; |
| report_error("%.*s:%d: Unknown case type", (int)object->VpiFile().length(), object->VpiFile().data(), object->VpiLineNo()); |
| } |
| } |
| vpi_release_handle(parent_h); |
| } |
| current_node = make_ast_node(cond_type); |
| vpiHandle itr = vpi_iterate(vpiExpr, obj_h); |
| while (vpiHandle expr_h = vpi_scan(itr)) { |
| // case ... inside statement, the operation is stored in UHDM inside case items |
| // Retrieve just the InsideOp arguments here, we don't add any special handling |
| if (vpi_get(vpiType, expr_h) == vpiOperation && vpi_get(vpiOpType, expr_h) == vpiInsideOp) { |
| visit_one_to_many({vpiOperand}, expr_h, [&](AST::AstNode *node) { |
| // Currently we are adding nodes directly to ancestor |
| // inside process_list_op, so after this function, we have |
| // nodes already in `current_node`. |
| // We should probably refactor this to return node instead. |
| // For now, make sure this function doesn't return any nodes. |
| log_assert(node == nullptr); |
| }); |
| // vpiListOp is returned in 2 cases: |
| // a, b, c ... -> multiple vpiListOp with single item |
| // [a : b] -> single vpiListOp with 2 items |
| // single item is handled by default, |
| // here handle 2 items with custom low_high_bound attribute |
| if (current_node->children.size() == 2) { |
| auto block = make_ast_node(AST::AST_BLOCK); |
| block->children = std::move(current_node->children); |
| current_node->children.clear(); |
| current_node->children.push_back(block); |
| current_node->attributes[UhdmAst::low_high_bound()] = AST::AstNode::mkconst_int(1, false, 1); |
| } |
| } else { |
| UhdmAst uhdm_ast(this, shared, indent + " "); |
| auto *node = uhdm_ast.process_object(expr_h); |
| if (node) { |
| current_node->children.push_back(node); |
| } |
| } |
| // FIXME: If we release the handle here, visiting vpiStmt fails for some reason |
| // vpi_release_handle(expr_h); |
| } |
| vpi_release_handle(itr); |
| if (current_node->children.empty()) { |
| current_node->children.push_back(new AST::AstNode(AST::AST_DEFAULT)); |
| } |
| visit_one_to_one({vpiStmt}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| if (node->type != AST::AST_BLOCK) { |
| auto block_node = new AST::AstNode(AST::AST_BLOCK); |
| block_node->children.push_back(node); |
| node = block_node; |
| } |
| current_node->children.push_back(node); |
| } |
| }); |
| } |
| |
| void UhdmAst::process_range(const UHDM::BaseClass *object) |
| { |
| current_node = make_ast_node(AST::AST_RANGE); |
| visit_one_to_one({vpiLeftRange, vpiRightRange}, obj_h, [&](AST::AstNode *node) { current_node->children.push_back(node); }); |
| if (current_node->children.size() > 0) { |
| if (current_node->children[0]->str == "unsized") { |
| log_error("%.*s:%d: Currently not supported object of type 'unsized range'\n", (int)object->VpiFile().length(), object->VpiFile().data(), |
| object->VpiLineNo()); |
| } |
| } |
| if (current_node->children.size() > 1) { |
| if (current_node->children[1]->str == "unsized") { |
| log_error("%.*s:%d: Currently not supported object of type 'unsized range'\n", (int)object->VpiFile().length(), object->VpiFile().data(), |
| object->VpiLineNo()); |
| } |
| } |
| } |
| |
| void UhdmAst::process_return() |
| { |
| current_node = make_ast_node(AST::AST_ASSIGN_EQ); |
| auto func_node = find_ancestor({AST::AST_FUNCTION, AST::AST_TASK}); |
| if (!func_node->children.empty()) { |
| auto lhs = new AST::AstNode(AST::AST_IDENTIFIER); |
| lhs->str = func_node->children[0]->str; |
| current_node->children.push_back(lhs); |
| } |
| visit_one_to_one({vpiCondition}, obj_h, [&](AST::AstNode *node) { current_node->children.push_back(node); }); |
| } |
| |
| void UhdmAst::process_function() |
| { |
| current_node = make_ast_node(vpi_get(vpiType, obj_h) == vpiFunction ? AST::AST_FUNCTION : AST::AST_TASK); |
| visit_one_to_one({vpiReturn}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| auto net_type = vpi_get(vpiNetType, obj_h); |
| node->is_reg = net_type == vpiReg; |
| node->str = current_node->str; |
| current_node->children.push_back(node); |
| } |
| }); |
| visit_one_to_many({vpiParameter, vpiParamAssign}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| add_or_replace_child(current_node, node); |
| } |
| }); |
| visit_one_to_many({vpiIODecl}, obj_h, [&](AST::AstNode *node) { |
| node->type = AST::AST_WIRE; |
| node->port_id = shared.next_port_id(); |
| current_node->children.push_back(node); |
| }); |
| visit_one_to_many({vpiVariables}, obj_h, [&](AST::AstNode *node) { current_node->children.push_back(node); }); |
| visit_one_to_one({vpiStmt}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| current_node->children.push_back(node); |
| } |
| }); |
| } |
| |
| void UhdmAst::process_hier_path() |
| { |
| current_node = make_ast_node(AST::AST_IDENTIFIER); |
| current_node->str = "\\"; |
| AST::AstNode *top_node = nullptr; |
| visit_one_to_many({vpiActual}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| if (node->str.find('[') != std::string::npos) |
| node->str = node->str.substr(0, node->str.find('[')); |
| // for first node, just set correct string and move any children |
| if (!top_node) { |
| current_node->str += node->str.substr(1); |
| current_node->children = std::move(node->children); |
| node->children.clear(); |
| top_node = current_node; |
| delete node; |
| } else { |
| if (node->str.empty()) { |
| log_assert(!node->children.empty()); |
| top_node->children.push_back(node->children[0]); |
| node->children.erase(node->children.begin()); |
| delete node; |
| } else { |
| node->type = static_cast<AST::AstNodeType>(AST::Extended::AST_DOT); |
| top_node->children.push_back(node); |
| top_node = node; |
| } |
| } |
| } |
| }); |
| } |
| |
| void UhdmAst::process_gen_scope_array() |
| { |
| current_node = make_ast_node(AST::AST_GENBLOCK); |
| visit_one_to_many({vpiGenScope}, obj_h, [&](AST::AstNode *genscope_node) { |
| for (auto *child : genscope_node->children) { |
| if (child->type == AST::AST_PARAMETER || child->type == AST::AST_LOCALPARAM) { |
| auto param_str = child->str.substr(1); |
| auto array_str = "[" + param_str + "]"; |
| visitEachDescendant(genscope_node, [&](AST::AstNode *node) { |
| auto pos = node->str.find(array_str); |
| if (pos != std::string::npos) { |
| node->type = AST::AST_PREFIX; |
| auto *param = new AST::AstNode(AST::AST_IDENTIFIER); |
| param->str = child->str; |
| node->children.push_back(param); |
| auto bracket = node->str.rfind(']'); |
| if (bracket + 2 <= node->str.size()) { |
| auto *field = new AST::AstNode(AST::AST_IDENTIFIER); |
| field->str = "\\" + node->str.substr(bracket + 2); |
| node->children.push_back(field); |
| } |
| node->str = node->str.substr(0, node->str.find('[')); |
| } |
| }); |
| } |
| } |
| current_node->children.insert(current_node->children.end(), genscope_node->children.begin(), genscope_node->children.end()); |
| genscope_node->children.clear(); |
| delete genscope_node; |
| }); |
| } |
| |
| void UhdmAst::process_tagged_pattern() |
| { |
| auto assign_node = find_ancestor({AST::AST_ASSIGN, AST::AST_ASSIGN_EQ, AST::AST_ASSIGN_LE}); |
| auto assign_type = AST::AST_ASSIGN; |
| AST::AstNode *lhs_node = nullptr; |
| if (assign_node) { |
| assign_type = assign_node->type; |
| lhs_node = assign_node->children[0]->clone(); |
| } else { |
| lhs_node = new AST::AstNode(AST::AST_IDENTIFIER); |
| auto ancestor = find_ancestor({AST::AST_WIRE, AST::AST_MEMORY, AST::AST_PARAMETER, AST::AST_LOCALPARAM}); |
| if (!ancestor) { |
| log_error("Couldn't find ancestor for tagged pattern!\n"); |
| } |
| lhs_node->str = ancestor->str; |
| } |
| current_node = new AST::AstNode(assign_type); |
| current_node->children.push_back(lhs_node); |
| auto typespec_h = vpi_handle(vpiTypespec, obj_h); |
| if (vpi_get(vpiType, typespec_h) == vpiStringTypespec) { |
| std::string field_name = vpi_get_str(vpiName, typespec_h); |
| if (field_name != "default") { // TODO: better support of the default keyword |
| auto field = new AST::AstNode(static_cast<AST::AstNodeType>(AST::Extended::AST_DOT)); |
| field->str = field_name; |
| current_node->children[0]->children.push_back(field); |
| } |
| } else if (vpi_get(vpiType, typespec_h) == vpiIntegerTypespec) { |
| s_vpi_value val; |
| vpi_get_value(typespec_h, &val); |
| auto range = new AST::AstNode(AST::AST_RANGE); |
| auto index = AST::AstNode::mkconst_int(val.value.integer, false); |
| range->children.push_back(index); |
| current_node->children[0]->children.push_back(range); |
| } |
| vpi_release_handle(typespec_h); |
| visit_one_to_one({vpiPattern}, obj_h, [&](AST::AstNode *node) { current_node->children.push_back(node); }); |
| } |
| |
| void UhdmAst::process_logic_var() |
| { |
| current_node = make_ast_node(AST::AST_WIRE); |
| current_node->is_logic = true; |
| std::vector<AST::AstNode *> packed_ranges; // comes before wire name |
| std::vector<AST::AstNode *> unpacked_ranges; // comes after wire name |
| // TODO: add const attribute, but it seems it is little more |
| // then just setting boolean value |
| // current_node->is_const = vpi_get(vpiConstantVariable, obj_h); |
| visit_one_to_one({vpiTypespec}, obj_h, [&](AST::AstNode *node) { |
| if (node->str.empty()) { |
| // anonymous typespec, move the children to variable |
| current_node->type = node->type; |
| current_node->children = std::move(node->children); |
| } else { |
| auto wiretype_node = new AST::AstNode(AST::AST_WIRETYPE); |
| wiretype_node->str = node->str; |
| current_node->children.push_back(wiretype_node); |
| current_node->is_custom_type = true; |
| } |
| current_node->is_signed = node->is_signed; |
| delete node; |
| }); |
| // TODO: Handling below seems similar to other typespec accesses for range. Candidate for extraction to a function. |
| if (auto typespec_h = vpi_handle(vpiTypespec, obj_h)) { |
| visit_one_to_many({vpiRange}, typespec_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); }); |
| vpi_release_handle(typespec_h); |
| } else { |
| visit_one_to_many({vpiRange}, obj_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); }); |
| } |
| visit_default_expr(obj_h); |
| add_multirange_wire(current_node, packed_ranges, unpacked_ranges); |
| } |
| |
| void UhdmAst::process_sys_func_call() |
| { |
| current_node = make_ast_node(AST::AST_FCALL); |
| |
| std::string task_calls[] = {"\\$display", "\\$monitor", "\\$write", "\\$time", "\\$readmemh", "\\$readmemb", "\\$finish", "\\$stop"}; |
| |
| if (current_node->str == "\\$signed") { |
| current_node->type = AST::AST_TO_SIGNED; |
| } else if (current_node->str == "\\$unsigned") { |
| current_node->type = AST::AST_TO_UNSIGNED; |
| } else if (std::find(std::begin(task_calls), std::end(task_calls), current_node->str) != std::end(task_calls)) { |
| current_node->type = AST::AST_TCALL; |
| } |
| |
| visit_one_to_many({vpiArgument}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| current_node->children.push_back(node); |
| } |
| }); |
| |
| if (current_node->str == "\\$display" || current_node->str == "\\$write") { |
| // According to standard, %h and %x mean the same, but %h is currently unsupported by mainline yosys |
| std::string replaced_string = std::regex_replace(current_node->children[0]->str, std::regex("%[h|H]"), "%x"); |
| delete current_node->children[0]; |
| current_node->children[0] = AST::AstNode::mkconst_str(replaced_string); |
| } |
| |
| std::string remove_backslash[] = {"\\$display", "\\$strobe", "\\$write", "\\$monitor", "\\$time", "\\$finish", |
| "\\$stop", "\\$dumpfile", "\\$dumpvars", "\\$dumpon", "\\$dumpoff", "\\$dumpall"}; |
| |
| if (std::find(std::begin(remove_backslash), std::end(remove_backslash), current_node->str) != std::end(remove_backslash)) |
| current_node->str = current_node->str.substr(1); |
| } |
| |
| void UhdmAst::process_tf_call(AST::AstNodeType type) |
| { |
| current_node = make_ast_node(type); |
| visit_one_to_many({vpiArgument}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| if (node->type == AST::AST_PARAMETER || node->type == AST::AST_LOCALPARAM) { |
| node->type = AST::AST_IDENTIFIER; |
| node->children.clear(); |
| } |
| current_node->children.push_back(node); |
| } |
| }); |
| // Prefer fully qualified name of a function (prefixed with a scope). |
| // This is important when a single function which has been imported from a package |
| // calls another function that is not imported in the calling scope. |
| if (vpiHandle function_h = vpi_handle(vpiFunction, obj_h)) { |
| current_node->str = get_name(function_h, true); |
| vpi_release_handle(function_h); |
| } |
| } |
| |
| void UhdmAst::process_immediate_assert() |
| { |
| current_node = make_ast_node(AST::AST_ASSERT); |
| visit_one_to_one({vpiExpr}, obj_h, [&](AST::AstNode *n) { |
| if (n) { |
| current_node->children.push_back(n); |
| } |
| }); |
| } |
| |
| void UhdmAst::process_logic_typespec() |
| { |
| current_node = make_ast_node(AST::AST_WIRE); |
| current_node->is_logic = true; |
| std::vector<AST::AstNode *> packed_ranges; // comes before wire name |
| std::vector<AST::AstNode *> unpacked_ranges; // comes after wire name |
| if (!current_node->str.empty() && current_node->str.find("::") == std::string::npos) { |
| std::string package_name = ""; |
| if (vpiHandle instance_h = vpi_handle(vpiInstance, obj_h)) { |
| if (vpi_get(vpiType, instance_h) == vpiPackage) { |
| package_name = get_object_name(instance_h, {vpiDefName}); |
| current_node->str = package_name + "::" + current_node->str.substr(1); |
| } |
| vpi_release_handle(instance_h); |
| } |
| } |
| visit_one_to_many({vpiRange}, obj_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); }); |
| add_multirange_wire(current_node, packed_ranges, unpacked_ranges); |
| current_node->is_signed = vpi_get(vpiSigned, obj_h); |
| } |
| |
| void UhdmAst::process_int_typespec() |
| { |
| std::vector<AST::AstNode *> packed_ranges; // comes before wire name |
| std::vector<AST::AstNode *> unpacked_ranges; // comes after wire name |
| current_node = make_ast_node(AST::AST_WIRE); |
| packed_ranges.push_back(make_range(31, 0)); |
| add_multirange_wire(current_node, packed_ranges, unpacked_ranges); |
| current_node->is_signed = vpi_get(vpiSigned, obj_h); |
| } |
| |
| void UhdmAst::process_shortint_typespec() |
| { |
| std::vector<AST::AstNode *> packed_ranges; // comes before wire name |
| std::vector<AST::AstNode *> unpacked_ranges; // comes after wire name |
| current_node = make_ast_node(AST::AST_WIRE); |
| packed_ranges.push_back(make_range(15, 0)); |
| add_multirange_wire(current_node, packed_ranges, unpacked_ranges); |
| current_node->is_signed = vpi_get(vpiSigned, obj_h); |
| } |
| |
| void UhdmAst::process_longint_typespec() |
| { |
| std::vector<AST::AstNode *> packed_ranges; // comes before wire name |
| std::vector<AST::AstNode *> unpacked_ranges; // comes after wire name |
| current_node = make_ast_node(AST::AST_WIRE); |
| packed_ranges.push_back(make_range(63, 0)); |
| add_multirange_wire(current_node, packed_ranges, unpacked_ranges); |
| current_node->is_signed = vpi_get(vpiSigned, obj_h); |
| } |
| |
| void UhdmAst::process_byte_typespec() |
| { |
| std::vector<AST::AstNode *> packed_ranges; // comes before wire name |
| std::vector<AST::AstNode *> unpacked_ranges; // comes after wire name |
| current_node = make_ast_node(AST::AST_WIRE); |
| packed_ranges.push_back(make_range(7, 0)); |
| add_multirange_wire(current_node, packed_ranges, unpacked_ranges); |
| current_node->is_signed = vpi_get(vpiSigned, obj_h); |
| } |
| |
| void UhdmAst::process_time_typespec() |
| { |
| std::vector<AST::AstNode *> packed_ranges; // comes before wire name |
| std::vector<AST::AstNode *> unpacked_ranges; // comes after wire name |
| current_node = make_ast_node(AST::AST_WIRE); |
| packed_ranges.push_back(make_range(63, 0)); |
| add_multirange_wire(current_node, packed_ranges, unpacked_ranges); |
| current_node->is_signed = false; |
| } |
| |
| void UhdmAst::process_string_var() |
| { |
| current_node = make_ast_node(AST::AST_WIRE); |
| current_node->is_string = true; |
| // FIXME: |
| // this is only basic support for strings, |
| // currently yosys doesn't support dynamic resize of wire |
| // based on string size |
| // here we try to get size of string based on provided const string |
| // if it is not available, we are setting size to explicite 64 bits |
| visit_one_to_one({vpiExpr}, obj_h, [&](AST::AstNode *expr_node) { |
| if (expr_node->type == AST::AST_CONSTANT) { |
| auto left_const = AST::AstNode::mkconst_int(expr_node->range_left, true); |
| auto right_const = AST::AstNode::mkconst_int(expr_node->range_right, true); |
| auto range = make_ast_node(AST::AST_RANGE, {left_const, right_const}); |
| current_node->children.push_back(range); |
| } |
| }); |
| if (current_node->children.empty()) { |
| auto left_const = AST::AstNode::mkconst_int(64, true); |
| auto right_const = AST::AstNode::mkconst_int(0, true); |
| auto range = make_ast_node(AST::AST_RANGE, {left_const, right_const}); |
| current_node->children.push_back(range); |
| } |
| visit_default_expr(obj_h); |
| } |
| |
| void UhdmAst::process_string_typespec() |
| { |
| current_node = make_ast_node(AST::AST_WIRE); |
| current_node->is_string = true; |
| // FIXME: |
| // this is only basic support for strings, |
| // currently yosys doesn't support dynamic resize of wire |
| // based on string size |
| // here, we are setting size to explicite 64 bits |
| auto left_const = AST::AstNode::mkconst_int(64, true); |
| auto right_const = AST::AstNode::mkconst_int(0, true); |
| auto range = make_ast_node(AST::AST_RANGE, {left_const, right_const}); |
| current_node->children.push_back(range); |
| } |
| |
| void UhdmAst::process_bit_typespec() |
| { |
| current_node = make_ast_node(AST::AST_WIRE); |
| visit_range(obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| current_node->children.push_back(node); |
| } |
| }); |
| current_node->is_signed = vpi_get(vpiSigned, obj_h); |
| } |
| |
| void UhdmAst::process_repeat() |
| { |
| auto loop_id = shared.next_loop_id(); |
| current_node = make_ast_node(AST::AST_BLOCK); |
| current_node->str = "$repeatdecl_block" + std::to_string(loop_id); |
| auto *loop = make_ast_node(AST::AST_REPEAT); |
| loop->str = "$loop" + std::to_string(loop_id); |
| current_node->children.push_back(loop); |
| visit_one_to_one({vpiCondition}, obj_h, [&](AST::AstNode *node) { loop->children.push_back(node); }); |
| visit_one_to_one({vpiStmt}, obj_h, [&](AST::AstNode *node) { |
| if (node->type != AST::AST_BLOCK) { |
| node = new AST::AstNode(AST::AST_BLOCK, node); |
| } |
| if (node->str.empty()) { |
| node->str = loop->str; // Needed in simplify step |
| } |
| loop->children.push_back(node); |
| }); |
| transform_breaks_continues(loop, current_node); |
| } |
| |
| void UhdmAst::process_var_select() |
| { |
| current_node = make_ast_node(AST::AST_IDENTIFIER); |
| visit_one_to_many({vpiIndex}, obj_h, [&](AST::AstNode *node) { |
| if (node->str == current_node->str) { |
| for (auto child : node->children) { |
| current_node->children.push_back(child); |
| } |
| node->children.clear(); |
| delete node; |
| } else { |
| auto range_node = new AST::AstNode(AST::AST_RANGE); |
| range_node->filename = current_node->filename; |
| range_node->location = current_node->location; |
| range_node->children.push_back(node); |
| current_node->children.push_back(range_node); |
| } |
| }); |
| } |
| |
| void UhdmAst::process_port() |
| { |
| current_node = make_ast_node(AST::AST_WIRE); |
| current_node->port_id = shared.next_port_id(); |
| vpiHandle lowConn_h = vpi_handle(vpiLowConn, obj_h); |
| std::vector<AST::AstNode *> packed_ranges; // comes before wire name |
| std::vector<AST::AstNode *> unpacked_ranges; // comes after wire name |
| if (lowConn_h) { |
| vpiHandle actual_h = vpi_handle(vpiActual, lowConn_h); |
| auto actual_type = vpi_get(vpiType, actual_h); |
| switch (actual_type) { |
| case vpiModport: { |
| vpiHandle iface_h = vpi_handle(vpiInterface, actual_h); |
| if (iface_h) { |
| std::string cellName, ifaceName; |
| if (auto s = vpi_get_str(vpiName, actual_h)) { |
| cellName = s; |
| sanitize_symbol_name(cellName); |
| } |
| if (auto s = vpi_get_str(vpiDefName, iface_h)) { |
| ifaceName = s; |
| sanitize_symbol_name(ifaceName); |
| } |
| current_node->type = AST::AST_INTERFACEPORT; |
| auto typeNode = new AST::AstNode(AST::AST_INTERFACEPORTTYPE); |
| // Skip '\' in cellName |
| typeNode->str = ifaceName + '.' + cellName.substr(1, cellName.length()); |
| current_node->children.push_back(typeNode); |
| shared.report.mark_handled(actual_h); |
| shared.report.mark_handled(iface_h); |
| vpi_release_handle(iface_h); |
| } |
| break; |
| } |
| case vpiInterface: { |
| auto typeNode = new AST::AstNode(AST::AST_INTERFACEPORTTYPE); |
| if (auto s = vpi_get_str(vpiDefName, actual_h)) { |
| typeNode->str = s; |
| sanitize_symbol_name(typeNode->str); |
| } |
| current_node->type = AST::AST_INTERFACEPORT; |
| current_node->children.push_back(typeNode); |
| shared.report.mark_handled(actual_h); |
| break; |
| } |
| case vpiLogicVar: |
| case vpiLogicNet: { |
| current_node->is_logic = true; |
| current_node->is_signed = vpi_get(vpiSigned, actual_h); |
| visit_one_to_many({vpiRange}, actual_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); }); |
| shared.report.mark_handled(actual_h); |
| break; |
| } |
| case vpiPackedArrayVar: |
| visit_one_to_many({vpiElement}, actual_h, [&](AST::AstNode *node) { |
| if (node && GetSize(node->children) == 1) { |
| current_node->children.push_back(node->children[0]->clone()); |
| if (node->children[0]->type == AST::AST_WIRETYPE) { |
| current_node->is_custom_type = true; |
| } |
| } |
| delete node; |
| }); |
| visit_one_to_many({vpiRange}, actual_h, [&](AST::AstNode *node) { current_node->children.push_back(node); }); |
| shared.report.mark_handled(actual_h); |
| break; |
| case vpiPackedArrayNet: |
| visit_one_to_many({vpiRange}, actual_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); }); |
| shared.report.mark_handled(actual_h); |
| break; |
| case vpiArrayVar: |
| visit_one_to_many({vpiRange}, actual_h, [&](AST::AstNode *node) { current_node->children.push_back(node); }); |
| shared.report.mark_handled(actual_h); |
| break; |
| case vpiEnumNet: |
| case vpiStructNet: |
| case vpiArrayNet: |
| case vpiStructVar: |
| case vpiUnionVar: |
| case vpiEnumVar: |
| case vpiBitVar: |
| case vpiByteVar: |
| case vpiShortIntVar: |
| case vpiLongIntVar: |
| case vpiIntVar: |
| case vpiIntegerVar: |
| break; |
| default: { |
| const uhdm_handle *const handle = (const uhdm_handle *)actual_h; |
| const UHDM::BaseClass *const object = (const UHDM::BaseClass *)handle->object; |
| report_error("%.*s:%d: Encountered unhandled type in process_port: %s\n", (int)object->VpiFile().length(), object->VpiFile().data(), |
| object->VpiLineNo(), UHDM::VpiTypeName(actual_h).c_str()); |
| break; |
| } |
| } |
| shared.report.mark_handled(lowConn_h); |
| vpi_release_handle(actual_h); |
| vpi_release_handle(lowConn_h); |
| } |
| visit_one_to_one({vpiTypedef}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| if (!current_node->children.empty() && current_node->children[0]->type != AST::AST_WIRETYPE) { |
| if (!node->str.empty()) { |
| auto wiretype_node = new AST::AstNode(AST::AST_WIRETYPE); |
| wiretype_node->str = node->str; |
| // wiretype needs to be 1st node (if port have also another range nodes) |
| current_node->children.insert(current_node->children.begin(), wiretype_node); |
| current_node->is_custom_type = true; |
| } else { |
| // anonymous typedef, just move children |
| current_node->children = std::move(node->children); |
| } |
| } |
| current_node->is_signed = current_node->is_signed || node->is_signed; |
| delete node; |
| } |
| }); |
| if (const int n = vpi_get(vpiDirection, obj_h)) { |
| if (n == vpiInput) { |
| current_node->is_input = true; |
| } else if (n == vpiOutput) { |
| current_node->is_output = true; |
| } else if (n == vpiInout) { |
| current_node->is_input = true; |
| current_node->is_output = true; |
| } |
| } |
| add_multirange_wire(current_node, packed_ranges, unpacked_ranges); |
| } |
| |
| void UhdmAst::process_net() |
| { |
| current_node = make_ast_node(AST::AST_WIRE); |
| std::vector<AST::AstNode *> packed_ranges; // comes before wire name |
| std::vector<AST::AstNode *> unpacked_ranges; // comes after wire name |
| auto net_type = vpi_get(vpiNetType, obj_h); |
| current_node->is_reg = net_type == vpiReg; |
| current_node->is_output = net_type == vpiOutput; |
| current_node->is_logic = !current_node->is_reg; |
| current_node->is_signed = vpi_get(vpiSigned, obj_h); |
| visit_one_to_one({vpiTypespec}, obj_h, [&](AST::AstNode *node) { |
| if (node && !node->str.empty()) { |
| auto wiretype_node = new AST::AstNode(AST::AST_WIRETYPE); |
| wiretype_node->str = node->str; |
| // wiretype needs to be 1st node |
| current_node->children.insert(current_node->children.begin(), wiretype_node); |
| current_node->is_custom_type = true; |
| } |
| delete node; |
| }); |
| if (vpiHandle typespec_h = vpi_handle(vpiTypespec, obj_h)) { |
| visit_one_to_many({vpiRange}, typespec_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); }); |
| vpi_release_handle(typespec_h); |
| } |
| add_multirange_wire(current_node, packed_ranges, unpacked_ranges); |
| } |
| |
| void UhdmAst::process_parameter() |
| { |
| auto type = vpi_get(vpiLocalParam, obj_h) == 1 ? AST::AST_LOCALPARAM : AST::AST_PARAMETER; |
| current_node = make_ast_node(type, {}, true); |
| std::vector<AST::AstNode *> packed_ranges; // comes before wire name |
| std::vector<AST::AstNode *> unpacked_ranges; // comes after wire name |
| visit_one_to_many({vpiRange}, obj_h, [&](AST::AstNode *node) { unpacked_ranges.push_back(node); }); |
| vpiHandle typespec_h = vpi_handle(vpiTypespec, obj_h); |
| if (typespec_h) { |
| int typespec_type = vpi_get(vpiType, typespec_h); |
| switch (typespec_type) { |
| case vpiBitTypespec: |
| case vpiLogicTypespec: { |
| current_node->is_logic = true; |
| visit_one_to_many({vpiRange}, typespec_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); }); |
| shared.report.mark_handled(typespec_h); |
| break; |
| } |
| case vpiByteTypespec: { |
| packed_ranges.push_back(make_range(7, 0)); |
| shared.report.mark_handled(typespec_h); |
| break; |
| } |
| case vpiEnumTypespec: |
| case vpiRealTypespec: |
| case vpiStringTypespec: { |
| shared.report.mark_handled(typespec_h); |
| break; |
| } |
| case vpiIntTypespec: |
| case vpiIntegerTypespec: { |
| visit_one_to_many({vpiRange}, typespec_h, [&](AST::AstNode *node) { packed_ranges.push_back(node); }); |
| if (packed_ranges.empty()) { |
| packed_ranges.push_back(make_range(31, 0)); |
| } |
| shared.report.mark_handled(typespec_h); |
| break; |
| } |
| case vpiShortIntTypespec: { |
| packed_ranges.push_back(make_range(15, 0)); |
| shared.report.mark_handled(typespec_h); |
| break; |
| } |
| case vpiTimeTypespec: |
| case vpiLongIntTypespec: { |
| packed_ranges.push_back(make_range(63, 0)); |
| shared.report.mark_handled(typespec_h); |
| break; |
| } |
| case vpiStructTypespec: { |
| visit_one_to_one({vpiTypespec}, obj_h, [&](AST::AstNode *node) { |
| if (node && !node->str.empty()) { |
| auto wiretype_node = make_ast_node(AST::AST_WIRETYPE); |
| wiretype_node->str = node->str; |
| current_node->children.push_back(wiretype_node); |
| } |
| current_node->is_custom_type = true; |
| auto it = shared.param_types.find(current_node->str); |
| if (it == shared.param_types.end()) { |
| shared.param_types.insert(std::make_pair(current_node->str, node)); |
| } else { |
| delete node; |
| } |
| }); |
| break; |
| } |
| case vpiPackedArrayTypespec: |
| case vpiArrayTypespec: { |
| shared.report.mark_handled(typespec_h); |
| visit_one_to_one({vpiElemTypespec}, typespec_h, [&](AST::AstNode *node) { |
| if (!node->str.empty()) { |
| auto wiretype_node = make_ast_node(AST::AST_WIRETYPE); |
| wiretype_node->str = node->str; |
| current_node->children.push_back(wiretype_node); |
| current_node->is_custom_type = true; |
| auto it = shared.param_types.find(current_node->str); |
| if (it == shared.param_types.end()) |
| shared.param_types.insert(std::make_pair(current_node->str, node->clone())); |
| } |
| if (node && node->attributes.count(UhdmAst::packed_ranges())) { |
| for (auto r : node->attributes[UhdmAst::packed_ranges()]->children) { |
| packed_ranges.push_back(r->clone()); |
| } |
| } |
| delete node; |
| }); |
| break; |
| } |
| default: { |
| const uhdm_handle *const handle = (const uhdm_handle *)typespec_h; |
| const UHDM::BaseClass *const object = (const UHDM::BaseClass *)handle->object; |
| report_error("%.*s:%d: Encountered unhandled typespec in process_parameter: '%.*s' of type '%s'\n", (int)object->VpiFile().length(), |
| object->VpiFile().data(), object->VpiLineNo(), (int)object->VpiName().length(), object->VpiName().data(), |
| UHDM::VpiTypeName(typespec_h).c_str()); |
| break; |
| } |
| } |
| vpi_release_handle(typespec_h); |
| } |
| AST::AstNode *constant_node = process_value(obj_h); |
| if (constant_node) { |
| constant_node->filename = current_node->filename; |
| constant_node->location = current_node->location; |
| current_node->children.push_back(constant_node); |
| } |
| add_multirange_wire(current_node, packed_ranges, unpacked_ranges); |
| } |
| |
| void UhdmAst::process_byte_var() |
| { |
| current_node = make_ast_node(AST::AST_WIRE); |
| current_node->children.push_back(make_range(7, 0)); |
| current_node->is_signed = vpi_get(vpiSigned, obj_h); |
| } |
| |
| void UhdmAst::process_long_int_var() |
| { |
| current_node = make_ast_node(AST::AST_WIRE); |
| current_node->children.push_back(make_range(63, 0)); |
| current_node->is_signed = vpi_get(vpiSigned, obj_h); |
| } |
| |
| void UhdmAst::process_immediate_cover() |
| { |
| current_node = make_ast_node(AST::AST_COVER); |
| visit_one_to_one({vpiExpr}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| current_node->children.push_back(node); |
| } |
| }); |
| } |
| |
| void UhdmAst::process_immediate_assume() |
| { |
| current_node = make_ast_node(AST::AST_ASSUME); |
| visit_one_to_one({vpiExpr}, obj_h, [&](AST::AstNode *node) { |
| if (node) { |
| current_node->children.push_back(node); |
| } |
| }); |
| } |
| |
| void UhdmAst::process_while() |
| { |
| auto loop_id = shared.next_loop_id(); |
| current_node = make_ast_node(AST::AST_BLOCK); |
| current_node->str = "$whiledecl_block" + std::to_string(loop_id); |
| auto *loop = make_ast_node(AST::AST_WHILE); |
| loop->str = "$loop" + std::to_string(loop_id); |
| current_node->children.push_back(loop); |
| visit_one_to_one({vpiCondition}, obj_h, [&](AST::AstNode *node) { loop->children.push_back(node); }); |
| visit_one_to_one({vpiStmt}, obj_h, [&](AST::AstNode *node) { |
| if (node->type != AST::AST_BLOCK) { |
| node = make_ast_node(AST::AST_BLOCK, {node}); |
| } |
| if (node->str.empty()) { |
| node->str = loop->str; // Needed in simplify step |
| } |
| loop->children.push_back(node); |
| }); |
| transform_breaks_continues(loop, current_node); |
| } |
| |
| void UhdmAst::process_gate() |
| { |
| current_node = make_ast_node(AST::AST_PRIMITIVE); |
| switch (vpi_get(vpiPrimType, obj_h)) { |
| case vpiAndPrim: |
| current_node->str = "and"; |
| break; |
| case vpiNandPrim: |
| current_node->str = "nand"; |
| break; |
| case vpiNorPrim: |
| current_node->str = "nor"; |
| break; |
| case vpiOrPrim: |
| current_node->str = "or"; |
| break; |
| case vpiXorPrim: |
| current_node->str = "xor"; |
| break; |
| case vpiXnorPrim: |
| current_node->str = "xnor"; |
| break; |
| case vpiBufPrim: |
| current_node->str = "buf"; |
| break; |
| case vpiNotPrim: |
| current_node->str = "not"; |
| break; |
| default: |
| log_file_error(current_node->filename, current_node->location.first_line, "Encountered unhandled gate type: %s", current_node->str.c_str()); |
| break; |
| } |
| visit_one_to_many({vpiPrimTerm}, obj_h, [&](AST::AstNode *node) { current_node->children.push_back(node); }); |
| } |
| |
| void UhdmAst::process_primterm() |
| { |
| current_node = make_ast_node(AST::AST_ARGUMENT); |
| visit_one_to_one({vpiExpr}, obj_h, [&](AST::AstNode *node) { current_node->children.push_back(node); }); |
| } |
| |
| void UhdmAst::process_unsupported_stmt(const UHDM::BaseClass *object, bool is_error) |
| { |
| const auto log_func = is_error ? log_error : log_warning; |
| std::string prefix = object->VpiLineNo() ? (std::string(object->VpiFile()) + ":" + std::to_string(object->VpiLineNo()) + ": ") : ""; |
| log_func("%sCurrently not supported object of type '%s'\n", prefix.c_str(), UHDM::VpiTypeName(obj_h).c_str()); |
| } |
| |
| AST::AstNode *UhdmAst::process_object(vpiHandle obj_handle) |
| { |
| obj_h = obj_handle; |
| const unsigned object_type = vpi_get(vpiType, obj_h); |
| const uhdm_handle *const handle = (const uhdm_handle *)obj_h; |
| const UHDM::BaseClass *const object = (const UHDM::BaseClass *)handle->object; |
| for (auto *obj : shared.nonSynthesizableObjects) { |
| if (!object->Compare(obj)) { |
| log_warning("%.*s:%d: Skipping non-synthesizable object of type '%s'\n", (int)object->VpiFile().length(), object->VpiFile().data(), |
| object->VpiLineNo(), UHDM::VpiTypeName(obj_h).c_str()); |
| return nullptr; |
| } |
| } |
| |
| if (shared.debug_flag) { |
| std::cout << indent << "Object '" << object->VpiName() << "' of type '" << UHDM::VpiTypeName(obj_h) << '\'' << std::endl; |
| } |
| |
| switch (object_type) { |
| case vpiDesign: |
| process_design(); |
| break; |
| case vpiParameter: |
| process_parameter(); |
| break; |
| case vpiPort: |
| process_port(); |
| break; |
| case vpiModule: |
| process_module(); |
| break; |
| case vpiStructTypespec: |
| process_struct_typespec(); |
| break; |
| case vpiUnionTypespec: |
| process_union_typespec(); |
| break; |
| case vpiPackedArrayTypespec: |
| process_packed_array_typespec(); |
| break; |
| case vpiArrayTypespec: |
| process_array_typespec(); |
| break; |
| case vpiTypespecMember: |
| process_typespec_member(); |
| break; |
| case vpiEnumTypespec: |
| process_enum_typespec(); |
| break; |
| case vpiEnumConst: |
| process_enum_const(); |
| break; |
| case vpiEnumVar: |
| case vpiEnumNet: |
| case vpiStructVar: |
| case vpiStructNet: |
| case vpiUnionVar: |
| process_custom_var(); |
| break; |
| case vpiShortIntVar: |
| case vpiIntVar: |
| case vpiIntegerVar: |
| process_int_var(); |
| break; |
| case vpiShortRealVar: |
| case vpiRealVar: |
| process_real_var(); |
| break; |
| case vpiPackedArrayVar: |
| process_packed_array_var(); |
| break; |
| case vpiArrayVar: |
| process_array_var(); |
| break; |
| case vpiParamAssign: |
| process_param_assign(); |
| break; |
| case vpiContAssign: |
| process_cont_assign(); |
| break; |
| case vpiAssignStmt: |
| case vpiAssignment: |
| process_assignment(object); |
| break; |
| case vpiInterfaceTypespec: |
| case vpiRefVar: |
| case vpiRefObj: |
| current_node = make_ast_node(AST::AST_IDENTIFIER); |
| break; |
| case vpiNet: |
| process_net(); |
| break; |
| case vpiArrayNet: |
| process_array_net(object); |
| break; |
| case vpiPackedArrayNet: |
| process_packed_array_net(); |
| break; |
| case vpiPackage: |
| process_package(); |
| break; |
| case vpiInterface: |
| process_interface(); |
| break; |
| case vpiModport: |
| process_modport(); |
| break; |
| case vpiIODecl: |
| process_io_decl(); |
| break; |
| case vpiAlways: |
| process_always(); |
| break; |
| case vpiEventControl: |
| process_event_control(object); |
| break; |
| case vpiInitial: |
| process_initial(); |
| break; |
| case vpiFinal: |
| process_unsupported_stmt(object, false); |
| break; |
| case vpiNamedBegin: |
| process_begin(true); |
| break; |
| case vpiBegin: |
| process_begin(false); |
| break; |
| case vpiCondition: |
| case vpiOperation: |
| process_operation(object); |
| break; |
| case vpiTaggedPattern: |
| process_tagged_pattern(); |
| break; |
| case vpiBitSelect: |
| process_bit_select(); |
| break; |
| case vpiPartSelect: |
| process_part_select(); |
| break; |
| case vpiIndexedPartSelect: |
| process_indexed_part_select(); |
| break; |
| case vpiVarSelect: |
| process_var_select(); |
| break; |
| case vpiIf: |
| case vpiIfElse: |
| process_if_else(); |
| break; |
| case vpiFor: |
| process_for(); |
| break; |
| case vpiBreak: |
| // Will be resolved later by loop processor |
| current_node = make_ast_node(static_cast<AST::AstNodeType>(AST::Extended::AST_BREAK)); |
| break; |
| case vpiContinue: |
| // Will be resolved later by loop processor |
| current_node = make_ast_node(static_cast<AST::AstNodeType>(AST::Extended::AST_CONTINUE)); |
| break; |
| case vpiGenScopeArray: |
| process_gen_scope_array(); |
| break; |
| case vpiGenScope: |
| process_gen_scope(); |
| break; |
| case vpiCase: |
| process_case(); |
| break; |
| case vpiCaseItem: |
| process_case_item(); |
| break; |
| case vpiConstant: |
| current_node = process_value(obj_h); |
| break; |
| case vpiRange: |
| process_range(object); |
| break; |
| case vpiReturn: |
| process_return(); |
| break; |
| case vpiFunction: |
| case vpiTask: |
| process_function(); |
| break; |
| case vpiBitVar: |
| case vpiLogicVar: |
| process_logic_var(); |
| break; |
| case vpiSysFuncCall: |
| process_sys_func_call(); |
| break; |
| case vpiFuncCall: |
| process_tf_call(AST::AST_FCALL); |
| break; |
| case vpiTaskCall: |
| process_tf_call(AST::AST_TCALL); |
| break; |
| case vpiImmediateAssert: |
| if (!shared.no_assert) |
| process_immediate_assert(); |
| break; |
| case vpiAssert: |
| if (!shared.no_assert) |
| process_unsupported_stmt(object); |
| break; |
| case vpiHierPath: |
| process_hier_path(); |
| break; |
| case UHDM::uhdmimport_typespec: |
| break; |
| case vpiLogicTypespec: |
| process_logic_typespec(); |
| break; |
| case vpiIntTypespec: |
| case vpiIntegerTypespec: |
| process_int_typespec(); |
| break; |
| case vpiShortIntTypespec: |
| process_shortint_typespec(); |
| break; |
| case vpiLongIntTypespec: |
| process_longint_typespec(); |
| break; |
| case vpiTimeTypespec: |
| process_time_typespec(); |
| break; |
| case vpiBitTypespec: |
| process_bit_typespec(); |
| break; |
| case vpiByteTypespec: |
| process_byte_typespec(); |
| break; |
| case vpiStringVar: |
| process_string_var(); |
| break; |
| case vpiStringTypespec: |
| process_string_typespec(); |
| break; |
| case vpiRepeat: |
| process_repeat(); |
| break; |
| case vpiByteVar: |
| process_byte_var(); |
| break; |
| case vpiLongIntVar: |
| process_long_int_var(); |
| break; |
| case vpiImmediateCover: |
| process_immediate_cover(); |
| break; |
| case vpiImmediateAssume: |
| process_immediate_assume(); |
| break; |
| case vpiAssume: |
| process_unsupported_stmt(object); |
| break; |
| case vpiWhile: |
| process_while(); |
| break; |
| case vpiGate: |
| process_gate(); |
| break; |
| case vpiPrimTerm: |
| process_primterm(); |
| break; |
| case vpiClockingBlock: |
| process_unsupported_stmt(object); |
| break; |
| case vpiTypeParameter: |
| // Instances in an `uhdmTopModules` tree already have all parameter references |
| // substituted with the parameter type/value by Surelog, |
| // so the plugin doesn't need to process the parameter itself. |
| // Other parameter types are handled by the plugin |
| // mainly because they were implemented before Surelog did the substitution. |
| break; |
| case vpiProgram: |
| default: |
| report_error("%.*s:%d: Encountered unhandled object '%.*s' of type '%s'\n", (int)object->VpiFile().length(), object->VpiFile().data(), |
| object->VpiLineNo(), (int)object->VpiName().length(), object->VpiName().data(), UHDM::VpiTypeName(obj_h).c_str()); |
| break; |
| } |
| |
| // Check if we initialized the node in switch-case |
| if (current_node) { |
| if (current_node->type != AST::AST_NONE) { |
| shared.report.mark_handled(object); |
| return current_node; |
| } |
| } |
| return nullptr; |
| } |
| |
| AST::AstNode *UhdmAst::visit_designs(const std::vector<vpiHandle> &designs) |
| { |
| attr_id_init(); |
| |
| current_node = new AST::AstNode(AST::AST_DESIGN); |
| for (auto design : designs) { |
| UhdmAst ast(this, shared, indent); |
| auto *processed_design_node = ast.process_object(design); |
| // Flatten multiple designs into one |
| current_node->children = std::move(processed_design_node->children); |
| delete processed_design_node; |
| } |
| |
| for (auto &[name, node] : shared.param_types) { |
| delete node; |
| } |
| shared.param_types.clear(); |
| |
| // Remove all internal attributes from the AST. |
| visitEachDescendant(current_node, delete_internal_attributes); |
| |
| attr_id_cleanup(); |
| |
| return current_node; |
| } |
| |
| void UhdmAst::report_error(const char *format, ...) const |
| { |
| va_list args; |
| va_start(args, format); |
| if (shared.stop_on_error) { |
| logv_error(format, args); |
| } else { |
| logv_warning(format, args); |
| } |
| } |
| |
| } // namespace systemverilog_plugin |