systemverilog-plugin: Add simplify.cc to third party files Signed-off-by: Magdalena Andrys <mandrys@antmicro.com>
diff --git a/systemverilog-plugin/third_party/yosys/simplify.cc b/systemverilog-plugin/third_party/yosys/simplify.cc new file mode 100644 index 0000000..78e5768 --- /dev/null +++ b/systemverilog-plugin/third_party/yosys/simplify.cc
@@ -0,0 +1,5674 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2012 Claire Xenia Wolf <claire@yosyshq.com> + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * --- + * + * This is the AST frontend library. + * + * The AST frontend library is not a frontend on it's own but provides a + * generic abstract syntax tree (AST) abstraction for HDL code and can be + * used by HDL frontends. See "ast.h" for an overview of the API and the + * Verilog frontend for an usage example. + * + */ + +#include "kernel/log.h" +#include "libs/sha1/sha1.h" +#include "frontends/verilog/verilog_frontend.h" +#include "ast.h" + +#include <sstream> +#include <stdarg.h> +#include <stdlib.h> +#include <math.h> + +YOSYS_NAMESPACE_BEGIN + +using namespace AST; +using namespace AST_INTERNAL; + +// Process a format string and arguments for $display, $write, $sprintf, etc + +std::string AstNode::process_format_str(const std::string &sformat, int next_arg, int stage, int width_hint, bool sign_hint) { + // Other arguments are placeholders. Process the string as we go through it + std::string sout; + for (size_t i = 0; i < sformat.length(); i++) + { + // format specifier + if (sformat[i] == '%') + { + // If there's no next character, that's a problem + if (i+1 >= sformat.length()) + log_file_error(filename, location.first_line, "System task `%s' called with `%%' at end of string.\n", str.c_str()); + + char cformat = sformat[++i]; + + // %% is special, does not need a matching argument + if (cformat == '%') + { + sout += '%'; + continue; + } + + bool got_len = false; + bool got_zlen = false; + int len_value = 0; + + while ('0' <= cformat && cformat <= '9') + { + if (!got_len && cformat == '0') + got_zlen = true; + + got_len = true; + len_value = 10*len_value + (cformat - '0'); + + cformat = sformat[++i]; + } + + // Simplify the argument + AstNode *node_arg = nullptr; + + // Everything from here on depends on the format specifier + switch (cformat) + { + case 's': + case 'S': + case 'd': + case 'D': + if (got_len && len_value != 0) + goto unsupported_format; + YS_FALLTHROUGH + case 'x': + case 'X': + if (next_arg >= GetSize(children)) + log_file_error(filename, location.first_line, "Missing argument for %%%c format specifier in system task `%s'.\n", + cformat, str.c_str()); + + node_arg = children[next_arg++]; + while (node_arg->simplify(true, false, false, stage, width_hint, sign_hint, false)) { } + if (node_arg->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "Failed to evaluate system task `%s' with non-constant argument.\n", str.c_str()); + break; + + case 'm': + case 'M': + if (got_len) + goto unsupported_format; + break; + + case 'l': + case 'L': + if (got_len) + goto unsupported_format; + break; + + default: + unsupported_format: + log_file_error(filename, location.first_line, "System task `%s' called with invalid/unsupported format specifier.\n", str.c_str()); + break; + } + + switch (cformat) + { + case 's': + case 'S': + sout += node_arg->bitsAsConst().decode_string(); + break; + + case 'd': + case 'D': + sout += stringf("%d", node_arg->bitsAsConst().as_int()); + break; + + case 'x': + case 'X': + { + Const val = node_arg->bitsAsConst(); + + while (GetSize(val) % 4 != 0) + val.bits.push_back(State::S0); + + int len = GetSize(val) / 4; + for (int i = len; i < len_value; i++) + sout += got_zlen ? '0' : ' '; + + for (int i = len-1; i >= 0; i--) { + Const digit = val.extract(4*i, 4); + if (digit.is_fully_def()) + sout += stringf(cformat == 'x' ? "%x" : "%X", digit.as_int()); + else + sout += cformat == 'x' ? "x" : "X"; + } + } + break; + + case 'm': + case 'M': + sout += log_id(current_module->name); + break; + + case 'l': + case 'L': + sout += log_id(current_module->name); + break; + + default: + log_abort(); + } + } + + // not a format specifier + else + sout += sformat[i]; + } + return sout; +} + + +void AstNode::annotateTypedEnums(AstNode *template_node) +{ + //check if enum + if (template_node->attributes.count(ID::enum_type)) { + //get reference to enum node: + std::string enum_type = template_node->attributes[ID::enum_type]->str.c_str(); + // log("enum_type=%s (count=%lu)\n", enum_type.c_str(), current_scope.count(enum_type)); + // log("current scope:\n"); + // for (auto &it : current_scope) + // log(" %s\n", it.first.c_str()); + log_assert(current_scope.count(enum_type) == 1); + AstNode *enum_node = current_scope.at(enum_type); + log_assert(enum_node->type == AST_ENUM); + while (enum_node->simplify(true, false, false, 1, -1, false, true)) { } + //get width from 1st enum item: + log_assert(enum_node->children.size() >= 1); + AstNode *enum_item0 = enum_node->children[0]; + log_assert(enum_item0->type == AST_ENUM_ITEM); + int width; + if (!enum_item0->range_valid) + width = 1; + else if (enum_item0->range_swapped) + width = enum_item0->range_right - enum_item0->range_left + 1; + else + width = enum_item0->range_left - enum_item0->range_right + 1; + log_assert(width > 0); + //add declared enum items: + for (auto enum_item : enum_node->children){ + log_assert(enum_item->type == AST_ENUM_ITEM); + //get is_signed + bool is_signed; + if (enum_item->children.size() == 1){ + is_signed = false; + } else if (enum_item->children.size() == 2){ + log_assert(enum_item->children[1]->type == AST_RANGE); + is_signed = enum_item->children[1]->is_signed; + } else { + log_error("enum_item children size==%lu, expected 1 or 2 for %s (%s)\n", + enum_item->children.size(), + enum_item->str.c_str(), enum_node->str.c_str() + ); + } + //start building attribute string + std::string enum_item_str = "\\enum_value_"; + //get enum item value + if(enum_item->children[0]->type != AST_CONSTANT){ + log_error("expected const, got %s for %s (%s)\n", + type2str(enum_item->children[0]->type).c_str(), + enum_item->str.c_str(), enum_node->str.c_str() + ); + } + RTLIL::Const val = enum_item->children[0]->bitsAsConst(width, is_signed); + enum_item_str.append(val.as_string()); + //set attribute for available val to enum item name mappings + attributes[enum_item_str.c_str()] = mkconst_str(enum_item->str); + } + } +} + +static bool name_has_dot(const std::string &name, std::string &struct_name) +{ + // check if plausible struct member name \sss.mmm + std::string::size_type pos; + if (name.substr(0, 1) == "\\" && (pos = name.find('.', 0)) != std::string::npos) { + struct_name = name.substr(0, pos); + return true; + } + return false; +} + +static 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 AstNode(AST_RANGE); + range->range_left = left; + range->range_right = right; + range->range_valid = true; + range->children.push_back(AstNode::mkconst_int(left, true)); + range->children.push_back(AstNode::mkconst_int(right, true)); + range->is_signed = is_signed; + return range; +} + +static int range_width(AstNode *node, AstNode *rnode) +{ + log_assert(rnode->type==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; +} + +[[noreturn]] static void struct_array_packing_error(AstNode *node) +{ + log_file_error(node->filename, node->location.first_line, "Unpacked array in packed struct/union member %s\n", node->str.c_str()); +} + +static void save_struct_range_dimensions(AstNode *node, AstNode *rnode) +{ + node->multirange_dimensions.push_back(rnode->range_right); + node->multirange_dimensions.push_back(range_width(node, rnode)); + node->multirange_swapped.push_back(rnode->range_swapped); +} + +static int get_struct_range_offset(AstNode *node, int dimension) +{ + return node->multirange_dimensions[2*dimension]; +} + +static int get_struct_range_width(AstNode *node, int dimension) +{ + return node->multirange_dimensions[2*dimension + 1]; +} + +static int size_packed_struct(AstNode *snode, int base_offset) +{ + // 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_UNION); + int offset = 0; + int packed_width = -1; + // 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_STRUCT || node->type == AST_UNION) { + // embedded struct or union + width = size_packed_struct(node, base_offset + offset); + // 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_STRUCT_ITEM); + if (node->children.size() > 0 && node->children[0]->type == AST_RANGE) { + // member width e.g. bit [7:0] a + width = range_width(node, node->children[0]); + if (node->children.size() == 2) { + // Unpacked array. Note that this is a Yosys extension; only packed data types + // and integer data types are allowed in packed structs / unions in SystemVerilog. + if (node->children[1]->type == AST_RANGE) { + // Unpacked array, e.g. bit [63:0] a [0:3] + auto rnode = node->children[1]; + if (rnode->children.size() == 1) { + // C-style array size, e.g. bit [63:0] a [4] + node->multirange_dimensions.push_back(0); + node->multirange_dimensions.push_back(rnode->range_left); + node->multirange_swapped.push_back(true); + width *= rnode->range_left; + } else { + save_struct_range_dimensions(node, rnode); + width *= range_width(node, rnode); + } + save_struct_range_dimensions(node, node->children[0]); + } + else { + // The Yosys extension for unpacked arrays in packed structs / unions + // only supports memories, i.e. e.g. logic [7:0] a [256] - see above. + struct_array_packing_error(node); + } + } else { + // Vector + save_struct_range_dimensions(node, node->children[0]); + } + // range nodes are now redundant + for (AstNode *child : node->children) + delete child; + node->children.clear(); + } + else if (node->children.size() > 0 && node->children[0]->type == AST_MULTIRANGE) { + // Packed array, e.g. bit [3:0][63:0] a + if (node->children.size() != 1) { + // The Yosys extension for unpacked arrays in packed structs / unions + // only supports memories, i.e. e.g. logic [7:0] a [256] - see above. + struct_array_packing_error(node); + } + width = 1; + for (auto rnode : node->children[0]->children) { + save_struct_range_dimensions(node, rnode); + width *= range_width(node, rnode); + } + // range nodes are now redundant + for (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; + } + } + return (is_union ? packed_width : offset); +} + +[[noreturn]] static void struct_op_error(AstNode *node) +{ + log_file_error(node->filename, node->location.first_line, "Unsupported operation for struct/union member %s\n", node->str.c_str()+1); +} + +static AstNode *node_int(int ival) +{ + return AstNode::mkconst_int(ival, true); +} + +static AstNode *multiply_by_const(AstNode *expr_node, int stride) +{ + return new AstNode(AST_MUL, expr_node, node_int(stride)); +} + +static AstNode *normalize_struct_index(AstNode *expr, AstNode *member_node, int dimension) +{ + expr = expr->clone(); + + int offset = get_struct_range_offset(member_node, dimension); + if (offset) { + expr = new AstNode(AST_SUB, expr, node_int(offset)); + } + + if (member_node->multirange_swapped[dimension]) { + // The dimension has swapped range; swap index into the struct accordingly. + int msb = get_struct_range_width(member_node, dimension) - 1; + expr = new AstNode(AST_SUB, node_int(msb), expr); + } + + return expr; +} + +static AstNode *struct_index_lsb_offset(AstNode *lsb_offset, AstNode *rnode, AstNode *member_node, int dimension, int &stride) +{ + stride /= get_struct_range_width(member_node, dimension); + auto right = normalize_struct_index(rnode->children.back(), member_node, dimension); + auto offset = stride > 1 ? multiply_by_const(right, stride) : right; + return lsb_offset ? new AstNode(AST_ADD, lsb_offset, offset) : offset; +} + +static AstNode *struct_index_msb_offset(AstNode *lsb_offset, AstNode *rnode, AstNode *member_node, int dimension, int stride) +{ + log_assert(rnode->children.size() <= 2); + + // Offset to add to LSB + AstNode *offset; + if (rnode->children.size() == 1) { + // Index, e.g. s.a[i] + offset = node_int(stride - 1); + } + else { + // rnode->children.size() == 2 + // Slice, e.g. s.a[i:j] + auto left = normalize_struct_index(rnode->children[0], member_node, dimension); + auto right = normalize_struct_index(rnode->children[1], member_node, dimension); + offset = new AstNode(AST_SUB, left, right); + if (stride > 1) { + // offset = (msb - lsb + 1)*stride - 1 + auto slice_width = new AstNode(AST_ADD, offset, node_int(1)); + offset = new AstNode(AST_SUB, multiply_by_const(slice_width, stride), node_int(1)); + } + } + + return new AstNode(AST_ADD, lsb_offset, offset); +} + + +AstNode *AST::make_struct_member_range(AstNode *node, AstNode *member_node) +{ + // Work out the range in the packed array that corresponds to a struct member + // taking into account any range operations applicable to the current node + // such as array indexing or slicing + int range_left = member_node->range_left; + int range_right = member_node->range_right; + if (node->children.empty()) { + // no range operations apply, return the whole width + return make_range(range_left - range_right, 0); + } + + if (node->children.size() != 1) { + struct_op_error(node); + } + + // Range operations + auto rnode = node->children[0]; + AstNode *lsb_offset = NULL; + int stride = range_left - range_right + 1; + size_t i = 0; + + // Calculate LSB offset for the final index / slice + if (rnode->type == AST_RANGE) { + lsb_offset = struct_index_lsb_offset(lsb_offset, rnode, member_node, i, stride); + } + else if (rnode->type == AST_MULTIRANGE) { + // Add offset for each dimension + auto mrnode = rnode; + for (i = 0; i < mrnode->children.size(); i++) { + rnode = mrnode->children[i]; + lsb_offset = struct_index_lsb_offset(lsb_offset, rnode, member_node, i, stride); + } + i--; // Step back to the final index / slice + } + else { + struct_op_error(node); + } + + // Calculate MSB offset for the final index / slice + auto msb_offset = struct_index_msb_offset(lsb_offset->clone(), rnode, member_node, i, stride); + + return new AstNode(AST_RANGE, msb_offset, lsb_offset); +} + +AstNode *AST::get_struct_member(const AstNode *node) +{ + AST::AstNode *member_node; + if (node->attributes.count(ID::wiretype) && (member_node = node->attributes.at(ID::wiretype)) && + (member_node->type == AST_STRUCT_ITEM || member_node->type == AST_STRUCT || member_node->type == AST_UNION)) + { + return member_node; + } + return NULL; +} + +static void add_members_to_scope(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_STRUCT || snode->type==AST_UNION); + for (auto *node : snode->children) { + auto member_name = name + "." + node->str; + current_scope[member_name] = node; + if (node->type != AST_STRUCT_ITEM) { + // embedded struct or union + add_members_to_scope(node, name + "." + node->str); + } + } +} + +static int get_max_offset(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_STRUCT || node->type==AST_UNION); + while (node->type != AST_STRUCT_ITEM) { + node = node->children[0]; + } + return node->range_left; +} + +static AstNode *make_packed_struct(AstNode *template_node, std::string &name) +{ + // create a wire for the packed struct + auto wnode = new AstNode(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(template_node); + auto range = make_range(offset, 0); + wnode->children.push_back(range); + // make sure this node is the one in scope for this name + current_scope[name] = wnode; + // add all the struct members to scope under the wire's name + add_members_to_scope(template_node, name); + return wnode; +} + +// check if a node or its children contains an assignment to the given variable +static bool node_contains_assignment_to(const AstNode* node, const AstNode* var) +{ + if (node->type == AST_ASSIGN_EQ || node->type == AST_ASSIGN_LE) { + // current node is iteslf an assignment + log_assert(node->children.size() >= 2); + const AstNode* lhs = node->children[0]; + if (lhs->type == AST_IDENTIFIER && lhs->str == var->str) + return false; + } + for (const AstNode* child : node->children) { + // if this child shadows the given variable + if (child != var && child->str == var->str && child->type == AST_WIRE) + break; // skip the remainder of this block/scope + // depth-first short circuit + if (!node_contains_assignment_to(child, var)) + return false; + } + return true; +} + +static std::string prefix_id(const std::string &prefix, const std::string &str) +{ + log_assert(!prefix.empty() && (prefix.front() == '$' || prefix.front() == '\\')); + log_assert(!str.empty() && (str.front() == '$' || str.front() == '\\')); + log_assert(prefix.back() == '.'); + if (str.front() == '\\') + return prefix + str.substr(1); + return prefix + str; +} + +// direct access to this global should be limited to the following two functions +static const RTLIL::Design *simplify_design_context = nullptr; + +void AST::set_simplify_design_context(const RTLIL::Design *design) +{ + log_assert(!simplify_design_context || !design); + simplify_design_context = design; +} + +// lookup the module with the given name in the current design context +static const RTLIL::Module* lookup_module(const std::string &name) +{ + return simplify_design_context->module(name); +} + +const RTLIL::Module* AstNode::lookup_cell_module() +{ + log_assert(type == AST_CELL); + + auto reprocess_after = [this] (const std::string &modname) { + if (!attributes.count(ID::reprocess_after)) + attributes[ID::reprocess_after] = AstNode::mkconst_str(modname); + }; + + const AstNode *celltype = nullptr; + for (const AstNode *child : children) + if (child->type == AST_CELLTYPE) { + celltype = child; + break; + } + log_assert(celltype != nullptr); + + const RTLIL::Module *module = lookup_module(celltype->str); + if (!module) + module = lookup_module("$abstract" + celltype->str); + if (!module) { + if (celltype->str.at(0) != '$') + reprocess_after(celltype->str); + return nullptr; + } + + // build a mapping from true param name to param value + size_t para_counter = 0; + dict<RTLIL::IdString, RTLIL::Const> cell_params_map; + for (AstNode *child : children) { + if (child->type != AST_PARASET) + continue; + + if (child->str.empty() && para_counter >= module->avail_parameters.size()) + return nullptr; // let hierarchy handle this error + IdString paraname = child->str.empty() ? module->avail_parameters[para_counter++] : child->str; + + const AstNode *value = child->children[0]; + if (value->type != AST_REALVALUE && value->type != AST_CONSTANT) + return nullptr; // let genrtlil handle this error + cell_params_map[paraname] = value->asParaConst(); + } + + // put the parameters in order and generate the derived module name + std::vector<std::pair<RTLIL::IdString, RTLIL::Const>> named_parameters; + for (RTLIL::IdString param : module->avail_parameters) { + auto it = cell_params_map.find(param); + if (it != cell_params_map.end()) + named_parameters.emplace_back(it->first, it->second); + } + std::string modname = celltype->str; + if (cell_params_map.size()) // not named_parameters to cover hierarchical defparams + modname = derived_module_name(celltype->str, named_parameters); + + // try to find the resolved module + module = lookup_module(modname); + if (!module) { + reprocess_after(modname); + return nullptr; + } + return module; +} + +// returns whether an expression contains an unbased unsized literal; does not +// check the literal exists in a self-determined context +static bool contains_unbased_unsized(const AstNode *node) +{ + if (node->type == AST_CONSTANT) + return node->is_unsized; + for (const AstNode *child : node->children) + if (contains_unbased_unsized(child)) + return true; + return false; +} + +// adds a wire to the current module with the given name that matches the +// dimensions of the given wire reference +void add_wire_for_ref(const RTLIL::Wire *ref, const std::string &str) +{ + AstNode *left = AstNode::mkconst_int(ref->width - 1 + ref->start_offset, true); + AstNode *right = AstNode::mkconst_int(ref->start_offset, true); + if (ref->upto) + std::swap(left, right); + AstNode *range = new AstNode(AST_RANGE, left, right); + + AstNode *wire = new AstNode(AST_WIRE, range); + wire->is_signed = ref->is_signed; + wire->is_logic = true; + wire->str = str; + + current_ast_mod->children.push_back(wire); + current_scope[str] = wire; +} + +enum class IdentUsage { + NotReferenced, // target variable is neither read or written in the block + Assigned, // target variable is always assigned before use + SyncRequired, // target variable may be used before it has been assigned +}; + +// determines whether a local variable a block is always assigned before it is +// used, meaning the nosync attribute can automatically be added to that +// variable +static IdentUsage always_asgn_before_use(const AstNode *node, const std::string &target) +{ + // This variable has been referenced before it has necessarily been assigned + // a value in this procedure. + if (node->type == AST_IDENTIFIER && node->str == target) + return IdentUsage::SyncRequired; + + // For case statements (which are also used for if/else), we check each + // possible branch. If the variable is assigned in all branches, then it is + // assigned, and a sync isn't required. If it used before assignment in any + // branch, then a sync is required. + if (node->type == AST_CASE) { + bool all_defined = true; + bool any_used = false; + bool has_default = false; + for (const AstNode *child : node->children) { + if (child->type == AST_COND && child->children.at(0)->type == AST_DEFAULT) + has_default = true; + IdentUsage nested = always_asgn_before_use(child, target); + if (nested != IdentUsage::Assigned && child->type == AST_COND) + all_defined = false; + if (nested == IdentUsage::SyncRequired) + any_used = true; + } + if (any_used) + return IdentUsage::SyncRequired; + else if (all_defined && has_default) + return IdentUsage::Assigned; + else + return IdentUsage::NotReferenced; + } + + // Check if this is an assignment to the target variable. For simplicity, we + // don't analyze sub-ranges of the variable. + if (node->type == AST_ASSIGN_EQ) { + const AstNode *ident = node->children.at(0); + if (ident->type == AST_IDENTIFIER && ident->str == target) + return IdentUsage::Assigned; + } + + for (const AstNode *child : node->children) { + IdentUsage nested = always_asgn_before_use(child, target); + if (nested != IdentUsage::NotReferenced) + return nested; + } + return IdentUsage::NotReferenced; +} + +static const std::string auto_nosync_prefix = "\\AutoNosync"; + +// mark a local variable in an always_comb block for automatic nosync +// consideration +static void mark_auto_nosync(AstNode *block, const AstNode *wire) +{ + log_assert(block->type == AST_BLOCK); + log_assert(wire->type == AST_WIRE); + block->attributes[auto_nosync_prefix + wire->str] = AstNode::mkconst_int(1, + false); +} + +// block names can be prefixed with an explicit scope during elaboration +static bool is_autonamed_block(const std::string &str) { + size_t last_dot = str.rfind('.'); + // unprefixed names: autonamed if the first char is a dollar sign + if (last_dot == std::string::npos) + return str.at(0) == '$'; // e.g., `$fordecl_block$1` + // prefixed names: autonamed if the final chunk begins with a dollar sign + return str.rfind(".$") == last_dot; // e.g., `\foo.bar.$fordecl_block$1` +} + +// check a procedural block for auto-nosync markings, remove them, and add +// nosync to local variables as necessary +static void check_auto_nosync(AstNode *node) +{ + std::vector<RTLIL::IdString> attrs_to_drop; + for (const auto& elem : node->attributes) { + // skip attributes that don't begin with the prefix + if (elem.first.compare(0, auto_nosync_prefix.size(), + auto_nosync_prefix.c_str())) + continue; + + // delete and remove the attribute once we're done iterating + attrs_to_drop.push_back(elem.first); + + // find the wire based on the attribute + std::string wire_name = elem.first.substr(auto_nosync_prefix.size()); + auto it = current_scope.find(wire_name); + if (it == current_scope.end()) + continue; + + // analyze the usage of the local variable in this block + IdentUsage ident_usage = always_asgn_before_use(node, wire_name); + if (ident_usage != IdentUsage::Assigned) + continue; + + // mark the wire with `nosync` + AstNode *wire = it->second; + log_assert(wire->type == AST_WIRE); + wire->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + } + + // remove the attributes we've "consumed" + for (const RTLIL::IdString &str : attrs_to_drop) { + auto it = node->attributes.find(str); + delete it->second; + node->attributes.erase(it); + } + + // check local variables in any nested blocks + for (AstNode *child : node->children) + check_auto_nosync(child); +} + +// convert the AST into a simpler AST that has all parameters substituted by their +// values, unrolled for-loops, expanded generate blocks, etc. when this function +// is done with an AST it can be converted into RTLIL using genRTLIL(). +// +// this function also does all name resolving and sets the id2ast member of all +// nodes that link to a different node using names and lexical scoping. +bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, int width_hint, bool sign_hint, bool in_param) +{ + static int recursion_counter = 0; + static bool deep_recursion_warning = false; + + if (recursion_counter++ == 1000 && deep_recursion_warning) { + log_warning("Deep recursion in AST simplifier.\nDoes this design contain overly long or deeply nested expressions, or excessive recursion?\n"); + deep_recursion_warning = false; + } + + static bool unevaluated_tern_branch = false; + + AstNode *newNode = NULL; + bool did_something = false; + +#if 0 + log("-------------\n"); + log("AST simplify[%d] depth %d at %s:%d on %s %p:\n", stage, recursion_counter, filename.c_str(), location.first_line, type2str(type).c_str(), this); + log("const_fold=%d, at_zero=%d, in_lvalue=%d, stage=%d, width_hint=%d, sign_hint=%d, in_param=%d\n", + int(const_fold), int(at_zero), int(in_lvalue), int(stage), int(width_hint), int(sign_hint), int(in_param)); + // dumpAst(NULL, "> "); +#endif + + if (stage == 0) + { + log_assert(type == AST_MODULE || type == AST_INTERFACE); + + deep_recursion_warning = true; + while (simplify(const_fold, at_zero, in_lvalue, 1, width_hint, sign_hint, in_param)) { } + + if (!flag_nomem2reg && !get_bool_attribute(ID::nomem2reg)) + { + dict<AstNode*, pool<std::string>> mem2reg_places; + dict<AstNode*, uint32_t> mem2reg_candidates, dummy_proc_flags; + uint32_t flags = flag_mem2reg ? AstNode::MEM2REG_FL_ALL : 0; + mem2reg_as_needed_pass1(mem2reg_places, mem2reg_candidates, dummy_proc_flags, flags); + + pool<AstNode*> mem2reg_set; + for (auto &it : mem2reg_candidates) + { + AstNode *mem = it.first; + uint32_t memflags = it.second; + bool this_nomeminit = flag_nomeminit; + log_assert((memflags & ~0x00ffff00) == 0); + + if (mem->get_bool_attribute(ID::nomem2reg)) + continue; + + if (mem->get_bool_attribute(ID::nomeminit) || get_bool_attribute(ID::nomeminit)) + this_nomeminit = true; + + if (memflags & AstNode::MEM2REG_FL_FORCED) + goto silent_activate; + + if (memflags & AstNode::MEM2REG_FL_EQ2) + goto verbose_activate; + + if (memflags & AstNode::MEM2REG_FL_SET_ASYNC) + goto verbose_activate; + + if ((memflags & AstNode::MEM2REG_FL_SET_INIT) && (memflags & AstNode::MEM2REG_FL_SET_ELSE) && this_nomeminit) + goto verbose_activate; + + if (memflags & AstNode::MEM2REG_FL_CMPLX_LHS) + goto verbose_activate; + + if ((memflags & AstNode::MEM2REG_FL_CONST_LHS) && !(memflags & AstNode::MEM2REG_FL_VAR_LHS)) + goto verbose_activate; + + // log("Note: Not replacing memory %s with list of registers (flags=0x%08lx).\n", mem->str.c_str(), long(memflags)); + continue; + + verbose_activate: + if (mem2reg_set.count(mem) == 0) { + std::string message = stringf("Replacing memory %s with list of registers.", mem->str.c_str()); + bool first_element = true; + for (auto &place : mem2reg_places[it.first]) { + message += stringf("%s%s", first_element ? " See " : ", ", place.c_str()); + first_element = false; + } + log_warning("%s\n", message.c_str()); + } + + silent_activate: + // log("Note: Replacing memory %s with list of registers (flags=0x%08lx).\n", mem->str.c_str(), long(memflags)); + mem2reg_set.insert(mem); + } + + for (auto node : mem2reg_set) + { + int mem_width, mem_size, addr_bits; + node->meminfo(mem_width, mem_size, addr_bits); + + int data_range_left = node->children[0]->range_left; + int data_range_right = node->children[0]->range_right; + + if (node->children[0]->range_swapped) + std::swap(data_range_left, data_range_right); + + for (int i = 0; i < mem_size; i++) { + AstNode *reg = new AstNode(AST_WIRE, new AstNode(AST_RANGE, + mkconst_int(data_range_left, true), mkconst_int(data_range_right, true))); + reg->str = stringf("%s[%d]", node->str.c_str(), i); + reg->is_reg = true; + reg->is_signed = node->is_signed; + for (auto &it : node->attributes) + if (it.first != ID::mem2reg) + reg->attributes.emplace(it.first, it.second->clone()); + reg->filename = node->filename; + reg->location = node->location; + children.push_back(reg); + while (reg->simplify(true, false, false, 1, -1, false, false)) { } + } + } + + AstNode *async_block = NULL; + while (mem2reg_as_needed_pass2(mem2reg_set, this, NULL, async_block)) { } + + vector<AstNode*> delnodes; + mem2reg_remove(mem2reg_set, delnodes); + + for (auto node : delnodes) + delete node; + } + + while (simplify(const_fold, at_zero, in_lvalue, 2, width_hint, sign_hint, in_param)) { } + recursion_counter--; + return false; + } + + current_filename = filename; + + // we do not look inside a task or function + // (but as soon as a task or function is instantiated we process the generated AST as usual) + if (type == AST_FUNCTION || type == AST_TASK) { + recursion_counter--; + return false; + } + + // deactivate all calls to non-synthesis system tasks + // note that $display, $finish, and $stop are used for synthesis-time DRC so they're not in this list + if ((type == AST_FCALL || type == AST_TCALL) && (str == "$strobe" || str == "$monitor" || str == "$time" || + str == "$dumpfile" || str == "$dumpvars" || str == "$dumpon" || str == "$dumpoff" || str == "$dumpall")) { + log_file_warning(filename, location.first_line, "Ignoring call to system %s %s.\n", type == AST_FCALL ? "function" : "task", str.c_str()); + delete_children(); + str = std::string(); + } + + if ((type == AST_TCALL) && (str == "$display" || str == "$write") && (!current_always || current_always->type != AST_INITIAL)) { + log_file_warning(filename, location.first_line, "System task `%s' outside initial block is unsupported.\n", str.c_str()); + delete_children(); + str = std::string(); + } + + // print messages if this a call to $display() or $write() + // This code implements only a small subset of Verilog-2005 $display() format specifiers, + // but should be good enough for most uses + if ((type == AST_TCALL) && ((str == "$display") || (str == "$write"))) + { + int nargs = GetSize(children); + if (nargs < 1) + log_file_error(filename, location.first_line, "System task `%s' got %d arguments, expected >= 1.\n", + str.c_str(), int(children.size())); + + // First argument is the format string + AstNode *node_string = children[0]; + while (node_string->simplify(true, false, false, stage, width_hint, sign_hint, false)) { } + if (node_string->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "Failed to evaluate system task `%s' with non-constant 1st argument.\n", str.c_str()); + std::string sformat = node_string->bitsAsConst().decode_string(); + std::string sout = process_format_str(sformat, 1, stage, width_hint, sign_hint); + // Finally, print the message (only include a \n for $display, not for $write) + log("%s", sout.c_str()); + if (str == "$display") + log("\n"); + delete_children(); + str = std::string(); + } + + // activate const folding if this is anything that must be evaluated statically (ranges, parameters, attributes, etc.) + if (type == AST_WIRE || type == AST_PARAMETER || type == AST_LOCALPARAM || type == AST_ENUM_ITEM || type == AST_DEFPARAM || type == AST_PARASET || type == AST_RANGE || type == AST_PREFIX || type == AST_TYPEDEF) + const_fold = true; + if (type == AST_IDENTIFIER && current_scope.count(str) > 0 && (current_scope[str]->type == AST_PARAMETER || current_scope[str]->type == AST_LOCALPARAM || current_scope[str]->type == AST_ENUM_ITEM)) + const_fold = true; + + // in certain cases a function must be evaluated constant. this is what in_param controls. + if (type == AST_PARAMETER || type == AST_LOCALPARAM || type == AST_DEFPARAM || type == AST_PARASET || type == AST_PREFIX) + in_param = true; + + std::map<std::string, AstNode*> backup_scope; + + // create name resolution entries for all objects with names + // also merge multiple declarations for the same wire (e.g. "output foobar; reg foobar;") + if (type == AST_MODULE || type == AST_INTERFACE) { + current_scope.clear(); + std::set<std::string> existing; + int counter = 0; + label_genblks(existing, counter); + std::map<std::string, AstNode*> this_wire_scope; + for (size_t i = 0; i < children.size(); i++) { + AstNode *node = children[i]; + + if (node->type == AST_WIRE) { + if (node->children.size() == 1 && node->children[0]->type == AST_RANGE) { + for (auto c : node->children[0]->children) { + if (!c->is_simple_const_expr()) { + if (attributes.count(ID::dynports)) + delete attributes.at(ID::dynports); + attributes[ID::dynports] = AstNode::mkconst_int(1, true); + } + } + } + if (this_wire_scope.count(node->str) > 0) { + AstNode *first_node = this_wire_scope[node->str]; + if (first_node->is_input && node->is_reg) + goto wires_are_incompatible; + if (!node->is_input && !node->is_output && node->is_reg && node->children.size() == 0) + goto wires_are_compatible; + if (first_node->children.size() == 0 && node->children.size() == 1 && node->children[0]->type == AST_RANGE) { + AstNode *r = node->children[0]; + if (r->range_valid && r->range_left == 0 && r->range_right == 0) { + delete r; + node->children.pop_back(); + } + } + if (first_node->children.size() != node->children.size()) + goto wires_are_incompatible; + for (size_t j = 0; j < node->children.size(); j++) { + AstNode *n1 = first_node->children[j], *n2 = node->children[j]; + if (n1->type == AST_RANGE && n2->type == AST_RANGE && n1->range_valid && n2->range_valid) { + if (n1->range_left != n2->range_left) + goto wires_are_incompatible; + if (n1->range_right != n2->range_right) + goto wires_are_incompatible; + } else if (*n1 != *n2) + goto wires_are_incompatible; + } + if (first_node->range_left != node->range_left) + goto wires_are_incompatible; + if (first_node->range_right != node->range_right) + goto wires_are_incompatible; + if (first_node->port_id == 0 && (node->is_input || node->is_output)) + goto wires_are_incompatible; + wires_are_compatible: + if (node->is_input) + first_node->is_input = true; + if (node->is_output) + first_node->is_output = true; + if (node->is_reg) + first_node->is_reg = true; + if (node->is_logic) + first_node->is_logic = true; + if (node->is_signed) + first_node->is_signed = true; + for (auto &it : node->attributes) { + if (first_node->attributes.count(it.first) > 0) + delete first_node->attributes[it.first]; + first_node->attributes[it.first] = it.second->clone(); + } + children.erase(children.begin()+(i--)); + did_something = true; + delete node; + continue; + wires_are_incompatible: + if (stage > 1) + log_file_error(filename, location.first_line, "Incompatible re-declaration of wire %s.\n", node->str.c_str()); + continue; + } + this_wire_scope[node->str] = node; + } + // these nodes appear at the top level in a module and can define names + if (node->type == AST_PARAMETER || node->type == AST_LOCALPARAM || node->type == AST_WIRE || node->type == AST_AUTOWIRE || node->type == AST_GENVAR || + node->type == AST_MEMORY || node->type == AST_FUNCTION || node->type == AST_TASK || node->type == AST_DPI_FUNCTION || node->type == AST_CELL || + node->type == AST_TYPEDEF) { + backup_scope[node->str] = current_scope[node->str]; + current_scope[node->str] = node; + } + if (node->type == AST_ENUM) { + current_scope[node->str] = node; + for (auto enode : node->children) { + log_assert(enode->type==AST_ENUM_ITEM); + if (current_scope.count(enode->str) == 0) + current_scope[enode->str] = enode; + else + log_file_error(filename, location.first_line, "enum item %s already exists\n", enode->str.c_str()); + } + } + } + for (size_t i = 0; i < children.size(); i++) { + AstNode *node = children[i]; + if (node->type == AST_PARAMETER || node->type == AST_LOCALPARAM || node->type == AST_WIRE || node->type == AST_AUTOWIRE || node->type == AST_MEMORY || node->type == AST_TYPEDEF) + while (node->simplify(true, false, false, 1, -1, false, node->type == AST_PARAMETER || node->type == AST_LOCALPARAM)) + did_something = true; + if (node->type == AST_ENUM) { + for (auto enode : node->children){ + log_assert(enode->type==AST_ENUM_ITEM); + while (node->simplify(true, false, false, 1, -1, false, in_param)) + did_something = true; + } + } + } + + for (AstNode *child : children) + if (child->type == AST_ALWAYS && + child->attributes.count(ID::always_comb)) + check_auto_nosync(child); + } + + // create name resolution entries for all objects with names + if (type == AST_PACKAGE) { + //add names to package scope + for (size_t i = 0; i < children.size(); i++) { + AstNode *node = children[i]; + // these nodes appear at the top level in a package and can define names + if (node->type == AST_PARAMETER || node->type == AST_LOCALPARAM || node->type == AST_TYPEDEF || node->type == AST_FUNCTION || node->type == AST_TASK) { + current_scope[node->str] = node; + } + if (node->type == AST_ENUM) { + current_scope[node->str] = node; + for (auto enode : node->children) { + log_assert(enode->type==AST_ENUM_ITEM); + if (current_scope.count(enode->str) == 0) + current_scope[enode->str] = enode; + else + log_file_error(filename, location.first_line, "enum item %s already exists in package\n", enode->str.c_str()); + } + } + } + } + + + auto backup_current_block = current_block; + auto backup_current_block_child = current_block_child; + auto backup_current_top_block = current_top_block; + auto backup_current_always = current_always; + auto backup_current_always_clocked = current_always_clocked; + + if (type == AST_ALWAYS || type == AST_INITIAL) + { + if (current_always != nullptr) + log_file_error(filename, location.first_line, "Invalid nesting of always blocks and/or initializations.\n"); + + current_always = this; + current_always_clocked = false; + + if (type == AST_ALWAYS) + for (auto child : children) { + if (child->type == AST_POSEDGE || child->type == AST_NEGEDGE) + current_always_clocked = true; + if (child->type == AST_EDGE && GetSize(child->children) == 1 && + child->children[0]->type == AST_IDENTIFIER && child->children[0]->str == "\\$global_clock") + current_always_clocked = true; + } + } + + if (type == AST_CELL) { + bool lookup_suggested = false; + + for (AstNode *child : children) { + // simplify any parameters to constants + if (child->type == AST_PARASET) + while (child->simplify(true, false, false, 1, -1, false, true)) { } + + // look for patterns which _may_ indicate ambiguity requiring + // resolution of the underlying module + if (child->type == AST_ARGUMENT) { + if (child->children.size() != 1) + continue; + const AstNode *value = child->children[0]; + if (value->type == AST_IDENTIFIER) { + const AstNode *elem = value->id2ast; + if (elem == nullptr) { + if (current_scope.count(value->str)) + elem = current_scope.at(value->str); + else + continue; + } + if (elem->type == AST_MEMORY) + // need to determine is the is a read or wire + lookup_suggested = true; + else if (elem->type == AST_WIRE && elem->is_signed && !value->children.empty()) + // this may be a fully sliced signed wire which needs + // to be indirected to produce an unsigned connection + lookup_suggested = true; + } + else if (contains_unbased_unsized(value)) + // unbased unsized literals extend to width of the context + lookup_suggested = true; + } + } + + const RTLIL::Module *module = nullptr; + if (lookup_suggested) + module = lookup_cell_module(); + if (module) { + size_t port_counter = 0; + for (AstNode *child : children) { + if (child->type != AST_ARGUMENT) + continue; + + // determine the full name of port this argument is connected to + RTLIL::IdString port_name; + if (child->str.size()) + port_name = child->str; + else { + if (port_counter >= module->ports.size()) + log_file_error(filename, location.first_line, + "Cell instance has more ports than the module!\n"); + port_name = module->ports[port_counter++]; + } + + // find the port's wire in the underlying module + const RTLIL::Wire *ref = module->wire(port_name); + if (ref == nullptr) + log_file_error(filename, location.first_line, + "Cell instance refers to port %s which does not exist in module %s!.\n", + log_id(port_name), log_id(module->name)); + + // select the argument, if present + log_assert(child->children.size() <= 1); + if (child->children.empty()) + continue; + AstNode *arg = child->children[0]; + + // plain identifiers never need indirection; this also prevents + // adding infinite levels of indirection + if (arg->type == AST_IDENTIFIER && arg->children.empty()) + continue; + + // only add indirection for standard inputs or outputs + if (ref->port_input == ref->port_output) + continue; + + did_something = true; + + // create the indirection wire + std::stringstream sstr; + sstr << "$indirect$" << ref->name.c_str() << "$" << RTLIL::encode_filename(filename) << ":" << location.first_line << "$" << (autoidx++); + std::string tmp_str = sstr.str(); + add_wire_for_ref(ref, tmp_str); + + AstNode *asgn = new AstNode(AST_ASSIGN); + current_ast_mod->children.push_back(asgn); + + AstNode *ident = new AstNode(AST_IDENTIFIER); + ident->str = tmp_str; + child->children[0] = ident->clone(); + + if (ref->port_input && !ref->port_output) { + asgn->children.push_back(ident); + asgn->children.push_back(arg); + } else { + log_assert(!ref->port_input && ref->port_output); + asgn->children.push_back(arg); + asgn->children.push_back(ident); + } + } + + + } + } + + int backup_width_hint = width_hint; + bool backup_sign_hint = sign_hint; + + bool detect_width_simple = false; + bool child_0_is_self_determined = false; + bool child_1_is_self_determined = false; + bool child_2_is_self_determined = false; + bool children_are_self_determined = false; + bool reset_width_after_children = false; + + switch (type) + { + case AST_ASSIGN_EQ: + case AST_ASSIGN_LE: + case AST_ASSIGN: + while (!children[0]->basic_prep && children[0]->simplify(false, false, true, stage, -1, false, in_param) == true) + did_something = true; + while (!children[1]->basic_prep && children[1]->simplify(false, false, false, stage, -1, false, in_param) == true) + did_something = true; + children[0]->detectSignWidth(backup_width_hint, backup_sign_hint); + children[1]->detectSignWidth(width_hint, sign_hint); + width_hint = max(width_hint, backup_width_hint); + child_0_is_self_determined = true; + // test only once, before optimizations and memory mappings but after assignment LHS was mapped to an identifier + if (children[0]->id2ast && !children[0]->was_checked) { + if ((type == AST_ASSIGN_LE || type == AST_ASSIGN_EQ) && children[0]->id2ast->is_logic) + children[0]->id2ast->is_reg = true; // if logic type is used in a block asignment + if ((type == AST_ASSIGN_LE || type == AST_ASSIGN_EQ) && !children[0]->id2ast->is_reg) + log_warning("wire '%s' is assigned in a block at %s.\n", children[0]->str.c_str(), loc_string().c_str()); + if (type == AST_ASSIGN && children[0]->id2ast->is_reg) { + bool is_rand_reg = false; + if (children[1]->type == AST_FCALL) { + if (children[1]->str == "\\$anyconst") + is_rand_reg = true; + if (children[1]->str == "\\$anyseq") + is_rand_reg = true; + if (children[1]->str == "\\$allconst") + is_rand_reg = true; + if (children[1]->str == "\\$allseq") + is_rand_reg = true; + } + if (!is_rand_reg) + log_warning("reg '%s' is assigned in a continuous assignment at %s.\n", children[0]->str.c_str(), loc_string().c_str()); + } + children[0]->was_checked = true; + } + break; + + case AST_STRUCT: + case AST_UNION: + std::cout << "str: " << str << " | " << __func__ << ":" << __LINE__ << std::endl; + if (!basic_prep) { + std::cout << "!basic_prep | " << __func__ << ":" << __LINE__ << std::endl; + for (auto *node : children) { + std::cout << "node str: " << node->str << " | " << __func__ << ":" << __LINE__ << std::endl; + // resolve any ranges + while (!node->basic_prep && node->simplify(true, false, false, stage, -1, false, false)) { + did_something = true; + } + } + // determine member offsets and widths + size_packed_struct(this, 0); + + // instance rather than just a type in a typedef or outer struct? + if (!str.empty() && str[0] == '\\') { + // instance so add a wire for the packed structure + auto wnode = make_packed_struct(this, str); + log_assert(current_ast_mod); + current_ast_mod->children.push_back(wnode); + } + basic_prep = true; + } + break; + + case AST_STRUCT_ITEM: + std::cout << " | " << __func__ << ":" << __LINE__ << std::endl; + break; + + case AST_ENUM: + //log("\nENUM %s: %d child %d\n", str.c_str(), basic_prep, children[0]->basic_prep); + if (!basic_prep) { + for (auto item_node : children) { + while (!item_node->basic_prep && item_node->simplify(false, false, false, stage, -1, false, in_param)) + did_something = true; + } + // allocate values (called more than once) + allocateDefaultEnumValues(); + } + break; + + case AST_PARAMETER: + case AST_LOCALPARAM: + // if parameter is implicit type which is the typename of a struct or union, + // save information about struct in wiretype attribute + if (children[0]->type == AST_IDENTIFIER && current_scope.count(children[0]->str) > 0) { + auto item_node = current_scope[children[0]->str]; + if (item_node->type == AST_STRUCT || item_node->type == AST_UNION) { + attributes[ID::wiretype] = item_node->clone(); + size_packed_struct(attributes[ID::wiretype], 0); + add_members_to_scope(attributes[ID::wiretype], str); + } + } + while (!children[0]->basic_prep && children[0]->simplify(false, false, false, stage, -1, false, true) == true) + did_something = true; + children[0]->detectSignWidth(width_hint, sign_hint); + if (children.size() > 1 && children[1]->type == AST_RANGE) { + while (!children[1]->basic_prep && children[1]->simplify(false, false, false, stage, -1, false, true) == true) + did_something = true; + if (!children[1]->range_valid) + log_file_error(filename, location.first_line, "Non-constant width range on parameter decl.\n"); + width_hint = max(width_hint, children[1]->range_left - children[1]->range_right + 1); + } + break; + case AST_ENUM_ITEM: + while (!children[0]->basic_prep && children[0]->simplify(false, false, false, stage, -1, false, in_param)) + did_something = true; + children[0]->detectSignWidth(width_hint, sign_hint); + if (children.size() > 1 && children[1]->type == AST_RANGE) { + while (!children[1]->basic_prep && children[1]->simplify(false, false, false, stage, -1, false, in_param)) + did_something = true; + if (!children[1]->range_valid) + log_file_error(filename, location.first_line, "Non-constant width range on enum item decl.\n"); + width_hint = max(width_hint, children[1]->range_left - children[1]->range_right + 1); + } + break; + + case AST_TO_BITS: + case AST_TO_SIGNED: + case AST_TO_UNSIGNED: + case AST_SELFSZ: + case AST_CAST_SIZE: + case AST_CONCAT: + case AST_REPLICATE: + case AST_REDUCE_AND: + case AST_REDUCE_OR: + case AST_REDUCE_XOR: + case AST_REDUCE_XNOR: + case AST_REDUCE_BOOL: + detect_width_simple = true; + children_are_self_determined = true; + break; + + case AST_NEG: + case AST_BIT_NOT: + case AST_POS: + case AST_BIT_AND: + case AST_BIT_OR: + case AST_BIT_XOR: + case AST_BIT_XNOR: + case AST_ADD: + case AST_SUB: + case AST_MUL: + case AST_DIV: + case AST_MOD: + detect_width_simple = true; + break; + + case AST_SHIFT_LEFT: + case AST_SHIFT_RIGHT: + case AST_SHIFT_SLEFT: + case AST_SHIFT_SRIGHT: + case AST_POW: + detect_width_simple = true; + child_1_is_self_determined = true; + break; + + case AST_LT: + case AST_LE: + case AST_EQ: + case AST_NE: + case AST_EQX: + case AST_NEX: + case AST_GE: + case AST_GT: + width_hint = -1; + sign_hint = true; + for (auto child : children) { + while (!child->basic_prep && child->simplify(false, false, in_lvalue, stage, -1, false, in_param) == true) + did_something = true; + child->detectSignWidthWorker(width_hint, sign_hint); + } + reset_width_after_children = true; + break; + + case AST_LOGIC_AND: + case AST_LOGIC_OR: + case AST_LOGIC_NOT: + detect_width_simple = true; + children_are_self_determined = true; + break; + + case AST_TERNARY: + child_0_is_self_determined = true; + break; + + case AST_MEMRD: + detect_width_simple = true; + children_are_self_determined = true; + break; + + case AST_FCALL: + case AST_TCALL: + children_are_self_determined = true; + break; + + default: + width_hint = -1; + sign_hint = false; + } + + if (detect_width_simple && width_hint < 0) { + if (type == AST_REPLICATE) + while (children[0]->simplify(true, false, in_lvalue, stage, -1, false, true) == true) + did_something = true; + for (auto child : children) + while (!child->basic_prep && child->simplify(false, false, in_lvalue, stage, -1, false, in_param) == true) + did_something = true; + detectSignWidth(width_hint, sign_hint); + } + + if (type == AST_FCALL && str == "\\$past") + detectSignWidth(width_hint, sign_hint); + + if (type == AST_TERNARY) { + if (width_hint < 0) { + while (!children[0]->basic_prep && children[0]->simplify(true, false, in_lvalue, stage, -1, false, in_param)) + did_something = true; + + bool backup_unevaluated_tern_branch = unevaluated_tern_branch; + AstNode *chosen = get_tern_choice().first; + + unevaluated_tern_branch = backup_unevaluated_tern_branch || chosen == children[2]; + while (!children[1]->basic_prep && children[1]->simplify(false, false, in_lvalue, stage, -1, false, in_param)) + did_something = true; + + unevaluated_tern_branch = backup_unevaluated_tern_branch || chosen == children[1]; + while (!children[2]->basic_prep && children[2]->simplify(false, false, in_lvalue, stage, -1, false, in_param)) + did_something = true; + + unevaluated_tern_branch = backup_unevaluated_tern_branch; + detectSignWidth(width_hint, sign_hint); + } + int width_hint_left, width_hint_right; + bool sign_hint_left, sign_hint_right; + bool found_real_left, found_real_right; + children[1]->detectSignWidth(width_hint_left, sign_hint_left, &found_real_left); + children[2]->detectSignWidth(width_hint_right, sign_hint_right, &found_real_right); + if (found_real_left || found_real_right) { + child_1_is_self_determined = true; + child_2_is_self_determined = true; + } + } + + if (type == AST_CONDX && children.size() > 0 && children.at(0)->type == AST_CONSTANT) { + for (auto &bit : children.at(0)->bits) + if (bit == State::Sz || bit == State::Sx) + bit = State::Sa; + } + + if (type == AST_CONDZ && children.size() > 0 && children.at(0)->type == AST_CONSTANT) { + for (auto &bit : children.at(0)->bits) + if (bit == State::Sz) + bit = State::Sa; + } + + if (const_fold && type == AST_CASE) + { + detectSignWidth(width_hint, sign_hint); + while (children[0]->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) { } + if (children[0]->type == AST_CONSTANT && children[0]->bits_only_01()) { + children[0]->is_signed = sign_hint; + RTLIL::Const case_expr = children[0]->bitsAsConst(width_hint, sign_hint); + std::vector<AstNode*> new_children; + new_children.push_back(children[0]); + for (int i = 1; i < GetSize(children); i++) { + AstNode *child = children[i]; + log_assert(child->type == AST_COND || child->type == AST_CONDX || child->type == AST_CONDZ); + for (auto v : child->children) { + if (v->type == AST_DEFAULT) + goto keep_const_cond; + if (v->type == AST_BLOCK) + continue; + while (v->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) { } + if (v->type == AST_CONSTANT && v->bits_only_01()) { + RTLIL::Const case_item_expr = v->bitsAsConst(width_hint, sign_hint); + RTLIL::Const match = const_eq(case_expr, case_item_expr, sign_hint, sign_hint, 1); + log_assert(match.bits.size() == 1); + if (match.bits.front() == RTLIL::State::S1) { + while (i+1 < GetSize(children)) + delete children[++i]; + goto keep_const_cond; + } + continue; + } + goto keep_const_cond; + } + if (0) + keep_const_cond: + new_children.push_back(child); + else + delete child; + } + new_children.swap(children); + } + } + + dict<std::string, pool<int>> backup_memwr_visible; + dict<std::string, pool<int>> final_memwr_visible; + + if (type == AST_CASE && stage == 2) { + backup_memwr_visible = current_memwr_visible; + final_memwr_visible = current_memwr_visible; + } + + // simplify all children first + // (iterate by index as e.g. auto wires can add new children in the process) + for (size_t i = 0; i < children.size(); i++) { + bool did_something_here = true; + bool backup_flag_autowire = flag_autowire; + bool backup_unevaluated_tern_branch = unevaluated_tern_branch; + if ((type == AST_GENFOR || type == AST_FOR) && i >= 3) + break; + if ((type == AST_GENIF || type == AST_GENCASE) && i >= 1) + break; + if (type == AST_GENBLOCK) + break; + if (type == AST_CELLARRAY && children[i]->type == AST_CELL) + continue; + if (type == AST_BLOCK && !str.empty()) + break; + if (type == AST_PREFIX && i >= 1) + break; + if (type == AST_DEFPARAM && i == 0) + flag_autowire = true; + if (type == AST_TERNARY && i > 0 && !unevaluated_tern_branch) { + AstNode *chosen = get_tern_choice().first; + unevaluated_tern_branch = chosen && chosen != children[i]; + } + while (did_something_here && i < children.size()) { + bool const_fold_here = const_fold, in_lvalue_here = in_lvalue; + int width_hint_here = width_hint; + bool sign_hint_here = sign_hint; + bool in_param_here = in_param; + if (i == 0 && (type == AST_REPLICATE || type == AST_WIRE)) + const_fold_here = true, in_param_here = true; + if (i == 0 && (type == AST_GENIF || type == AST_GENCASE)) + in_param_here = true; + if (i == 1 && (type == AST_FOR || type == AST_GENFOR)) + in_param_here = true; + if (type == AST_PARAMETER || type == AST_LOCALPARAM) + const_fold_here = true; + if (i == 0 && (type == AST_ASSIGN || type == AST_ASSIGN_EQ || type == AST_ASSIGN_LE)) + in_lvalue_here = true; + if (type == AST_BLOCK) { + current_block = this; + current_block_child = children[i]; + } + if ((type == AST_ALWAYS || type == AST_INITIAL) && children[i]->type == AST_BLOCK) + current_top_block = children[i]; + if (i == 0 && child_0_is_self_determined) + width_hint_here = -1, sign_hint_here = false; + if (i == 1 && child_1_is_self_determined) + width_hint_here = -1, sign_hint_here = false; + if (i == 2 && child_2_is_self_determined) + width_hint_here = -1, sign_hint_here = false; + if (children_are_self_determined) + width_hint_here = -1, sign_hint_here = false; + did_something_here = children[i]->simplify(const_fold_here, at_zero, in_lvalue_here, stage, width_hint_here, sign_hint_here, in_param_here); + if (did_something_here) + did_something = true; + } + if (stage == 2 && children[i]->type == AST_INITIAL && current_ast_mod != this) { + current_ast_mod->children.push_back(children[i]); + children.erase(children.begin() + (i--)); + did_something = true; + } + flag_autowire = backup_flag_autowire; + unevaluated_tern_branch = backup_unevaluated_tern_branch; + if (stage == 2 && type == AST_CASE) { + for (auto &x : current_memwr_visible) { + for (int y : x.second) + final_memwr_visible[x.first].insert(y); + } + current_memwr_visible = backup_memwr_visible; + } + } + for (auto &attr : attributes) { + while (attr.second->simplify(true, false, false, stage, -1, false, true)) + did_something = true; + } + if (type == AST_CASE && stage == 2) { + current_memwr_visible = final_memwr_visible; + } + if (type == AST_ALWAYS && stage == 2) { + current_memwr_visible.clear(); + current_memwr_count.clear(); + } + + if (reset_width_after_children) { + width_hint = backup_width_hint; + sign_hint = backup_sign_hint; + if (width_hint < 0) + detectSignWidth(width_hint, sign_hint); + } + + current_block = backup_current_block; + current_block_child = backup_current_block_child; + current_top_block = backup_current_top_block; + current_always = backup_current_always; + current_always_clocked = backup_current_always_clocked; + + for (auto it = backup_scope.begin(); it != backup_scope.end(); it++) { + if (it->second == NULL) + current_scope.erase(it->first); + else + current_scope[it->first] = it->second; + } + + current_filename = filename; + + if (type == AST_MODULE || type == AST_INTERFACE) + current_scope.clear(); + + // convert defparam nodes to cell parameters + if (type == AST_DEFPARAM && !children.empty()) + { + if (children[0]->type != AST_IDENTIFIER) + log_file_error(filename, location.first_line, "Module name in defparam contains non-constant expressions!\n"); + + string modname, paramname = children[0]->str; + + size_t pos = paramname.rfind('.'); + + while (pos != 0 && pos != std::string::npos) + { + modname = paramname.substr(0, pos); + + if (current_scope.count(modname)) + break; + + pos = paramname.rfind('.', pos - 1); + } + + if (pos == std::string::npos) + log_file_error(filename, location.first_line, "Can't find object for defparam `%s`!\n", RTLIL::unescape_id(paramname).c_str()); + + paramname = "\\" + paramname.substr(pos+1); + + if (current_scope.at(modname)->type != AST_CELL) + log_file_error(filename, location.first_line, "Defparam argument `%s . %s` does not match a cell!\n", + RTLIL::unescape_id(modname).c_str(), RTLIL::unescape_id(paramname).c_str()); + + AstNode *paraset = new AstNode(AST_PARASET, children[1]->clone(), GetSize(children) > 2 ? children[2]->clone() : NULL); + paraset->str = paramname; + + AstNode *cell = current_scope.at(modname); + cell->children.insert(cell->children.begin() + 1, paraset); + delete_children(); + } + + // resolve typedefs + if (type == AST_TYPEDEF) { + log_assert(children.size() == 1); + auto type_node = children[0]; + log_assert(type_node->type == AST_WIRE || type_node->type == AST_MEMORY || type_node->type == AST_STRUCT || type_node->type == AST_UNION); + while (type_node->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) { + did_something = true; + } + log_assert(!type_node->is_custom_type); + } + + // resolve types of wires + if (type == AST_WIRE || type == AST_MEMORY) { + if (is_custom_type) { + log_assert(children.size() >= 1); + log_assert(children[0]->type == AST_WIRETYPE); + auto type_name = children[0]->str; + if (!current_scope.count(type_name)) { + log_file_error(filename, location.first_line, "Unknown identifier `%s' used as type name\n", type_name.c_str()); + } + AstNode *resolved_type_node = current_scope.at(type_name); + if (resolved_type_node->type != AST_TYPEDEF) + log_file_error(filename, location.first_line, "`%s' does not name a type\n", type_name.c_str()); + log_assert(resolved_type_node->children.size() == 1); + AstNode *template_node = resolved_type_node->children[0]; + + // Ensure typedef itself is fully simplified + while (template_node->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {}; + + if (template_node->type == AST_STRUCT || template_node->type == AST_UNION) { + // replace with wire representing the packed structure + newNode = make_packed_struct(template_node, str); + newNode->attributes[ID::wiretype] = mkconst_str(resolved_type_node->str); + // add original input/output attribute to resolved wire + newNode->is_input = this->is_input; + newNode->is_output = this->is_output; + current_scope[str] = this; + goto apply_newNode; + } + + // Remove type reference + delete children[0]; + children.erase(children.begin()); + + if (type == AST_WIRE) + type = template_node->type; + is_reg = template_node->is_reg; + is_logic = template_node->is_logic; + is_signed = template_node->is_signed; + is_string = template_node->is_string; + is_custom_type = template_node->is_custom_type; + + range_valid = template_node->range_valid; + range_swapped = template_node->range_swapped; + range_left = template_node->range_left; + range_right = template_node->range_right; + + attributes[ID::wiretype] = mkconst_str(resolved_type_node->str); + + // if an enum then add attributes to support simulator tracing + annotateTypedEnums(template_node); + + // Insert clones children from template at beginning + for (int i = 0; i < GetSize(template_node->children); i++) + children.insert(children.begin() + i, template_node->children[i]->clone()); + + if (type == AST_MEMORY && GetSize(children) == 1) { + // Single-bit memories must have [0:0] range + AstNode *rng = make_range(0, 0); + children.insert(children.begin(), rng); + } + did_something = true; + } + log_assert(!is_custom_type); + } + + // resolve types of parameters + if (type == AST_LOCALPARAM || type == AST_PARAMETER) { + if (is_custom_type) { + log_assert(children.size() == 2); + log_assert(children[1]->type == AST_WIRETYPE); + auto type_name = children[1]->str; + if (!current_scope.count(type_name)) { + log_file_error(filename, location.first_line, "Unknown identifier `%s' used as type name\n", type_name.c_str()); + } + AstNode *resolved_type_node = current_scope.at(type_name); + if (resolved_type_node->type != AST_TYPEDEF) + log_file_error(filename, location.first_line, "`%s' does not name a type\n", type_name.c_str()); + log_assert(resolved_type_node->children.size() == 1); + AstNode *template_node = resolved_type_node->children[0]; + + // Ensure typedef itself is fully simplified + while (template_node->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {}; + + if (template_node->type == AST_STRUCT || template_node->type == AST_UNION) { + // replace with wire representing the packed structure + newNode = make_packed_struct(template_node, str); + newNode->attributes[ID::wiretype] = mkconst_str(resolved_type_node->str); + newNode->type = type; + current_scope[str] = this; + // copy param value, it needs to be 1st value + delete children[1]; + children.pop_back(); + newNode->children.insert(newNode->children.begin(), children[0]->clone()); + goto apply_newNode; + } + delete children[1]; + children.pop_back(); + + if (template_node->type == AST_MEMORY) + log_file_error(filename, location.first_line, "unpacked array type `%s' cannot be used for a parameter\n", children[1]->str.c_str()); + is_signed = template_node->is_signed; + is_string = template_node->is_string; + is_custom_type = template_node->is_custom_type; + + range_valid = template_node->range_valid; + range_swapped = template_node->range_swapped; + range_left = template_node->range_left; + range_right = template_node->range_right; + attributes[ID::wiretype] = mkconst_str(resolved_type_node->str); + for (auto template_child : template_node->children) + children.push_back(template_child->clone()); + did_something = true; + } + log_assert(!is_custom_type); + } + + // resolve constant prefixes + if (type == AST_PREFIX) { + if (children[0]->type != AST_CONSTANT) { + // dumpAst(NULL, "> "); + log_file_error(filename, location.first_line, "Index in generate block prefix syntax is not constant!\n"); + } + if (children[1]->type == AST_PREFIX) + children[1]->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param); + log_assert(children[1]->type == AST_IDENTIFIER); + newNode = children[1]->clone(); + const char *second_part = children[1]->str.c_str(); + if (second_part[0] == '\\') + second_part++; + newNode->str = stringf("%s[%d].%s", str.c_str(), children[0]->integer, second_part); + goto apply_newNode; + } + + // evaluate TO_BITS nodes + if (type == AST_TO_BITS) { + if (children[0]->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "Left operand of to_bits expression is not constant!\n"); + if (children[1]->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "Right operand of to_bits expression is not constant!\n"); + RTLIL::Const new_value = children[1]->bitsAsConst(children[0]->bitsAsConst().as_int(), children[1]->is_signed); + newNode = mkconst_bits(new_value.bits, children[1]->is_signed); + goto apply_newNode; + } + + // annotate constant ranges + if (type == AST_RANGE) { + bool old_range_valid = range_valid; + range_valid = false; + range_swapped = false; + range_left = -1; + range_right = 0; + log_assert(children.size() >= 1); + if (children[0]->type == AST_CONSTANT) { + range_valid = true; + range_left = children[0]->integer; + if (children.size() == 1) + range_right = range_left; + } + if (children.size() >= 2) { + if (children[1]->type == AST_CONSTANT) + range_right = children[1]->integer; + else + range_valid = false; + } + if (old_range_valid != range_valid) + did_something = true; + if (range_valid && range_right > range_left) { + int tmp = range_right; + range_right = range_left; + range_left = tmp; + range_swapped = true; + } + } + + // annotate wires with their ranges + if (type == AST_WIRE) { + if (children.size() > 0) { + if (children[0]->range_valid) { + if (!range_valid) + did_something = true; + range_valid = true; + range_swapped = children[0]->range_swapped; + range_left = children[0]->range_left; + range_right = children[0]->range_right; + bool force_upto = false, force_downto = false; + if (attributes.count(ID::force_upto)) { + AstNode *val = attributes[ID::force_upto]; + if (val->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "Attribute `force_upto' with non-constant value!\n"); + force_upto = val->asAttrConst().as_bool(); + } + if (attributes.count(ID::force_downto)) { + AstNode *val = attributes[ID::force_downto]; + if (val->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "Attribute `force_downto' with non-constant value!\n"); + force_downto = val->asAttrConst().as_bool(); + } + if (force_upto && force_downto) + log_file_error(filename, location.first_line, "Attributes `force_downto' and `force_upto' cannot be both set!\n"); + if ((force_upto && !range_swapped) || (force_downto && range_swapped)) { + std::swap(range_left, range_right); + range_swapped = force_upto; + } + } + } else { + if (!range_valid) + did_something = true; + range_valid = true; + range_swapped = false; + range_left = 0; + range_right = 0; + } + } + + // resolve multiranges on memory decl + if (type == AST_MEMORY && children.size() > 1 && children[1]->type == AST_MULTIRANGE) + { + int total_size = 1; + multirange_dimensions.clear(); + multirange_swapped.clear(); + for (auto range : children[1]->children) { + if (!range->range_valid) + log_file_error(filename, location.first_line, "Non-constant range on memory decl.\n"); + multirange_dimensions.push_back(min(range->range_left, range->range_right)); + multirange_dimensions.push_back(max(range->range_left, range->range_right) - min(range->range_left, range->range_right) + 1); + multirange_swapped.push_back(range->range_swapped); + total_size *= multirange_dimensions.back(); + } + delete children[1]; + children[1] = new AstNode(AST_RANGE, AstNode::mkconst_int(0, true), AstNode::mkconst_int(total_size-1, true)); + did_something = true; + } + + // resolve multiranges on memory access + if (type == AST_IDENTIFIER && id2ast && id2ast->type == AST_MEMORY && children.size() > 0 && children[0]->type == AST_MULTIRANGE) + { + AstNode *index_expr = nullptr; + + integer = children[0]->children.size(); // save original number of dimensions for $size() etc. + for (int i = 0; 2*i < GetSize(id2ast->multirange_dimensions); i++) + { + if (GetSize(children[0]->children) <= i) + log_file_error(filename, location.first_line, "Insufficient number of array indices for %s.\n", log_id(str)); + + AstNode *new_index_expr = children[0]->children[i]->children.at(0)->clone(); + + if (id2ast->multirange_dimensions[2*i]) + new_index_expr = new AstNode(AST_SUB, new_index_expr, AstNode::mkconst_int(id2ast->multirange_dimensions[2*i], true)); + + if (i == 0) + index_expr = new_index_expr; + else + index_expr = new AstNode(AST_ADD, new AstNode(AST_MUL, index_expr, AstNode::mkconst_int(id2ast->multirange_dimensions[2*i+1], true)), new_index_expr); + } + + for (int i = GetSize(id2ast->multirange_dimensions)/2; i < GetSize(children[0]->children); i++) + children.push_back(children[0]->children[i]->clone()); + + delete children[0]; + if (index_expr == nullptr) + children.erase(children.begin()); + else + children[0] = new AstNode(AST_RANGE, index_expr); + + did_something = true; + } + + // trim/extend parameters + if (type == AST_PARAMETER || type == AST_LOCALPARAM || type == AST_ENUM_ITEM) { + if (children.size() > 1 && children[1]->type == AST_RANGE) { + if (!children[1]->range_valid) + log_file_error(filename, location.first_line, "Non-constant width range on parameter decl.\n"); + int width = std::abs(children[1]->range_left - children[1]->range_right) + 1; + if (children[0]->type == AST_REALVALUE) { + RTLIL::Const constvalue = children[0]->realAsConst(width); + log_file_warning(filename, location.first_line, "converting real value %e to binary %s.\n", + children[0]->realvalue, log_signal(constvalue)); + delete children[0]; + children[0] = mkconst_bits(constvalue.bits, sign_hint); + did_something = true; + } + if (children[0]->type == AST_CONSTANT) { + if (width != int(children[0]->bits.size())) { + RTLIL::SigSpec sig(children[0]->bits); + sig.extend_u0(width, children[0]->is_signed); + AstNode *old_child_0 = children[0]; + children[0] = mkconst_bits(sig.as_const().bits, is_signed); + delete old_child_0; + } + children[0]->is_signed = is_signed; + } + range_valid = true; + range_swapped = children[1]->range_swapped; + range_left = children[1]->range_left; + range_right = children[1]->range_right; + } else + if (children.size() > 1 && children[1]->type == AST_REALVALUE && children[0]->type == AST_CONSTANT) { + double as_realvalue = children[0]->asReal(sign_hint); + delete children[0]; + children[0] = new AstNode(AST_REALVALUE); + children[0]->realvalue = as_realvalue; + did_something = true; + } + } + + if (type == AST_IDENTIFIER && !basic_prep) { + // check if a plausible struct member sss.mmmm + std::string sname; + if (name_has_dot(str, sname)) { + if (current_scope.count(str) > 0) { + auto item_node = current_scope[str]; + if (item_node->type == AST_STRUCT_ITEM || item_node->type == AST_STRUCT || item_node->type == AST_UNION) { + // structure member, rewrite this node to reference the packed struct wire + auto range = make_struct_member_range(this, item_node); + newNode = new AstNode(AST_IDENTIFIER, range); + newNode->str = sname; + // save type and original number of dimensions for $size() etc. + newNode->attributes[ID::wiretype] = item_node->clone(); + if (!item_node->multirange_dimensions.empty() && children.size() > 0) { + if (children[0]->type == AST_RANGE) + newNode->integer = 1; + else if (children[0]->type == AST_MULTIRANGE) + newNode->integer = children[0]->children.size(); + } + newNode->basic_prep = true; + if (item_node->is_signed) + newNode = new AstNode(AST_TO_SIGNED, newNode); + goto apply_newNode; + } + } + } + } + // annotate identifiers using scope resolution and create auto-wires as needed + if (type == AST_IDENTIFIER) { + if (current_scope.count(str) == 0) { + AstNode *current_scope_ast = (current_ast_mod == nullptr) ? current_ast : current_ast_mod; + str = try_pop_module_prefix(); + for (auto node : current_scope_ast->children) { + //log("looking at mod scope child %s\n", type2str(node->type).c_str()); + switch (node->type) { + case AST_PARAMETER: + case AST_LOCALPARAM: + case AST_WIRE: + case AST_AUTOWIRE: + case AST_GENVAR: + case AST_MEMORY: + case AST_FUNCTION: + case AST_TASK: + case AST_DPI_FUNCTION: + //log("found child %s, %s\n", type2str(node->type).c_str(), node->str.c_str()); + if (str == node->str) { + //log("add %s, type %s to scope\n", str.c_str(), type2str(node->type).c_str()); + current_scope[node->str] = node; + } + break; + case AST_ENUM: + current_scope[node->str] = node; + for (auto enum_node : node->children) { + log_assert(enum_node->type==AST_ENUM_ITEM); + if (str == enum_node->str) { + //log("\nadding enum item %s to scope\n", str.c_str()); + current_scope[str] = enum_node; + } + } + break; + default: + break; + } + } + } + if (current_scope.count(str) == 0) { + if (current_ast_mod == nullptr) { + log_file_error(filename, location.first_line, "Identifier `%s' is implicitly declared outside of a module.\n", str.c_str()); + } else if (flag_autowire || str == "\\$global_clock") { + AstNode *auto_wire = new AstNode(AST_AUTOWIRE); + auto_wire->str = str; + current_ast_mod->children.push_back(auto_wire); + current_scope[str] = auto_wire; + did_something = true; + } else { + log_file_error(filename, location.first_line, "Identifier `%s' is implicitly declared and `default_nettype is set to none.\n", str.c_str()); + } + } + if (id2ast != current_scope[str]) { + id2ast = current_scope[str]; + did_something = true; + } + } + + // split memory access with bit select to individual statements + if (type == AST_IDENTIFIER && children.size() == 2 && children[0]->type == AST_RANGE && children[1]->type == AST_RANGE && !in_lvalue && stage == 2) + { + if (id2ast == NULL || id2ast->type != AST_MEMORY || children[0]->children.size() != 1) + log_file_error(filename, location.first_line, "Invalid bit-select on memory access!\n"); + + int mem_width, mem_size, addr_bits; + id2ast->meminfo(mem_width, mem_size, addr_bits); + + int data_range_left = id2ast->children[0]->range_left; + int data_range_right = id2ast->children[0]->range_right; + + if (id2ast->children[0]->range_swapped) + std::swap(data_range_left, data_range_right); + + std::stringstream sstr; + sstr << "$mem2bits$" << str << "$" << RTLIL::encode_filename(filename) << ":" << location.first_line << "$" << (autoidx++); + std::string wire_id = sstr.str(); + + AstNode *wire = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(data_range_left, true), mkconst_int(data_range_right, true))); + wire->str = wire_id; + if (current_block) + wire->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + current_ast_mod->children.push_back(wire); + while (wire->simplify(true, false, false, 1, -1, false, false)) { } + + AstNode *data = clone(); + delete data->children[1]; + data->children.pop_back(); + + AstNode *assign = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), data); + assign->children[0]->str = wire_id; + assign->children[0]->was_checked = true; + + if (current_block) + { + size_t assign_idx = 0; + while (assign_idx < current_block->children.size() && current_block->children[assign_idx] != current_block_child) + assign_idx++; + log_assert(assign_idx < current_block->children.size()); + current_block->children.insert(current_block->children.begin()+assign_idx, assign); + wire->is_reg = true; + } + else + { + AstNode *proc = new AstNode(AST_ALWAYS, new AstNode(AST_BLOCK)); + proc->children[0]->children.push_back(assign); + current_ast_mod->children.push_back(proc); + } + + newNode = new AstNode(AST_IDENTIFIER, children[1]->clone()); + newNode->str = wire_id; + newNode->integer = integer; // save original number of dimensions for $size() etc. + newNode->id2ast = wire; + goto apply_newNode; + } + + if (type == AST_WHILE) + log_file_error(filename, location.first_line, "While loops are only allowed in constant functions!\n"); + + if (type == AST_REPEAT) + { + AstNode *count = children[0]; + AstNode *body = children[1]; + + // eval count expression + while (count->simplify(true, false, false, stage, 32, true, false)) { } + + if (count->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "Repeat loops outside must have constant repeat counts!\n"); + + // convert to a block with the body repeated n times + type = AST_BLOCK; + children.clear(); + for (int i = 0; i < count->bitsAsConst().as_int(); i++) + children.insert(children.begin(), body->clone()); + + delete count; + delete body; + did_something = true; + } + + // unroll for loops and generate-for blocks + if ((type == AST_GENFOR || type == AST_FOR) && children.size() != 0) + { + AstNode *init_ast = children[0]; + AstNode *while_ast = children[1]; + AstNode *next_ast = children[2]; + AstNode *body_ast = children[3]; + + while (body_ast->type == AST_GENBLOCK && body_ast->str.empty() && + body_ast->children.size() == 1 && body_ast->children.at(0)->type == AST_GENBLOCK) + body_ast = body_ast->children.at(0); + + const char* loop_type_str = "procedural"; + const char* var_type_str = "register"; + AstNodeType var_type = AST_WIRE; + if (type == AST_GENFOR) { + loop_type_str = "generate"; + var_type_str = "genvar"; + var_type = AST_GENVAR; + } + + if (init_ast->type != AST_ASSIGN_EQ) + log_file_error(filename, location.first_line, "Unsupported 1st expression of %s for-loop!\n", loop_type_str); + if (next_ast->type != AST_ASSIGN_EQ) + log_file_error(filename, location.first_line, "Unsupported 3rd expression of %s for-loop!\n", loop_type_str); + + if (init_ast->children[0]->id2ast == NULL || init_ast->children[0]->id2ast->type != var_type) + log_file_error(filename, location.first_line, "Left hand side of 1st expression of %s for-loop is not a %s!\n", loop_type_str, var_type_str); + if (next_ast->children[0]->id2ast == NULL || next_ast->children[0]->id2ast->type != var_type) + log_file_error(filename, location.first_line, "Left hand side of 3rd expression of %s for-loop is not a %s!\n", loop_type_str, var_type_str); + + if (init_ast->children[0]->id2ast != next_ast->children[0]->id2ast) + log_file_error(filename, location.first_line, "Incompatible left-hand sides in 1st and 3rd expression of %s for-loop!\n", loop_type_str); + + // eval 1st expression + AstNode *varbuf = init_ast->children[1]->clone(); + { + int expr_width_hint = -1; + bool expr_sign_hint = true; + varbuf->detectSignWidth(expr_width_hint, expr_sign_hint); + while (varbuf->simplify(true, false, false, stage, 32, true, false)) { } + } + + if (varbuf->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "Right hand side of 1st expression of %s for-loop is not constant!\n", loop_type_str); + + auto resolved = current_scope.at(init_ast->children[0]->str); + if (resolved->range_valid) { + int const_size = varbuf->range_left - varbuf->range_right; + int resolved_size = resolved->range_left - resolved->range_right; + if (const_size < resolved_size) { + for (int i = const_size; i < resolved_size; i++) + varbuf->bits.push_back(resolved->is_signed ? varbuf->bits.back() : State::S0); + varbuf->range_left = resolved->range_left; + varbuf->range_right = resolved->range_right; + varbuf->range_swapped = resolved->range_swapped; + varbuf->range_valid = resolved->range_valid; + } + } + + varbuf = new AstNode(AST_LOCALPARAM, varbuf); + varbuf->str = init_ast->children[0]->str; + + AstNode *backup_scope_varbuf = current_scope[varbuf->str]; + current_scope[varbuf->str] = varbuf; + + size_t current_block_idx = 0; + if (type == AST_FOR) { + while (current_block_idx < current_block->children.size() && + current_block->children[current_block_idx] != current_block_child) + current_block_idx++; + } + + while (1) + { + // eval 2nd expression + AstNode *buf = while_ast->clone(); + { + int expr_width_hint = -1; + bool expr_sign_hint = true; + buf->detectSignWidth(expr_width_hint, expr_sign_hint); + while (buf->simplify(true, false, false, stage, expr_width_hint, expr_sign_hint, false)) { } + } + + if (buf->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "2nd expression of %s for-loop is not constant!\n", loop_type_str); + + if (buf->integer == 0) { + delete buf; + break; + } + delete buf; + + // expand body + int index = varbuf->children[0]->integer; + log_assert(body_ast->type == AST_GENBLOCK || body_ast->type == AST_BLOCK); + log_assert(!body_ast->str.empty()); + buf = body_ast->clone(); + + std::stringstream sstr; + sstr << buf->str << "[" << index << "]."; + std::string prefix = sstr.str(); + + // create a scoped localparam for the current value of the loop variable + AstNode *local_index = varbuf->clone(); + size_t pos = local_index->str.rfind('.'); + if (pos != std::string::npos) // remove outer prefix + local_index->str = "\\" + local_index->str.substr(pos + 1); + local_index->str = prefix_id(prefix, local_index->str); + current_scope[local_index->str] = local_index; + current_ast_mod->children.push_back(local_index); + + buf->expand_genblock(prefix); + + if (type == AST_GENFOR) { + for (size_t i = 0; i < buf->children.size(); i++) { + buf->children[i]->simplify(const_fold, false, false, stage, -1, false, false); + current_ast_mod->children.push_back(buf->children[i]); + } + } else { + for (size_t i = 0; i < buf->children.size(); i++) + current_block->children.insert(current_block->children.begin() + current_block_idx++, buf->children[i]); + } + buf->children.clear(); + delete buf; + + // eval 3rd expression + buf = next_ast->children[1]->clone(); + { + int expr_width_hint = -1; + bool expr_sign_hint = true; + buf->detectSignWidth(expr_width_hint, expr_sign_hint); + while (buf->simplify(true, false, false, stage, expr_width_hint, expr_sign_hint, true)) { } + } + + if (buf->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "Right hand side of 3rd expression of %s for-loop is not constant (%s)!\n", loop_type_str, type2str(buf->type).c_str()); + + delete varbuf->children[0]; + varbuf->children[0] = buf; + } + + if (type == AST_FOR) { + AstNode *buf = next_ast->clone(); + delete buf->children[1]; + buf->children[1] = varbuf->children[0]->clone(); + current_block->children.insert(current_block->children.begin() + current_block_idx++, buf); + } + + current_scope[varbuf->str] = backup_scope_varbuf; + delete varbuf; + delete_children(); + did_something = true; + } + + // check for local objects in unnamed block + if (type == AST_BLOCK && str.empty()) + { + for (size_t i = 0; i < children.size(); i++) + if (children[i]->type == AST_WIRE || children[i]->type == AST_MEMORY || children[i]->type == AST_PARAMETER || children[i]->type == AST_LOCALPARAM || children[i]->type == AST_TYPEDEF) + { + log_assert(!VERILOG_FRONTEND::sv_mode); + log_file_error(children[i]->filename, children[i]->location.first_line, "Local declaration in unnamed block is only supported in SystemVerilog mode!\n"); + } + } + + // transform block with name + if (type == AST_BLOCK && !str.empty()) + { + expand_genblock(str + "."); + + // if this is an autonamed block is in an always_comb + if (current_always && current_always->attributes.count(ID::always_comb) + && is_autonamed_block(str)) + // track local variables in this block so we can consider adding + // nosync once the block has been fully elaborated + for (AstNode *child : children) + if (child->type == AST_WIRE && + !child->attributes.count(ID::nosync)) + mark_auto_nosync(this, child); + + std::vector<AstNode*> new_children; + for (size_t i = 0; i < children.size(); i++) + if (children[i]->type == AST_WIRE || children[i]->type == AST_MEMORY || children[i]->type == AST_PARAMETER || children[i]->type == AST_LOCALPARAM || children[i]->type == AST_TYPEDEF) { + children[i]->simplify(false, false, false, stage, -1, false, false); + current_ast_mod->children.push_back(children[i]); + current_scope[children[i]->str] = children[i]; + } else + new_children.push_back(children[i]); + + children.swap(new_children); + did_something = true; + str.clear(); + } + + // simplify unconditional generate block + if (type == AST_GENBLOCK && children.size() != 0) + { + if (!str.empty()) { + expand_genblock(str + "."); + } + + for (size_t i = 0; i < children.size(); i++) { + children[i]->simplify(const_fold, false, false, stage, -1, false, false); + current_ast_mod->children.push_back(children[i]); + } + + children.clear(); + did_something = true; + } + + // simplify generate-if blocks + if (type == AST_GENIF && children.size() != 0) + { + AstNode *buf = children[0]->clone(); + while (buf->simplify(true, false, false, stage, width_hint, sign_hint, false)) { } + if (buf->type != AST_CONSTANT) { + // for (auto f : log_files) + // dumpAst(f, "verilog-ast> "); + log_file_error(filename, location.first_line, "Condition for generate if is not constant!\n"); + } + if (buf->asBool() != 0) { + delete buf; + buf = children[1]->clone(); + } else { + delete buf; + buf = children.size() > 2 ? children[2]->clone() : NULL; + } + + if (buf) + { + if (buf->type != AST_GENBLOCK) + buf = new AstNode(AST_GENBLOCK, buf); + + if (!buf->str.empty()) { + buf->expand_genblock(buf->str + "."); + } + + for (size_t i = 0; i < buf->children.size(); i++) { + buf->children[i]->simplify(const_fold, false, false, stage, -1, false, false); + current_ast_mod->children.push_back(buf->children[i]); + } + + buf->children.clear(); + delete buf; + } + + delete_children(); + did_something = true; + } + + // simplify generate-case blocks + if (type == AST_GENCASE && children.size() != 0) + { + AstNode *buf = children[0]->clone(); + while (buf->simplify(true, false, false, stage, width_hint, sign_hint, false)) { } + if (buf->type != AST_CONSTANT) { + // for (auto f : log_files) + // dumpAst(f, "verilog-ast> "); + log_file_error(filename, location.first_line, "Condition for generate case is not constant!\n"); + } + + bool ref_signed = buf->is_signed; + RTLIL::Const ref_value = buf->bitsAsConst(); + delete buf; + + AstNode *selected_case = NULL; + for (size_t i = 1; i < children.size(); i++) + { + log_assert(children.at(i)->type == AST_COND || children.at(i)->type == AST_CONDX || children.at(i)->type == AST_CONDZ); + + AstNode *this_genblock = NULL; + for (auto child : children.at(i)->children) { + log_assert(this_genblock == NULL); + if (child->type == AST_GENBLOCK) + this_genblock = child; + } + + for (auto child : children.at(i)->children) + { + if (child->type == AST_DEFAULT) { + if (selected_case == NULL) + selected_case = this_genblock; + continue; + } + if (child->type == AST_GENBLOCK) + continue; + + buf = child->clone(); + while (buf->simplify(true, false, false, stage, width_hint, sign_hint, true)) { } + if (buf->type != AST_CONSTANT) { + // for (auto f : log_files) + // dumpAst(f, "verilog-ast> "); + log_file_error(filename, location.first_line, "Expression in generate case is not constant!\n"); + } + + bool is_selected = RTLIL::const_eq(ref_value, buf->bitsAsConst(), ref_signed && buf->is_signed, ref_signed && buf->is_signed, 1).as_bool(); + delete buf; + + if (is_selected) { + selected_case = this_genblock; + i = children.size(); + break; + } + } + } + + if (selected_case != NULL) + { + log_assert(selected_case->type == AST_GENBLOCK); + buf = selected_case->clone(); + + if (!buf->str.empty()) { + buf->expand_genblock(buf->str + "."); + } + + for (size_t i = 0; i < buf->children.size(); i++) { + buf->children[i]->simplify(const_fold, false, false, stage, -1, false, false); + current_ast_mod->children.push_back(buf->children[i]); + } + + buf->children.clear(); + delete buf; + } + + delete_children(); + did_something = true; + } + + // unroll cell arrays + if (type == AST_CELLARRAY) + { + if (!children.at(0)->range_valid) + log_file_error(filename, location.first_line, "Non-constant array range on cell array.\n"); + + newNode = new AstNode(AST_GENBLOCK); + int num = max(children.at(0)->range_left, children.at(0)->range_right) - min(children.at(0)->range_left, children.at(0)->range_right) + 1; + + for (int i = 0; i < num; i++) { + int idx = children.at(0)->range_left > children.at(0)->range_right ? children.at(0)->range_right + i : children.at(0)->range_right - i; + AstNode *new_cell = children.at(1)->clone(); + newNode->children.push_back(new_cell); + new_cell->str += stringf("[%d]", idx); + if (new_cell->type == AST_PRIMITIVE) { + log_file_error(filename, location.first_line, "Cell arrays of primitives are currently not supported.\n"); + } else { + log_assert(new_cell->children.at(0)->type == AST_CELLTYPE); + new_cell->children.at(0)->str = stringf("$array:%d:%d:%s", i, num, new_cell->children.at(0)->str.c_str()); + } + } + + goto apply_newNode; + } + + // replace primitives with assignments + if (type == AST_PRIMITIVE) + { + if (children.size() < 2) + log_file_error(filename, location.first_line, "Insufficient number of arguments for primitive `%s'!\n", str.c_str()); + + std::vector<AstNode*> children_list; + for (auto child : children) { + log_assert(child->type == AST_ARGUMENT); + log_assert(child->children.size() == 1); + children_list.push_back(child->children[0]); + child->children.clear(); + delete child; + } + children.clear(); + + if (str == "bufif0" || str == "bufif1" || str == "notif0" || str == "notif1") + { + if (children_list.size() != 3) + log_file_error(filename, location.first_line, "Invalid number of arguments for primitive `%s'!\n", str.c_str()); + + std::vector<RTLIL::State> z_const(1, RTLIL::State::Sz); + + AstNode *mux_input = children_list.at(1); + if (str == "notif0" || str == "notif1") { + mux_input = new AstNode(AST_BIT_NOT, mux_input); + } + AstNode *node = new AstNode(AST_TERNARY, children_list.at(2)); + if (str == "bufif0") { + node->children.push_back(AstNode::mkconst_bits(z_const, false)); + node->children.push_back(mux_input); + } else { + node->children.push_back(mux_input); + node->children.push_back(AstNode::mkconst_bits(z_const, false)); + } + + str.clear(); + type = AST_ASSIGN; + children.push_back(children_list.at(0)); + children.back()->was_checked = true; + children.push_back(node); + did_something = true; + } + else if (str == "buf" || str == "not") + { + AstNode *input = children_list.back(); + if (str == "not") + input = new AstNode(AST_BIT_NOT, input); + + newNode = new AstNode(AST_GENBLOCK); + for (auto it = children_list.begin(); it != std::prev(children_list.end()); it++) { + newNode->children.push_back(new AstNode(AST_ASSIGN, *it, input->clone())); + newNode->children.back()->was_checked = true; + } + delete input; + + did_something = true; + } + else + { + AstNodeType op_type = AST_NONE; + bool invert_results = false; + + if (str == "and") + op_type = AST_BIT_AND; + if (str == "nand") + op_type = AST_BIT_AND, invert_results = true; + if (str == "or") + op_type = AST_BIT_OR; + if (str == "nor") + op_type = AST_BIT_OR, invert_results = true; + if (str == "xor") + op_type = AST_BIT_XOR; + if (str == "xnor") + op_type = AST_BIT_XOR, invert_results = true; + log_assert(op_type != AST_NONE); + + AstNode *node = children_list[1]; + if (op_type != AST_POS) + for (size_t i = 2; i < children_list.size(); i++) { + node = new AstNode(op_type, node, children_list[i]); + node->location = location; + } + if (invert_results) + node = new AstNode(AST_BIT_NOT, node); + + str.clear(); + type = AST_ASSIGN; + children.push_back(children_list[0]); + children.back()->was_checked = true; + children.push_back(node); + did_something = true; + } + } + + // replace dynamic ranges in left-hand side expressions (e.g. "foo[bar] <= 1'b1;") with + // either a big case block that selects the correct single-bit assignment, or mask and + // shift operations. + if (type == AST_ASSIGN_EQ || type == AST_ASSIGN_LE) + { + if (children[0]->type != AST_IDENTIFIER || children[0]->children.size() == 0) + goto skip_dynamic_range_lvalue_expansion; + if (children[0]->children[0]->range_valid || did_something) + goto skip_dynamic_range_lvalue_expansion; + if (children[0]->id2ast == NULL || children[0]->id2ast->type != AST_WIRE) + goto skip_dynamic_range_lvalue_expansion; + if (!children[0]->id2ast->range_valid) + goto skip_dynamic_range_lvalue_expansion; + + int source_width = children[0]->id2ast->range_left - children[0]->id2ast->range_right + 1; + int source_offset = children[0]->id2ast->range_right; + int result_width = 1; + AST::AstNode *member_node = get_struct_member(children[0]); + if (member_node) { + // Clamp chunk to range of member within struct/union. + log_assert(!source_offset && !children[0]->id2ast->range_swapped); + source_width = member_node->range_left - member_node->range_right + 1; + } + + AstNode *shift_expr = NULL; + AstNode *range = children[0]->children[0]; + + if (range->children.size() == 1) { + shift_expr = range->children[0]->clone(); + } else { + shift_expr = range->children[1]->clone(); + AstNode *left_at_zero_ast = range->children[0]->clone(); + AstNode *right_at_zero_ast = range->children[1]->clone(); + while (left_at_zero_ast->simplify(true, true, false, stage, -1, false, false)) { } + while (right_at_zero_ast->simplify(true, true, false, stage, -1, false, false)) { } + if (left_at_zero_ast->type != AST_CONSTANT || right_at_zero_ast->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "Unsupported expression on dynamic range select on signal `%s'!\n", str.c_str()); + result_width = abs(int(left_at_zero_ast->integer - right_at_zero_ast->integer)) + 1; + delete left_at_zero_ast; + delete right_at_zero_ast; + } + + bool use_case_method = false; + + if (children[0]->id2ast->attributes.count(ID::nowrshmsk)) { + AstNode *node = children[0]->id2ast->attributes.at(ID::nowrshmsk); + while (node->simplify(true, false, false, stage, -1, false, false)) { } + if (node->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "Non-constant value for `nowrshmsk' attribute on `%s'!\n", children[0]->id2ast->str.c_str()); + if (node->asAttrConst().as_bool()) + use_case_method = true; + } + + if (!use_case_method && current_always->detect_latch(children[0]->str)) + use_case_method = true; + + if (use_case_method) + { + // big case block + + did_something = true; + newNode = new AstNode(AST_CASE, shift_expr); + for (int i = 0; i < source_width; i++) { + int start_bit = source_offset + i; + int end_bit = std::min(start_bit+result_width,source_width) - 1; + AstNode *cond = new AstNode(AST_COND, mkconst_int(start_bit, true)); + AstNode *lvalue = children[0]->clone(); + lvalue->delete_children(); + if (member_node) + lvalue->attributes[ID::wiretype] = member_node->clone(); + lvalue->children.push_back(new AstNode(AST_RANGE, + mkconst_int(end_bit, true), mkconst_int(start_bit, true))); + cond->children.push_back(new AstNode(AST_BLOCK, new AstNode(type, lvalue, children[1]->clone()))); + newNode->children.push_back(cond); + } + } + else + { + // mask and shift operations, disabled for now + + AstNode *wire_mask = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(source_width-1, true), mkconst_int(0, true))); + wire_mask->str = stringf("$bitselwrite$mask$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++); + wire_mask->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + wire_mask->is_logic = true; + while (wire_mask->simplify(true, false, false, 1, -1, false, false)) { } + current_ast_mod->children.push_back(wire_mask); + + AstNode *wire_data = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(source_width-1, true), mkconst_int(0, true))); + wire_data->str = stringf("$bitselwrite$data$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++); + wire_data->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + wire_data->is_logic = true; + while (wire_data->simplify(true, false, false, 1, -1, false, false)) { } + current_ast_mod->children.push_back(wire_data); + + int shamt_width_hint = -1; + bool shamt_sign_hint = true; + shift_expr->detectSignWidth(shamt_width_hint, shamt_sign_hint); + + AstNode *wire_sel = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(shamt_width_hint-1, true), mkconst_int(0, true))); + wire_sel->str = stringf("$bitselwrite$sel$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++); + wire_sel->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + wire_sel->is_logic = true; + wire_sel->is_signed = shamt_sign_hint; + while (wire_sel->simplify(true, false, false, 1, -1, false, false)) { } + current_ast_mod->children.push_back(wire_sel); + + did_something = true; + newNode = new AstNode(AST_BLOCK); + + AstNode *lvalue = children[0]->clone(); + lvalue->delete_children(); + if (member_node) + lvalue->attributes[ID::wiretype] = member_node->clone(); + + AstNode *ref_mask = new AstNode(AST_IDENTIFIER); + ref_mask->str = wire_mask->str; + ref_mask->id2ast = wire_mask; + ref_mask->was_checked = true; + + AstNode *ref_data = new AstNode(AST_IDENTIFIER); + ref_data->str = wire_data->str; + ref_data->id2ast = wire_data; + ref_data->was_checked = true; + + AstNode *ref_sel = new AstNode(AST_IDENTIFIER); + ref_sel->str = wire_sel->str; + ref_sel->id2ast = wire_sel; + ref_sel->was_checked = true; + + AstNode *old_data = lvalue->clone(); + if (type == AST_ASSIGN_LE) + old_data->lookahead = true; + + AstNode *s = new AstNode(AST_ASSIGN_EQ, ref_sel->clone(), shift_expr); + newNode->children.push_back(s); + + AstNode *shamt = ref_sel; + + // convert to signed while preserving the sign and value + shamt = new AstNode(AST_CAST_SIZE, mkconst_int(shamt_width_hint + 1, true), shamt); + shamt = new AstNode(AST_TO_SIGNED, shamt); + + // offset the shift amount by the lower bound of the dimension + int start_bit = source_offset; + shamt = new AstNode(AST_SUB, shamt, mkconst_int(start_bit, true)); + + // reflect the shift amount if the dimension is swapped + if (children[0]->id2ast->range_swapped) + shamt = new AstNode(AST_SUB, mkconst_int(source_width - result_width, true), shamt); + + // AST_SHIFT uses negative amounts for shifting left + shamt = new AstNode(AST_NEG, shamt); + + AstNode *t; + + t = mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false); + t = new AstNode(AST_SHIFT, t, shamt->clone()); + t = new AstNode(AST_ASSIGN_EQ, ref_mask->clone(), t); + newNode->children.push_back(t); + + t = new AstNode(AST_BIT_AND, mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false), children[1]->clone()); + t = new AstNode(AST_SHIFT, t, shamt); + t = new AstNode(AST_ASSIGN_EQ, ref_data->clone(), t); + newNode->children.push_back(t); + + t = new AstNode(AST_BIT_AND, old_data, new AstNode(AST_BIT_NOT, ref_mask)); + t = new AstNode(AST_BIT_OR, t, ref_data); + t = new AstNode(type, lvalue, t); + newNode->children.push_back(t); + } + + goto apply_newNode; + } +skip_dynamic_range_lvalue_expansion:; + + if (stage > 1 && (type == AST_ASSERT || type == AST_ASSUME || type == AST_LIVE || type == AST_FAIR || type == AST_COVER) && current_block != NULL) + { + std::stringstream sstr; + sstr << "$formal$" << RTLIL::encode_filename(filename) << ":" << location.first_line << "$" << (autoidx++); + std::string id_check = sstr.str() + "_CHECK", id_en = sstr.str() + "_EN"; + + AstNode *wire_check = new AstNode(AST_WIRE); + wire_check->str = id_check; + wire_check->was_checked = true; + current_ast_mod->children.push_back(wire_check); + current_scope[wire_check->str] = wire_check; + while (wire_check->simplify(true, false, false, 1, -1, false, false)) { } + + AstNode *wire_en = new AstNode(AST_WIRE); + wire_en->str = id_en; + wire_en->was_checked = true; + current_ast_mod->children.push_back(wire_en); + if (current_always_clocked) { + current_ast_mod->children.push_back(new AstNode(AST_INITIAL, new AstNode(AST_BLOCK, new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), AstNode::mkconst_int(0, false, 1))))); + current_ast_mod->children.back()->children[0]->children[0]->children[0]->str = id_en; + current_ast_mod->children.back()->children[0]->children[0]->children[0]->was_checked = true; + } + current_scope[wire_en->str] = wire_en; + while (wire_en->simplify(true, false, false, 1, -1, false, false)) { } + + AstNode *check_defval; + if (type == AST_LIVE || type == AST_FAIR) { + check_defval = new AstNode(AST_REDUCE_BOOL, children[0]->clone()); + } else { + std::vector<RTLIL::State> x_bit; + x_bit.push_back(RTLIL::State::Sx); + check_defval = mkconst_bits(x_bit, false); + } + + AstNode *assign_check = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), check_defval); + assign_check->children[0]->str = id_check; + assign_check->children[0]->was_checked = true; + + AstNode *assign_en = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), mkconst_int(0, false, 1)); + assign_en->children[0]->str = id_en; + assign_en->children[0]->was_checked = true; + + AstNode *default_signals = new AstNode(AST_BLOCK); + default_signals->children.push_back(assign_check); + default_signals->children.push_back(assign_en); + current_top_block->children.insert(current_top_block->children.begin(), default_signals); + + if (type == AST_LIVE || type == AST_FAIR) { + assign_check = nullptr; + } else { + assign_check = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), new AstNode(AST_REDUCE_BOOL, children[0]->clone())); + assign_check->children[0]->str = id_check; + assign_check->children[0]->was_checked = true; + } + + if (current_always == nullptr || current_always->type != AST_INITIAL) { + assign_en = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), mkconst_int(1, false, 1)); + } else { + assign_en = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), new AstNode(AST_FCALL)); + assign_en->children[1]->str = "\\$initstate"; + } + assign_en->children[0]->str = id_en; + assign_en->children[0]->was_checked = true; + + newNode = new AstNode(AST_BLOCK); + if (assign_check != nullptr) + newNode->children.push_back(assign_check); + newNode->children.push_back(assign_en); + + AstNode *assertnode = new AstNode(type); + assertnode->location = location; + assertnode->str = str; + assertnode->children.push_back(new AstNode(AST_IDENTIFIER)); + assertnode->children.push_back(new AstNode(AST_IDENTIFIER)); + assertnode->children[0]->str = id_check; + assertnode->children[1]->str = id_en; + assertnode->attributes.swap(attributes); + current_ast_mod->children.push_back(assertnode); + + goto apply_newNode; + } + + if (stage > 1 && (type == AST_ASSERT || type == AST_ASSUME || type == AST_LIVE || type == AST_FAIR || type == AST_COVER) && children.size() == 1) + { + children.push_back(mkconst_int(1, false, 1)); + did_something = true; + } + + // found right-hand side identifier for memory -> replace with memory read port + if (stage > 1 && type == AST_IDENTIFIER && id2ast != NULL && id2ast->type == AST_MEMORY && !in_lvalue && + children.size() == 1 && children[0]->type == AST_RANGE && children[0]->children.size() == 1) { + newNode = new AstNode(AST_MEMRD, children[0]->children[0]->clone()); + newNode->str = str; + newNode->id2ast = id2ast; + goto apply_newNode; + } + + // assignment with nontrivial member in left-hand concat expression -> split assignment + if ((type == AST_ASSIGN_EQ || type == AST_ASSIGN_LE) && children[0]->type == AST_CONCAT && width_hint > 0) + { + bool found_nontrivial_member = false; + + for (auto child : children[0]->children) { + if (child->type == AST_IDENTIFIER && child->id2ast != NULL && child->id2ast->type == AST_MEMORY) + found_nontrivial_member = true; + } + + if (found_nontrivial_member) + { + newNode = new AstNode(AST_BLOCK); + + AstNode *wire_tmp = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(width_hint-1, true), mkconst_int(0, true))); + wire_tmp->str = stringf("$splitcmplxassign$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++); + current_ast_mod->children.push_back(wire_tmp); + current_scope[wire_tmp->str] = wire_tmp; + wire_tmp->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + while (wire_tmp->simplify(true, false, false, 1, -1, false, false)) { } + wire_tmp->is_logic = true; + + AstNode *wire_tmp_id = new AstNode(AST_IDENTIFIER); + wire_tmp_id->str = wire_tmp->str; + + newNode->children.push_back(new AstNode(AST_ASSIGN_EQ, wire_tmp_id, children[1]->clone())); + newNode->children.back()->was_checked = true; + + int cursor = 0; + for (auto child : children[0]->children) + { + int child_width_hint = -1; + bool child_sign_hint = true; + child->detectSignWidth(child_width_hint, child_sign_hint); + + AstNode *rhs = wire_tmp_id->clone(); + rhs->children.push_back(new AstNode(AST_RANGE, AstNode::mkconst_int(cursor+child_width_hint-1, true), AstNode::mkconst_int(cursor, true))); + newNode->children.push_back(new AstNode(type, child->clone(), rhs)); + + cursor += child_width_hint; + } + + goto apply_newNode; + } + } + + // assignment with memory in left-hand side expression -> replace with memory write port + if (stage > 1 && (type == AST_ASSIGN_EQ || type == AST_ASSIGN_LE) && children[0]->type == AST_IDENTIFIER && + children[0]->id2ast && children[0]->id2ast->type == AST_MEMORY && children[0]->id2ast->children.size() >= 2 && + children[0]->id2ast->children[0]->range_valid && children[0]->id2ast->children[1]->range_valid && + (children[0]->children.size() == 1 || children[0]->children.size() == 2) && children[0]->children[0]->type == AST_RANGE) + { + std::stringstream sstr; + sstr << "$memwr$" << children[0]->str << "$" << RTLIL::encode_filename(filename) << ":" << location.first_line << "$" << (autoidx++); + std::string id_addr = sstr.str() + "_ADDR", id_data = sstr.str() + "_DATA", id_en = sstr.str() + "_EN"; + + int mem_width, mem_size, addr_bits; + bool mem_signed = children[0]->id2ast->is_signed; + children[0]->id2ast->meminfo(mem_width, mem_size, addr_bits); + + newNode = new AstNode(AST_BLOCK); + AstNode *defNode = new AstNode(AST_BLOCK); + + int data_range_left = children[0]->id2ast->children[0]->range_left; + int data_range_right = children[0]->id2ast->children[0]->range_right; + int mem_data_range_offset = std::min(data_range_left, data_range_right); + + int addr_width_hint = -1; + bool addr_sign_hint = true; + children[0]->children[0]->children[0]->detectSignWidthWorker(addr_width_hint, addr_sign_hint); + addr_bits = std::max(addr_bits, addr_width_hint); + + std::vector<RTLIL::State> x_bits_addr, x_bits_data, set_bits_en; + for (int i = 0; i < addr_bits; i++) + x_bits_addr.push_back(RTLIL::State::Sx); + for (int i = 0; i < mem_width; i++) + x_bits_data.push_back(RTLIL::State::Sx); + for (int i = 0; i < mem_width; i++) + set_bits_en.push_back(RTLIL::State::S1); + + AstNode *node_addr = nullptr; + if (children[0]->children[0]->children[0]->isConst()) { + node_addr = children[0]->children[0]->children[0]->clone(); + } else { + AstNode *wire_addr = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(addr_bits-1, true), mkconst_int(0, true))); + wire_addr->str = id_addr; + wire_addr->was_checked = true; + current_ast_mod->children.push_back(wire_addr); + current_scope[wire_addr->str] = wire_addr; + while (wire_addr->simplify(true, false, false, 1, -1, false, false)) { } + + AstNode *assign_addr = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), mkconst_bits(x_bits_addr, false)); + assign_addr->children[0]->str = id_addr; + assign_addr->children[0]->was_checked = true; + defNode->children.push_back(assign_addr); + + assign_addr = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), children[0]->children[0]->children[0]->clone()); + assign_addr->children[0]->str = id_addr; + assign_addr->children[0]->was_checked = true; + newNode->children.push_back(assign_addr); + + node_addr = new AstNode(AST_IDENTIFIER); + node_addr->str = id_addr; + } + + AstNode *node_data = nullptr; + if (children[0]->children.size() == 1 && children[1]->isConst()) { + node_data = children[1]->clone(); + } else { + AstNode *wire_data = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(mem_width-1, true), mkconst_int(0, true))); + wire_data->str = id_data; + wire_data->was_checked = true; + wire_data->is_signed = mem_signed; + current_ast_mod->children.push_back(wire_data); + current_scope[wire_data->str] = wire_data; + while (wire_data->simplify(true, false, false, 1, -1, false, false)) { } + + AstNode *assign_data = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), mkconst_bits(x_bits_data, false)); + assign_data->children[0]->str = id_data; + assign_data->children[0]->was_checked = true; + defNode->children.push_back(assign_data); + + node_data = new AstNode(AST_IDENTIFIER); + node_data->str = id_data; + } + + AstNode *wire_en = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(mem_width-1, true), mkconst_int(0, true))); + wire_en->str = id_en; + wire_en->was_checked = true; + current_ast_mod->children.push_back(wire_en); + current_scope[wire_en->str] = wire_en; + while (wire_en->simplify(true, false, false, 1, -1, false, false)) { } + + AstNode *assign_en_first = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), mkconst_int(0, false, mem_width)); + assign_en_first->children[0]->str = id_en; + assign_en_first->children[0]->was_checked = true; + defNode->children.push_back(assign_en_first); + + AstNode *node_en = new AstNode(AST_IDENTIFIER); + node_en->str = id_en; + + if (!defNode->children.empty()) + current_top_block->children.insert(current_top_block->children.begin(), defNode); + else + delete defNode; + + AstNode *assign_data = nullptr; + AstNode *assign_en = nullptr; + if (children[0]->children.size() == 2) + { + if (children[0]->children[1]->range_valid) + { + int offset = children[0]->children[1]->range_right; + int width = children[0]->children[1]->range_left - offset + 1; + offset -= mem_data_range_offset; + + std::vector<RTLIL::State> padding_x(offset, RTLIL::State::Sx); + + assign_data = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), + new AstNode(AST_CONCAT, mkconst_bits(padding_x, false), children[1]->clone())); + assign_data->children[0]->str = id_data; + assign_data->children[0]->was_checked = true; + + for (int i = 0; i < mem_width; i++) + set_bits_en[i] = offset <= i && i < offset+width ? RTLIL::State::S1 : RTLIL::State::S0; + assign_en = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), mkconst_bits(set_bits_en, false)); + assign_en->children[0]->str = id_en; + assign_en->children[0]->was_checked = true; + } + else + { + AstNode *the_range = children[0]->children[1]; + AstNode *left_at_zero_ast = the_range->children[0]->clone(); + AstNode *right_at_zero_ast = the_range->children.size() >= 2 ? the_range->children[1]->clone() : left_at_zero_ast->clone(); + AstNode *offset_ast = right_at_zero_ast->clone(); + + if (mem_data_range_offset) + offset_ast = new AstNode(AST_SUB, offset_ast, mkconst_int(mem_data_range_offset, true)); + + while (left_at_zero_ast->simplify(true, true, false, 1, -1, false, false)) { } + while (right_at_zero_ast->simplify(true, true, false, 1, -1, false, false)) { } + if (left_at_zero_ast->type != AST_CONSTANT || right_at_zero_ast->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "Unsupported expression on dynamic range select on signal `%s'!\n", str.c_str()); + int width = abs(int(left_at_zero_ast->integer - right_at_zero_ast->integer)) + 1; + + assign_data = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), + new AstNode(AST_SHIFT_LEFT, children[1]->clone(), offset_ast->clone())); + assign_data->children[0]->str = id_data; + assign_data->children[0]->was_checked = true; + + for (int i = 0; i < mem_width; i++) + set_bits_en[i] = i < width ? RTLIL::State::S1 : RTLIL::State::S0; + assign_en = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), + new AstNode(AST_SHIFT_LEFT, mkconst_bits(set_bits_en, false), offset_ast->clone())); + assign_en->children[0]->str = id_en; + assign_en->children[0]->was_checked = true; + + delete left_at_zero_ast; + delete right_at_zero_ast; + delete offset_ast; + } + } + else + { + if (!(children[0]->children.size() == 1 && children[1]->isConst())) { + assign_data = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), children[1]->clone()); + assign_data->children[0]->str = id_data; + assign_data->children[0]->was_checked = true; + } + + assign_en = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), mkconst_bits(set_bits_en, false)); + assign_en->children[0]->str = id_en; + assign_en->children[0]->was_checked = true; + } + if (assign_data) + newNode->children.push_back(assign_data); + if (assign_en) + newNode->children.push_back(assign_en); + + AstNode *wrnode; + if (current_always->type == AST_INITIAL) + wrnode = new AstNode(AST_MEMINIT, node_addr, node_data, node_en, mkconst_int(1, false)); + else + wrnode = new AstNode(AST_MEMWR, node_addr, node_data, node_en); + wrnode->str = children[0]->str; + wrnode->id2ast = children[0]->id2ast; + wrnode->location = location; + if (wrnode->type == AST_MEMWR) { + int portid = current_memwr_count[wrnode->str]++; + wrnode->children.push_back(mkconst_int(portid, false)); + std::vector<RTLIL::State> priority_mask; + for (int i = 0; i < portid; i++) { + bool has_prio = current_memwr_visible[wrnode->str].count(i); + priority_mask.push_back(State(has_prio)); + } + wrnode->children.push_back(mkconst_bits(priority_mask, false)); + current_memwr_visible[wrnode->str].insert(portid); + current_always->children.push_back(wrnode); + } else { + current_ast_mod->children.push_back(wrnode); + } + + if (newNode->children.empty()) { + delete newNode; + newNode = new AstNode(); + } + goto apply_newNode; + } + + // replace function and task calls with the code from the function or task + if ((type == AST_FCALL || type == AST_TCALL) && !str.empty()) + { + if (type == AST_FCALL) + { + if (str == "\\$initstate") + { + int myidx = autoidx++; + + AstNode *wire = new AstNode(AST_WIRE); + wire->str = stringf("$initstate$%d_wire", myidx); + current_ast_mod->children.push_back(wire); + while (wire->simplify(true, false, false, 1, -1, false, false)) { } + + AstNode *cell = new AstNode(AST_CELL, new AstNode(AST_CELLTYPE), new AstNode(AST_ARGUMENT, new AstNode(AST_IDENTIFIER))); + cell->str = stringf("$initstate$%d", myidx); + cell->children[0]->str = "$initstate"; + cell->children[1]->str = "\\Y"; + cell->children[1]->children[0]->str = wire->str; + cell->children[1]->children[0]->id2ast = wire; + current_ast_mod->children.push_back(cell); + while (cell->simplify(true, false, false, 1, -1, false, false)) { } + + newNode = new AstNode(AST_IDENTIFIER); + newNode->str = wire->str; + newNode->id2ast = wire; + goto apply_newNode; + } + + if (str == "\\$past") + { + if (width_hint < 0) + goto replace_fcall_later; + + int num_steps = 1; + + if (GetSize(children) != 1 && GetSize(children) != 2) + log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1 or 2.\n", + RTLIL::unescape_id(str).c_str(), int(children.size())); + + if (!current_always_clocked) + log_file_error(filename, location.first_line, "System function %s is only allowed in clocked blocks.\n", + RTLIL::unescape_id(str).c_str()); + + if (GetSize(children) == 2) + { + AstNode *buf = children[1]->clone(); + while (buf->simplify(true, false, false, stage, -1, false, false)) { } + if (buf->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-constant value.\n", str.c_str()); + + num_steps = buf->asInt(true); + delete buf; + } + + AstNode *block = nullptr; + + for (auto child : current_always->children) + if (child->type == AST_BLOCK) + block = child; + + log_assert(block != nullptr); + + if (num_steps == 0) { + newNode = children[0]->clone(); + goto apply_newNode; + } + + int myidx = autoidx++; + AstNode *outreg = nullptr; + + for (int i = 0; i < num_steps; i++) + { + AstNode *reg = new AstNode(AST_WIRE, new AstNode(AST_RANGE, + mkconst_int(width_hint-1, true), mkconst_int(0, true))); + + reg->str = stringf("$past$%s:%d$%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, myidx, i); + reg->is_reg = true; + reg->is_signed = sign_hint; + + current_ast_mod->children.push_back(reg); + + while (reg->simplify(true, false, false, 1, -1, false, false)) { } + + AstNode *regid = new AstNode(AST_IDENTIFIER); + regid->str = reg->str; + regid->id2ast = reg; + regid->was_checked = true; + + AstNode *rhs = nullptr; + + if (outreg == nullptr) { + rhs = children.at(0)->clone(); + } else { + rhs = new AstNode(AST_IDENTIFIER); + rhs->str = outreg->str; + rhs->id2ast = outreg; + } + + block->children.push_back(new AstNode(AST_ASSIGN_LE, regid, rhs)); + outreg = reg; + } + + newNode = new AstNode(AST_IDENTIFIER); + newNode->str = outreg->str; + newNode->id2ast = outreg; + goto apply_newNode; + } + + if (str == "\\$stable" || str == "\\$rose" || str == "\\$fell" || str == "\\$changed") + { + if (GetSize(children) != 1) + log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1.\n", + RTLIL::unescape_id(str).c_str(), int(children.size())); + + if (!current_always_clocked) + log_file_error(filename, location.first_line, "System function %s is only allowed in clocked blocks.\n", + RTLIL::unescape_id(str).c_str()); + + AstNode *present = children.at(0)->clone(); + AstNode *past = clone(); + past->str = "\\$past"; + + if (str == "\\$stable") + newNode = new AstNode(AST_EQ, past, present); + + else if (str == "\\$changed") + newNode = new AstNode(AST_NE, past, present); + + else if (str == "\\$rose") + newNode = new AstNode(AST_LOGIC_AND, + new AstNode(AST_LOGIC_NOT, new AstNode(AST_BIT_AND, past, mkconst_int(1,false))), + new AstNode(AST_BIT_AND, present, mkconst_int(1,false))); + + else if (str == "\\$fell") + newNode = new AstNode(AST_LOGIC_AND, + new AstNode(AST_BIT_AND, past, mkconst_int(1,false)), + new AstNode(AST_LOGIC_NOT, new AstNode(AST_BIT_AND, present, mkconst_int(1,false)))); + + else + log_abort(); + + goto apply_newNode; + } + + // $anyconst and $anyseq are mapped in AstNode::genRTLIL() + if (str == "\\$anyconst" || str == "\\$anyseq" || str == "\\$allconst" || str == "\\$allseq") { + recursion_counter--; + return false; + } + + if (str == "\\$clog2") + { + if (children.size() != 1) + log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1.\n", + RTLIL::unescape_id(str).c_str(), int(children.size())); + + AstNode *buf = children[0]->clone(); + while (buf->simplify(true, false, false, stage, width_hint, sign_hint, false)) { } + if (buf->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-constant value.\n", str.c_str()); + + RTLIL::Const arg_value = buf->bitsAsConst(); + if (arg_value.as_bool()) + arg_value = const_sub(arg_value, 1, false, false, GetSize(arg_value)); + delete buf; + + uint32_t result = 0; + for (size_t i = 0; i < arg_value.bits.size(); i++) + if (arg_value.bits.at(i) == RTLIL::State::S1) + result = i + 1; + + newNode = mkconst_int(result, true); + goto apply_newNode; + } + + if (str == "\\$size" || str == "\\$bits" || str == "\\$high" || str == "\\$low" || str == "\\$left" || str == "\\$right") + { + int dim = 1; + if (str == "\\$bits") { + if (children.size() != 1) + log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1.\n", + RTLIL::unescape_id(str).c_str(), int(children.size())); + } else { + if (children.size() != 1 && children.size() != 2) + log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1 or 2.\n", + RTLIL::unescape_id(str).c_str(), int(children.size())); + if (children.size() == 2) { + AstNode *buf = children[1]->clone(); + // Evaluate constant expression + while (buf->simplify(true, false, false, stage, width_hint, sign_hint, false)) { } + dim = buf->asInt(false); + delete buf; + } + } + AstNode *buf = children[0]->clone(); + int mem_depth = 1; + int result, high = 0, low = 0, left = 0, right = 0, width = 1; // defaults for a simple wire + AstNode *id_ast = NULL; + + // Is this needed? + //while (buf->simplify(true, false, false, stage, width_hint, sign_hint, false)) { } + buf->detectSignWidth(width_hint, sign_hint); + + if (buf->type == AST_IDENTIFIER) { + id_ast = buf->id2ast; + if (id_ast == NULL && current_scope.count(buf->str)) + id_ast = current_scope.at(buf->str); + if (!id_ast) + log_file_error(filename, location.first_line, "Failed to resolve identifier %s for width detection!\n", buf->str.c_str()); + + // Check for item in packed struct / union + AST::AstNode *item_node = get_struct_member(buf); + if (id_ast->type == AST_WIRE && item_node) { + // The dimension of the original array expression is saved in the 'integer' field + dim += buf->integer; + if (item_node->multirange_dimensions.empty()) { + if (dim != 1) + log_file_error(filename, location.first_line, "Dimension %d out of range in `%s', as it only has one dimension!\n", dim, item_node->str.c_str()); + left = high = item_node->range_left; + right = low = item_node->range_right; + } else { + int dims = GetSize(item_node->multirange_dimensions)/2; + if (dim < 1 || dim > dims) + log_file_error(filename, location.first_line, "Dimension %d out of range in `%s', as it only has dimensions 1..%d!\n", dim, item_node->str.c_str(), dims); + right = low = get_struct_range_offset(item_node, dim - 1); + left = high = low + get_struct_range_width(item_node, dim - 1) - 1; + if (item_node->multirange_swapped[dim - 1]) { + std::swap(left, right); + } + for (int i = dim; i < dims; i++) { + mem_depth *= get_struct_range_width(item_node, i); + } + } + } + // Otherwise, we have 4 cases: + // wire x; ==> AST_WIRE, no AST_RANGE children + // wire [1:0]x; ==> AST_WIRE, AST_RANGE children + // wire [1:0]x[1:0]; ==> AST_MEMORY, two AST_RANGE children (1st for packed, 2nd for unpacked) + // wire [1:0]x[1:0][1:0]; ==> AST_MEMORY, one AST_RANGE child (0) for packed, then AST_MULTIRANGE child (1) for unpacked + // (updated: actually by the time we are here, AST_MULTIRANGE is converted into one big AST_RANGE) + // case 0 handled by default + else if ((id_ast->type == AST_WIRE || id_ast->type == AST_MEMORY) && id_ast->children.size() > 0) { + // handle packed array left/right for case 1, and cases 2/3 when requesting the last dimension (packed side) + AstNode *wire_range = id_ast->children[0]; + left = wire_range->children[0]->integer; + right = wire_range->children[1]->integer; + high = max(left, right); + low = min(left, right); + } + if (id_ast->type == AST_MEMORY) { + // a slice of our identifier means we advance to the next dimension, e.g. $size(a[3]) + if (buf->children.size() > 0) { + // something is hanging below this identifier + if (buf->children[0]->type == AST_RANGE && buf->integer == 0) + // if integer == 0, this node was originally created as AST_RANGE so it's dimension is 1 + dim++; + // more than one range, e.g. $size(a[3][2]) + else // created an AST_MULTIRANGE, converted to AST_RANGE, but original dimension saved in 'integer' field + dim += buf->integer; // increment by multirange size + } + + // We got here only if the argument is a memory + // Otherwise $size() and $bits() return the expression width + AstNode *mem_range = id_ast->children[1]; + if (str == "\\$bits") { + if (mem_range->type == AST_RANGE) { + if (!mem_range->range_valid) + log_file_error(filename, location.first_line, "Failed to detect width of memory access `%s'!\n", buf->str.c_str()); + mem_depth = mem_range->range_left - mem_range->range_right + 1; + } else + log_file_error(filename, location.first_line, "Unknown memory depth AST type in `%s'!\n", buf->str.c_str()); + } else { + // $size(), $left(), $right(), $high(), $low() + int dims = 1; + if (mem_range->type == AST_RANGE) { + if (id_ast->multirange_dimensions.empty()) { + if (!mem_range->range_valid) + log_file_error(filename, location.first_line, "Failed to detect width of memory access `%s'!\n", buf->str.c_str()); + if (dim == 1) { + left = mem_range->range_right; + right = mem_range->range_left; + high = max(left, right); + low = min(left, right); + } + } else { + dims = GetSize(id_ast->multirange_dimensions)/2; + if (dim <= dims) { + width_hint = id_ast->multirange_dimensions[2*dim-1]; + high = id_ast->multirange_dimensions[2*dim-2] + id_ast->multirange_dimensions[2*dim-1] - 1; + low = id_ast->multirange_dimensions[2*dim-2]; + if (id_ast->multirange_swapped[dim-1]) { + left = low; + right = high; + } else { + right = low; + left = high; + } + } else if ((dim > dims+1) || (dim < 0)) + log_file_error(filename, location.first_line, "Dimension %d out of range in `%s', as it only has dimensions 1..%d!\n", dim, buf->str.c_str(), dims+1); + } + } else { + log_file_error(filename, location.first_line, "Unknown memory depth AST type in `%s'!\n", buf->str.c_str()); + } + } + } + width = high - low + 1; + } else { + width = width_hint; + } + delete buf; + if (str == "\\$high") + result = high; + else if (str == "\\$low") + result = low; + else if (str == "\\$left") + result = left; + else if (str == "\\$right") + result = right; + else if (str == "\\$size") + result = width; + else { // str == "\\$bits" + result = width * mem_depth; + } + newNode = mkconst_int(result, true); + goto apply_newNode; + } + + if (str == "\\$ln" || str == "\\$log10" || str == "\\$exp" || str == "\\$sqrt" || str == "\\$pow" || + str == "\\$floor" || str == "\\$ceil" || str == "\\$sin" || str == "\\$cos" || str == "\\$tan" || + str == "\\$asin" || str == "\\$acos" || str == "\\$atan" || str == "\\$atan2" || str == "\\$hypot" || + str == "\\$sinh" || str == "\\$cosh" || str == "\\$tanh" || str == "\\$asinh" || str == "\\$acosh" || str == "\\$atanh" || + str == "\\$rtoi" || str == "\\$itor") + { + bool func_with_two_arguments = str == "\\$pow" || str == "\\$atan2" || str == "\\$hypot"; + double x = 0, y = 0; + + if (func_with_two_arguments) { + if (children.size() != 2) + log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 2.\n", + RTLIL::unescape_id(str).c_str(), int(children.size())); + } else { + if (children.size() != 1) + log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1.\n", + RTLIL::unescape_id(str).c_str(), int(children.size())); + } + + if (children.size() >= 1) { + while (children[0]->simplify(true, false, false, stage, width_hint, sign_hint, false)) { } + if (!children[0]->isConst()) + log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-constant argument.\n", + RTLIL::unescape_id(str).c_str()); + int child_width_hint = width_hint; + bool child_sign_hint = sign_hint; + children[0]->detectSignWidth(child_width_hint, child_sign_hint); + x = children[0]->asReal(child_sign_hint); + } + + if (children.size() >= 2) { + while (children[1]->simplify(true, false, false, stage, width_hint, sign_hint, false)) { } + if (!children[1]->isConst()) + log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-constant argument.\n", + RTLIL::unescape_id(str).c_str()); + int child_width_hint = width_hint; + bool child_sign_hint = sign_hint; + children[1]->detectSignWidth(child_width_hint, child_sign_hint); + y = children[1]->asReal(child_sign_hint); + } + + if (str == "\\$rtoi") { + newNode = AstNode::mkconst_int(x, true); + } else { + newNode = new AstNode(AST_REALVALUE); + if (str == "\\$ln") newNode->realvalue = ::log(x); + else if (str == "\\$log10") newNode->realvalue = ::log10(x); + else if (str == "\\$exp") newNode->realvalue = ::exp(x); + else if (str == "\\$sqrt") newNode->realvalue = ::sqrt(x); + else if (str == "\\$pow") newNode->realvalue = ::pow(x, y); + else if (str == "\\$floor") newNode->realvalue = ::floor(x); + else if (str == "\\$ceil") newNode->realvalue = ::ceil(x); + else if (str == "\\$sin") newNode->realvalue = ::sin(x); + else if (str == "\\$cos") newNode->realvalue = ::cos(x); + else if (str == "\\$tan") newNode->realvalue = ::tan(x); + else if (str == "\\$asin") newNode->realvalue = ::asin(x); + else if (str == "\\$acos") newNode->realvalue = ::acos(x); + else if (str == "\\$atan") newNode->realvalue = ::atan(x); + else if (str == "\\$atan2") newNode->realvalue = ::atan2(x, y); + else if (str == "\\$hypot") newNode->realvalue = ::hypot(x, y); + else if (str == "\\$sinh") newNode->realvalue = ::sinh(x); + else if (str == "\\$cosh") newNode->realvalue = ::cosh(x); + else if (str == "\\$tanh") newNode->realvalue = ::tanh(x); + else if (str == "\\$asinh") newNode->realvalue = ::asinh(x); + else if (str == "\\$acosh") newNode->realvalue = ::acosh(x); + else if (str == "\\$atanh") newNode->realvalue = ::atanh(x); + else if (str == "\\$itor") newNode->realvalue = x; + else log_abort(); + } + goto apply_newNode; + } + + if (str == "\\$sformatf") { + AstNode *node_string = children[0]; + while (node_string->simplify(true, false, false, stage, width_hint, sign_hint, false)) { } + if (node_string->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-constant 1st argument.\n", str.c_str()); + std::string sformat = node_string->bitsAsConst().decode_string(); + std::string sout = process_format_str(sformat, 1, stage, width_hint, sign_hint); + newNode = AstNode::mkconst_str(sout); + goto apply_newNode; + } + + if (str == "\\$countbits") { + if (children.size() < 2) + log_file_error(filename, location.first_line, "System function %s got %d arguments, expected at least 2.\n", + RTLIL::unescape_id(str).c_str(), int(children.size())); + + std::vector<RTLIL::State> control_bits; + + // Determine which bits to count + for (size_t i = 1; i < children.size(); i++) { + AstNode *node = children[i]; + while (node->simplify(true, false, false, stage, -1, false, false)) { } + if (node->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-constant control bit argument.\n", str.c_str()); + if (node->bits.size() != 1) + log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with control bit width != 1.\n", str.c_str()); + control_bits.push_back(node->bits[0]); + } + + // Detect width of exp (first argument of $countbits) + int exp_width = -1; + bool exp_sign = false; + AstNode *exp = children[0]; + exp->detectSignWidth(exp_width, exp_sign, NULL); + + newNode = mkconst_int(0, false); + + for (int i = 0; i < exp_width; i++) { + // Generate nodes for: exp << i >> ($size(exp) - 1) + // ^^ ^^ + AstNode *lsh_node = new AstNode(AST_SHIFT_LEFT, exp->clone(), mkconst_int(i, false)); + AstNode *rsh_node = new AstNode(AST_SHIFT_RIGHT, lsh_node, mkconst_int(exp_width - 1, false)); + + AstNode *or_node = nullptr; + + for (RTLIL::State control_bit : control_bits) { + // Generate node for: (exp << i >> ($size(exp) - 1)) === control_bit + // ^^^ + AstNode *eq_node = new AstNode(AST_EQX, rsh_node->clone(), mkconst_bits({control_bit}, false)); + + // Or the result for each checked bit value + if (or_node) + or_node = new AstNode(AST_LOGIC_OR, or_node, eq_node); + else + or_node = eq_node; + } + + // We should have at least one element in control_bits, + // because we checked for the number of arguments above + log_assert(or_node != nullptr); + + delete rsh_node; + + // Generate node for adding with result of previous bit + newNode = new AstNode(AST_ADD, newNode, or_node); + } + + goto apply_newNode; + } + + if (str == "\\$countones" || str == "\\$isunknown" || str == "\\$onehot" || str == "\\$onehot0") { + if (children.size() != 1) + log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1.\n", + RTLIL::unescape_id(str).c_str(), int(children.size())); + + AstNode *countbits = clone(); + countbits->str = "\\$countbits"; + + if (str == "\\$countones") { + countbits->children.push_back(mkconst_bits({RTLIL::State::S1}, false)); + newNode = countbits; + } else if (str == "\\$isunknown") { + countbits->children.push_back(mkconst_bits({RTLIL::Sx}, false)); + countbits->children.push_back(mkconst_bits({RTLIL::Sz}, false)); + newNode = new AstNode(AST_GT, countbits, mkconst_int(0, false)); + } else if (str == "\\$onehot") { + countbits->children.push_back(mkconst_bits({RTLIL::State::S1}, false)); + newNode = new AstNode(AST_EQ, countbits, mkconst_int(1, false)); + } else if (str == "\\$onehot0") { + countbits->children.push_back(mkconst_bits({RTLIL::State::S1}, false)); + newNode = new AstNode(AST_LE, countbits, mkconst_int(1, false)); + } else { + log_abort(); + } + + goto apply_newNode; + } + + if (current_scope.count(str) != 0 && current_scope[str]->type == AST_DPI_FUNCTION) + { + AstNode *dpi_decl = current_scope[str]; + + std::string rtype, fname; + std::vector<std::string> argtypes; + std::vector<AstNode*> args; + + rtype = RTLIL::unescape_id(dpi_decl->children.at(0)->str); + fname = RTLIL::unescape_id(dpi_decl->children.at(1)->str); + + for (int i = 2; i < GetSize(dpi_decl->children); i++) + { + if (i-2 >= GetSize(children)) + log_file_error(filename, location.first_line, "Insufficient number of arguments in DPI function call.\n"); + + argtypes.push_back(RTLIL::unescape_id(dpi_decl->children.at(i)->str)); + args.push_back(children.at(i-2)->clone()); + while (args.back()->simplify(true, false, false, stage, -1, false, true)) { } + + if (args.back()->type != AST_CONSTANT && args.back()->type != AST_REALVALUE) + log_file_error(filename, location.first_line, "Failed to evaluate DPI function with non-constant argument.\n"); + } + + newNode = dpi_call(rtype, fname, argtypes, args); + + for (auto arg : args) + delete arg; + + goto apply_newNode; + } + + if (current_scope.count(str) == 0) + str = try_pop_module_prefix(); + if (current_scope.count(str) == 0 || current_scope[str]->type != AST_FUNCTION) + log_file_error(filename, location.first_line, "Can't resolve function name `%s'.\n", str.c_str()); + } + + if (type == AST_TCALL) + { + if (str == "$finish" || str == "$stop") + { + if (!current_always || current_always->type != AST_INITIAL) + log_file_error(filename, location.first_line, "System task `%s' outside initial block is unsupported.\n", str.c_str()); + + log_file_error(filename, location.first_line, "System task `%s' executed.\n", str.c_str()); + } + + if (str == "\\$readmemh" || str == "\\$readmemb") + { + if (GetSize(children) < 2 || GetSize(children) > 4) + log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 2-4.\n", + RTLIL::unescape_id(str).c_str(), int(children.size())); + + AstNode *node_filename = children[0]->clone(); + while (node_filename->simplify(true, false, false, stage, width_hint, sign_hint, false)) { } + if (node_filename->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-constant 1st argument.\n", str.c_str()); + + AstNode *node_memory = children[1]->clone(); + while (node_memory->simplify(true, false, false, stage, width_hint, sign_hint, false)) { } + if (node_memory->type != AST_IDENTIFIER || node_memory->id2ast == nullptr || node_memory->id2ast->type != AST_MEMORY) + log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-memory 2nd argument.\n", str.c_str()); + + int start_addr = -1, finish_addr = -1; + + if (GetSize(children) > 2) { + AstNode *node_addr = children[2]->clone(); + while (node_addr->simplify(true, false, false, stage, width_hint, sign_hint, false)) { } + if (node_addr->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-constant 3rd argument.\n", str.c_str()); + start_addr = int(node_addr->asInt(false)); + } + + if (GetSize(children) > 3) { + AstNode *node_addr = children[3]->clone(); + while (node_addr->simplify(true, false, false, stage, width_hint, sign_hint, false)) { } + if (node_addr->type != AST_CONSTANT) + log_file_error(filename, location.first_line, "Failed to evaluate system function `%s' with non-constant 4th argument.\n", str.c_str()); + finish_addr = int(node_addr->asInt(false)); + } + + bool unconditional_init = false; + if (current_always->type == AST_INITIAL) { + pool<AstNode*> queue; + log_assert(current_always->children[0]->type == AST_BLOCK); + queue.insert(current_always->children[0]); + while (!unconditional_init && !queue.empty()) { + pool<AstNode*> next_queue; + for (auto n : queue) + for (auto c : n->children) { + if (c == this) + unconditional_init = true; + next_queue.insert(c); + } + next_queue.swap(queue); + } + } + + newNode = readmem(str == "\\$readmemh", node_filename->bitsAsConst().decode_string(), node_memory->id2ast, start_addr, finish_addr, unconditional_init); + delete node_filename; + delete node_memory; + goto apply_newNode; + } + + if (current_scope.count(str) == 0) + str = try_pop_module_prefix(); + if (current_scope.count(str) == 0 || current_scope[str]->type != AST_TASK) + log_file_error(filename, location.first_line, "Can't resolve task name `%s'.\n", str.c_str()); + } + + + std::stringstream sstr; + sstr << str << "$func$" << RTLIL::encode_filename(filename) << ":" << location.first_line << "$" << (autoidx++) << '.'; + std::string prefix = sstr.str(); + + AstNode *decl = current_scope[str]; + if (unevaluated_tern_branch && decl->is_recursive_function()) + goto replace_fcall_later; + decl = decl->clone(); + decl->replace_result_wire_name_in_function(str, "$result"); // enables recursion + decl->expand_genblock(prefix); + + if (decl->type == AST_FUNCTION && !decl->attributes.count(ID::via_celltype)) + { + bool require_const_eval = decl->has_const_only_constructs(); + bool all_args_const = true; + for (auto child : children) { + while (child->simplify(true, false, false, 1, -1, false, true)) { } + if (child->type != AST_CONSTANT && child->type != AST_REALVALUE) + all_args_const = false; + } + + if (all_args_const) { + AstNode *func_workspace = decl->clone(); + func_workspace->str = prefix_id(prefix, "$result"); + newNode = func_workspace->eval_const_function(this, in_param || require_const_eval); + delete func_workspace; + if (newNode) { + delete decl; + goto apply_newNode; + } + } + + if (in_param) + log_file_error(filename, location.first_line, "Non-constant function call in constant expression.\n"); + if (require_const_eval) + log_file_error(filename, location.first_line, "Function %s can only be called with constant arguments.\n", str.c_str()); + } + + size_t arg_count = 0; + dict<std::string, AstNode*> wire_cache; + vector<AstNode*> new_stmts; + vector<AstNode*> output_assignments; + + if (current_block == NULL) + { + log_assert(type == AST_FCALL); + + AstNode *wire = NULL; + std::string res_name = prefix_id(prefix, "$result"); + for (auto child : decl->children) + if (child->type == AST_WIRE && child->str == res_name) + wire = child->clone(); + log_assert(wire != NULL); + + wire->port_id = 0; + wire->is_input = false; + wire->is_output = false; + + current_scope[wire->str] = wire; + current_ast_mod->children.push_back(wire); + while (wire->simplify(true, false, false, 1, -1, false, false)) { } + + AstNode *lvalue = new AstNode(AST_IDENTIFIER); + lvalue->str = wire->str; + + AstNode *always = new AstNode(AST_ALWAYS, new AstNode(AST_BLOCK, + new AstNode(AST_ASSIGN_EQ, lvalue, clone()))); + always->children[0]->children[0]->was_checked = true; + + current_ast_mod->children.push_back(always); + + goto replace_fcall_with_id; + } + + if (decl->attributes.count(ID::via_celltype)) + { + std::string celltype = decl->attributes.at(ID::via_celltype)->asAttrConst().decode_string(); + std::string outport = str; + + if (celltype.find(' ') != std::string::npos) { + int pos = celltype.find(' '); + outport = RTLIL::escape_id(celltype.substr(pos+1)); + celltype = RTLIL::escape_id(celltype.substr(0, pos)); + } else + celltype = RTLIL::escape_id(celltype); + + AstNode *cell = new AstNode(AST_CELL, new AstNode(AST_CELLTYPE)); + cell->str = prefix.substr(0, GetSize(prefix)-1); + cell->children[0]->str = celltype; + + for (auto attr : decl->attributes) + if (attr.first.str().rfind("\\via_celltype_defparam_", 0) == 0) + { + AstNode *cell_arg = new AstNode(AST_PARASET, attr.second->clone()); + cell_arg->str = RTLIL::escape_id(attr.first.substr(strlen("\\via_celltype_defparam_"))); + cell->children.push_back(cell_arg); + } + + for (auto child : decl->children) + if (child->type == AST_WIRE && (child->is_input || child->is_output || (type == AST_FCALL && child->str == str))) + { + AstNode *wire = child->clone(); + wire->port_id = 0; + wire->is_input = false; + wire->is_output = false; + current_ast_mod->children.push_back(wire); + while (wire->simplify(true, false, false, 1, -1, false, false)) { } + + AstNode *wire_id = new AstNode(AST_IDENTIFIER); + wire_id->str = wire->str; + + if ((child->is_input || child->is_output) && arg_count < children.size()) + { + AstNode *arg = children[arg_count++]->clone(); + AstNode *assign = child->is_input ? + new AstNode(AST_ASSIGN_EQ, wire_id->clone(), arg) : + new AstNode(AST_ASSIGN_EQ, arg, wire_id->clone()); + assign->children[0]->was_checked = true; + + for (auto it = current_block->children.begin(); it != current_block->children.end(); it++) { + if (*it != current_block_child) + continue; + current_block->children.insert(it, assign); + break; + } + } + + AstNode *cell_arg = new AstNode(AST_ARGUMENT, wire_id); + cell_arg->str = child->str == str ? outport : child->str; + cell->children.push_back(cell_arg); + } + + current_ast_mod->children.push_back(cell); + goto replace_fcall_with_id; + } + + for (auto child : decl->children) + if (child->type == AST_WIRE || child->type == AST_MEMORY || child->type == AST_PARAMETER || child->type == AST_LOCALPARAM || child->type == AST_ENUM_ITEM) + { + AstNode *wire = nullptr; + + if (wire_cache.count(child->str)) + { + wire = wire_cache.at(child->str); + bool contains_value = wire->type == AST_LOCALPARAM; + if (wire->children.size() == contains_value) { + for (auto c : child->children) + wire->children.push_back(c->clone()); + } else if (!child->children.empty()) { + while (child->simplify(true, false, false, stage, -1, false, false)) { } + if (GetSize(child->children) == GetSize(wire->children) - contains_value) { + for (int i = 0; i < GetSize(child->children); i++) + if (*child->children.at(i) != *wire->children.at(i + contains_value)) + goto tcall_incompatible_wires; + } else { + tcall_incompatible_wires: + log_file_error(filename, location.first_line, "Incompatible re-declaration of wire %s.\n", child->str.c_str()); + } + } + } + else + { + wire = child->clone(); + wire->port_id = 0; + wire->is_input = false; + wire->is_output = false; + wire->is_reg = true; + wire->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + if (child->type == AST_ENUM_ITEM) + wire->attributes[ID::enum_base_type] = child->attributes[ID::enum_base_type]; + + wire_cache[child->str] = wire; + + current_scope[wire->str] = wire; + current_ast_mod->children.push_back(wire); + } + + while (wire->simplify(true, false, false, 1, -1, false, false)) { } + + if ((child->is_input || child->is_output) && arg_count < children.size()) + { + AstNode *arg = children[arg_count++]->clone(); + // convert purely constant arguments into localparams + if (child->is_input && child->type == AST_WIRE && arg->type == AST_CONSTANT && node_contains_assignment_to(decl, child)) { + wire->type = AST_LOCALPARAM; + if (wire->attributes.count(ID::nosync)) + delete wire->attributes.at(ID::nosync); + wire->attributes.erase(ID::nosync); + wire->children.insert(wire->children.begin(), arg->clone()); + // args without a range implicitly have width 1 + if (wire->children.back()->type != AST_RANGE) { + // check if this wire is redeclared with an explicit size + bool uses_explicit_size = false; + for (const AstNode *other_child : decl->children) + if (other_child->type == AST_WIRE && child->str == other_child->str + && !other_child->children.empty() + && other_child->children.back()->type == AST_RANGE) { + uses_explicit_size = true; + break; + } + if (!uses_explicit_size) { + AstNode* range = new AstNode(); + range->type = AST_RANGE; + wire->children.push_back(range); + range->children.push_back(mkconst_int(0, true)); + range->children.push_back(mkconst_int(0, true)); + } + } + // updates the sizing + while (wire->simplify(true, false, false, 1, -1, false, false)) { } + delete arg; + continue; + } + AstNode *wire_id = new AstNode(AST_IDENTIFIER); + wire_id->str = wire->str; + AstNode *assign = child->is_input ? + new AstNode(AST_ASSIGN_EQ, wire_id, arg) : + new AstNode(AST_ASSIGN_EQ, arg, wire_id); + assign->children[0]->was_checked = true; + if (child->is_input) + new_stmts.push_back(assign); + else + output_assignments.push_back(assign); + } + } + + for (auto child : decl->children) + if (child->type != AST_WIRE && child->type != AST_MEMORY && child->type != AST_PARAMETER && child->type != AST_LOCALPARAM) + new_stmts.push_back(child->clone()); + + new_stmts.insert(new_stmts.end(), output_assignments.begin(), output_assignments.end()); + + for (auto it = current_block->children.begin(); ; it++) { + log_assert(it != current_block->children.end()); + if (*it == current_block_child) { + current_block->children.insert(it, new_stmts.begin(), new_stmts.end()); + break; + } + } + + replace_fcall_with_id: + delete decl; + if (type == AST_FCALL) { + delete_children(); + type = AST_IDENTIFIER; + str = prefix_id(prefix, "$result"); + } + if (type == AST_TCALL) + str = ""; + did_something = true; + } + +replace_fcall_later:; + + // perform const folding when activated + if (const_fold) + { + bool string_op; + std::vector<RTLIL::State> tmp_bits; + RTLIL::Const (*const_func)(const RTLIL::Const&, const RTLIL::Const&, bool, bool, int); + RTLIL::Const dummy_arg; + + switch (type) + { + case AST_IDENTIFIER: + if (current_scope.count(str) > 0 && (current_scope[str]->type == AST_PARAMETER || current_scope[str]->type == AST_LOCALPARAM || current_scope[str]->type == AST_ENUM_ITEM)) { + if (current_scope[str]->children[0]->type == AST_CONSTANT) { + if (children.size() != 0 && children[0]->type == AST_RANGE && children[0]->range_valid) { + std::vector<RTLIL::State> data; + bool param_upto = current_scope[str]->range_valid && current_scope[str]->range_swapped; + int param_offset = current_scope[str]->range_valid ? current_scope[str]->range_right : 0; + int param_width = current_scope[str]->range_valid ? current_scope[str]->range_left - current_scope[str]->range_right + 1 : + GetSize(current_scope[str]->children[0]->bits); + int tmp_range_left = children[0]->range_left, tmp_range_right = children[0]->range_right; + if (param_upto) { + tmp_range_left = (param_width + 2*param_offset) - children[0]->range_right - 1; + tmp_range_right = (param_width + 2*param_offset) - children[0]->range_left - 1; + } + AST::AstNode *member_node = get_struct_member(this); + int chunk_offset = member_node ? member_node->range_right : 0; + log_assert(!(chunk_offset && param_upto)); + for (int i = tmp_range_right; i <= tmp_range_left; i++) { + int index = i - param_offset; + if (0 <= index && index < param_width) + data.push_back(current_scope[str]->children[0]->bits[chunk_offset + index]); + else + data.push_back(RTLIL::State::Sx); + } + newNode = mkconst_bits(data, false); + } else + if (children.size() == 0) + newNode = current_scope[str]->children[0]->clone(); + } else + if (current_scope[str]->children[0]->isConst()) + newNode = current_scope[str]->children[0]->clone(); + } + else if (at_zero && current_scope.count(str) > 0) { + AstNode *node = current_scope[str]; + if (node->type == AST_WIRE || node->type == AST_AUTOWIRE || node->type == AST_MEMORY) + newNode = mkconst_int(0, sign_hint, width_hint); + } + break; + case AST_MEMRD: + if (at_zero) { + newNode = mkconst_int(0, sign_hint, width_hint); + } + break; + case AST_BIT_NOT: + if (children[0]->type == AST_CONSTANT) { + RTLIL::Const y = RTLIL::const_not(children[0]->bitsAsConst(width_hint, sign_hint), dummy_arg, sign_hint, false, width_hint); + newNode = mkconst_bits(y.bits, sign_hint); + } + break; + case AST_TO_SIGNED: + case AST_TO_UNSIGNED: + if (children[0]->type == AST_CONSTANT) { + RTLIL::Const y = children[0]->bitsAsConst(width_hint, sign_hint); + newNode = mkconst_bits(y.bits, type == AST_TO_SIGNED); + } + break; + if (0) { case AST_BIT_AND: const_func = RTLIL::const_and; } + if (0) { case AST_BIT_OR: const_func = RTLIL::const_or; } + if (0) { case AST_BIT_XOR: const_func = RTLIL::const_xor; } + if (0) { case AST_BIT_XNOR: const_func = RTLIL::const_xnor; } + if (children[0]->type == AST_CONSTANT && children[1]->type == AST_CONSTANT) { + RTLIL::Const y = const_func(children[0]->bitsAsConst(width_hint, sign_hint), + children[1]->bitsAsConst(width_hint, sign_hint), sign_hint, sign_hint, width_hint); + newNode = mkconst_bits(y.bits, sign_hint); + } + break; + if (0) { case AST_REDUCE_AND: const_func = RTLIL::const_reduce_and; } + if (0) { case AST_REDUCE_OR: const_func = RTLIL::const_reduce_or; } + if (0) { case AST_REDUCE_XOR: const_func = RTLIL::const_reduce_xor; } + if (0) { case AST_REDUCE_XNOR: const_func = RTLIL::const_reduce_xnor; } + if (0) { case AST_REDUCE_BOOL: const_func = RTLIL::const_reduce_bool; } + if (children[0]->type == AST_CONSTANT) { + RTLIL::Const y = const_func(RTLIL::Const(children[0]->bits), dummy_arg, false, false, -1); + newNode = mkconst_bits(y.bits, false); + } + break; + case AST_LOGIC_NOT: + if (children[0]->type == AST_CONSTANT) { + RTLIL::Const y = RTLIL::const_logic_not(RTLIL::Const(children[0]->bits), dummy_arg, children[0]->is_signed, false, -1); + newNode = mkconst_bits(y.bits, false); + } else + if (children[0]->isConst()) { + newNode = mkconst_int(children[0]->asReal(sign_hint) == 0, false, 1); + } + break; + if (0) { case AST_LOGIC_AND: const_func = RTLIL::const_logic_and; } + if (0) { case AST_LOGIC_OR: const_func = RTLIL::const_logic_or; } + if (children[0]->type == AST_CONSTANT && children[1]->type == AST_CONSTANT) { + RTLIL::Const y = const_func(RTLIL::Const(children[0]->bits), RTLIL::Const(children[1]->bits), + children[0]->is_signed, children[1]->is_signed, -1); + newNode = mkconst_bits(y.bits, false); + } else + if (children[0]->isConst() && children[1]->isConst()) { + if (type == AST_LOGIC_AND) + newNode = mkconst_int((children[0]->asReal(sign_hint) != 0) && (children[1]->asReal(sign_hint) != 0), false, 1); + else + newNode = mkconst_int((children[0]->asReal(sign_hint) != 0) || (children[1]->asReal(sign_hint) != 0), false, 1); + } + break; + if (0) { case AST_SHIFT_LEFT: const_func = RTLIL::const_shl; } + if (0) { case AST_SHIFT_RIGHT: const_func = RTLIL::const_shr; } + if (0) { case AST_SHIFT_SLEFT: const_func = RTLIL::const_sshl; } + if (0) { case AST_SHIFT_SRIGHT: const_func = RTLIL::const_sshr; } + if (0) { case AST_POW: const_func = RTLIL::const_pow; } + if (children[0]->type == AST_CONSTANT && children[1]->type == AST_CONSTANT) { + RTLIL::Const y = const_func(children[0]->bitsAsConst(width_hint, sign_hint), + RTLIL::Const(children[1]->bits), sign_hint, type == AST_POW ? children[1]->is_signed : false, width_hint); + newNode = mkconst_bits(y.bits, sign_hint); + } else + if (type == AST_POW && children[0]->isConst() && children[1]->isConst()) { + newNode = new AstNode(AST_REALVALUE); + newNode->realvalue = pow(children[0]->asReal(sign_hint), children[1]->asReal(sign_hint)); + } + break; + if (0) { case AST_LT: const_func = RTLIL::const_lt; } + if (0) { case AST_LE: const_func = RTLIL::const_le; } + if (0) { case AST_EQ: const_func = RTLIL::const_eq; } + if (0) { case AST_NE: const_func = RTLIL::const_ne; } + if (0) { case AST_EQX: const_func = RTLIL::const_eqx; } + if (0) { case AST_NEX: const_func = RTLIL::const_nex; } + if (0) { case AST_GE: const_func = RTLIL::const_ge; } + if (0) { case AST_GT: const_func = RTLIL::const_gt; } + if (children[0]->type == AST_CONSTANT && children[1]->type == AST_CONSTANT) { + int cmp_width = max(children[0]->bits.size(), children[1]->bits.size()); + bool cmp_signed = children[0]->is_signed && children[1]->is_signed; + RTLIL::Const y = const_func(children[0]->bitsAsConst(cmp_width, cmp_signed), + children[1]->bitsAsConst(cmp_width, cmp_signed), cmp_signed, cmp_signed, 1); + newNode = mkconst_bits(y.bits, false); + } else + if (children[0]->isConst() && children[1]->isConst()) { + bool cmp_signed = (children[0]->type == AST_REALVALUE || children[0]->is_signed) && (children[1]->type == AST_REALVALUE || children[1]->is_signed); + switch (type) { + case AST_LT: newNode = mkconst_int(children[0]->asReal(cmp_signed) < children[1]->asReal(cmp_signed), false, 1); break; + case AST_LE: newNode = mkconst_int(children[0]->asReal(cmp_signed) <= children[1]->asReal(cmp_signed), false, 1); break; + case AST_EQ: newNode = mkconst_int(children[0]->asReal(cmp_signed) == children[1]->asReal(cmp_signed), false, 1); break; + case AST_NE: newNode = mkconst_int(children[0]->asReal(cmp_signed) != children[1]->asReal(cmp_signed), false, 1); break; + case AST_EQX: newNode = mkconst_int(children[0]->asReal(cmp_signed) == children[1]->asReal(cmp_signed), false, 1); break; + case AST_NEX: newNode = mkconst_int(children[0]->asReal(cmp_signed) != children[1]->asReal(cmp_signed), false, 1); break; + case AST_GE: newNode = mkconst_int(children[0]->asReal(cmp_signed) >= children[1]->asReal(cmp_signed), false, 1); break; + case AST_GT: newNode = mkconst_int(children[0]->asReal(cmp_signed) > children[1]->asReal(cmp_signed), false, 1); break; + default: log_abort(); + } + } + break; + if (0) { case AST_ADD: const_func = RTLIL::const_add; } + if (0) { case AST_SUB: const_func = RTLIL::const_sub; } + if (0) { case AST_MUL: const_func = RTLIL::const_mul; } + if (0) { case AST_DIV: const_func = RTLIL::const_div; } + if (0) { case AST_MOD: const_func = RTLIL::const_mod; } + if (children[0]->type == AST_CONSTANT && children[1]->type == AST_CONSTANT) { + RTLIL::Const y = const_func(children[0]->bitsAsConst(width_hint, sign_hint), + children[1]->bitsAsConst(width_hint, sign_hint), sign_hint, sign_hint, width_hint); + newNode = mkconst_bits(y.bits, sign_hint); + } else + if (children[0]->isConst() && children[1]->isConst()) { + newNode = new AstNode(AST_REALVALUE); + switch (type) { + case AST_ADD: newNode->realvalue = children[0]->asReal(sign_hint) + children[1]->asReal(sign_hint); break; + case AST_SUB: newNode->realvalue = children[0]->asReal(sign_hint) - children[1]->asReal(sign_hint); break; + case AST_MUL: newNode->realvalue = children[0]->asReal(sign_hint) * children[1]->asReal(sign_hint); break; + case AST_DIV: newNode->realvalue = children[0]->asReal(sign_hint) / children[1]->asReal(sign_hint); break; + case AST_MOD: newNode->realvalue = fmod(children[0]->asReal(sign_hint), children[1]->asReal(sign_hint)); break; + default: log_abort(); + } + } + break; + if (0) { case AST_SELFSZ: const_func = RTLIL::const_pos; } + if (0) { case AST_POS: const_func = RTLIL::const_pos; } + if (0) { case AST_NEG: const_func = RTLIL::const_neg; } + if (children[0]->type == AST_CONSTANT) { + RTLIL::Const y = const_func(children[0]->bitsAsConst(width_hint, sign_hint), dummy_arg, sign_hint, false, width_hint); + newNode = mkconst_bits(y.bits, sign_hint); + } else + if (children[0]->isConst()) { + newNode = new AstNode(AST_REALVALUE); + if (type == AST_NEG) + newNode->realvalue = -children[0]->asReal(sign_hint); + else + newNode->realvalue = +children[0]->asReal(sign_hint); + } + break; + case AST_TERNARY: + if (children[0]->isConst()) + { + auto pair = get_tern_choice(); + AstNode *choice = pair.first; + AstNode *not_choice = pair.second; + + if (choice != NULL) { + if (choice->type == AST_CONSTANT) { + int other_width_hint = width_hint; + bool other_sign_hint = sign_hint, other_real = false; + not_choice->detectSignWidth(other_width_hint, other_sign_hint, &other_real); + if (other_real) { + newNode = new AstNode(AST_REALVALUE); + choice->detectSignWidth(width_hint, sign_hint); + newNode->realvalue = choice->asReal(sign_hint); + } else { + RTLIL::Const y = choice->bitsAsConst(width_hint, sign_hint); + if (choice->is_string && y.bits.size() % 8 == 0 && sign_hint == false) + newNode = mkconst_str(y.bits); + else + newNode = mkconst_bits(y.bits, sign_hint); + } + } else + if (choice->isConst()) { + newNode = choice->clone(); + } + } else if (children[1]->type == AST_CONSTANT && children[2]->type == AST_CONSTANT) { + RTLIL::Const a = children[1]->bitsAsConst(width_hint, sign_hint); + RTLIL::Const b = children[2]->bitsAsConst(width_hint, sign_hint); + log_assert(a.bits.size() == b.bits.size()); + for (size_t i = 0; i < a.bits.size(); i++) + if (a.bits[i] != b.bits[i]) + a.bits[i] = RTLIL::State::Sx; + newNode = mkconst_bits(a.bits, sign_hint); + } else if (children[1]->isConst() && children[2]->isConst()) { + newNode = new AstNode(AST_REALVALUE); + if (children[1]->asReal(sign_hint) == children[2]->asReal(sign_hint)) + newNode->realvalue = children[1]->asReal(sign_hint); + else + // IEEE Std 1800-2012 Sec. 11.4.11 states that the entry in Table 7-1 for + // the data type in question should be returned if the ?: is ambiguous. The + // value in Table 7-1 for the 'real' type is 0.0. + newNode->realvalue = 0.0; + } + } + break; + case AST_CAST_SIZE: + if (children.at(0)->type == AST_CONSTANT && children.at(1)->type == AST_CONSTANT) { + int width = children[0]->bitsAsConst().as_int(); + RTLIL::Const val; + if (children[1]->is_unsized) + val = children[1]->bitsAsUnsizedConst(width); + else + val = children[1]->bitsAsConst(width); + newNode = mkconst_bits(val.bits, children[1]->is_signed); + } + break; + case AST_CONCAT: + string_op = !children.empty(); + for (auto it = children.begin(); it != children.end(); it++) { + if ((*it)->type != AST_CONSTANT) + goto not_const; + if (!(*it)->is_string) + string_op = false; + tmp_bits.insert(tmp_bits.end(), (*it)->bits.begin(), (*it)->bits.end()); + } + newNode = string_op ? mkconst_str(tmp_bits) : mkconst_bits(tmp_bits, false); + break; + case AST_REPLICATE: + if (children.at(0)->type != AST_CONSTANT || children.at(1)->type != AST_CONSTANT) + goto not_const; + for (int i = 0; i < children[0]->bitsAsConst().as_int(); i++) + tmp_bits.insert(tmp_bits.end(), children.at(1)->bits.begin(), children.at(1)->bits.end()); + newNode = children.at(1)->is_string ? mkconst_str(tmp_bits) : mkconst_bits(tmp_bits, false); + break; + default: + not_const: + break; + } + } + + // if any of the above set 'newNode' -> use 'newNode' as template to update 'this' + if (newNode) { +apply_newNode: + // fprintf(stderr, "----\n"); + // dumpAst(stderr, "- "); + // newNode->dumpAst(stderr, "+ "); + log_assert(newNode != NULL); + newNode->filename = filename; + newNode->location = location; + newNode->cloneInto(this); + delete newNode; + did_something = true; + } + + if (!did_something) + basic_prep = true; + + recursion_counter--; + return did_something; +} + +void AstNode::replace_result_wire_name_in_function(const std::string &from, const std::string &to) +{ + for (AstNode *child : children) + child->replace_result_wire_name_in_function(from, to); + if (str == from && type != AST_FCALL && type != AST_TCALL) + str = to; +} + +// replace a readmem[bh] TCALL ast node with a block of memory assignments +AstNode *AstNode::readmem(bool is_readmemh, std::string mem_filename, AstNode *memory, int start_addr, int finish_addr, bool unconditional_init) +{ + int mem_width, mem_size, addr_bits; + memory->meminfo(mem_width, mem_size, addr_bits); + + AstNode *block = new AstNode(AST_BLOCK); + + AstNode *meminit = nullptr; + int next_meminit_cursor=0; + vector<State> meminit_bits; + vector<State> en_bits; + int meminit_size=0; + + for (int i = 0; i < mem_width; i++) + en_bits.push_back(State::S1); + + std::ifstream f; + f.open(mem_filename.c_str()); + if (f.fail()) { +#ifdef _WIN32 + char slash = '\\'; +#else + char slash = '/'; +#endif + std::string path = filename.substr(0, filename.find_last_of(slash)+1); + f.open(path + mem_filename.c_str()); + yosys_input_files.insert(path + mem_filename); + } else { + yosys_input_files.insert(mem_filename); + } + if (f.fail() || GetSize(mem_filename) == 0) + log_file_error(filename, location.first_line, "Can not open file `%s` for %s.\n", mem_filename.c_str(), str.c_str()); + + log_assert(GetSize(memory->children) == 2 && memory->children[1]->type == AST_RANGE && memory->children[1]->range_valid); + int range_left = memory->children[1]->range_left, range_right = memory->children[1]->range_right; + int range_min = min(range_left, range_right), range_max = max(range_left, range_right); + + if (start_addr < 0) + start_addr = range_min; + + if (finish_addr < 0) + finish_addr = range_max + 1; + + bool in_comment = false; + int increment = start_addr <= finish_addr ? +1 : -1; + int cursor = start_addr; + + while (!f.eof()) + { + std::string line, token; + std::getline(f, line); + + for (int i = 0; i < GetSize(line); i++) { + if (in_comment && line.compare(i, 2, "*/") == 0) { + line[i] = ' '; + line[i+1] = ' '; + in_comment = false; + continue; + } + if (!in_comment && line.compare(i, 2, "/*") == 0) + in_comment = true; + if (in_comment) + line[i] = ' '; + } + + while (1) + { + token = next_token(line, " \t\r\n"); + if (token.empty() || token.compare(0, 2, "//") == 0) + break; + + if (token[0] == '@') { + token = token.substr(1); + const char *nptr = token.c_str(); + char *endptr; + cursor = strtol(nptr, &endptr, 16); + if (!*nptr || *endptr) + log_file_error(filename, location.first_line, "Can not parse address `%s` for %s.\n", nptr, str.c_str()); + continue; + } + + AstNode *value = VERILOG_FRONTEND::const2ast(stringf("%d'%c", mem_width, is_readmemh ? 'h' : 'b') + token); + + if (unconditional_init) + { + if (meminit == nullptr || cursor != next_meminit_cursor) + { + if (meminit != nullptr) { + meminit->children[1] = AstNode::mkconst_bits(meminit_bits, false); + meminit->children[3] = AstNode::mkconst_int(meminit_size, false); + } + + meminit = new AstNode(AST_MEMINIT); + meminit->children.push_back(AstNode::mkconst_int(cursor, false)); + meminit->children.push_back(nullptr); + meminit->children.push_back(AstNode::mkconst_bits(en_bits, false)); + meminit->children.push_back(nullptr); + meminit->str = memory->str; + meminit->id2ast = memory; + meminit_bits.clear(); + meminit_size = 0; + + current_ast_mod->children.push_back(meminit); + next_meminit_cursor = cursor; + } + + meminit_size++; + next_meminit_cursor++; + meminit_bits.insert(meminit_bits.end(), value->bits.begin(), value->bits.end()); + delete value; + } + else + { + block->children.push_back(new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER, new AstNode(AST_RANGE, AstNode::mkconst_int(cursor, false))), value)); + block->children.back()->children[0]->str = memory->str; + block->children.back()->children[0]->id2ast = memory; + block->children.back()->children[0]->was_checked = true; + } + + cursor += increment; + if ((cursor == finish_addr+increment) || (increment > 0 && cursor > range_max) || (increment < 0 && cursor < range_min)) + break; + } + + if ((cursor == finish_addr+increment) || (increment > 0 && cursor > range_max) || (increment < 0 && cursor < range_min)) + break; + } + + if (meminit != nullptr) { + meminit->children[1] = AstNode::mkconst_bits(meminit_bits, false); + meminit->children[3] = AstNode::mkconst_int(meminit_size, false); + } + + return block; +} + +// annotate the names of all wires and other named objects in a named generate +// or procedural block; nested blocks are themselves annotated such that the +// prefix is carried forward, but resolution of their children is deferred +void AstNode::expand_genblock(const std::string &prefix) +{ + if (type == AST_IDENTIFIER || type == AST_FCALL || type == AST_TCALL || type == AST_WIRETYPE || type == AST_PREFIX) { + log_assert(!str.empty()); + + // search starting in the innermost scope and then stepping outward + for (size_t ppos = prefix.size() - 1; ppos; --ppos) { + if (prefix.at(ppos) != '.') continue; + + std::string new_prefix = prefix.substr(0, ppos + 1); + auto attempt_resolve = [&new_prefix](const std::string &ident) -> std::string { + std::string new_name = prefix_id(new_prefix, ident); + if (current_scope.count(new_name)) + return new_name; + return {}; + }; + + // attempt to resolve the full identifier + std::string resolved = attempt_resolve(str); + if (!resolved.empty()) { + str = resolved; + break; + } + + // attempt to resolve hierarchical prefixes within the identifier, + // as the prefix could refer to a local scope which exists but + // hasn't yet been elaborated + for (size_t spos = str.size() - 1; spos; --spos) { + if (str.at(spos) != '.') continue; + resolved = attempt_resolve(str.substr(0, spos)); + if (!resolved.empty()) { + str = resolved + str.substr(spos); + ppos = 1; // break outer loop + break; + } + } + + } + } + + auto prefix_node = [&prefix](AstNode* child) { + if (child->str.empty()) return; + std::string new_name = prefix_id(prefix, child->str); + if (child->type == AST_FUNCTION) + child->replace_result_wire_name_in_function(child->str, new_name); + else + child->str = new_name; + current_scope[new_name] = child; + }; + + for (size_t i = 0; i < children.size(); i++) { + AstNode *child = children[i]; + + switch (child->type) { + case AST_WIRE: + case AST_MEMORY: + case AST_PARAMETER: + case AST_LOCALPARAM: + case AST_FUNCTION: + case AST_TASK: + case AST_CELL: + case AST_TYPEDEF: + case AST_ENUM_ITEM: + case AST_GENVAR: + prefix_node(child); + break; + + case AST_BLOCK: + case AST_GENBLOCK: + if (!child->str.empty()) + prefix_node(child); + break; + + case AST_ENUM: + current_scope[child->str] = child; + for (auto enode : child->children){ + log_assert(enode->type == AST_ENUM_ITEM); + prefix_node(enode); + } + break; + + default: + break; + } + } + + for (size_t i = 0; i < children.size(); i++) { + AstNode *child = children[i]; + // AST_PREFIX member names should not be prefixed; we recurse into them + // as normal to ensure indices and ranges are properly resolved, and + // then restore the previous string + if (type == AST_PREFIX && i == 1) { + std::string backup_scope_name = child->str; + child->expand_genblock(prefix); + child->str = backup_scope_name; + continue; + } + // functions/tasks may reference wires, constants, etc. in this scope + if (child->type == AST_FUNCTION || child->type == AST_TASK) + continue; + // named blocks pick up the current prefix and will expanded later + if ((child->type == AST_GENBLOCK || child->type == AST_BLOCK) && !child->str.empty()) + continue; + + child->expand_genblock(prefix); + } +} + +// add implicit AST_GENBLOCK names according to IEEE 1364-2005 Section 12.4.3 or +// IEEE 1800-2017 Section 27.6 +void AstNode::label_genblks(std::set<std::string>& existing, int &counter) +{ + switch (type) { + case AST_GENIF: + case AST_GENFOR: + case AST_GENCASE: + // seeing a proper generate control flow construct increments the + // counter once + ++counter; + for (AstNode *child : children) + child->label_genblks(existing, counter); + break; + + case AST_GENBLOCK: { + // if this block is unlabeled, generate its corresponding unique name + for (int padding = 0; str.empty(); ++padding) { + std::string candidate = "\\genblk"; + for (int i = 0; i < padding; ++i) + candidate += '0'; + candidate += std::to_string(counter); + if (!existing.count(candidate)) + str = candidate; + } + // within a genblk, the counter starts fresh + std::set<std::string> existing_local = existing; + int counter_local = 0; + for (AstNode *child : children) + child->label_genblks(existing_local, counter_local); + break; + } + + default: + // track names which could conflict with implicit genblk names + if (str.rfind("\\genblk", 0) == 0) + existing.insert(str); + for (AstNode *child : children) + child->label_genblks(existing, counter); + break; + } +} + +// helper function for mem2reg_as_needed_pass1 +static void mark_memories_assign_lhs_complex(dict<AstNode*, pool<std::string>> &mem2reg_places, + dict<AstNode*, uint32_t> &mem2reg_candidates, AstNode *that) +{ + for (auto &child : that->children) + mark_memories_assign_lhs_complex(mem2reg_places, mem2reg_candidates, child); + + if (that->type == AST_IDENTIFIER && that->id2ast && that->id2ast->type == AST_MEMORY) { + AstNode *mem = that->id2ast; + if (!(mem2reg_candidates[mem] & AstNode::MEM2REG_FL_CMPLX_LHS)) + mem2reg_places[mem].insert(stringf("%s:%d", RTLIL::encode_filename(that->filename).c_str(), that->location.first_line)); + mem2reg_candidates[mem] |= AstNode::MEM2REG_FL_CMPLX_LHS; + } +} + +// find memories that should be replaced by registers +void AstNode::mem2reg_as_needed_pass1(dict<AstNode*, pool<std::string>> &mem2reg_places, + dict<AstNode*, uint32_t> &mem2reg_candidates, dict<AstNode*, uint32_t> &proc_flags, uint32_t &flags) +{ + uint32_t children_flags = 0; + int lhs_children_counter = 0; + + if (type == AST_TYPEDEF) + return; // don't touch content of typedefs + + if (type == AST_ASSIGN || type == AST_ASSIGN_LE || type == AST_ASSIGN_EQ) + { + // mark all memories that are used in a complex expression on the left side of an assignment + for (auto &lhs_child : children[0]->children) + mark_memories_assign_lhs_complex(mem2reg_places, mem2reg_candidates, lhs_child); + + if (children[0]->type == AST_IDENTIFIER && children[0]->id2ast && children[0]->id2ast->type == AST_MEMORY) + { + AstNode *mem = children[0]->id2ast; + + // activate mem2reg if this is assigned in an async proc + if (flags & AstNode::MEM2REG_FL_ASYNC) { + if (!(mem2reg_candidates[mem] & AstNode::MEM2REG_FL_SET_ASYNC)) + mem2reg_places[mem].insert(stringf("%s:%d", RTLIL::encode_filename(filename).c_str(), location.first_line)); + mem2reg_candidates[mem] |= AstNode::MEM2REG_FL_SET_ASYNC; + } + + // remember if this is assigned blocking (=) + if (type == AST_ASSIGN_EQ) { + if (!(proc_flags[mem] & AstNode::MEM2REG_FL_EQ1)) + mem2reg_places[mem].insert(stringf("%s:%d", RTLIL::encode_filename(filename).c_str(), location.first_line)); + proc_flags[mem] |= AstNode::MEM2REG_FL_EQ1; + } + + // for proper (non-init) writes: remember if this is a constant index or not + if ((flags & MEM2REG_FL_INIT) == 0) { + if (children[0]->children.size() && children[0]->children[0]->type == AST_RANGE && children[0]->children[0]->children.size()) { + if (children[0]->children[0]->children[0]->type == AST_CONSTANT) + mem2reg_candidates[mem] |= AstNode::MEM2REG_FL_CONST_LHS; + else + mem2reg_candidates[mem] |= AstNode::MEM2REG_FL_VAR_LHS; + } + } + + // remember where this is + if (flags & MEM2REG_FL_INIT) { + if (!(mem2reg_candidates[mem] & AstNode::MEM2REG_FL_SET_INIT)) + mem2reg_places[mem].insert(stringf("%s:%d", RTLIL::encode_filename(filename).c_str(), location.first_line)); + mem2reg_candidates[mem] |= AstNode::MEM2REG_FL_SET_INIT; + } else { + if (!(mem2reg_candidates[mem] & AstNode::MEM2REG_FL_SET_ELSE)) + mem2reg_places[mem].insert(stringf("%s:%d", RTLIL::encode_filename(filename).c_str(), location.first_line)); + mem2reg_candidates[mem] |= AstNode::MEM2REG_FL_SET_ELSE; + } + } + + lhs_children_counter = 1; + } + + if (type == AST_IDENTIFIER && id2ast && id2ast->type == AST_MEMORY) + { + AstNode *mem = id2ast; + + // flag if used after blocking assignment (in same proc) + if ((proc_flags[mem] & AstNode::MEM2REG_FL_EQ1) && !(mem2reg_candidates[mem] & AstNode::MEM2REG_FL_EQ2)) { + mem2reg_places[mem].insert(stringf("%s:%d", RTLIL::encode_filename(filename).c_str(), location.first_line)); + mem2reg_candidates[mem] |= AstNode::MEM2REG_FL_EQ2; + } + } + + // also activate if requested, either by using mem2reg attribute or by declaring array as 'wire' instead of 'reg' or 'logic' + if (type == AST_MEMORY && (get_bool_attribute(ID::mem2reg) || (flags & AstNode::MEM2REG_FL_ALL) || !(is_reg || is_logic))) + mem2reg_candidates[this] |= AstNode::MEM2REG_FL_FORCED; + + if ((type == AST_MODULE || type == AST_INTERFACE) && get_bool_attribute(ID::mem2reg)) + children_flags |= AstNode::MEM2REG_FL_ALL; + + dict<AstNode*, uint32_t> *proc_flags_p = NULL; + + if (type == AST_ALWAYS) { + int count_edge_events = 0; + for (auto child : children) + if (child->type == AST_POSEDGE || child->type == AST_NEGEDGE) + count_edge_events++; + if (count_edge_events != 1) + children_flags |= AstNode::MEM2REG_FL_ASYNC; + proc_flags_p = new dict<AstNode*, uint32_t>; + } + else if (type == AST_INITIAL) { + children_flags |= AstNode::MEM2REG_FL_INIT; + proc_flags_p = new dict<AstNode*, uint32_t>; + } + + uint32_t backup_flags = flags; + flags |= children_flags; + log_assert((flags & ~0x000000ff) == 0); + + for (auto child : children) + { + if (lhs_children_counter > 0) { + lhs_children_counter--; + if (child->children.size() && child->children[0]->type == AST_RANGE && child->children[0]->children.size()) { + for (auto c : child->children[0]->children) { + if (proc_flags_p) + c->mem2reg_as_needed_pass1(mem2reg_places, mem2reg_candidates, *proc_flags_p, flags); + else + c->mem2reg_as_needed_pass1(mem2reg_places, mem2reg_candidates, proc_flags, flags); + } + } + } else + if (proc_flags_p) + child->mem2reg_as_needed_pass1(mem2reg_places, mem2reg_candidates, *proc_flags_p, flags); + else + child->mem2reg_as_needed_pass1(mem2reg_places, mem2reg_candidates, proc_flags, flags); + } + + flags &= ~children_flags | backup_flags; + + if (proc_flags_p) { +#ifndef NDEBUG + for (auto it : *proc_flags_p) + log_assert((it.second & ~0xff000000) == 0); +#endif + delete proc_flags_p; + } +} + +bool AstNode::mem2reg_check(pool<AstNode*> &mem2reg_set) +{ + if (type != AST_IDENTIFIER || !id2ast || !mem2reg_set.count(id2ast)) + return false; + + if (children.empty() || children[0]->type != AST_RANGE || GetSize(children[0]->children) != 1) + log_file_error(filename, location.first_line, "Invalid array access.\n"); + + return true; +} + +void AstNode::mem2reg_remove(pool<AstNode*> &mem2reg_set, vector<AstNode*> &delnodes) +{ + log_assert(mem2reg_set.count(this) == 0); + + if (mem2reg_set.count(id2ast)) + id2ast = nullptr; + + for (size_t i = 0; i < children.size(); i++) { + if (mem2reg_set.count(children[i]) > 0) { + delnodes.push_back(children[i]); + children.erase(children.begin() + (i--)); + } else { + children[i]->mem2reg_remove(mem2reg_set, delnodes); + } + } +} + +// actually replace memories with registers +bool AstNode::mem2reg_as_needed_pass2(pool<AstNode*> &mem2reg_set, AstNode *mod, AstNode *block, AstNode *&async_block) +{ + bool did_something = false; + + if (type == AST_BLOCK) + block = this; + + if (type == AST_FUNCTION || type == AST_TASK) + return false; + + if (type == AST_TYPEDEF) + return false; + + if (type == AST_MEMINIT && id2ast && mem2reg_set.count(id2ast)) + { + log_assert(children[0]->type == AST_CONSTANT); + log_assert(children[1]->type == AST_CONSTANT); + log_assert(children[2]->type == AST_CONSTANT); + log_assert(children[3]->type == AST_CONSTANT); + + int cursor = children[0]->asInt(false); + Const data = children[1]->bitsAsConst(); + Const en = children[2]->bitsAsConst(); + int length = children[3]->asInt(false); + + if (length != 0) + { + AstNode *block = new AstNode(AST_INITIAL, new AstNode(AST_BLOCK)); + mod->children.push_back(block); + block = block->children[0]; + + int wordsz = GetSize(data) / length; + + for (int i = 0; i < length; i++) { + int pos = 0; + while (pos < wordsz) { + if (en[pos] != State::S1) { + pos++; + } else { + int epos = pos + 1; + while (epos < wordsz && en[epos] == State::S1) + epos++; + int clen = epos - pos; + AstNode *range = new AstNode(AST_RANGE, AstNode::mkconst_int(cursor+i, false)); + if (pos != 0 || epos != wordsz) { + int left; + int right; + AstNode *mrange = id2ast->children[0]; + if (mrange->range_left < mrange->range_right) { + right = mrange->range_right - pos; + left = mrange->range_right - epos + 1; + } else { + right = mrange->range_right + pos; + left = mrange->range_right + epos - 1; + } + range = new AstNode(AST_MULTIRANGE, range, new AstNode(AST_RANGE, AstNode::mkconst_int(left, true), AstNode::mkconst_int(right, true))); + } + AstNode *target = new AstNode(AST_IDENTIFIER, range); + target->str = str; + target->id2ast = id2ast; + target->was_checked = true; + block->children.push_back(new AstNode(AST_ASSIGN_EQ, target, mkconst_bits(data.extract(i*wordsz + pos, clen).bits, false))); + pos = epos; + } + } + } + } + + AstNode *newNode = new AstNode(AST_NONE); + newNode->cloneInto(this); + delete newNode; + + did_something = true; + } + + if (type == AST_ASSIGN && block == NULL && children[0]->mem2reg_check(mem2reg_set)) + { + if (async_block == NULL) { + async_block = new AstNode(AST_ALWAYS, new AstNode(AST_BLOCK)); + mod->children.push_back(async_block); + } + + AstNode *newNode = clone(); + newNode->type = AST_ASSIGN_EQ; + newNode->children[0]->was_checked = true; + async_block->children[0]->children.push_back(newNode); + + newNode = new AstNode(AST_NONE); + newNode->cloneInto(this); + delete newNode; + + did_something = true; + } + + if ((type == AST_ASSIGN_LE || type == AST_ASSIGN_EQ) && children[0]->mem2reg_check(mem2reg_set) && + children[0]->children[0]->children[0]->type != AST_CONSTANT) + { + std::stringstream sstr; + sstr << "$mem2reg_wr$" << children[0]->str << "$" << RTLIL::encode_filename(filename) << ":" << location.first_line << "$" << (autoidx++); + std::string id_addr = sstr.str() + "_ADDR", id_data = sstr.str() + "_DATA"; + + int mem_width, mem_size, addr_bits; + bool mem_signed = children[0]->id2ast->is_signed; + children[0]->id2ast->meminfo(mem_width, mem_size, addr_bits); + + AstNode *wire_addr = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(addr_bits-1, true), mkconst_int(0, true))); + wire_addr->str = id_addr; + wire_addr->is_reg = true; + wire_addr->was_checked = true; + wire_addr->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + mod->children.push_back(wire_addr); + while (wire_addr->simplify(true, false, false, 1, -1, false, false)) { } + + AstNode *wire_data = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(mem_width-1, true), mkconst_int(0, true))); + wire_data->str = id_data; + wire_data->is_reg = true; + wire_data->was_checked = true; + wire_data->is_signed = mem_signed; + wire_data->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + mod->children.push_back(wire_data); + while (wire_data->simplify(true, false, false, 1, -1, false, false)) { } + + log_assert(block != NULL); + size_t assign_idx = 0; + while (assign_idx < block->children.size() && block->children[assign_idx] != this) + assign_idx++; + log_assert(assign_idx < block->children.size()); + + AstNode *assign_addr = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), children[0]->children[0]->children[0]->clone()); + assign_addr->children[0]->str = id_addr; + assign_addr->children[0]->was_checked = true; + block->children.insert(block->children.begin()+assign_idx+1, assign_addr); + + AstNode *case_node = new AstNode(AST_CASE, new AstNode(AST_IDENTIFIER)); + case_node->children[0]->str = id_addr; + for (int i = 0; i < mem_size; i++) { + if (children[0]->children[0]->children[0]->type == AST_CONSTANT && int(children[0]->children[0]->children[0]->integer) != i) + continue; + AstNode *cond_node = new AstNode(AST_COND, AstNode::mkconst_int(i, false, addr_bits), new AstNode(AST_BLOCK)); + AstNode *assign_reg = new AstNode(type, new AstNode(AST_IDENTIFIER), new AstNode(AST_IDENTIFIER)); + if (children[0]->children.size() == 2) + assign_reg->children[0]->children.push_back(children[0]->children[1]->clone()); + assign_reg->children[0]->str = stringf("%s[%d]", children[0]->str.c_str(), i); + assign_reg->children[1]->str = id_data; + cond_node->children[1]->children.push_back(assign_reg); + case_node->children.push_back(cond_node); + } + block->children.insert(block->children.begin()+assign_idx+2, case_node); + + children[0]->delete_children(); + children[0]->range_valid = false; + children[0]->id2ast = NULL; + children[0]->str = id_data; + type = AST_ASSIGN_EQ; + children[0]->was_checked = true; + + did_something = true; + } + + if (mem2reg_check(mem2reg_set)) + { + AstNode *bit_part_sel = NULL; + if (children.size() == 2) + bit_part_sel = children[1]->clone(); + + if (children[0]->children[0]->type == AST_CONSTANT) + { + int id = children[0]->children[0]->integer; + int left = id2ast->children[1]->children[0]->integer; + int right = id2ast->children[1]->children[1]->integer; + bool valid_const_access = + (left <= id && id <= right) || + (right <= id && id <= left); + if (valid_const_access) + { + str = stringf("%s[%d]", str.c_str(), id); + delete_children(); + range_valid = false; + id2ast = NULL; + } + else + { + int width; + if (bit_part_sel) + { + bit_part_sel->dumpAst(nullptr, "? "); + if (bit_part_sel->children.size() == 1) + width = 0; + else + width = bit_part_sel->children[0]->integer - + bit_part_sel->children[1]->integer; + delete bit_part_sel; + bit_part_sel = nullptr; + } + else + { + width = id2ast->children[0]->children[0]->integer - + id2ast->children[0]->children[1]->integer; + } + width = abs(width) + 1; + + delete_children(); + + std::vector<RTLIL::State> x_bits; + for (int i = 0; i < width; i++) + x_bits.push_back(RTLIL::State::Sx); + AstNode *constant = AstNode::mkconst_bits(x_bits, false); + constant->cloneInto(this); + delete constant; + } + } + else + { + std::stringstream sstr; + sstr << "$mem2reg_rd$" << str << "$" << RTLIL::encode_filename(filename) << ":" << location.first_line << "$" << (autoidx++); + std::string id_addr = sstr.str() + "_ADDR", id_data = sstr.str() + "_DATA"; + + int mem_width, mem_size, addr_bits; + bool mem_signed = id2ast->is_signed; + id2ast->meminfo(mem_width, mem_size, addr_bits); + + AstNode *wire_addr = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(addr_bits-1, true), mkconst_int(0, true))); + wire_addr->str = id_addr; + wire_addr->is_reg = true; + wire_addr->was_checked = true; + if (block) + wire_addr->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + mod->children.push_back(wire_addr); + while (wire_addr->simplify(true, false, false, 1, -1, false, false)) { } + + AstNode *wire_data = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(mem_width-1, true), mkconst_int(0, true))); + wire_data->str = id_data; + wire_data->is_reg = true; + wire_data->was_checked = true; + wire_data->is_signed = mem_signed; + if (block) + wire_data->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + mod->children.push_back(wire_data); + while (wire_data->simplify(true, false, false, 1, -1, false, false)) { } + + AstNode *assign_addr = new AstNode(block ? AST_ASSIGN_EQ : AST_ASSIGN, new AstNode(AST_IDENTIFIER), children[0]->children[0]->clone()); + assign_addr->children[0]->str = id_addr; + assign_addr->children[0]->was_checked = true; + + AstNode *case_node = new AstNode(AST_CASE, new AstNode(AST_IDENTIFIER)); + case_node->children[0]->str = id_addr; + + for (int i = 0; i < mem_size; i++) { + if (children[0]->children[0]->type == AST_CONSTANT && int(children[0]->children[0]->integer) != i) + continue; + AstNode *cond_node = new AstNode(AST_COND, AstNode::mkconst_int(i, false, addr_bits), new AstNode(AST_BLOCK)); + AstNode *assign_reg = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), new AstNode(AST_IDENTIFIER)); + assign_reg->children[0]->str = id_data; + assign_reg->children[0]->was_checked = true; + assign_reg->children[1]->str = stringf("%s[%d]", str.c_str(), i); + cond_node->children[1]->children.push_back(assign_reg); + case_node->children.push_back(cond_node); + } + + std::vector<RTLIL::State> x_bits; + for (int i = 0; i < mem_width; i++) + x_bits.push_back(RTLIL::State::Sx); + + AstNode *cond_node = new AstNode(AST_COND, new AstNode(AST_DEFAULT), new AstNode(AST_BLOCK)); + AstNode *assign_reg = new AstNode(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), AstNode::mkconst_bits(x_bits, false)); + assign_reg->children[0]->str = id_data; + assign_reg->children[0]->was_checked = true; + cond_node->children[1]->children.push_back(assign_reg); + case_node->children.push_back(cond_node); + + if (block) + { + size_t assign_idx = 0; + while (assign_idx < block->children.size() && !block->children[assign_idx]->contains(this)) + assign_idx++; + log_assert(assign_idx < block->children.size()); + block->children.insert(block->children.begin()+assign_idx, case_node); + block->children.insert(block->children.begin()+assign_idx, assign_addr); + } + else + { + AstNode *proc = new AstNode(AST_ALWAYS, new AstNode(AST_BLOCK)); + proc->children[0]->children.push_back(case_node); + mod->children.push_back(proc); + mod->children.push_back(assign_addr); + } + + delete_children(); + range_valid = false; + id2ast = NULL; + str = id_data; + } + + if (bit_part_sel) + children.push_back(bit_part_sel); + + did_something = true; + } + + log_assert(id2ast == NULL || mem2reg_set.count(id2ast) == 0); + + auto children_list = children; + for (size_t i = 0; i < children_list.size(); i++) + if (children_list[i]->mem2reg_as_needed_pass2(mem2reg_set, mod, block, async_block)) + did_something = true; + + return did_something; +} + +// calculate memory dimensions +void AstNode::meminfo(int &mem_width, int &mem_size, int &addr_bits) +{ + log_assert(type == AST_MEMORY); + + mem_width = children[0]->range_left - children[0]->range_right + 1; + mem_size = children[1]->range_left - children[1]->range_right; + + if (mem_size < 0) + mem_size *= -1; + mem_size += min(children[1]->range_left, children[1]->range_right) + 1; + + addr_bits = 1; + while ((1 << addr_bits) < mem_size) + addr_bits++; +} + +bool AstNode::detect_latch(const std::string &var) +{ + switch (type) + { + case AST_ALWAYS: + for (auto &c : children) + { + switch (c->type) + { + case AST_POSEDGE: + case AST_NEGEDGE: + return false; + case AST_EDGE: + break; + case AST_BLOCK: + if (!c->detect_latch(var)) + return false; + break; + default: + log_abort(); + } + } + return true; + case AST_BLOCK: + for (auto &c : children) + if (!c->detect_latch(var)) + return false; + return true; + case AST_CASE: + { + bool r = true; + for (auto &c : children) { + if (c->type == AST_COND) { + if (c->children.at(1)->detect_latch(var)) + return true; + r = false; + } + if (c->type == AST_DEFAULT) { + if (c->children.at(0)->detect_latch(var)) + return true; + r = false; + } + } + return r; + } + case AST_ASSIGN_EQ: + case AST_ASSIGN_LE: + if (children.at(0)->type == AST_IDENTIFIER && + children.at(0)->children.empty() && children.at(0)->str == var) + return false; + return true; + default: + return true; + } +} + +bool AstNode::has_const_only_constructs() +{ + if (type == AST_WHILE || type == AST_REPEAT) + return true; + for (auto child : children) + if (child->has_const_only_constructs()) + return true; + return false; +} + +bool AstNode::is_simple_const_expr() +{ + if (type == AST_IDENTIFIER) + return false; + for (auto child : children) + if (!child->is_simple_const_expr()) + return false; + return true; +} + +// helper function for AstNode::eval_const_function() +bool AstNode::replace_variables(std::map<std::string, AstNode::varinfo_t> &variables, AstNode *fcall, bool must_succeed) +{ + if (type == AST_IDENTIFIER && variables.count(str)) { + int offset = variables.at(str).offset, width = variables.at(str).val.bits.size(); + if (!children.empty()) { + if (children.size() != 1 || children.at(0)->type != AST_RANGE) { + if (!must_succeed) + return false; + log_file_error(filename, location.first_line, "Memory access in constant function is not supported\n%s: ...called from here.\n", + fcall->loc_string().c_str()); + } + if (!children.at(0)->replace_variables(variables, fcall, must_succeed)) + return false; + while (simplify(true, false, false, 1, -1, false, true)) { } + if (!children.at(0)->range_valid) { + if (!must_succeed) + return false; + log_file_error(filename, location.first_line, "Non-constant range\n%s: ... called from here.\n", + fcall->loc_string().c_str()); + } + offset = min(children.at(0)->range_left, children.at(0)->range_right); + width = min(std::abs(children.at(0)->range_left - children.at(0)->range_right) + 1, width); + } + offset -= variables.at(str).offset; + if (variables.at(str).range_swapped) + offset = -offset; + std::vector<RTLIL::State> &var_bits = variables.at(str).val.bits; + std::vector<RTLIL::State> new_bits(var_bits.begin() + offset, var_bits.begin() + offset + width); + AstNode *newNode = mkconst_bits(new_bits, variables.at(str).is_signed); + newNode->cloneInto(this); + delete newNode; + return true; + } + + for (auto &child : children) + if (!child->replace_variables(variables, fcall, must_succeed)) + return false; + return true; +} + +// attempt to statically evaluate a functions with all-const arguments +AstNode *AstNode::eval_const_function(AstNode *fcall, bool must_succeed) +{ + std::map<std::string, AstNode*> backup_scope = current_scope; + std::map<std::string, AstNode::varinfo_t> variables; + std::vector<AstNode*> to_delete; + AstNode *block = new AstNode(AST_BLOCK); + AstNode *result = nullptr; + + size_t argidx = 0; + for (auto child : children) + { + block->children.push_back(child->clone()); + } + + while (!block->children.empty()) + { + AstNode *stmt = block->children.front(); + +#if 0 + log("-----------------------------------\n"); + for (auto &it : variables) + log("%20s %40s\n", it.first.c_str(), log_signal(it.second.val)); + stmt->dumpAst(NULL, "stmt> "); +#endif + + if (stmt->type == AST_WIRE) + { + while (stmt->simplify(true, false, false, 1, -1, false, true)) { } + if (!stmt->range_valid) { + if (!must_succeed) + goto finished; + log_file_error(stmt->filename, stmt->location.first_line, "Can't determine size of variable %s\n%s: ... called from here.\n", + stmt->str.c_str(), fcall->loc_string().c_str()); + } + AstNode::varinfo_t &variable = variables[stmt->str]; + int width = abs(stmt->range_left - stmt->range_right) + 1; + // if this variable has already been declared as an input, check the + // sizes match if it already had an explicit size + if (variable.arg && variable.explicitly_sized && variable.val.size() != width) { + log_file_error(filename, location.first_line, "Incompatible re-declaration of constant function wire %s.\n", stmt->str.c_str()); + } + variable.val = RTLIL::Const(RTLIL::State::Sx, width); + variable.offset = stmt->range_swapped ? stmt->range_left : stmt->range_right; + variable.range_swapped = stmt->range_swapped; + variable.is_signed = stmt->is_signed; + variable.explicitly_sized = stmt->children.size() && + stmt->children.back()->type == AST_RANGE; + // identify the argument corresponding to this wire, if applicable + if (stmt->is_input && argidx < fcall->children.size()) { + variable.arg = fcall->children.at(argidx++); + } + // load the constant arg's value into this variable + if (variable.arg) { + if (variable.arg->type == AST_CONSTANT) { + variable.val = variable.arg->bitsAsConst(width); + } else { + log_assert(variable.arg->type == AST_REALVALUE); + variable.val = variable.arg->realAsConst(width); + } + } + current_scope[stmt->str] = stmt; + + block->children.erase(block->children.begin()); + to_delete.push_back(stmt); + continue; + } + + log_assert(variables.count(str) != 0); + + if (stmt->type == AST_LOCALPARAM) + { + while (stmt->simplify(true, false, false, 1, -1, false, true)) { } + + current_scope[stmt->str] = stmt; + + block->children.erase(block->children.begin()); + to_delete.push_back(stmt); + continue; + } + + if (stmt->type == AST_ASSIGN_EQ) + { + if (stmt->children.at(0)->type == AST_IDENTIFIER && stmt->children.at(0)->children.size() != 0 && + stmt->children.at(0)->children.at(0)->type == AST_RANGE) + if (!stmt->children.at(0)->children.at(0)->replace_variables(variables, fcall, must_succeed)) + goto finished; + if (!stmt->children.at(1)->replace_variables(variables, fcall, must_succeed)) + goto finished; + while (stmt->simplify(true, false, false, 1, -1, false, true)) { } + + if (stmt->type != AST_ASSIGN_EQ) + continue; + + if (stmt->children.at(1)->type != AST_CONSTANT) { + if (!must_succeed) + goto finished; + log_file_error(stmt->filename, stmt->location.first_line, "Non-constant expression in constant function\n%s: ... called from here. X\n", + fcall->loc_string().c_str()); + } + + if (stmt->children.at(0)->type != AST_IDENTIFIER) { + if (!must_succeed) + goto finished; + log_file_error(stmt->filename, stmt->location.first_line, "Unsupported composite left hand side in constant function\n%s: ... called from here.\n", + fcall->loc_string().c_str()); + } + + if (!variables.count(stmt->children.at(0)->str)) { + if (!must_succeed) + goto finished; + log_file_error(stmt->filename, stmt->location.first_line, "Assignment to non-local variable in constant function\n%s: ... called from here.\n", + fcall->loc_string().c_str()); + } + + if (stmt->children.at(0)->children.empty()) { + variables[stmt->children.at(0)->str].val = stmt->children.at(1)->bitsAsConst(variables[stmt->children.at(0)->str].val.bits.size()); + } else { + AstNode *range = stmt->children.at(0)->children.at(0); + if (!range->range_valid) { + if (!must_succeed) + goto finished; + log_file_error(range->filename, range->location.first_line, "Non-constant range\n%s: ... called from here.\n", + fcall->loc_string().c_str()); + } + int offset = min(range->range_left, range->range_right); + int width = std::abs(range->range_left - range->range_right) + 1; + varinfo_t &v = variables[stmt->children.at(0)->str]; + RTLIL::Const r = stmt->children.at(1)->bitsAsConst(v.val.bits.size()); + for (int i = 0; i < width; i++) { + int index = i + offset - v.offset; + if (v.range_swapped) + index = -index; + v.val.bits.at(index) = r.bits.at(i); + } + } + + delete block->children.front(); + block->children.erase(block->children.begin()); + continue; + } + + if (stmt->type == AST_FOR) + { + block->children.insert(block->children.begin(), stmt->children.at(0)); + stmt->children.at(3)->children.push_back(stmt->children.at(2)); + stmt->children.erase(stmt->children.begin() + 2); + stmt->children.erase(stmt->children.begin()); + stmt->type = AST_WHILE; + continue; + } + + if (stmt->type == AST_WHILE) + { + AstNode *cond = stmt->children.at(0)->clone(); + if (!cond->replace_variables(variables, fcall, must_succeed)) + goto finished; + while (cond->simplify(true, false, false, 1, -1, false, true)) { } + + if (cond->type != AST_CONSTANT) { + if (!must_succeed) + goto finished; + log_file_error(stmt->filename, stmt->location.first_line, "Non-constant expression in constant function\n%s: ... called from here.\n", + fcall->loc_string().c_str()); + } + + if (cond->asBool()) { + block->children.insert(block->children.begin(), stmt->children.at(1)->clone()); + } else { + delete block->children.front(); + block->children.erase(block->children.begin()); + } + + delete cond; + continue; + } + + if (stmt->type == AST_REPEAT) + { + AstNode *num = stmt->children.at(0)->clone(); + if (!num->replace_variables(variables, fcall, must_succeed)) + goto finished; + while (num->simplify(true, false, false, 1, -1, false, true)) { } + + if (num->type != AST_CONSTANT) { + if (!must_succeed) + goto finished; + log_file_error(stmt->filename, stmt->location.first_line, "Non-constant expression in constant function\n%s: ... called from here.\n", + fcall->loc_string().c_str()); + } + + block->children.erase(block->children.begin()); + for (int i = 0; i < num->bitsAsConst().as_int(); i++) + block->children.insert(block->children.begin(), stmt->children.at(1)->clone()); + + delete stmt; + delete num; + continue; + } + + if (stmt->type == AST_CASE) + { + AstNode *expr = stmt->children.at(0)->clone(); + if (!expr->replace_variables(variables, fcall, must_succeed)) + goto finished; + while (expr->simplify(true, false, false, 1, -1, false, true)) { } + + AstNode *sel_case = NULL; + for (size_t i = 1; i < stmt->children.size(); i++) + { + bool found_match = false; + log_assert(stmt->children.at(i)->type == AST_COND || stmt->children.at(i)->type == AST_CONDX || stmt->children.at(i)->type == AST_CONDZ); + + if (stmt->children.at(i)->children.front()->type == AST_DEFAULT) { + sel_case = stmt->children.at(i)->children.back(); + continue; + } + + for (size_t j = 0; j+1 < stmt->children.at(i)->children.size() && !found_match; j++) + { + AstNode *cond = stmt->children.at(i)->children.at(j)->clone(); + if (!cond->replace_variables(variables, fcall, must_succeed)) + goto finished; + + cond = new AstNode(AST_EQ, expr->clone(), cond); + while (cond->simplify(true, false, false, 1, -1, false, true)) { } + + if (cond->type != AST_CONSTANT) { + if (!must_succeed) + goto finished; + log_file_error(stmt->filename, stmt->location.first_line, "Non-constant expression in constant function\n%s: ... called from here.\n", + fcall->loc_string().c_str()); + } + + found_match = cond->asBool(); + delete cond; + } + + if (found_match) { + sel_case = stmt->children.at(i)->children.back(); + break; + } + } + + block->children.erase(block->children.begin()); + if (sel_case) + block->children.insert(block->children.begin(), sel_case->clone()); + delete stmt; + delete expr; + continue; + } + + if (stmt->type == AST_BLOCK) + { + if (!stmt->str.empty()) + stmt->expand_genblock(stmt->str + "."); + + block->children.erase(block->children.begin()); + block->children.insert(block->children.begin(), stmt->children.begin(), stmt->children.end()); + stmt->children.clear(); + delete stmt; + continue; + } + + if (!must_succeed) + goto finished; + log_file_error(stmt->filename, stmt->location.first_line, "Unsupported language construct in constant function\n%s: ... called from here.\n", + fcall->loc_string().c_str()); + log_abort(); + } + + result = AstNode::mkconst_bits(variables.at(str).val.bits, variables.at(str).is_signed); + +finished: + delete block; + current_scope = backup_scope; + + for (auto it : to_delete) { + delete it; + } + to_delete.clear(); + + return result; +} + +void AstNode::allocateDefaultEnumValues() +{ + log_assert(type==AST_ENUM); + log_assert(children.size() > 0); + if (children.front()->attributes.count(ID::enum_base_type)) + return; // already elaborated + int last_enum_int = -1; + for (auto node : children) { + log_assert(node->type==AST_ENUM_ITEM); + node->attributes[ID::enum_base_type] = mkconst_str(str); + for (size_t i = 0; i < node->children.size(); i++) { + switch (node->children[i]->type) { + case AST_NONE: + // replace with auto-incremented constant + delete node->children[i]; + node->children[i] = AstNode::mkconst_int(++last_enum_int, true); + break; + case AST_CONSTANT: + // explicit constant (or folded expression) + // TODO: can't extend 'x or 'z item + last_enum_int = node->children[i]->integer; + break; + default: + // ignore ranges + break; + } + // TODO: range check + } + } +} + +bool AstNode::is_recursive_function() const +{ + std::set<const AstNode *> visited; + std::function<bool(const AstNode *node)> visit = [&](const AstNode *node) { + if (visited.count(node)) + return node == this; + visited.insert(node); + if (node->type == AST_FCALL) { + auto it = current_scope.find(node->str); + if (it != current_scope.end() && visit(it->second)) + return true; + } + for (const AstNode *child : node->children) { + if (visit(child)) + return true; + } + return false; + }; + + log_assert(type == AST_FUNCTION); + return visit(this); +} + +std::pair<AstNode*, AstNode*> AstNode::get_tern_choice() +{ + if (!children[0]->isConst()) + return {}; + + bool found_sure_true = false; + bool found_maybe_true = false; + + if (children[0]->type == AST_CONSTANT) + for (auto &bit : children[0]->bits) { + if (bit == RTLIL::State::S1) + found_sure_true = true; + if (bit > RTLIL::State::S1) + found_maybe_true = true; + } + else + found_sure_true = children[0]->asReal(true) != 0; + + AstNode *choice = nullptr, *not_choice = nullptr; + if (found_sure_true) + choice = children[1], not_choice = children[2]; + else if (!found_maybe_true) + choice = children[2], not_choice = children[1]; + + return {choice, not_choice}; +} + +std::string AstNode::try_pop_module_prefix() const +{ + AstNode *current_scope_ast = (current_ast_mod == nullptr) ? current_ast : current_ast_mod; + size_t pos = str.find('.', 1); + if (str[0] == '\\' && pos != std::string::npos) { + std::string new_str = "\\" + str.substr(pos + 1); + if (current_scope.count(new_str)) { + std::string prefix = str.substr(0, pos); + auto it = current_scope_ast->attributes.find(ID::hdlname); + if ((it != current_scope_ast->attributes.end() && it->second->str == prefix) + || prefix == current_scope_ast->str) + return new_str; + } + } + return str; +} + +YOSYS_NAMESPACE_END