diff options
Diffstat (limited to 'frontends/ast')
-rw-r--r-- | frontends/ast/ast.cc | 138 | ||||
-rw-r--r-- | frontends/ast/ast.h | 29 | ||||
-rw-r--r-- | frontends/ast/genrtlil.cc | 47 | ||||
-rw-r--r-- | frontends/ast/simplify.cc | 495 |
4 files changed, 600 insertions, 109 deletions
diff --git a/frontends/ast/ast.cc b/frontends/ast/ast.cc index 4fbc238b0..6097f02f5 100644 --- a/frontends/ast/ast.cc +++ b/frontends/ast/ast.cc @@ -344,7 +344,7 @@ void AstNode::dumpAst(FILE *f, std::string indent) const } if (!multirange_swapped.empty()) { fprintf(f, " multirange_swapped=["); - for (auto v : multirange_swapped) + for (bool v : multirange_swapped) fprintf(f, " %d", v); fprintf(f, " ]"); } @@ -854,7 +854,7 @@ RTLIL::Const AstNode::bitsAsConst(int width) return bitsAsConst(width, is_signed); } -RTLIL::Const AstNode::asAttrConst() +RTLIL::Const AstNode::asAttrConst() const { log_assert(type == AST_CONSTANT); @@ -869,8 +869,17 @@ RTLIL::Const AstNode::asAttrConst() return val; } -RTLIL::Const AstNode::asParaConst() +RTLIL::Const AstNode::asParaConst() const { + if (type == AST_REALVALUE) + { + AstNode *strnode = AstNode::mkconst_str(stringf("%f", realvalue)); + RTLIL::Const val = strnode->asAttrConst(); + val.flags |= RTLIL::CONST_FLAG_REAL; + delete strnode; + return val; + } + RTLIL::Const val = asAttrConst(); if (is_signed) val.flags |= RTLIL::CONST_FLAG_SIGNED; @@ -983,8 +992,7 @@ static bool param_has_no_default(const AstNode *param) { (children.size() == 1 && children[0]->type == AST_RANGE); } -// create and add a new AstModule from an AST_MODULE AST node -static void process_module(RTLIL::Design *design, AstNode *ast, bool defer, AstNode *original_ast = NULL, bool quiet = false) +static RTLIL::Module *process_module(RTLIL::Design *design, AstNode *ast, bool defer, AstNode *original_ast = NULL, bool quiet = false) { log_assert(current_scope.empty()); log_assert(ast->type == AST_MODULE || ast->type == AST_INTERFACE); @@ -1044,8 +1052,11 @@ static void process_module(RTLIL::Design *design, AstNode *ast, bool defer, AstN } } - // TODO(zachjs): make design available to simplify() in the future + // simplify this module or interface using the current design as context + // for lookup up ports and wires within cells + set_simplify_design_context(design); while (ast->simplify(!flag_noopt, false, false, 0, -1, false, false)) { } + set_simplify_design_context(nullptr); if (flag_dump_ast2) { log("Dumping AST after simplification:\n"); @@ -1172,6 +1183,9 @@ static void process_module(RTLIL::Design *design, AstNode *ast, bool defer, AstN continue; module->attributes[attr.first] = attr.second->asAttrConst(); } + for (const AstNode *node : ast->children) + if (node->type == AST_PARAMETER) + current_module->avail_parameters(node->str); } if (ast->type == AST_INTERFACE) @@ -1197,6 +1211,42 @@ static void process_module(RTLIL::Design *design, AstNode *ast, bool defer, AstN } design->add(current_module); + return current_module; +} + +RTLIL::Module * +AST_INTERNAL::process_and_replace_module(RTLIL::Design *design, + RTLIL::Module *old_module, + AstNode *new_ast, + AstNode *original_ast) +{ + // The old module will be deleted. Rename and mark for deletion, using + // a static counter to make sure we get a unique name. + static unsigned counter; + std::ostringstream new_name; + new_name << old_module->name.str() + << "_before_process_and_replace_module_" + << counter; + ++counter; + + design->rename(old_module, new_name.str()); + old_module->set_bool_attribute(ID::to_delete); + + // Check if the module was the top module. If it was, we need to remove + // the top attribute and put it on the new module. + bool is_top = false; + if (old_module->get_bool_attribute(ID::initial_top)) { + old_module->attributes.erase(ID::initial_top); + is_top = true; + } + + // Generate RTLIL from AST for the new module and add to the design: + RTLIL::Module* new_module = process_module(design, new_ast, false, original_ast); + + if (is_top) + new_module->set_bool_attribute(ID::top); + + return new_module; } // renames identifiers in tasks and functions within a package @@ -1410,13 +1460,32 @@ void AST::explode_interface_port(AstNode *module_ast, RTLIL::Module * intfmodule } } +// AstModules may contain cells marked with ID::reprocess_after, which indicates +// that it should be reprocessed once the specified module has been elaborated. +bool AstModule::reprocess_if_necessary(RTLIL::Design *design) +{ + for (const RTLIL::Cell *cell : cells()) + { + std::string modname = cell->get_string_attribute(ID::reprocess_after); + if (modname.empty()) + continue; + if (design->module(modname) || design->module("$abstract" + modname)) { + log("Reprocessing module %s because instantiated module %s has become available.\n", + log_id(name), log_id(modname)); + loadconfig(); + process_and_replace_module(design, this, ast, NULL); + return true; + } + } + return false; +} + // When an interface instance is found in a module, the whole RTLIL for the module will be rederived again // from AST. The interface members are copied into the AST module with the prefix of the interface. -void AstModule::reprocess_module(RTLIL::Design *design, const dict<RTLIL::IdString, RTLIL::Module*> &local_interfaces) +void AstModule::expand_interfaces(RTLIL::Design *design, const dict<RTLIL::IdString, RTLIL::Module*> &local_interfaces) { loadconfig(); - bool is_top = false; AstNode *new_ast = ast->clone(); for (auto &intf : local_interfaces) { std::string intfname = intf.first.str(); @@ -1473,28 +1542,15 @@ void AstModule::reprocess_module(RTLIL::Design *design, const dict<RTLIL::IdStri } } - // The old module will be deleted. Rename and mark for deletion: - std::string original_name = this->name.str(); - std::string changed_name = original_name + "_before_replacing_local_interfaces"; - design->rename(this, changed_name); - this->set_bool_attribute(ID::to_delete); + // Generate RTLIL from AST for the new module and add to the design, + // renaming this module to move it out of the way. + RTLIL::Module* new_module = + process_and_replace_module(design, this, new_ast, ast_before_replacing_interface_ports); - // Check if the module was the top module. If it was, we need to remove the top attribute and put it on the - // new module. - if (this->get_bool_attribute(ID::initial_top)) { - this->attributes.erase(ID::initial_top); - is_top = true; - } - - // Generate RTLIL from AST for the new module and add to the design: - process_module(design, new_ast, false, ast_before_replacing_interface_ports); - delete(new_ast); - RTLIL::Module* mod = design->module(original_name); - if (is_top) - mod->set_bool_attribute(ID::top); + delete new_ast; // Set the attribute "interfaces_replaced_in_module" so that it does not happen again. - mod->set_bool_attribute(ID::interfaces_replaced_in_module); + new_module->set_bool_attribute(ID::interfaces_replaced_in_module); } // create a new parametric module (when needed) and return the name of the generated module - WITH support for interfaces @@ -1628,6 +1684,17 @@ static std::string serialize_param_value(const RTLIL::Const &val) { return res; } +std::string AST::derived_module_name(std::string stripped_name, const std::vector<std::pair<RTLIL::IdString, RTLIL::Const>> ¶meters) { + std::string para_info; + for (const auto &elem : parameters) + para_info += stringf("%s=%s", elem.first.c_str(), serialize_param_value(elem.second).c_str()); + + if (para_info.size() > 60) + return "$paramod$" + sha1(para_info) + stripped_name; + else + return "$paramod" + stripped_name + para_info; +} + // create a new parametric module (when needed) and return the name of the generated module std::string AstModule::derive_common(RTLIL::Design *design, const dict<RTLIL::IdString, RTLIL::Const> ¶meters, AstNode **new_ast_out, bool quiet) { @@ -1636,9 +1703,8 @@ std::string AstModule::derive_common(RTLIL::Design *design, const dict<RTLIL::Id if (stripped_name.compare(0, 9, "$abstract") == 0) stripped_name = stripped_name.substr(9); - std::string para_info; - int para_counter = 0; + std::vector<std::pair<RTLIL::IdString, RTLIL::Const>> named_parameters; for (const auto child : ast->children) { if (child->type != AST_PARAMETER) continue; @@ -1647,25 +1713,21 @@ std::string AstModule::derive_common(RTLIL::Design *design, const dict<RTLIL::Id if (it != parameters.end()) { if (!quiet) log("Parameter %s = %s\n", child->str.c_str(), log_signal(it->second)); - para_info += stringf("%s=%s", child->str.c_str(), serialize_param_value(it->second).c_str()); + named_parameters.emplace_back(child->str, it->second); continue; } it = parameters.find(stringf("$%d", para_counter)); if (it != parameters.end()) { if (!quiet) log("Parameter %d (%s) = %s\n", para_counter, child->str.c_str(), log_signal(it->second)); - para_info += stringf("%s=%s", child->str.c_str(), serialize_param_value(it->second).c_str()); + named_parameters.emplace_back(child->str, it->second); continue; } } - std::string modname; - if (parameters.size() == 0) - modname = stripped_name; - else if (para_info.size() > 60) - modname = "$paramod$" + sha1(para_info) + stripped_name; - else - modname = "$paramod" + stripped_name + para_info; + std::string modname = stripped_name; + if (parameters.size()) // not named_parameters to cover hierarchical defparams + modname = derived_module_name(stripped_name, named_parameters); if (design->has(modname)) return modname; diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h index 63104bca4..80497c131 100644 --- a/frontends/ast/ast.h +++ b/frontends/ast/ast.h @@ -262,11 +262,13 @@ namespace AST void mem2reg_remove(pool<AstNode*> &mem2reg_set, vector<AstNode*> &delnodes); void meminfo(int &mem_width, int &mem_size, int &addr_bits); bool detect_latch(const std::string &var); + const RTLIL::Module* lookup_cell_module(); // additional functionality for evaluating constant functions struct varinfo_t { RTLIL::Const val; int offset; + bool range_swapped; bool is_signed; AstNode *arg = nullptr; bool explicitly_sized; @@ -313,8 +315,8 @@ namespace AST RTLIL::Const bitsAsConst(int width, bool is_signed); RTLIL::Const bitsAsConst(int width = -1); RTLIL::Const bitsAsUnsizedConst(int width); - RTLIL::Const asAttrConst(); - RTLIL::Const asParaConst(); + RTLIL::Const asAttrConst() const; + RTLIL::Const asParaConst() const; uint64_t asInt(bool is_signed); bool bits_only_01() const; bool asBool() const; @@ -348,7 +350,8 @@ namespace AST RTLIL::IdString derive(RTLIL::Design *design, const dict<RTLIL::IdString, RTLIL::Const> ¶meters, bool mayfail) override; RTLIL::IdString derive(RTLIL::Design *design, const dict<RTLIL::IdString, RTLIL::Const> ¶meters, const dict<RTLIL::IdString, RTLIL::Module*> &interfaces, const dict<RTLIL::IdString, RTLIL::IdString> &modports, bool mayfail) override; std::string derive_common(RTLIL::Design *design, const dict<RTLIL::IdString, RTLIL::Const> ¶meters, AstNode **new_ast_out, bool quiet = false); - void reprocess_module(RTLIL::Design *design, const dict<RTLIL::IdString, RTLIL::Module *> &local_interfaces) override; + void expand_interfaces(RTLIL::Design *design, const dict<RTLIL::IdString, RTLIL::Module *> &local_interfaces) override; + bool reprocess_if_necessary(RTLIL::Design *design) override; RTLIL::Module *clone() const override; void loadconfig() const; }; @@ -377,6 +380,14 @@ namespace AST // struct helper exposed from simplify for genrtlil AstNode *make_struct_member_range(AstNode *node, AstNode *member_node); + + // generate standard $paramod... derived module name; parameters should be + // in the order they are declared in the instantiated module + std::string derived_module_name(std::string stripped_name, const std::vector<std::pair<RTLIL::IdString, RTLIL::Const>> ¶meters); + + // used to provide simplify() access to the current design for looking up + // modules, ports, wires, etc. + void set_simplify_design_context(const RTLIL::Design *design); } namespace AST_INTERNAL @@ -395,6 +406,18 @@ namespace AST_INTERNAL extern dict<std::string, pool<int>> current_memwr_visible; struct LookaheadRewriter; struct ProcessGenerator; + + // Create and add a new AstModule from new_ast, then use it to replace + // old_module in design, renaming old_module to move it out of the way. + // Return the new module. + // + // If original_ast is not null, it will be used as the AST node for the + // new module. Otherwise, new_ast will be used. + RTLIL::Module * + process_and_replace_module(RTLIL::Design *design, + RTLIL::Module *old_module, + AST::AstNode *new_ast, + AST::AstNode *original_ast = nullptr); } YOSYS_NAMESPACE_END diff --git a/frontends/ast/genrtlil.cc b/frontends/ast/genrtlil.cc index c82664b98..020b4e5e8 100644 --- a/frontends/ast/genrtlil.cc +++ b/frontends/ast/genrtlil.cc @@ -877,7 +877,7 @@ void AstNode::detectSignWidthWorker(int &width_hint, bool &sign_hint, bool *foun this_width = id_ast->children[0]->range_left - id_ast->children[0]->range_right + 1; if (children.size() > 1) range = children[1]; - } else if (id_ast->type == AST_STRUCT_ITEM) { + } else if (id_ast->type == AST_STRUCT_ITEM || id_ast->type == AST_STRUCT) { AstNode *tmp_range = make_struct_member_range(this, id_ast); this_width = tmp_range->range_left - tmp_range->range_right + 1; delete tmp_range; @@ -932,7 +932,8 @@ void AstNode::detectSignWidthWorker(int &width_hint, bool &sign_hint, bool *foun if (children.at(0)->type != AST_CONSTANT) log_file_error(filename, location.first_line, "Static cast with non constant expression!\n"); children.at(1)->detectSignWidthWorker(width_hint, sign_hint); - width_hint = children.at(0)->bitsAsConst().as_int(); + this_width = children.at(0)->bitsAsConst().as_int(); + width_hint = max(width_hint, this_width); if (width_hint <= 0) log_file_error(filename, location.first_line, "Static cast with zero or negative size!\n"); break; @@ -1087,6 +1088,11 @@ void AstNode::detectSignWidthWorker(int &width_hint, bool &sign_hint, bool *foun } break; } + if (str == "\\$size" || str == "\\$bits" || str == "\\$high" || str == "\\$low" || str == "\\$left" || str == "\\$right") { + width_hint = 32; + sign_hint = true; + break; + } if (current_scope.count(str)) { // This width detection is needed for function calls which are @@ -1126,8 +1132,9 @@ void AstNode::detectSignWidthWorker(int &width_hint, bool &sign_hint, bool *foun // everything should have been handled above -> print error if not. default: + AstNode *current_scope_ast = current_ast_mod == nullptr ? current_ast : current_ast_mod; for (auto f : log_files) - current_ast_mod->dumpAst(f, "verilog-ast> "); + current_scope_ast->dumpAst(f, "verilog-ast> "); log_file_error(filename, location.first_line, "Don't know how to detect sign and width for %s node!\n", type2str(type).c_str()); } @@ -1524,13 +1531,20 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint) // changing the size of signal can be done directly using RTLIL::SigSpec case AST_CAST_SIZE: { RTLIL::SigSpec size = children[0]->genRTLIL(); - RTLIL::SigSpec sig = children[1]->genRTLIL(); if (!size.is_fully_const()) log_file_error(filename, location.first_line, "Static cast with non constant expression!\n"); int width = size.as_int(); if (width <= 0) log_file_error(filename, location.first_line, "Static cast with zero or negative size!\n"); - sig.extend_u0(width, sign_hint); + // determine the *signedness* of the expression + int sub_width_hint = -1; + bool sub_sign_hint = true; + children[1]->detectSignWidth(sub_width_hint, sub_sign_hint); + // generate the signal given the *cast's* size and the + // *expression's* signedness + RTLIL::SigSpec sig = children[1]->genWidthRTLIL(width, sub_sign_hint); + // context may effect this node's signedness, but not that of the + // casted expression is_signed = sign_hint; return sig; } @@ -1917,21 +1931,15 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint) continue; } if (child->type == AST_PARASET) { - int extra_const_flags = 0; IdString paraname = child->str.empty() ? stringf("$%d", ++para_counter) : child->str; - if (child->children[0]->type == AST_REALVALUE) { + const AstNode *value = child->children[0]; + if (value->type == AST_REALVALUE) log_file_warning(filename, location.first_line, "Replacing floating point parameter %s.%s = %f with string.\n", - log_id(cell), log_id(paraname), child->children[0]->realvalue); - extra_const_flags = RTLIL::CONST_FLAG_REAL; - auto strnode = AstNode::mkconst_str(stringf("%f", child->children[0]->realvalue)); - strnode->cloneInto(child->children[0]); - delete strnode; - } - if (child->children[0]->type != AST_CONSTANT) + log_id(cell), log_id(paraname), value->realvalue); + else if (value->type != AST_CONSTANT) log_file_error(filename, location.first_line, "Parameter %s.%s with non-constant value!\n", log_id(cell), log_id(paraname)); - cell->parameters[paraname] = child->children[0]->asParaConst(); - cell->parameters[paraname].flags |= extra_const_flags; + cell->parameters[paraname] = value->asParaConst(); continue; } if (child->type == AST_ARGUMENT) { @@ -1948,7 +1956,12 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint) if (sig.is_wire()) { // if the resulting SigSpec is a wire, its // signedness should match that of the AstNode - log_assert(arg->is_signed == sig.as_wire()->is_signed); + if (arg->type == AST_IDENTIFIER && arg->id2ast && arg->id2ast->is_signed && !arg->is_signed) + // fully-sliced signed wire will be resolved + // once the module becomes available + log_assert(attributes.count(ID::reprocess_after)); + else + log_assert(arg->is_signed == sig.as_wire()->is_signed); } else if (arg->is_signed) { // non-trivial signed nodes are indirected through // signed wires to enable sign extension diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index f713cf8e1..4d7c4f522 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -307,6 +307,10 @@ static int size_packed_struct(AstNode *snode, int base_offset) if (node->type == AST_STRUCT || node->type == AST_UNION) { // embedded struct or union width = size_packed_struct(node, base_offset + offset); + // set range of struct + node->range_right = base_offset + offset; + node->range_left = base_offset + offset + width - 1; + node->range_valid = true; } else { log_assert(node->type == AST_STRUCT_ITEM); @@ -493,14 +497,12 @@ static void add_members_to_scope(AstNode *snode, std::string name) // in case later referenced in assignments log_assert(snode->type==AST_STRUCT || snode->type==AST_UNION); for (auto *node : snode->children) { + auto member_name = name + "." + node->str; + current_scope[member_name] = node; if (node->type != AST_STRUCT_ITEM) { // embedded struct or union add_members_to_scope(node, name + "." + node->str); } - else { - auto member_name = name + "." + node->str; - current_scope[member_name] = node; - } } } @@ -564,6 +566,237 @@ static std::string prefix_id(const std::string &prefix, const std::string &str) return prefix + str; } +// direct access to this global should be limited to the following two functions +static const RTLIL::Design *simplify_design_context = nullptr; + +void AST::set_simplify_design_context(const RTLIL::Design *design) +{ + log_assert(!simplify_design_context || !design); + simplify_design_context = design; +} + +// lookup the module with the given name in the current design context +static const RTLIL::Module* lookup_module(const std::string &name) +{ + return simplify_design_context->module(name); +} + +const RTLIL::Module* AstNode::lookup_cell_module() +{ + log_assert(type == AST_CELL); + + auto reprocess_after = [this] (const std::string &modname) { + if (!attributes.count(ID::reprocess_after)) + attributes[ID::reprocess_after] = AstNode::mkconst_str(modname); + }; + + const AstNode *celltype = nullptr; + for (const AstNode *child : children) + if (child->type == AST_CELLTYPE) { + celltype = child; + break; + } + log_assert(celltype != nullptr); + + const RTLIL::Module *module = lookup_module(celltype->str); + if (!module) + module = lookup_module("$abstract" + celltype->str); + if (!module) { + if (celltype->str.at(0) != '$') + reprocess_after(celltype->str); + return nullptr; + } + + // build a mapping from true param name to param value + size_t para_counter = 0; + dict<RTLIL::IdString, RTLIL::Const> cell_params_map; + for (AstNode *child : children) { + if (child->type != AST_PARASET) + continue; + + if (child->str.empty() && para_counter >= module->avail_parameters.size()) + return nullptr; // let hierarchy handle this error + IdString paraname = child->str.empty() ? module->avail_parameters[para_counter++] : child->str; + + const AstNode *value = child->children[0]; + if (value->type != AST_REALVALUE && value->type != AST_CONSTANT) + return nullptr; // let genrtlil handle this error + cell_params_map[paraname] = value->asParaConst(); + } + + // put the parameters in order and generate the derived module name + std::vector<std::pair<RTLIL::IdString, RTLIL::Const>> named_parameters; + for (RTLIL::IdString param : module->avail_parameters) { + auto it = cell_params_map.find(param); + if (it != cell_params_map.end()) + named_parameters.emplace_back(it->first, it->second); + } + std::string modname = celltype->str; + if (cell_params_map.size()) // not named_parameters to cover hierarchical defparams + modname = derived_module_name(celltype->str, named_parameters); + + // try to find the resolved module + module = lookup_module(modname); + if (!module) { + reprocess_after(modname); + return nullptr; + } + return module; +} + +// returns whether an expression contains an unbased unsized literal; does not +// check the literal exists in a self-determined context +static bool contains_unbased_unsized(const AstNode *node) +{ + if (node->type == AST_CONSTANT) + return node->is_unsized; + for (const AstNode *child : node->children) + if (contains_unbased_unsized(child)) + return true; + return false; +} + +// adds a wire to the current module with the given name that matches the +// dimensions of the given wire reference +void add_wire_for_ref(const RTLIL::Wire *ref, const std::string &str) +{ + AstNode *left = AstNode::mkconst_int(ref->width - 1 + ref->start_offset, true); + AstNode *right = AstNode::mkconst_int(ref->start_offset, true); + if (ref->upto) + std::swap(left, right); + AstNode *range = new AstNode(AST_RANGE, left, right); + + AstNode *wire = new AstNode(AST_WIRE, range); + wire->is_signed = ref->is_signed; + wire->is_logic = true; + wire->str = str; + + current_ast_mod->children.push_back(wire); + current_scope[str] = wire; +} + +enum class IdentUsage { + NotReferenced, // target variable is neither read or written in the block + Assigned, // target variable is always assigned before use + SyncRequired, // target variable may be used before it has been assigned +}; + +// determines whether a local variable a block is always assigned before it is +// used, meaning the nosync attribute can automatically be added to that +// variable +static IdentUsage always_asgn_before_use(const AstNode *node, const std::string &target) +{ + // This variable has been referenced before it has necessarily been assigned + // a value in this procedure. + if (node->type == AST_IDENTIFIER && node->str == target) + return IdentUsage::SyncRequired; + + // For case statements (which are also used for if/else), we check each + // possible branch. If the variable is assigned in all branches, then it is + // assigned, and a sync isn't required. If it used before assignment in any + // branch, then a sync is required. + if (node->type == AST_CASE) { + bool all_defined = true; + bool any_used = false; + bool has_default = false; + for (const AstNode *child : node->children) { + if (child->type == AST_COND && child->children.at(0)->type == AST_DEFAULT) + has_default = true; + IdentUsage nested = always_asgn_before_use(child, target); + if (nested != IdentUsage::Assigned && child->type == AST_COND) + all_defined = false; + if (nested == IdentUsage::SyncRequired) + any_used = true; + } + if (any_used) + return IdentUsage::SyncRequired; + else if (all_defined && has_default) + return IdentUsage::Assigned; + else + return IdentUsage::NotReferenced; + } + + // Check if this is an assignment to the target variable. For simplicity, we + // don't analyze sub-ranges of the variable. + if (node->type == AST_ASSIGN_EQ) { + const AstNode *ident = node->children.at(0); + if (ident->type == AST_IDENTIFIER && ident->str == target) + return IdentUsage::Assigned; + } + + for (const AstNode *child : node->children) { + IdentUsage nested = always_asgn_before_use(child, target); + if (nested != IdentUsage::NotReferenced) + return nested; + } + return IdentUsage::NotReferenced; +} + +static const std::string auto_nosync_prefix = "\\AutoNosync"; + +// mark a local variable in an always_comb block for automatic nosync +// consideration +static void mark_auto_nosync(AstNode *block, const AstNode *wire) +{ + log_assert(block->type == AST_BLOCK); + log_assert(wire->type == AST_WIRE); + block->attributes[auto_nosync_prefix + wire->str] = AstNode::mkconst_int(1, + false); +} + +// block names can be prefixed with an explicit scope during elaboration +static bool is_autonamed_block(const std::string &str) { + size_t last_dot = str.rfind('.'); + // unprefixed names: autonamed if the first char is a dollar sign + if (last_dot == std::string::npos) + return str.at(0) == '$'; // e.g., `$fordecl_block$1` + // prefixed names: autonamed if the final chunk begins with a dollar sign + return str.rfind(".$") == last_dot; // e.g., `\foo.bar.$fordecl_block$1` +} + +// check a procedural block for auto-nosync markings, remove them, and add +// nosync to local variables as necessary +static void check_auto_nosync(AstNode *node) +{ + std::vector<RTLIL::IdString> attrs_to_drop; + for (const auto& elem : node->attributes) { + // skip attributes that don't begin with the prefix + if (elem.first.compare(0, auto_nosync_prefix.size(), + auto_nosync_prefix.c_str())) + continue; + + // delete and remove the attribute once we're done iterating + attrs_to_drop.push_back(elem.first); + + // find the wire based on the attribute + std::string wire_name = elem.first.substr(auto_nosync_prefix.size()); + auto it = current_scope.find(wire_name); + if (it == current_scope.end()) + continue; + + // analyze the usage of the local variable in this block + IdentUsage ident_usage = always_asgn_before_use(node, wire_name); + if (ident_usage != IdentUsage::Assigned) + continue; + + // mark the wire with `nosync` + AstNode *wire = it->second; + log_assert(wire->type == AST_WIRE); + wire->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + } + + // remove the attributes we've "consumed" + for (const RTLIL::IdString &str : attrs_to_drop) { + auto it = node->attributes.find(str); + delete it->second; + node->attributes.erase(it); + } + + // check local variables in any nested blocks + for (AstNode *child : node->children) + check_auto_nosync(child); +} + // convert the AST into a simpler AST that has all parameters substituted by their // values, unrolled for-loops, expanded generate blocks, etc. when this function // is done with an AST it can be converted into RTLIL using genRTLIL(). @@ -871,6 +1104,11 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, } } } + + for (AstNode *child : children) + if (child->type == AST_ALWAYS && + child->attributes.count(ID::always_comb)) + check_auto_nosync(child); } // create name resolution entries for all objects with names @@ -920,19 +1158,110 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, } } - if (type == AST_ARGUMENT) - { - if (children.size() == 1 && children[0]->type == AST_CONSTANT) - { - // HACK: For port bindings using unbased unsized literals, mark them - // signed so they sign-extend. The hierarchy will still incorrectly - // generate a warning complaining about resizing the expression. - // This also doesn't handle the complex of something like a ternary - // expression bound to a port, where the actual size of the port is - // needed to resolve the expression correctly. - AstNode *arg = children[0]; - if (arg->is_unsized) - arg->is_signed = true; + if (type == AST_CELL) { + bool lookup_suggested = false; + + for (AstNode *child : children) { + // simplify any parameters to constants + if (child->type == AST_PARASET) + while (child->simplify(true, false, false, 1, -1, false, true)) { } + + // look for patterns which _may_ indicate ambiguity requiring + // resolution of the underlying module + if (child->type == AST_ARGUMENT) { + if (child->children.size() != 1) + continue; + const AstNode *value = child->children[0]; + if (value->type == AST_IDENTIFIER) { + const AstNode *elem = value->id2ast; + if (elem == nullptr) { + if (current_scope.count(value->str)) + elem = current_scope.at(value->str); + else + continue; + } + if (elem->type == AST_MEMORY) + // need to determine is the is a read or wire + lookup_suggested = true; + else if (elem->type == AST_WIRE && elem->is_signed && !value->children.empty()) + // this may be a fully sliced signed wire which needs + // to be indirected to produce an unsigned connection + lookup_suggested = true; + } + else if (contains_unbased_unsized(value)) + // unbased unsized literals extend to width of the context + lookup_suggested = true; + } + } + + const RTLIL::Module *module = nullptr; + if (lookup_suggested) + module = lookup_cell_module(); + if (module) { + size_t port_counter = 0; + for (AstNode *child : children) { + if (child->type != AST_ARGUMENT) + continue; + + // determine the full name of port this argument is connected to + RTLIL::IdString port_name; + if (child->str.size()) + port_name = child->str; + else { + if (port_counter >= module->ports.size()) + log_file_error(filename, location.first_line, + "Cell instance has more ports than the module!\n"); + port_name = module->ports[port_counter++]; + } + + // find the port's wire in the underlying module + const RTLIL::Wire *ref = module->wire(port_name); + if (ref == nullptr) + log_file_error(filename, location.first_line, + "Cell instance refers to port %s which does not exist in module %s!.\n", + log_id(port_name), log_id(module->name)); + + // select the argument, if present + log_assert(child->children.size() <= 1); + if (child->children.empty()) + continue; + AstNode *arg = child->children[0]; + + // plain identifiers never need indirection; this also prevents + // adding infinite levels of indirection + if (arg->type == AST_IDENTIFIER && arg->children.empty()) + continue; + + // only add indirection for standard inputs or outputs + if (ref->port_input == ref->port_output) + continue; + + did_something = true; + + // create the indirection wire + std::stringstream sstr; + sstr << "$indirect$" << ref->name.c_str() << "$" << filename << ":" << location.first_line << "$" << (autoidx++); + std::string tmp_str = sstr.str(); + add_wire_for_ref(ref, tmp_str); + + AstNode *asgn = new AstNode(AST_ASSIGN); + current_ast_mod->children.push_back(asgn); + + AstNode *ident = new AstNode(AST_IDENTIFIER); + ident->str = tmp_str; + child->children[0] = ident->clone(); + + if (ref->port_input && !ref->port_output) { + asgn->children.push_back(ident); + asgn->children.push_back(arg); + } else { + log_assert(!ref->port_input && ref->port_output); + asgn->children.push_back(arg); + asgn->children.push_back(ident); + } + } + + } } @@ -1024,6 +1353,16 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, case AST_PARAMETER: case AST_LOCALPARAM: + // if parameter is implicit type which is the typename of a struct or union, + // save information about struct in wiretype attribute + if (children[0]->type == AST_IDENTIFIER && current_scope.count(children[0]->str) > 0) { + auto item_node = current_scope[children[0]->str]; + if (item_node->type == AST_STRUCT || item_node->type == AST_UNION) { + attributes[ID::wiretype] = item_node->clone(); + size_packed_struct(attributes[ID::wiretype], 0); + add_members_to_scope(attributes[ID::wiretype], str); + } + } while (!children[0]->basic_prep && children[0]->simplify(false, false, false, stage, -1, false, true) == true) did_something = true; children[0]->detectSignWidth(width_hint, sign_hint); @@ -1189,11 +1528,10 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, if (const_fold && type == AST_CASE) { - int width_hint; - bool sign_hint; detectSignWidth(width_hint, sign_hint); while (children[0]->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) { } if (children[0]->type == AST_CONSTANT && children[0]->bits_only_01()) { + children[0]->is_signed = sign_hint; RTLIL::Const case_expr = children[0]->bitsAsConst(width_hint, sign_hint); std::vector<AstNode*> new_children; new_children.push_back(children[0]); @@ -1413,6 +1751,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, if (template_node->type == AST_STRUCT || template_node->type == AST_UNION) { // replace with wire representing the packed structure newNode = make_packed_struct(template_node, str); + newNode->attributes[ID::wiretype] = mkconst_str(resolved_type_node->str); // add original input/output attribute to resolved wire newNode->is_input = this->is_input; newNode->is_output = this->is_output; @@ -1461,18 +1800,33 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, if (is_custom_type) { log_assert(children.size() == 2); log_assert(children[1]->type == AST_WIRETYPE); - if (!current_scope.count(children[1]->str)) - log_file_error(filename, location.first_line, "Unknown identifier `%s' used as type name\n", children[1]->str.c_str()); - AstNode *resolved_type_node = current_scope.at(children[1]->str); + auto type_name = children[1]->str; + if (!current_scope.count(type_name)) { + log_file_error(filename, location.first_line, "Unknown identifier `%s' used as type name\n", type_name.c_str()); + } + AstNode *resolved_type_node = current_scope.at(type_name); if (resolved_type_node->type != AST_TYPEDEF) - log_file_error(filename, location.first_line, "`%s' does not name a type\n", children[1]->str.c_str()); + log_file_error(filename, location.first_line, "`%s' does not name a type\n", type_name.c_str()); log_assert(resolved_type_node->children.size() == 1); AstNode *template_node = resolved_type_node->children[0]; - delete children[1]; - children.pop_back(); // Ensure typedef itself is fully simplified - while(template_node->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {}; + while (template_node->simplify(const_fold, at_zero, in_lvalue, stage, width_hint, sign_hint, in_param)) {}; + + if (template_node->type == AST_STRUCT || template_node->type == AST_UNION) { + // replace with wire representing the packed structure + newNode = make_packed_struct(template_node, str); + newNode->attributes[ID::wiretype] = mkconst_str(resolved_type_node->str); + newNode->type = type; + current_scope[str] = this; + // copy param value, it needs to be 1st value + delete children[1]; + children.pop_back(); + newNode->children.insert(newNode->children.begin(), children[0]->clone()); + goto apply_newNode; + } + delete children[1]; + children.pop_back(); if (template_node->type == AST_MEMORY) log_file_error(filename, location.first_line, "unpacked array type `%s' cannot be used for a parameter\n", children[1]->str.c_str()); @@ -1687,7 +2041,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, if (name_has_dot(str, sname)) { if (current_scope.count(str) > 0) { auto item_node = current_scope[str]; - if (item_node->type == AST_STRUCT_ITEM) { + if (item_node->type == AST_STRUCT_ITEM || item_node->type == AST_STRUCT) { // structure member, rewrite this node to reference the packed struct wire auto range = make_struct_member_range(this, item_node); newNode = new AstNode(AST_IDENTIFIER, range); @@ -2010,6 +2364,16 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, { expand_genblock(str + "."); + // if this is an autonamed block is in an always_comb + if (current_always && current_always->attributes.count(ID::always_comb) + && is_autonamed_block(str)) + // track local variables in this block so we can consider adding + // nosync once the block has been fully elaborated + for (AstNode *child : children) + if (child->type == AST_WIRE && + !child->attributes.count(ID::nosync)) + mark_auto_nosync(this, child); + std::vector<AstNode*> new_children; for (size_t i = 0; i < children.size(); i++) if (children[i]->type == AST_WIRE || children[i]->type == AST_MEMORY || children[i]->type == AST_PARAMETER || children[i]->type == AST_LOCALPARAM || children[i]->type == AST_TYPEDEF) { @@ -2363,6 +2727,18 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, while (wire_data->simplify(true, false, false, 1, -1, false, false)) { } current_ast_mod->children.push_back(wire_data); + int shamt_width_hint = -1; + bool shamt_sign_hint = true; + shift_expr->detectSignWidth(shamt_width_hint, shamt_sign_hint); + + AstNode *wire_sel = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(shamt_width_hint-1, true), mkconst_int(0, true))); + wire_sel->str = stringf("$bitselwrite$sel$%s:%d$%d", filename.c_str(), location.first_line, autoidx++); + wire_sel->attributes[ID::nosync] = AstNode::mkconst_int(1, false); + wire_sel->is_logic = true; + wire_sel->is_signed = shamt_sign_hint; + while (wire_sel->simplify(true, false, false, 1, -1, false, false)) { } + current_ast_mod->children.push_back(wire_sel); + did_something = true; newNode = new AstNode(AST_BLOCK); @@ -2379,39 +2755,44 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, ref_data->id2ast = wire_data; ref_data->was_checked = true; + AstNode *ref_sel = new AstNode(AST_IDENTIFIER); + ref_sel->str = wire_sel->str; + ref_sel->id2ast = wire_sel; + ref_sel->was_checked = true; + AstNode *old_data = lvalue->clone(); if (type == AST_ASSIGN_LE) old_data->lookahead = true; - AstNode *shamt = shift_expr; + AstNode *s = new AstNode(AST_ASSIGN_EQ, ref_sel->clone(), shift_expr); + newNode->children.push_back(s); - int shamt_width_hint = 0; - bool shamt_sign_hint = true; - shamt->detectSignWidth(shamt_width_hint, shamt_sign_hint); + AstNode *shamt = ref_sel; + // convert to signed while preserving the sign and value + shamt = new AstNode(AST_CAST_SIZE, mkconst_int(shamt_width_hint + 1, true), shamt); + shamt = new AstNode(AST_TO_SIGNED, shamt); + + // offset the shift amount by the lower bound of the dimension int start_bit = children[0]->id2ast->range_right; - bool use_shift = shamt_sign_hint; + shamt = new AstNode(AST_SUB, shamt, mkconst_int(start_bit, true)); - if (start_bit != 0) { - shamt = new AstNode(AST_SUB, shamt, mkconst_int(start_bit, true)); - use_shift = true; - } + // reflect the shift amount if the dimension is swapped + if (children[0]->id2ast->range_swapped) + shamt = new AstNode(AST_SUB, mkconst_int(source_width - result_width, true), shamt); + + // AST_SHIFT uses negative amounts for shifting left + shamt = new AstNode(AST_NEG, shamt); AstNode *t; t = mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false); - if (use_shift) - t = new AstNode(AST_SHIFT, t, new AstNode(AST_NEG, shamt->clone())); - else - t = new AstNode(AST_SHIFT_LEFT, t, shamt->clone()); + t = new AstNode(AST_SHIFT, t, shamt->clone()); t = new AstNode(AST_ASSIGN_EQ, ref_mask->clone(), t); newNode->children.push_back(t); t = new AstNode(AST_BIT_AND, mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false), children[1]->clone()); - if (use_shift) - t = new AstNode(AST_SHIFT, t, new AstNode(AST_NEG, shamt)); - else - t = new AstNode(AST_SHIFT_LEFT, t, shamt); + t = new AstNode(AST_SHIFT, t, shamt); t = new AstNode(AST_ASSIGN_EQ, ref_data->clone(), t); newNode->children.push_back(t); @@ -4045,7 +4426,7 @@ AstNode *AstNode::readmem(bool is_readmemh, std::string mem_filename, AstNode *m // prefix is carried forward, but resolution of their children is deferred void AstNode::expand_genblock(const std::string &prefix) { - if (type == AST_IDENTIFIER || type == AST_FCALL || type == AST_TCALL || type == AST_WIRETYPE) { + if (type == AST_IDENTIFIER || type == AST_FCALL || type == AST_TCALL || type == AST_WIRETYPE || type == AST_PREFIX) { log_assert(!str.empty()); // search starting in the innermost scope and then stepping outward @@ -4131,10 +4512,15 @@ void AstNode::expand_genblock(const std::string &prefix) for (size_t i = 0; i < children.size(); i++) { AstNode *child = children[i]; - // AST_PREFIX member names should not be prefixed; a nested AST_PREFIX - // still needs to recursed-into - if (type == AST_PREFIX && i == 1 && child->type == AST_IDENTIFIER) + // AST_PREFIX member names should not be prefixed; we recurse into them + // as normal to ensure indices and ranges are properly resolved, and + // then restore the previous string + if (type == AST_PREFIX && i == 1) { + std::string backup_scope_name = child->str; + child->expand_genblock(prefix); + child->str = backup_scope_name; continue; + } // functions/tasks may reference wires, constants, etc. in this scope if (child->type == AST_FUNCTION || child->type == AST_TASK) continue; @@ -4788,6 +5174,8 @@ bool AstNode::replace_variables(std::map<std::string, AstNode::varinfo_t> &varia width = min(std::abs(children.at(0)->range_left - children.at(0)->range_right) + 1, width); } offset -= variables.at(str).offset; + if (variables.at(str).range_swapped) + offset = -offset; std::vector<RTLIL::State> &var_bits = variables.at(str).val.bits; std::vector<RTLIL::State> new_bits(var_bits.begin() + offset, var_bits.begin() + offset + width); AstNode *newNode = mkconst_bits(new_bits, variables.at(str).is_signed); @@ -4845,7 +5233,8 @@ AstNode *AstNode::eval_const_function(AstNode *fcall, bool must_succeed) log_file_error(filename, location.first_line, "Incompatible re-declaration of constant function wire %s.\n", stmt->str.c_str()); } variable.val = RTLIL::Const(RTLIL::State::Sx, width); - variable.offset = min(stmt->range_left, stmt->range_right); + variable.offset = stmt->range_swapped ? stmt->range_left : stmt->range_right; + variable.range_swapped = stmt->range_swapped; variable.is_signed = stmt->is_signed; variable.explicitly_sized = stmt->children.size() && stmt->children.back()->type == AST_RANGE; @@ -4930,8 +5319,12 @@ AstNode *AstNode::eval_const_function(AstNode *fcall, bool must_succeed) 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); + for (int i = 0; i < width; i++) { + int index = i + offset - v.offset; + if (v.range_swapped) + index = -index; + v.val.bits.at(index) = r.bits.at(i); + } } delete block->children.front(); |