/* * yosys -- Yosys Open SYnthesis Suite * * Copyright (C) 2012 Clifford Wolf * * 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 #include #include #include YOSYS_NAMESPACE_BEGIN using namespace AST; using namespace AST_INTERNAL; // 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 pair last_blocking_assignment_warn; static bool deep_recursion_warning = false; if (recursion_counter++ == 1000 && deep_recursion_warning) { log_warning("Deep recursion in AST simplifier.\nDoes this design contain insanely long expressions?\n"); deep_recursion_warning = false; } AstNode *newNode = NULL; bool did_something = false; #if 0 log("-------------\n"); log("AST simplify[%d] depth %d at %s:%d,\n", stage, recursion_counter, filename.c_str(), linenum); 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); last_blocking_assignment_warn = pair(); 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("\\nomem2reg")) { dict> mem2reg_places; dict 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 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("\\nomem2reg")) continue; if (mem->get_bool_attribute("\\nomeminit") || get_bool_attribute("\\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; // 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); for (int i = 0; i < mem_size; i++) { AstNode *reg = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(mem_width-1, true), mkconst_int(0, true))); reg->str = stringf("%s[%d]", node->str.c_str(), i); reg->is_reg = true; reg->is_signed = node->is_signed; 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 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; set_line_num(linenum); // 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_warning("Ignoring call to system %s %s at %s:%d.\n", type == AST_FCALL ? "function" : "task", str.c_str(), filename.c_str(), linenum); delete_children(); str = std::string(); } if ((type == AST_TCALL) && (str == "$display" || str == "$write") && (!current_always || current_always->type != AST_INITIAL)) { log_warning("System task `%s' outside initial block is unsupported at %s:%d.\n", str.c_str(), filename.c_str(), linenum); 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_error("System task `%s' got %d arguments, expected >= 1 at %s:%d.\n", str.c_str(), int(children.size()), filename.c_str(), linenum); // 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_error("Failed to evaluate system task `%s' with non-constant 1st argument at %s:%d.\n", str.c_str(), filename.c_str(), linenum); std::string sformat = node_string->bitsAsConst().decode_string(); // Other arguments are placeholders. Process the string as we go through it std::string sout; int next_arg = 1; 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_error("System task `%s' called with `%%' at end of string at %s:%d.\n", str.c_str(), filename.c_str(), linenum); char cformat = sformat[++i]; // %% is special, does not need a matching argument if (cformat == '%') { sout += '%'; continue; } // 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': case 'x': case 'X': if (next_arg >= GetSize(children)) log_error("Missing argument for %%%c format specifier in system task `%s' at %s:%d.\n", cformat, str.c_str(), filename.c_str(), linenum); 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_error("Failed to evaluate system task `%s' with non-constant argument at %s:%d.\n", str.c_str(), filename.c_str(), linenum); break; case 'm': case 'M': break; default: log_error("System task `%s' called with invalid/unsupported format specifier at %s:%d.\n", str.c_str(), filename.c_str(), linenum); break; } switch (cformat) { case 's': case 'S': sout += node_arg->bitsAsConst().decode_string(); break; case 'd': case 'D': { char tmp[128]; snprintf(tmp, sizeof(tmp), "%d", node_arg->bitsAsConst().as_int()); sout += tmp; } break; case 'x': case 'X': { char tmp[128]; snprintf(tmp, sizeof(tmp), "%x", node_arg->bitsAsConst().as_int()); sout += tmp; } break; case 'm': case 'M': sout += log_id(current_module->name); break; default: log_abort(); } } // not a format specifier else sout += sformat[i]; } // 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_DEFPARAM || type == AST_PARASET || type == AST_RANGE || type == AST_PREFIX) const_fold = true; if (type == AST_IDENTIFIER && current_scope.count(str) > 0 && (current_scope[str]->type == AST_PARAMETER || current_scope[str]->type == AST_LOCALPARAM)) 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 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) { current_scope.clear(); std::map this_wire_scope; for (size_t i = 0; i < children.size(); i++) { AstNode *node = children[i]; if (node->type == AST_WIRE) { if (this_wire_scope.count(node->str) > 0) { AstNode *first_node = this_wire_scope[node->str]; 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_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_error("Incompatible re-declaration of wire %s at %s:%d.\n", node->str.c_str(), filename.c_str(), linenum); continue; } this_wire_scope[node->str] = node; } 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) { backup_scope[node->str] = current_scope[node->str]; current_scope[node->str] = node; } } 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) while (node->simplify(true, false, false, 1, -1, false, node->type == AST_PARAMETER || node->type == AST_LOCALPARAM)) did_something = true; } } 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; if (type == AST_ALWAYS || type == AST_INITIAL) current_always = this; 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; break; case AST_PARAMETER: case AST_LOCALPARAM: 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_error("Non-constant width range on parameter decl at %s:%d.\n", filename.c_str(), linenum); 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_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: detect_width_simple = true; child_0_is_self_determined = true; break; case AST_MEMRD: detect_width_simple = true; 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_TERNARY) { 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) { 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()) { std::vector 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()) { if (v->bits == children[0]->bits) { 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); } } // 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; 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_BLOCK && !str.empty()) break; if (type == AST_PREFIX && i >= 1) break; 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 (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; } } for (auto &attr : attributes) { while (attr.second->simplify(true, false, false, stage, -1, false, true)) did_something = true; } 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; 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; set_line_num(linenum); if (type == AST_MODULE) current_scope.clear(); // convert defparam nodes to cell parameters if (type == AST_DEFPARAM && !str.empty()) { size_t pos = str.rfind('.'); if (pos == std::string::npos) log_error("Defparam `%s' does not contain a dot (module/parameter separator) at %s:%d!\n", RTLIL::unescape_id(str).c_str(), filename.c_str(), linenum); std::string modname = str.substr(0, pos), paraname = "\\" + str.substr(pos+1); if (current_scope.count(modname) == 0 || current_scope.at(modname)->type != AST_CELL) log_error("Can't find cell for defparam `%s . %s` at %s:%d!\n", RTLIL::unescape_id(modname).c_str(), RTLIL::unescape_id(paraname).c_str(), filename.c_str(), linenum); AstNode *cell = current_scope.at(modname), *paraset = clone(); cell->children.insert(cell->children.begin() + 1, paraset); paraset->type = AST_PARASET; paraset->str = paraname; str.clear(); } // resolve constant prefixes if (type == AST_PREFIX) { if (children[0]->type != AST_CONSTANT) { // dumpAst(NULL, "> "); log_error("Index in generate block prefix syntax at %s:%d is not constant!\n", filename.c_str(), linenum); } 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_error("Left operand of to_bits expression is not constant at %s:%d!\n", filename.c_str(), linenum); if (children[1]->type != AST_CONSTANT) log_error("Right operand of to_bits expression is not constant at %s:%d!\n", filename.c_str(), linenum); 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_left >= 0 && 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; } } 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(); for (auto range : children[1]->children) { if (!range->range_valid) log_error("Non-constant range on memory decl at %s:%d.\n", filename.c_str(), linenum); 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); 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; for (int i = 0; 2*i < GetSize(id2ast->multirange_dimensions); i++) { if (GetSize(children[0]->children) < i) log_error("Insufficient number of array indices for %s at %s:%d.\n", log_id(str), filename.c_str(), linenum); 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) { if (children.size() > 1 && children[1]->type == AST_RANGE) { if (!children[1]->range_valid) log_error("Non-constant width range on parameter decl at %s:%d.\n", filename.c_str(), linenum); 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_warning("converting real value %e to binary %s at %s:%d.\n", children[0]->realvalue, log_signal(constvalue), filename.c_str(), linenum); 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; } } // annotate identifiers using scope resolution and create auto-wires as needed if (type == AST_IDENTIFIER) { if (current_scope.count(str) == 0) { for (auto node : current_ast_mod->children) { 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) && str == node->str) { current_scope[node->str] = node; break; } } } if (current_scope.count(str) == 0) { // log_warning("Creating auto-wire `%s' in module `%s'.\n", str.c_str(), current_ast_mod->str.c_str()); 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; } 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) { if (id2ast == NULL || id2ast->type != AST_MEMORY || children[0]->children.size() != 1) log_error("Invalid bit-select on memory access at %s:%d!\n", filename.c_str(), linenum); 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; std::stringstream sstr; sstr << "$mem2bits$" << str << "$" << filename << ":" << linenum << "$" << (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["\\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; 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->id2ast = wire; goto apply_newNode; } if (type == AST_WHILE) log_error("While loops are only allowed in constant functions at %s:%d!\n", filename.c_str(), linenum); if (type == AST_REPEAT) log_error("Repeat loops are only allowed in constant functions at %s:%d!\n", filename.c_str(), linenum); // 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); if (init_ast->type != AST_ASSIGN_EQ) log_error("Unsupported 1st expression of generate for-loop at %s:%d!\n", filename.c_str(), linenum); if (next_ast->type != AST_ASSIGN_EQ) log_error("Unsupported 3rd expression of generate for-loop at %s:%d!\n", filename.c_str(), linenum); if (type == AST_GENFOR) { if (init_ast->children[0]->id2ast == NULL || init_ast->children[0]->id2ast->type != AST_GENVAR) log_error("Left hand side of 1st expression of generate for-loop at %s:%d is not a gen var!\n", filename.c_str(), linenum); if (next_ast->children[0]->id2ast == NULL || next_ast->children[0]->id2ast->type != AST_GENVAR) log_error("Left hand side of 3rd expression of generate for-loop at %s:%d is not a gen var!\n", filename.c_str(), linenum); } else { if (init_ast->children[0]->id2ast == NULL || init_ast->children[0]->id2ast->type != AST_WIRE) log_error("Left hand side of 1st expression of generate for-loop at %s:%d is not a register!\n", filename.c_str(), linenum); if (next_ast->children[0]->id2ast == NULL || next_ast->children[0]->id2ast->type != AST_WIRE) log_error("Left hand side of 3rd expression of generate for-loop at %s:%d is not a register!\n", filename.c_str(), linenum); } if (init_ast->children[0]->id2ast != next_ast->children[0]->id2ast) log_error("Incompatible left-hand sides in 1st and 3rd expression of generate for-loop at %s:%d!\n", filename.c_str(), linenum); // eval 1st expression AstNode *varbuf = init_ast->children[1]->clone(); while (varbuf->simplify(true, false, false, stage, 32, true, false)) { } if (varbuf->type != AST_CONSTANT) log_error("Right hand side of 1st expression of generate for-loop at %s:%d is not constant!\n", filename.c_str(), linenum); 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(); while (buf->simplify(true, false, false, stage, width_hint, sign_hint, false)) { } if (buf->type != AST_CONSTANT) log_error("2nd expression of generate for-loop at %s:%d is not constant!\n", filename.c_str(), linenum); if (buf->integer == 0) { delete buf; break; } delete buf; // expand body int index = varbuf->children[0]->integer; if (body_ast->type == AST_GENBLOCK) buf = body_ast->clone(); else buf = new AstNode(AST_GENBLOCK, body_ast->clone()); if (buf->str.empty()) { std::stringstream sstr; sstr << "$genblock$" << filename << ":" << linenum << "$" << (autoidx++); buf->str = sstr.str(); } std::map name_map; std::stringstream sstr; sstr << buf->str << "[" << index << "]."; buf->expand_genblock(varbuf->str, sstr.str(), name_map); if (type == AST_GENFOR) { for (size_t i = 0; i < buf->children.size(); i++) { buf->children[i]->simplify(false, 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(); while (buf->simplify(true, false, false, stage, 32, true, false)) { } if (buf->type != AST_CONSTANT) log_error("Right hand side of 3rd expression of generate for-loop at %s:%d is not constant!\n", filename.c_str(), linenum); delete varbuf->children[0]; varbuf->children[0] = buf; } current_scope[varbuf->str] = backup_scope_varbuf; delete varbuf; delete_children(); did_something = true; } // transform block with name if (type == AST_BLOCK && !str.empty()) { std::map name_map; expand_genblock(std::string(), str + ".", name_map); std::vector new_children; for (size_t i = 0; i < children.size(); i++) if (children[i]->type == AST_WIRE || children[i]->type == AST_PARAMETER || children[i]->type == AST_LOCALPARAM) { 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()) { std::map name_map; expand_genblock(std::string(), str + ".", name_map); } for (size_t i = 0; i < children.size(); i++) { children[i]->simplify(false, 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_error("Condition for generate if at %s:%d is not constant!\n", filename.c_str(), linenum); } 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()) { std::map name_map; buf->expand_genblock(std::string(), buf->str + ".", name_map); } for (size_t i = 0; i < buf->children.size(); i++) { buf->children[i]->simplify(false, 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_error("Condition for generate case at %s:%d is not constant!\n", filename.c_str(), linenum); } 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, false)) { } if (buf->type != AST_CONSTANT) { // for (auto f : log_files) // dumpAst(f, "verilog-ast> "); log_error("Expression in generate case at %s:%d is not constant!\n", filename.c_str(), linenum); } 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()) { std::map name_map; buf->expand_genblock(std::string(), buf->str + ".", name_map); } for (size_t i = 0; i < buf->children.size(); i++) { buf->children[i]->simplify(false, 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_error("Non-constant array range on cell array at %s:%d.\n", filename.c_str(), linenum); 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_error("Cell arrays of primitives are currently not supported at %s:%d.\n", filename.c_str(), linenum); } 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_error("Insufficient number of arguments for primitive `%s' at %s:%d!\n", str.c_str(), filename.c_str(), linenum); std::vector 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_error("Invalid number of arguments for primitive `%s' at %s:%d!\n", str.c_str(), filename.c_str(), linenum); std::vector 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.push_back(node); 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; if (str == "buf") op_type = AST_POS; if (str == "not") op_type = AST_POS, 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]); if (invert_results) node = new AstNode(AST_BIT_NOT, node); str.clear(); type = AST_ASSIGN; children.push_back(children_list[0]); children.push_back(node); did_something = true; } } // replace dynamic ranges in left-hand side expressions (e.g. "foo[bar] <= 1'b1;") with // a big case block that selects the correct single-bit assignment. 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 result_width = 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_error("Unsupported expression on dynamic range select on signal `%s' at %s:%d!\n", str.c_str(), filename.c_str(), linenum); result_width = abs(int(left_at_zero_ast->integer - right_at_zero_ast->integer)) + 1; } did_something = true; newNode = new AstNode(AST_CASE, shift_expr); for (int i = 0; i <= source_width-result_width; i++) { int start_bit = children[0]->id2ast->range_right + i; AstNode *cond = new AstNode(AST_COND, mkconst_int(start_bit, true)); AstNode *lvalue = children[0]->clone(); lvalue->delete_children(); lvalue->children.push_back(new AstNode(AST_RANGE, mkconst_int(start_bit+result_width-1, 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); } goto apply_newNode; } skip_dynamic_range_lvalue_expansion:; if (stage > 1 && (type == AST_ASSERT || type == AST_ASSUME || type == AST_PREDICT) && current_block != NULL) { std::stringstream sstr; sstr << "$formal$" << filename << ":" << linenum << "$" << (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; 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; current_ast_mod->children.push_back(wire_en); if (current_always == nullptr || current_always->type != AST_INITIAL) { 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_scope[wire_en->str] = wire_en; while (wire_en->simplify(true, false, false, 1, -1, false, false)) { } std::vector x_bit; x_bit.push_back(RTLIL::State::Sx); AstNode *assign_check = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), mkconst_bits(x_bit, false)); assign_check->children[0]->str = id_check; AstNode *assign_en = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), mkconst_int(0, false, 1)); assign_en->children[0]->str = id_en; 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); 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; 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; newNode = new AstNode(AST_BLOCK); newNode->children.push_back(assign_check); newNode->children.push_back(assign_en); AstNode *assertnode = new AstNode(type); 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_PREDICT) && 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", filename.c_str(), linenum, autoidx++); current_ast_mod->children.push_back(wire_tmp); current_scope[wire_tmp->str] = wire_tmp; wire_tmp->attributes["\\nosync"] = AstNode::mkconst_int(1, false); while (wire_tmp->simplify(true, false, false, 1, -1, false, false)) { } 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())); 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 << "$" << filename << ":" << linenum << "$" << (autoidx++); std::string id_addr = sstr.str() + "_ADDR", id_data = sstr.str() + "_DATA", id_en = sstr.str() + "_EN"; if (type == AST_ASSIGN_EQ) { pair this_blocking_assignment_warn(filename, linenum); if (this_blocking_assignment_warn != last_blocking_assignment_warn) log_warning("Blocking assignment to memory in line %s:%d is handled like a non-blocking assignment.\n", filename.c_str(), linenum); last_blocking_assignment_warn = this_blocking_assignment_warn; } int mem_width, mem_size, addr_bits; children[0]->id2ast->meminfo(mem_width, mem_size, addr_bits); 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); 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; 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 *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; 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 *wire_en = nullptr; if (current_always->type != AST_INITIAL) { 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; 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)) { } } std::vector 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 *assign_addr = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), mkconst_bits(x_bits_addr, false)); assign_addr->children[0]->str = id_addr; AstNode *assign_data = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), mkconst_bits(x_bits_data, false)); assign_data->children[0]->str = id_data; AstNode *assign_en = nullptr; if (current_always->type != AST_INITIAL) { assign_en = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), mkconst_int(0, false, mem_width)); assign_en->children[0]->str = id_en; } AstNode *default_signals = new AstNode(AST_BLOCK); default_signals->children.push_back(assign_addr); default_signals->children.push_back(assign_data); if (current_always->type != AST_INITIAL) default_signals->children.push_back(assign_en); current_top_block->children.insert(current_top_block->children.begin(), default_signals); assign_addr = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), children[0]->children[0]->children[0]->clone()); assign_addr->children[0]->str = id_addr; 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 padding_x(offset, RTLIL::State::Sx); assign_data = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), new AstNode(AST_CONCAT, mkconst_bits(padding_x, false), children[1]->clone())); assign_data->children[0]->str = id_data; if (current_always->type != AST_INITIAL) { 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_LE, new AstNode(AST_IDENTIFIER), mkconst_bits(set_bits_en, false)); assign_en->children[0]->str = id_en; } } 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_error("Unsupported expression on dynamic range select on signal `%s' at %s:%d!\n", str.c_str(), filename.c_str(), linenum); int width = left_at_zero_ast->integer - right_at_zero_ast->integer + 1; assign_data = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), new AstNode(AST_SHIFT_LEFT, children[1]->clone(), offset_ast->clone())); assign_data->children[0]->str = id_data; if (current_always->type != AST_INITIAL) { 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_LE, 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; } delete left_at_zero_ast; delete right_at_zero_ast; delete offset_ast; } } else { assign_data = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), children[1]->clone()); assign_data->children[0]->str = id_data; if (current_always->type != AST_INITIAL) { assign_en = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), mkconst_bits(set_bits_en, false)); assign_en->children[0]->str = id_en; } } newNode = new AstNode(AST_BLOCK); newNode->children.push_back(assign_addr); newNode->children.push_back(assign_data); if (current_always->type != AST_INITIAL) newNode->children.push_back(assign_en); AstNode *wrnode = new AstNode(current_always->type == AST_INITIAL ? AST_MEMINIT : AST_MEMWR); wrnode->children.push_back(new AstNode(AST_IDENTIFIER)); wrnode->children.push_back(new AstNode(AST_IDENTIFIER)); if (current_always->type != AST_INITIAL) wrnode->children.push_back(new AstNode(AST_IDENTIFIER)); else wrnode->children.push_back(AstNode::mkconst_int(1, false)); wrnode->str = children[0]->str; wrnode->id2ast = children[0]->id2ast; wrnode->children[0]->str = id_addr; wrnode->children[1]->str = id_data; if (current_always->type != AST_INITIAL) wrnode->children[2]->str = id_en; current_ast_mod->children.push_back(wrnode); 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; } // $anyconst and $aconst are mapped in AstNode::genRTLIL() if (str == "\\$anyconst" || str == "\\$aconst") return false; if (str == "\\$clog2") { if (children.size() != 1) log_error("System function %s got %d arguments, expected 1 at %s:%d.\n", RTLIL::unescape_id(str).c_str(), int(children.size()), filename.c_str(), linenum); AstNode *buf = children[0]->clone(); while (buf->simplify(true, false, false, stage, width_hint, sign_hint, false)) { } if (buf->type != AST_CONSTANT) log_error("Failed to evaluate system function `%s' with non-constant value at %s:%d.\n", str.c_str(), filename.c_str(), linenum); 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, false); 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") { 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_error("System function %s got %d arguments, expected 2 at %s:%d.\n", RTLIL::unescape_id(str).c_str(), int(children.size()), filename.c_str(), linenum); } else { if (children.size() != 1) log_error("System function %s got %d arguments, expected 1 at %s:%d.\n", RTLIL::unescape_id(str).c_str(), int(children.size()), filename.c_str(), linenum); } if (children.size() >= 1) { while (children[0]->simplify(true, false, false, stage, width_hint, sign_hint, false)) { } if (!children[0]->isConst()) log_error("Failed to evaluate system function `%s' with non-constant argument at %s:%d.\n", RTLIL::unescape_id(str).c_str(), filename.c_str(), linenum); 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_error("Failed to evaluate system function `%s' with non-constant argument at %s:%d.\n", RTLIL::unescape_id(str).c_str(), filename.c_str(), linenum); 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); } 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 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 argtypes; std::vector 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_error("Insufficient number of arguments in DPI function call at %s:%d.\n", filename.c_str(), linenum); 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_error("Failed to evaluate DPI function with non-constant argument at %s:%d.\n", filename.c_str(), linenum); } newNode = dpi_call(rtype, fname, argtypes, args); for (auto arg : args) delete arg; goto apply_newNode; } if (current_scope.count(str) == 0 || current_scope[str]->type != AST_FUNCTION) log_error("Can't resolve function name `%s' at %s:%d.\n", str.c_str(), filename.c_str(), linenum); } if (type == AST_TCALL) { if (str == "$finish" || str == "$stop") { if (!current_always || current_always->type != AST_INITIAL) log_error("System task `%s' outside initial block is unsupported at %s:%d.\n", str.c_str(), filename.c_str(), linenum); log_error("System task `%s' executed at %s:%d.\n", str.c_str(), filename.c_str(), linenum); } if (str == "\\$readmemh" || str == "\\$readmemb") { if (GetSize(children) < 2 || GetSize(children) > 4) log_error("System function %s got %d arguments, expected 2-4 at %s:%d.\n", RTLIL::unescape_id(str).c_str(), int(children.size()), filename.c_str(), linenum); 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_error("Failed to evaluate system function `%s' with non-constant 1st argument at %s:%d.\n", str.c_str(), filename.c_str(), linenum); 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_error("Failed to evaluate system function `%s' with non-memory 2nd argument at %s:%d.\n", str.c_str(), filename.c_str(), linenum); 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_error("Failed to evaluate system function `%s' with non-constant 3rd argument at %s:%d.\n", str.c_str(), filename.c_str(), linenum); 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_error("Failed to evaluate system function `%s' with non-constant 4th argument at %s:%d.\n", str.c_str(), filename.c_str(), linenum); finish_addr = int(node_addr->asInt(false)); } bool unconditional_init = false; if (current_always->type == AST_INITIAL) { pool queue; log_assert(current_always->children[0]->type == AST_BLOCK); queue.insert(current_always->children[0]); while (!unconditional_init && !queue.empty()) { pool 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); goto apply_newNode; } if (current_scope.count(str) == 0 || current_scope[str]->type != AST_TASK) log_error("Can't resolve task name `%s' at %s:%d.\n", str.c_str(), filename.c_str(), linenum); } AstNode *decl = current_scope[str]; std::stringstream sstr; sstr << "$func$" << str << "$" << filename << ":" << linenum << "$" << (autoidx++) << "$"; std::string prefix = sstr.str(); bool recommend_const_eval = false; bool require_const_eval = in_param ? false : has_const_only_constructs(recommend_const_eval); if ((in_param || recommend_const_eval || require_const_eval) && !decl->attributes.count("\\via_celltype")) { bool all_args_const = true; for (auto child : children) { while (child->simplify(true, false, false, 1, -1, false, true)) { } if (child->type != AST_CONSTANT) all_args_const = false; } if (all_args_const) { AstNode *func_workspace = current_scope[str]->clone(); newNode = func_workspace->eval_const_function(this); delete func_workspace; goto apply_newNode; } if (in_param) log_error("Non-constant function call in constant expression at %s:%d.\n", filename.c_str(), linenum); if (require_const_eval) log_error("Function %s can only be called with constant arguments at %s:%d.\n", str.c_str(), filename.c_str(), linenum); } size_t arg_count = 0; std::map replace_rules; vector added_mod_children; dict wire_cache; if (current_block == NULL) { log_assert(type == AST_FCALL); AstNode *wire = NULL; for (auto child : decl->children) if (child->type == AST_WIRE && child->str == str) wire = child->clone(); log_assert(wire != NULL); wire->str = prefix + str; 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 *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()))); current_ast_mod->children.push_back(always); goto replace_fcall_with_id; } if (decl->attributes.count("\\via_celltype")) { std::string celltype = decl->attributes.at("\\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.str().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->str = prefix + wire->str; 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()); 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_PARAMETER || child->type == AST_LOCALPARAM) { AstNode *wire = nullptr; if (wire_cache.count(child->str)) { wire = wire_cache.at(child->str); if (wire->children.empty()) { for (auto c : child->children) wire->children.push_back(c->clone()); } else { if (!child->children.empty()) log_error("Incompatible re-declaration of wire %s at %s:%d.\n", child->str.c_str(), filename.c_str(), linenum); } } else { wire = child->clone(); wire->str = prefix + wire->str; wire->port_id = 0; wire->is_input = false; wire->is_output = false; if (!child->is_output) wire->attributes["\\nosync"] = AstNode::mkconst_int(1, false); wire_cache[child->str] = wire; current_ast_mod->children.push_back(wire); added_mod_children.push_back(wire); } if (child->type == AST_WIRE) while (wire->simplify(true, false, false, 1, -1, false, false)) { } replace_rules[child->str] = wire->str; current_scope[wire->str] = wire; if ((child->is_input || child->is_output) && arg_count < children.size()) { AstNode *arg = children[arg_count++]->clone(); 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); 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; } } } for (auto child : added_mod_children) { child->replace_ids(prefix, replace_rules); while (child->simplify(true, false, false, 1, -1, false, false)) { } } for (auto child : decl->children) if (child->type != AST_WIRE && child->type != AST_PARAMETER && child->type != AST_LOCALPARAM) { AstNode *stmt = child->clone(); stmt->replace_ids(prefix, replace_rules); for (auto it = current_block->children.begin(); it != current_block->children.end(); it++) { if (*it != current_block_child) continue; current_block->children.insert(it, stmt); break; } } replace_fcall_with_id: if (type == AST_FCALL) { delete_children(); type = AST_IDENTIFIER; str = prefix + str; } if (type == AST_TCALL) str = ""; did_something = true; } // perform const folding when activated if (const_fold) { bool string_op; std::vector 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)) { if (current_scope[str]->children[0]->type == AST_CONSTANT) { if (children.size() != 0 && children[0]->type == AST_RANGE && children[0]->range_valid) { std::vector 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; } 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[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 && (current_scope[str]->type == AST_WIRE || current_scope[str]->type == AST_AUTOWIRE)) { 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_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_POS) newNode->realvalue = +children[0]->asReal(sign_hint); else newNode->realvalue = -children[0]->asReal(sign_hint); } break; case AST_TERNARY: if (children[0]->isConst()) { 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(sign_hint) != 0; AstNode *choice = NULL, *not_choice = NULL; if (found_sure_true) choice = children[1], not_choice = children[2]; else if (!found_maybe_true) choice = children[2], not_choice = children[1]; 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_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->linenum = linenum; newNode->cloneInto(this); delete newNode; did_something = true; } if (!did_something) basic_prep = true; recursion_counter--; return did_something; } static void replace_result_wire_name_in_function(AstNode *node, std::string &from, std::string &to) { for (auto &it : node->children) replace_result_wire_name_in_function(it, from, to); if (node->str == from) node->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 meminit_bits; int meminit_size=0; std::ifstream f; f.open(mem_filename.c_str()); if (f.fail()) log_error("Can not open file `%s` for %s at %s:%d.\n", mem_filename.c_str(), str.c_str(), filename.c_str(), linenum); 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.substr(i, 2) == "*/") { line[i] = ' '; line[i+1] = ' '; in_comment = false; continue; } if (!in_comment && line.substr(i, 2) == "/*") in_comment = true; if (in_comment) line[i] = ' '; } while (1) { token = next_token(line, " \t\r\n"); if (token.empty() || token.substr(0, 2) == "//") 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_error("Can not parse address `%s` for %s at %s:%d.\n", nptr, str.c_str(), filename.c_str(), linenum); 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[2] = 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(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; } 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[2] = AstNode::mkconst_int(meminit_size, false); } return block; } // annotate the names of all wires and other named objects in a generate block void AstNode::expand_genblock(std::string index_var, std::string prefix, std::map &name_map) { if (!index_var.empty() && type == AST_IDENTIFIER && str == index_var) { current_scope[index_var]->children[0]->cloneInto(this); return; } if ((type == AST_IDENTIFIER || type == AST_FCALL || type == AST_TCALL) && name_map.count(str) > 0) str = name_map[str]; std::map backup_name_map; for (size_t i = 0; i < children.size(); i++) { AstNode *child = children[i]; if (child->type == AST_WIRE || child->type == AST_MEMORY || child->type == AST_PARAMETER || child->type == AST_LOCALPARAM || child->type == AST_FUNCTION || child->type == AST_TASK || child->type == AST_CELL) { if (backup_name_map.size() == 0) backup_name_map = name_map; std::string new_name = prefix[0] == '\\' ? prefix.substr(1) : prefix; size_t pos = child->str.rfind('.'); if (pos == std::string::npos) pos = child->str[0] == '\\' ? 1 : 0; else pos = pos + 1; new_name = child->str.substr(0, pos) + new_name + child->str.substr(pos); if (new_name[0] != '$' && new_name[0] != '\\') new_name = prefix[0] + new_name; name_map[child->str] = new_name; if (child->type == AST_FUNCTION) replace_result_wire_name_in_function(child, 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]; if (child->type != AST_FUNCTION && child->type != AST_TASK && child->type != AST_PREFIX) child->expand_genblock(index_var, prefix, name_map); } if (backup_name_map.size() > 0) name_map.swap(backup_name_map); } // rename stuff (used when tasks of functions are instantiated) void AstNode::replace_ids(const std::string &prefix, const std::map &rules) { if (type == AST_BLOCK) { std::map new_rules = rules; std::string new_prefix = prefix + str; for (auto child : children) if (child->type == AST_WIRE) { new_rules[child->str] = new_prefix + child->str; child->str = new_prefix + child->str; } for (auto child : children) if (child->type != AST_WIRE) child->replace_ids(new_prefix, new_rules); } else { if (type == AST_IDENTIFIER && rules.count(str) > 0) str = rules.at(str); for (auto child : children) child->replace_ids(prefix, rules); } } // helper function for mem2reg_as_needed_pass1 static void mark_memories_assign_lhs_complex(dict> &mem2reg_places, dict &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", that->filename.c_str(), that->linenum)); mem2reg_candidates[mem] |= AstNode::MEM2REG_FL_CMPLX_LHS; } } // find memories that should be replaced by registers void AstNode::mem2reg_as_needed_pass1(dict> &mem2reg_places, dict &mem2reg_candidates, dict &proc_flags, uint32_t &flags) { uint32_t children_flags = 0; int ignore_children_counter = 0; 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", filename.c_str(), linenum)); 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", filename.c_str(), linenum)); proc_flags[mem] |= AstNode::MEM2REG_FL_EQ1; } // 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", filename.c_str(), linenum)); 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", filename.c_str(), linenum)); mem2reg_candidates[mem] |= AstNode::MEM2REG_FL_SET_ELSE; } } ignore_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", filename.c_str(), linenum)); 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' if (type == AST_MEMORY && (get_bool_attribute("\\mem2reg") || (flags & AstNode::MEM2REG_FL_ALL) || !is_reg)) mem2reg_candidates[this] |= AstNode::MEM2REG_FL_FORCED; if (type == AST_MODULE && get_bool_attribute("\\mem2reg")) children_flags |= AstNode::MEM2REG_FL_ALL; dict *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; } if (type == AST_INITIAL) { children_flags |= AstNode::MEM2REG_FL_INIT; proc_flags_p = new dict; } uint32_t backup_flags = flags; flags |= children_flags; log_assert((flags & ~0x000000ff) == 0); for (auto child : children) if (ignore_children_counter > 0) ignore_children_counter--; 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 &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_error("Invalid array access at %s:%d.\n", filename.c_str(), linenum); return true; } void AstNode::mem2reg_remove(pool &mem2reg_set, vector &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 &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_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; 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 << "$" << filename << ":" << linenum << "$" << (autoidx++); std::string id_addr = sstr.str() + "_ADDR", id_data = sstr.str() + "_DATA"; int mem_width, mem_size, addr_bits; 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->attributes["\\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->attributes["\\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; 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; 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; str = stringf("%s[%d]", str.c_str(), id); delete_children(); range_valid = false; id2ast = NULL; } else { std::stringstream sstr; sstr << "$mem2reg_rd$" << str << "$" << filename << ":" << linenum << "$" << (autoidx++); std::string id_addr = sstr.str() + "_ADDR", id_data = sstr.str() + "_DATA"; int mem_width, mem_size, addr_bits; 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; if (block) wire_addr->attributes["\\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; if (block) wire_data->attributes["\\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; 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[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 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; 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); } 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::has_const_only_constructs(bool &recommend_const_eval) { if (type == AST_FOR) recommend_const_eval = true; if (type == AST_WHILE || type == AST_REPEAT) return true; if (type == AST_FCALL && current_scope.count(str)) if (current_scope[str]->has_const_only_constructs(recommend_const_eval)) return true; for (auto child : children) if (child->AstNode::has_const_only_constructs(recommend_const_eval)) return true; return false; } // helper function for AstNode::eval_const_function() void AstNode::replace_variables(std::map &variables, AstNode *fcall) { 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) log_error("Memory access in constant function is not supported in %s:%d (called from %s:%d).\n", filename.c_str(), linenum, fcall->filename.c_str(), fcall->linenum); children.at(0)->replace_variables(variables, fcall); while (simplify(true, false, false, 1, -1, false, true)) { } if (!children.at(0)->range_valid) log_error("Non-constant range in %s:%d (called from %s:%d).\n", filename.c_str(), linenum, fcall->filename.c_str(), fcall->linenum); 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; std::vector &var_bits = variables.at(str).val.bits; std::vector 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; } for (auto &child : children) child->replace_variables(variables, fcall); } // evaluate functions with all-const arguments AstNode *AstNode::eval_const_function(AstNode *fcall) { std::map backup_scope; std::map variables; bool delete_temp_block = false; AstNode *block = NULL; size_t argidx = 0; for (auto child : children) { if (child->type == AST_BLOCK) { log_assert(block == NULL); block = child; continue; } if (child->type == AST_WIRE) { while (child->simplify(true, false, false, 1, -1, false, true)) { } if (!child->range_valid) log_error("Can't determine size of variable %s in %s:%d (called from %s:%d).\n", child->str.c_str(), child->filename.c_str(), child->linenum, fcall->filename.c_str(), fcall->linenum); variables[child->str].val = RTLIL::Const(RTLIL::State::Sx, abs(child->range_left - child->range_right)+1); variables[child->str].offset = min(child->range_left, child->range_right); variables[child->str].is_signed = child->is_signed; if (child->is_input && argidx < fcall->children.size()) variables[child->str].val = fcall->children.at(argidx++)->bitsAsConst(variables[child->str].val.bits.size()); backup_scope[child->str] = current_scope[child->str]; current_scope[child->str] = child; continue; } log_assert(block == NULL); delete_temp_block = true; block = new AstNode(AST_BLOCK); block->children.push_back(child->clone()); } log_assert(block != NULL); log_assert(variables.count(str) != 0); 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_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) stmt->children.at(0)->children.at(0)->replace_variables(variables, fcall); stmt->children.at(1)->replace_variables(variables, fcall); 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) log_error("Non-constant expression in constant function at %s:%d (called from %s:%d). X\n", stmt->filename.c_str(), stmt->linenum, fcall->filename.c_str(), fcall->linenum); if (stmt->children.at(0)->type != AST_IDENTIFIER) log_error("Unsupported composite left hand side in constant function at %s:%d (called from %s:%d).\n", stmt->filename.c_str(), stmt->linenum, fcall->filename.c_str(), fcall->linenum); if (!variables.count(stmt->children.at(0)->str)) log_error("Assignment to non-local variable in constant function at %s:%d (called from %s:%d).\n", stmt->filename.c_str(), stmt->linenum, fcall->filename.c_str(), fcall->linenum); 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) log_error("Non-constant range in %s:%d (called from %s:%d).\n", range->filename.c_str(), range->linenum, fcall->filename.c_str(), fcall->linenum); 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++) v.val.bits.at(i+offset-v.offset) = 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(); cond->replace_variables(variables, fcall); while (cond->simplify(true, false, false, 1, -1, false, true)) { } if (cond->type != AST_CONSTANT) log_error("Non-constant expression in constant function at %s:%d (called from %s:%d).\n", stmt->filename.c_str(), stmt->linenum, fcall->filename.c_str(), fcall->linenum); 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(); num->replace_variables(variables, fcall); while (num->simplify(true, false, false, 1, -1, false, true)) { } if (num->type != AST_CONSTANT) log_error("Non-constant expression in constant function at %s:%d (called from %s:%d).\n", stmt->filename.c_str(), stmt->linenum, fcall->filename.c_str(), fcall->linenum); 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(); expr->replace_variables(variables, fcall); 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(); cond->replace_variables(variables, fcall); cond = new AstNode(AST_EQ, expr->clone(), cond); while (cond->simplify(true, false, false, 1, -1, false, true)) { } if (cond->type != AST_CONSTANT) log_error("Non-constant expression in constant function at %s:%d (called from %s:%d).\n", stmt->filename.c_str(), stmt->linenum, fcall->filename.c_str(), fcall->linenum); 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) { block->children.erase(block->children.begin()); block->children.insert(block->children.begin(), stmt->children.begin(), stmt->children.end()); stmt->children.clear(); delete stmt; continue; } log_error("Unsupported language construct in constant function at %s:%d (called from %s:%d).\n", stmt->filename.c_str(), stmt->linenum, fcall->filename.c_str(), fcall->linenum); log_abort(); } if (delete_temp_block) delete block; for (auto &it : backup_scope) if (it.second == NULL) current_scope.erase(it.first); else current_scope[it.first] = it.second; return AstNode::mkconst_bits(variables.at(str).val.bits, variables.at(str).is_signed); } YOSYS_NAMESPACE_END