From 7764d0ba1dcf064ae487ee985c43083a0909e7f4 Mon Sep 17 00:00:00 2001 From: Clifford Wolf Date: Sat, 5 Jan 2013 11:13:26 +0100 Subject: initial import --- frontends/ast/Makefile.inc | 5 + frontends/ast/ast.cc | 859 +++++++++++++++++++++++++++++++++++ frontends/ast/ast.h | 228 ++++++++++ frontends/ast/genrtlil.cc | 1054 ++++++++++++++++++++++++++++++++++++++++++ frontends/ast/simplify.cc | 1081 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 3227 insertions(+) create mode 100644 frontends/ast/Makefile.inc create mode 100644 frontends/ast/ast.cc create mode 100644 frontends/ast/ast.h create mode 100644 frontends/ast/genrtlil.cc create mode 100644 frontends/ast/simplify.cc (limited to 'frontends/ast') diff --git a/frontends/ast/Makefile.inc b/frontends/ast/Makefile.inc new file mode 100644 index 00000000..993ead92 --- /dev/null +++ b/frontends/ast/Makefile.inc @@ -0,0 +1,5 @@ + +OBJS += frontends/ast/ast.o +OBJS += frontends/ast/simplify.o +OBJS += frontends/ast/genrtlil.o + diff --git a/frontends/ast/ast.cc b/frontends/ast/ast.cc new file mode 100644 index 00000000..160e9c42 --- /dev/null +++ b/frontends/ast/ast.cc @@ -0,0 +1,859 @@ +/* + * 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 "kernel/sha1.h" +#include "ast.h" + +#include +#include +#include + +using namespace AST; +using namespace AST_INTERNAL; + +// instanciate global variables (public API) +namespace AST { + std::string current_filename; + void (*set_line_num)(int) = NULL; + int (*get_line_num)() = NULL; +} + +// instanciate global variables (private API) +namespace AST_INTERNAL { + bool flag_dump_ast, flag_dump_ast_diff, flag_dump_vlog, flag_nolatches, flag_nomem2reg; + AstNode *current_ast, *current_ast_mod; + std::map current_scope; + RTLIL::SigSpec *genRTLIL_subst_from = NULL; + RTLIL::SigSpec *genRTLIL_subst_to = NULL; + AstNode *current_top_block, *current_block, *current_block_child; + AstModule *current_module; +} + +// convert node types to string +std::string AST::type2str(AstNodeType type) +{ + switch (type) + { +#define X(_item) case _item: return #_item; + X(AST_NONE) + X(AST_DESIGN) + X(AST_MODULE) + X(AST_TASK) + X(AST_FUNCTION) + X(AST_WIRE) + X(AST_MEMORY) + X(AST_AUTOWIRE) + X(AST_PARAMETER) + X(AST_LOCALPARAM) + X(AST_PARASET) + X(AST_ARGUMENT) + X(AST_RANGE) + X(AST_CONSTANT) + X(AST_CELLTYPE) + X(AST_IDENTIFIER) + X(AST_FCALL) + X(AST_TO_SIGNED) + X(AST_TO_UNSIGNED) + X(AST_CONCAT) + X(AST_REPLICATE) + X(AST_BIT_NOT) + X(AST_BIT_AND) + X(AST_BIT_OR) + X(AST_BIT_XOR) + X(AST_BIT_XNOR) + X(AST_REDUCE_AND) + X(AST_REDUCE_OR) + X(AST_REDUCE_XOR) + X(AST_REDUCE_XNOR) + X(AST_REDUCE_BOOL) + X(AST_SHIFT_LEFT) + X(AST_SHIFT_RIGHT) + X(AST_SHIFT_SLEFT) + X(AST_SHIFT_SRIGHT) + X(AST_LT) + X(AST_LE) + X(AST_EQ) + X(AST_NE) + X(AST_GE) + X(AST_GT) + X(AST_ADD) + X(AST_SUB) + X(AST_MUL) + X(AST_DIV) + X(AST_MOD) + X(AST_POW) + X(AST_POS) + X(AST_NEG) + X(AST_LOGIC_AND) + X(AST_LOGIC_OR) + X(AST_LOGIC_NOT) + X(AST_TERNARY) + X(AST_MEMRD) + X(AST_MEMWR) + X(AST_TCALL) + X(AST_ASSIGN) + X(AST_CELL) + X(AST_PRIMITIVE) + X(AST_ALWAYS) + X(AST_BLOCK) + X(AST_ASSIGN_EQ) + X(AST_ASSIGN_LE) + X(AST_CASE) + X(AST_COND) + X(AST_DEFAULT) + X(AST_FOR) + X(AST_GENVAR) + X(AST_GENFOR) + X(AST_GENIF) + X(AST_GENBLOCK) + X(AST_POSEDGE) + X(AST_NEGEDGE) + X(AST_EDGE) +#undef X + default: + assert(!"Missing enum to string def in AST::type2str()."); + abort(); + } +} + +// create new node (AstNode constructor) +// (the optional child arguments make it easier to create AST trees) +AstNode::AstNode(AstNodeType type, AstNode *child1, AstNode *child2) +{ + this->type = type; + filename = current_filename; + linenum = get_line_num(); + is_input = false; + is_output = false; + is_reg = false; + is_signed = false; + range_valid = false; + port_id = 0; + range_left = -1; + range_right = 0; + integer = 0; + id2ast = NULL; + + if (child1) + children.push_back(child1); + if (child2) + children.push_back(child2); +} + +// create a (deep recursive) copy of a node +AstNode *AstNode::clone() +{ + AstNode *that = new AstNode; + *that = *this; + for (auto &it : that->children) + it = it->clone(); + for (auto &it : that->attributes) + it.second = it.second->clone(); + return that; +} + +// create a (deep recursive) copy of a node use 'other' as target root node +void AstNode::cloneInto(AstNode *other) +{ + AstNode *tmp = clone(); + other->delete_children(); + *other = *tmp; + tmp->children.clear(); + tmp->attributes.clear(); + delete tmp; +} + +// delete all children in this node +void AstNode::delete_children() +{ + for (auto &it : children) + delete it; + children.clear(); + + for (auto &it : attributes) + delete it.second; + attributes.clear(); +} + +// AstNode destructor +AstNode::~AstNode() +{ + delete_children(); +} + +// create a nice text representation of the node +// (traverse tree by recursion, use 'other' pointer for diffing two AST trees) +void AstNode::dumpAst(FILE *f, std::string indent, AstNode *other) +{ + if (f == NULL) { + for (auto f : log_files) + dumpAst(f, indent, other); + return; + } + if (other != NULL) { + if (type != other->type) + goto found_diff_to_other; + if (children.size() != other->children.size()) + goto found_diff_to_other; + if (str != other->str) + goto found_diff_to_other; + if (bits != other->bits) + goto found_diff_to_other; + if (is_input != other->is_input) + goto found_diff_to_other; + if (is_output != other->is_output) + goto found_diff_to_other; + if (is_reg != other->is_reg) + goto found_diff_to_other; + if (is_signed != other->is_signed) + goto found_diff_to_other; + if (range_valid != other->range_valid) + goto found_diff_to_other; + if (port_id != other->port_id) + goto found_diff_to_other; + if (range_left != other->range_left) + goto found_diff_to_other; + if (range_right != other->range_right) + goto found_diff_to_other; + if (integer != other->integer) + goto found_diff_to_other; + if (0) { + found_diff_to_other: + other->dumpAst(f, indent + "- "); + this->dumpAst(f, indent + "+ "); + return; + } + } + + std::string type_name = type2str(type); + fprintf(f, "%s%s <%s:%d>", indent.c_str(), type_name.c_str(), filename.c_str(), linenum); + if (!str.empty()) + fprintf(f, " str='%s'", str.c_str()); + if (!bits.empty()) { + fprintf(f, " bits='"); + for (size_t i = bits.size(); i > 0; i--) + fprintf(f, "%c", bits[i-1] == RTLIL::S0 ? '0' : + bits[i-1] == RTLIL::S1 ? '1' : + bits[i-1] == RTLIL::Sx ? 'x' : + bits[i-1] == RTLIL::Sz ? 'z' : '?'); + fprintf(f, "'(%zd)", bits.size()); + } + if (is_input) + fprintf(f, " input"); + if (is_output) + fprintf(f, " output"); + if (is_reg) + fprintf(f, " reg"); + if (is_signed) + fprintf(f, " signed"); + if (port_id > 0) + fprintf(f, " port=%d", port_id); + if (range_valid || range_left != -1 || range_right != 0) + fprintf(f, " range=[%d:%d]%s", range_left, range_right, range_valid ? "" : "!"); + if (integer != 0) + fprintf(f, " int=%u", (int)integer); + fprintf(f, "\n"); + + for (size_t i = 0; i < children.size(); i++) + children[i]->dumpAst(f, indent + " ", other ? other->children[i] : NULL); +} + +// helper function for AstNode::dumpVlog() +static std::string id2vl(std::string txt) +{ + if (txt.size() > 1 && txt[0] == '\\') + txt = txt.substr(1); + for (size_t i = 0; i < txt.size(); i++) { + if ('A' <= txt[i] && txt[i] <= 'Z') continue; + if ('a' <= txt[i] && txt[i] <= 'z') continue; + if ('0' <= txt[i] && txt[i] <= '9') continue; + if (txt[i] == '_') continue; + txt = "\\" + txt + " "; + break; + } + return txt; +} + +// dump AST node as verilog pseudo-code +void AstNode::dumpVlog(FILE *f, std::string indent) +{ + bool first = true; + std::string txt; + std::vector rem_children1, rem_children2; + + if (f == NULL) { + for (auto f : log_files) + dumpVlog(f, indent); + return; + } + + switch (type) + { + case AST_MODULE: + fprintf(f, "%s" "module %s(", indent.c_str(), id2vl(str).c_str()); + for (auto child : children) + if (child->type == AST_WIRE && (child->is_input || child->is_output)) { + fprintf(f, "%s%s", first ? "" : ", ", id2vl(child->str).c_str()); + first = false; + } + fprintf(f, ");\n"); + + for (auto child : children) + if (child->type == AST_PARAMETER || child->type == AST_LOCALPARAM) + child->dumpVlog(f, indent + " "); + else + rem_children1.push_back(child); + + for (auto child : rem_children1) + if (child->type == AST_WIRE || child->type == AST_AUTOWIRE || child->type == AST_MEMORY) + child->dumpVlog(f, indent + " "); + else + rem_children2.push_back(child); + rem_children1.clear(); + + for (auto child : rem_children2) + if (child->type == AST_TASK || child->type == AST_FUNCTION) + child->dumpVlog(f, indent + " "); + else + rem_children1.push_back(child); + rem_children2.clear(); + + for (auto child : rem_children1) + child->dumpVlog(f, indent + " "); + rem_children1.clear(); + + fprintf(f, "%s" "endmodule\n", indent.c_str()); + break; + + case AST_WIRE: + if (is_input && is_output) + fprintf(f, "%s" "inout", indent.c_str()); + else if (is_input) + fprintf(f, "%s" "input", indent.c_str()); + else if (is_output) + fprintf(f, "%s" "output", indent.c_str()); + else if (!is_reg) + fprintf(f, "%s" "wire", indent.c_str()); + if (is_reg) + fprintf(f, "%s" "reg", (is_input || is_output) ? " " : indent.c_str()); + if (is_signed) + fprintf(f, " signed"); + for (auto child : children) { + fprintf(f, " "); + child->dumpVlog(f, ""); + } + fprintf(f, " %s", id2vl(str).c_str()); + fprintf(f, ";\n"); + break; + + case AST_MEMORY: + fprintf(f, "%s" "memory", indent.c_str()); + if (is_signed) + fprintf(f, " signed"); + for (auto child : children) { + fprintf(f, " "); + child->dumpVlog(f, ""); + if (first) + fprintf(f, " %s", id2vl(str).c_str()); + first = false; + } + fprintf(f, ";\n"); + break; + + case AST_RANGE: + if (range_valid) + fprintf(f, "[%d:%d]", range_left, range_right); + else { + for (auto child : children) { + fprintf(f, "%c", first ? '[' : ':'); + child->dumpVlog(f, ""); + first = false; + } + fprintf(f, "]"); + } + break; + + case AST_ALWAYS: + fprintf(f, "%s" "always @(", indent.c_str()); + for (auto child : children) { + if (child->type != AST_POSEDGE && child->type != AST_NEGEDGE && child->type != AST_EDGE) + continue; + if (!first) + fprintf(f, ", "); + child->dumpVlog(f, ""); + first = false; + } + fprintf(f, ")\n"); + for (auto child : children) { + if (child->type != AST_POSEDGE && child->type != AST_NEGEDGE && child->type != AST_EDGE) + child->dumpVlog(f, indent + " "); + } + break; + + case AST_POSEDGE: + case AST_NEGEDGE: + case AST_EDGE: + if (type == AST_POSEDGE) + fprintf(f, "posedge "); + if (type == AST_NEGEDGE) + fprintf(f, "negedge "); + for (auto child : children) + child->dumpVlog(f, ""); + break; + + case AST_IDENTIFIER: + fprintf(f, "%s", id2vl(str).c_str()); + for (auto child : children) + child->dumpVlog(f, ""); + break; + + case AST_CONSTANT: + if (!str.empty()) + fprintf(f, "\"%s\"", str.c_str()); + else if (bits.size() == 32) + fprintf(f, "%d", RTLIL::Const(bits).as_int()); + else + fprintf(f, "%zd'b %s", bits.size(), RTLIL::Const(bits).as_string().c_str()); + break; + + case AST_BLOCK: + if (children.size() == 1) { + children[0]->dumpVlog(f, indent); + } else { + fprintf(f, "%s" "begin\n", indent.c_str()); + for (auto child : children) + child->dumpVlog(f, indent + " "); + fprintf(f, "%s" "end\n", indent.c_str()); + } + break; + + case AST_CASE: + fprintf(f, "%s" "case (", indent.c_str()); + children[0]->dumpVlog(f, ""); + fprintf(f, ")\n"); + for (size_t i = 1; i < children.size(); i++) { + AstNode *child = children[i]; + child->dumpVlog(f, indent + " "); + } + fprintf(f, "%s" "endcase\n", indent.c_str()); + break; + + case AST_COND: + for (auto child : children) { + if (child->type == AST_BLOCK) { + fprintf(f, ":\n"); + child->dumpVlog(f, indent + " "); + first = true; + } else { + fprintf(f, "%s", first ? indent.c_str() : ", "); + if (child->type == AST_DEFAULT) + fprintf(f, "default"); + else + child->dumpVlog(f, ""); + first = false; + } + } + break; + + case AST_ASSIGN_EQ: + case AST_ASSIGN_LE: + fprintf(f, "%s", indent.c_str()); + children[0]->dumpVlog(f, ""); + fprintf(f, " %s ", type == AST_ASSIGN_EQ ? "=" : "<="); + children[1]->dumpVlog(f, ""); + fprintf(f, ";\n"); + break; + + case AST_CONCAT: + fprintf(f, "{"); + for (auto child : children) { + if (!first) + fprintf(f, ", "); + child->dumpVlog(f, ""); + first = false; + } + fprintf(f, "}"); + break; + + case AST_REPLICATE: + fprintf(f, "{"); + children[0]->dumpVlog(f, ""); + fprintf(f, "{"); + children[1]->dumpVlog(f, ""); + fprintf(f, "}}"); + break; + + if (0) { case AST_BIT_NOT: txt = "~"; } + if (0) { case AST_REDUCE_AND: txt = "&"; } + if (0) { case AST_REDUCE_OR: txt = "|"; } + if (0) { case AST_REDUCE_XOR: txt = "^"; } + if (0) { case AST_REDUCE_XNOR: txt = "~^"; } + if (0) { case AST_REDUCE_BOOL: txt = "|"; } + if (0) { case AST_POS: txt = "+"; } + if (0) { case AST_NEG: txt = "-"; } + if (0) { case AST_LOGIC_NOT: txt = "!"; } + fprintf(f, "%s(", txt.c_str()); + children[0]->dumpVlog(f, ""); + fprintf(f, ")"); + break; + + if (0) { case AST_BIT_AND: txt = "&"; } + if (0) { case AST_BIT_OR: txt = "|"; } + if (0) { case AST_BIT_XOR: txt = "^"; } + if (0) { case AST_BIT_XNOR: txt = "~^"; } + if (0) { case AST_SHIFT_LEFT: txt = "<<"; } + if (0) { case AST_SHIFT_RIGHT: txt = ">>"; } + if (0) { case AST_SHIFT_SLEFT: txt = "<<<"; } + if (0) { case AST_SHIFT_SRIGHT: txt = ">>>"; } + if (0) { case AST_LT: txt = "<"; } + if (0) { case AST_LE: txt = "<="; } + if (0) { case AST_EQ: txt = "=="; } + if (0) { case AST_NE: txt = "!="; } + if (0) { case AST_GE: txt = ">="; } + if (0) { case AST_GT: txt = ">"; } + if (0) { case AST_ADD: txt = "+"; } + if (0) { case AST_SUB: txt = "-"; } + if (0) { case AST_MUL: txt = "*"; } + if (0) { case AST_DIV: txt = "/"; } + if (0) { case AST_MOD: txt = "%"; } + if (0) { case AST_POW: txt = "**"; } + if (0) { case AST_LOGIC_AND: txt = "&&"; } + if (0) { case AST_LOGIC_OR: txt = "||"; } + fprintf(f, "("); + children[0]->dumpVlog(f, ""); + fprintf(f, ")%s(", txt.c_str()); + children[1]->dumpVlog(f, ""); + fprintf(f, ")"); + break; + + case AST_TERNARY: + fprintf(f, "("); + children[0]->dumpVlog(f, ""); + fprintf(f, ") ? ("); + children[1]->dumpVlog(f, ""); + fprintf(f, ") : ("); + children[2]->dumpVlog(f, ""); + fprintf(f, ")"); + break; + + default: + std::string type_name = type2str(type); + fprintf(f, "%s" "/** %s **/%s", indent.c_str(), type_name.c_str(), indent.empty() ? "" : "\n"); + // dumpAst(f, indent, NULL); + } +} + +// check if two AST nodes are identical +bool AstNode::operator==(const AstNode &other) const +{ + if (type != other.type) + return false; + if (children.size() != other.children.size()) + return false; + if (str != other.str) + return false; + if (bits != other.bits) + return false; + if (is_input != other.is_input) + return false; + if (is_output != other.is_output) + return false; + if (is_reg != other.is_reg) + return false; + if (is_signed != other.is_signed) + return false; + if (range_valid != other.range_valid) + return false; + if (port_id != other.port_id) + return false; + if (range_left != other.range_left) + return false; + if (range_right != other.range_right) + return false; + if (integer != other.integer) + return false; + for (size_t i = 0; i < children.size(); i++) + if (*children[i] != *other.children[i]) + return false; + return true; +} + +// check if two AST nodes are not identical +bool AstNode::operator!=(const AstNode &other) const +{ + return !(*this == other); +} + +// check if this AST contains the given node +bool AstNode::contains(const AstNode *other) const +{ + if (this == other) + return true; + for (auto child : children) + if (child->contains(other)) + return true; + return false; +} + +// create an AST node for a constant (using a 32 bit int as value) +AstNode *AstNode::mkconst_int(uint32_t v, bool is_signed, int width) +{ + AstNode *node = new AstNode(AST_CONSTANT); + node->integer = v; + node->is_signed = is_signed; + for (int i = 0; i < width; i++) { + node->bits.push_back((v & 1) ? RTLIL::S1 : RTLIL::S0); + v = v >> 1; + } + node->range_valid = true; + node->range_left = width-1; + node->range_right = 0; + return node; +} + +// create an AST node for a constant (using a bit vector as value) +AstNode *AstNode::mkconst_bits(const std::vector &v, bool is_signed) +{ + AstNode *node = new AstNode(AST_CONSTANT); + node->is_signed = is_signed; + node->bits = v; + for (size_t i = 0; i < 32; i++) { + if (i < node->bits.size()) + node->integer |= (node->bits[i] == RTLIL::S1) << i; + else if (is_signed) + node->integer |= (node->bits.back() == RTLIL::S1) << i; + } + node->range_valid = true; + node->range_left = node->bits.size(); + node->range_right = 0; + return node; +} + +// create a new AstModule from an AST_MODULE AST node +static AstModule* process_module(AstNode *ast) +{ + assert(ast->type == AST_MODULE); + log("Generating RTLIL representation for module `%s'.\n", ast->str.c_str()); + + current_ast_mod = ast; + AstNode *ast_before_simplify = ast->clone(); + + while (ast->simplify(false, false, false, 0)) { } + + if (flag_dump_ast) { + log("Dumping verilog AST (as requested by %s option):\n", flag_dump_ast_diff ? "dump_ast_diff" : "dump_ast"); + ast->dumpAst(NULL, " ", flag_dump_ast_diff ? ast_before_simplify : NULL); + log("--- END OF AST DUMP ---\n"); + } + + if (flag_dump_vlog) { + log("Dumping verilog AST (as requested by dump_vlog option):\n"); + ast->dumpVlog(NULL, " "); + log("--- END OF AST DUMP ---\n"); + } + + current_module = new AstModule; + current_module->ast = NULL; + current_module->name = ast->str; + current_module->attributes["\\src"] = stringf("%s:%d", ast->filename.c_str(), ast->linenum); + for (auto &attr : ast->attributes) { + if (attr.second->type != AST_CONSTANT) + log_error("Attribute `%s' with non-constant value at %s:%d!\n", + attr.first.c_str(), ast->filename.c_str(), ast->linenum); + current_module->attributes[attr.first].str = attr.second->str; + current_module->attributes[attr.first].bits = attr.second->bits; + } + for (size_t i = 0; i < ast->children.size(); i++) { + AstNode *node = ast->children[i]; + if (node->type == AST_WIRE || node->type == AST_MEMORY) + node->genRTLIL(); + } + for (size_t i = 0; i < ast->children.size(); i++) { + AstNode *node = ast->children[i]; + if (node->type != AST_WIRE && node->type != AST_MEMORY) + node->genRTLIL(); + } + + current_module->ast = ast_before_simplify; + current_module->nolatches = flag_nolatches; + current_module->nomem2reg = flag_nomem2reg; + return current_module; +} + +// create AstModule instances for all modules in the AST tree and add them to 'design' +void AST::process(RTLIL::Design *design, AstNode *ast, bool dump_ast, bool dump_ast_diff, bool dump_vlog, bool nolatches, bool nomem2reg) +{ + current_ast = ast; + flag_dump_ast = dump_ast; + flag_dump_ast_diff = dump_ast_diff; + flag_dump_vlog = dump_vlog; + flag_nolatches = nolatches; + flag_nomem2reg = nomem2reg; + + assert(current_ast->type == AST_DESIGN); + for (auto it = current_ast->children.begin(); it != current_ast->children.end(); it++) { + if (design->modules.count((*it)->str) != 0) + log_error("Re-definition of module `%s' at %s:%d!\n", + (*it)->str.c_str(), (*it)->filename.c_str(), (*it)->linenum); + design->modules[(*it)->str] = process_module(*it); + } +} + +// AstModule destructor +AstModule::~AstModule() +{ + if (ast != NULL) + delete ast; +} + +// create a new parametric module (when needed) and return the name of the generated module +RTLIL::IdString AstModule::derive(RTLIL::Design *design, std::map parameters) +{ + log_header("Executing AST frontend in derive mode using pre-parsed AST for module `%s'.\n", name.c_str()); + + current_ast = NULL; + flag_dump_ast = false; + flag_dump_ast_diff = false; + flag_dump_vlog = false; + flag_nolatches = nolatches; + flag_nomem2reg = nomem2reg; + use_internal_line_num(); + + std::vector hash_data; + hash_data.insert(hash_data.end(), name.begin(), name.end()); + hash_data.push_back(0); + + AstNode *new_ast = ast->clone(); + + int para_counter = 0; + for (auto it = new_ast->children.begin(); it != new_ast->children.end(); it++) { + AstNode *child = *it; + if (child->type != AST_PARAMETER) + continue; + para_counter++; + std::string para_id = child->str; + if (parameters.count(child->str) > 0) { + log("Parameter %s = %s\n", child->str.c_str(), log_signal(RTLIL::SigSpec(parameters[child->str]))); + rewrite_parameter: + child->delete_children(); + child->children.push_back(AstNode::mkconst_bits(parameters[para_id].bits, false)); + hash_data.insert(hash_data.end(), child->str.begin(), child->str.end()); + hash_data.push_back(0); + hash_data.insert(hash_data.end(), parameters[para_id].bits.begin(), parameters[para_id].bits.end()); + hash_data.push_back(0xff); + parameters.erase(para_id); + continue; + } + char buf[100]; + snprintf(buf, 100, "$%d", para_counter); + if (parameters.count(buf) > 0) { + para_id = buf; + log("Parameter %d (%s) = %s\n", para_counter, child->str.c_str(), log_signal(RTLIL::SigSpec(parameters[para_id]))); + goto rewrite_parameter; + } + } + if (parameters.size() > 0) + log_error("Requested parameter `%s' does not exist in module `%s'!\n", parameters.begin()->first.c_str(), name.c_str()); + + unsigned char hash[20]; + unsigned char *hash_data2 = new unsigned char[hash_data.size()]; + for (size_t i = 0; i < hash_data.size(); i++) + hash_data2[i] = hash_data[i]; + sha1::calc(hash_data2, hash_data.size(), hash); + delete[] hash_data2; + + char hexstring[41]; + sha1::toHexString(hash, hexstring); + + std::string modname = "$paramod$" + std::string(hexstring) + "$" + name; + + if (design->modules.count(modname) == 0) { + new_ast->str = modname; + design->modules[modname] = process_module(new_ast); + } else { + log("Found cached RTLIL representation for module `%s'.\n", modname.c_str()); + } + + delete new_ast; + return modname; +} + +// recompile a module from AST with updated widths for auto-wires +// (auto-wires are wires that are used but not declared an thus have an automatically determined width) +void AstModule::update_auto_wires(std::map auto_sizes) +{ + log_header("Executing AST frontend in update_auto_wires mode using pre-parsed AST for module `%s'.\n", name.c_str()); + + current_ast = NULL; + flag_dump_ast = false; + flag_dump_ast_diff = false; + flag_dump_vlog = false; + flag_nolatches = nolatches; + flag_nomem2reg = nomem2reg; + use_internal_line_num(); + + for (auto it = auto_sizes.begin(); it != auto_sizes.end(); it++) { + log("Adding extra wire declaration to AST: wire [%d:0] %s\n", it->second - 1, it->first.c_str()); + AstNode *wire = new AstNode(AST_WIRE, new AstNode(AST_RANGE, AstNode::mkconst_int(it->second - 1, true), AstNode::mkconst_int(0, true))); + wire->str = it->first; + ast->children.insert(ast->children.begin(), wire); + } + + AstModule *newmod = process_module(ast); + + delete ast; + ast = newmod->ast; + newmod->ast = NULL; + + wires.swap(newmod->wires); + cells.swap(newmod->cells); + processes.swap(newmod->processes); + connections.swap(newmod->connections); + attributes.swap(newmod->attributes); + delete newmod; +} + +// internal dummy line number callbacks +namespace { + int internal_line_num; + void internal_set_line_num(int n) { + internal_line_num = n; + } + int internal_get_line_num() { + return internal_line_num; + } +} + +// use internal dummy line number callbacks +void AST::use_internal_line_num() +{ + set_line_num = &internal_set_line_num; + get_line_num = &internal_get_line_num; +} + diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h new file mode 100644 index 00000000..f7c9328c --- /dev/null +++ b/frontends/ast/ast.h @@ -0,0 +1,228 @@ +/* + * 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. + * + */ + +#ifndef AST_H +#define AST_H + +#include "kernel/rtlil.h" +#include +#include + +namespace AST +{ + // all node types, type2str() must be extended + // whenever a new node type is added here + enum AstNodeType + { + AST_NONE, + AST_DESIGN, + AST_MODULE, + AST_TASK, + AST_FUNCTION, + + AST_WIRE, + AST_MEMORY, + AST_AUTOWIRE, + AST_PARAMETER, + AST_LOCALPARAM, + AST_PARASET, + AST_ARGUMENT, + AST_RANGE, + AST_CONSTANT, + AST_CELLTYPE, + AST_IDENTIFIER, + + AST_FCALL, + AST_TO_SIGNED, + AST_TO_UNSIGNED, + AST_CONCAT, + AST_REPLICATE, + AST_BIT_NOT, + AST_BIT_AND, + AST_BIT_OR, + AST_BIT_XOR, + AST_BIT_XNOR, + AST_REDUCE_AND, + AST_REDUCE_OR, + AST_REDUCE_XOR, + AST_REDUCE_XNOR, + AST_REDUCE_BOOL, + AST_SHIFT_LEFT, + AST_SHIFT_RIGHT, + AST_SHIFT_SLEFT, + AST_SHIFT_SRIGHT, + AST_LT, + AST_LE, + AST_EQ, + AST_NE, + AST_GE, + AST_GT, + AST_ADD, + AST_SUB, + AST_MUL, + AST_DIV, + AST_MOD, + AST_POW, + AST_POS, + AST_NEG, + AST_LOGIC_AND, + AST_LOGIC_OR, + AST_LOGIC_NOT, + AST_TERNARY, + AST_MEMRD, + AST_MEMWR, + + AST_TCALL, + AST_ASSIGN, + AST_CELL, + AST_PRIMITIVE, + AST_ALWAYS, + AST_BLOCK, + AST_ASSIGN_EQ, + AST_ASSIGN_LE, + AST_CASE, + AST_COND, + AST_DEFAULT, + AST_FOR, + + AST_GENVAR, + AST_GENFOR, + AST_GENIF, + AST_GENBLOCK, + + AST_POSEDGE, + AST_NEGEDGE, + AST_EDGE + }; + + // convert an node type to a string (e.g. for debug output) + std::string type2str(AstNodeType type); + + // The AST is built using instances of this struct + struct AstNode + { + // this nodes type + AstNodeType type; + + // the list of child nodes for this node + std::vector children; + + // the list of attributes assigned to this node + std::map attributes; + + // node content - most of it is unused in most node types + std::string str; + std::vector bits; + bool is_input, is_output, is_reg, is_signed, range_valid; + int port_id, range_left, range_right; + uint32_t integer; + + // this is set by simplify and used during RTLIL generation + AstNode *id2ast; + + // this is the original sourcecode location that resulted in this AST node + // it is automatically set by the constructor using AST::current_filename and + // the AST::get_line_num() callback function. + std::string filename; + int linenum; + + // creating and deleting nodes + AstNode(AstNodeType type = AST_NONE, AstNode *child1 = NULL, AstNode *child2 = NULL); + AstNode *clone(); + void cloneInto(AstNode *other); + void delete_children(); + ~AstNode(); + + // simplify() creates a simpler AST by unrolling for-loops, expanding generate blocks, etc. + // it also sets the id2ast pointers so that identifier lookups are fast in genRTLIL() + bool simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage); + void expand_genblock(std::string index_var, std::string prefix, std::map &name_map); + void replace_ids(std::map &rules); + void mem2reg_as_needed_pass1(std::set &mem2reg_set, std::set &mem2reg_candidates, bool sync_proc, bool async_proc); + void mem2reg_as_needed_pass2(std::set &mem2reg_set, AstNode *mod, AstNode *block); + void meminfo(int &mem_width, int &mem_size, int &addr_bits); + + // create a human-readable text representation of the AST (for debugging) + void dumpAst(FILE *f, std::string indent, AstNode *other = NULL); + void dumpVlog(FILE *f, std::string indent); + + // create RTLIL code for this AST node + // for expressions the resulting signal vector is returned + // all generated cell instances, etc. are written to the RTLIL::Module pointed to by AST_INTERNAL::current_module + RTLIL::SigSpec genRTLIL(int width_hint = -1); + RTLIL::SigSpec genWidthRTLIL(int width, RTLIL::SigSpec *subst_from = NULL, RTLIL::SigSpec *subst_to = NULL); + + // compare AST nodes + bool operator==(const AstNode &other) const; + bool operator!=(const AstNode &other) const; + bool contains(const AstNode *other) const; + + // helper functions for creating AST nodes for constants + static AstNode *mkconst_int(uint32_t v, bool is_signed, int width = 32); + static AstNode *mkconst_bits(const std::vector &v, bool is_signed); + }; + + // process an AST tree (ast must point to an AST_DESIGN node) and generate RTLIL code + void process(RTLIL::Design *design, AstNode *ast, bool dump_ast = false, bool dump_ast_diff = false, bool dump_vlog = false, bool nolatches = false, bool nomem2reg = false); + + // parametric modules are supported directly by the AST library + // therfore we need our own derivate of RTLIL::Module with overloaded virtual functions + struct AstModule : RTLIL::Module { + AstNode *ast; + bool nolatches, nomem2reg; + virtual ~AstModule(); + virtual RTLIL::IdString derive(RTLIL::Design *design, std::map parameters); + virtual void update_auto_wires(std::map auto_sizes); + }; + + // this must be set by the language frontend before parsing the sources + // the AstNode constructor then uses current_filename and get_line_num() + // to initialize the filename and linenum properties of new nodes + extern std::string current_filename; + extern void (*set_line_num)(int); + extern int (*get_line_num)(); + + // set set_line_num and get_line_num to internal dummy functions + // (done by simplify(), AstModule::derive and AstModule::update_auto_wires to control + // the filename and linenum properties of new nodes not generated by a frontend parser) + void use_internal_line_num(); +} + +namespace AST_INTERNAL +{ + // internal state variables + extern bool flag_dump_ast, flag_dump_ast_diff, flag_nolatches, flag_nomem2reg; + extern AST::AstNode *current_ast, *current_ast_mod; + extern std::map current_scope; + extern RTLIL::SigSpec *genRTLIL_subst_from, *genRTLIL_subst_to; + extern AST::AstNode *current_top_block, *current_block, *current_block_child; + extern AST::AstModule *current_module; + struct ProcessGenerator; +} + +#endif diff --git a/frontends/ast/genrtlil.cc b/frontends/ast/genrtlil.cc new file mode 100644 index 00000000..9f1acb61 --- /dev/null +++ b/frontends/ast/genrtlil.cc @@ -0,0 +1,1054 @@ +/* + * 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 "kernel/sha1.h" +#include "ast.h" + +#include +#include +#include + +using namespace AST; +using namespace AST_INTERNAL; + +// helper function for creating RTLIL code for unary operations +static RTLIL::SigSpec uniop2rtlil(AstNode *that, std::string type, int result_width, const RTLIL::SigSpec &arg, bool gen_attributes = true) +{ + std::stringstream sstr; + sstr << type << "$" << that->filename << ":" << that->linenum << "$" << (RTLIL::autoidx++); + + RTLIL::Cell *cell = new RTLIL::Cell; + cell->attributes["\\src"] = stringf("%s:%d", that->filename.c_str(), that->linenum); + cell->name = sstr.str(); + cell->type = type; + current_module->cells[cell->name] = cell; + + RTLIL::Wire *wire = new RTLIL::Wire; + wire->attributes["\\src"] = stringf("%s:%d", that->filename.c_str(), that->linenum); + wire->name = cell->name + "_Y"; + wire->width = result_width; + current_module->wires[wire->name] = wire; + + RTLIL::SigChunk chunk; + chunk.wire = wire; + chunk.width = wire->width; + chunk.offset = 0; + + RTLIL::SigSpec sig; + sig.chunks.push_back(chunk); + sig.width = chunk.width; + + if (gen_attributes) + for (auto &attr : that->attributes) { + if (attr.second->type != AST_CONSTANT) + log_error("Attribute `%s' with non-constant value at %s:%d!\n", + attr.first.c_str(), that->filename.c_str(), that->linenum); + cell->attributes[attr.first].str = attr.second->str; + cell->attributes[attr.first].bits = attr.second->bits; + } + + cell->parameters["\\A_SIGNED"] = RTLIL::Const(that->children[0]->is_signed); + cell->parameters["\\A_WIDTH"] = RTLIL::Const(arg.width); + cell->connections["\\A"] = arg; + + cell->parameters["\\Y_WIDTH"] = result_width; + cell->connections["\\Y"] = sig; + return sig; +} + +// helper function for creating RTLIL code for binary operations +static RTLIL::SigSpec binop2rtlil(AstNode *that, std::string type, int result_width, const RTLIL::SigSpec &left, const RTLIL::SigSpec &right) +{ + std::stringstream sstr; + sstr << type << "$" << that->filename << ":" << that->linenum << "$" << (RTLIL::autoidx++); + + RTLIL::Cell *cell = new RTLIL::Cell; + cell->attributes["\\src"] = stringf("%s:%d", that->filename.c_str(), that->linenum); + cell->name = sstr.str(); + cell->type = type; + current_module->cells[cell->name] = cell; + + RTLIL::Wire *wire = new RTLIL::Wire; + wire->attributes["\\src"] = stringf("%s:%d", that->filename.c_str(), that->linenum); + wire->name = cell->name + "_Y"; + wire->width = result_width; + current_module->wires[wire->name] = wire; + + RTLIL::SigChunk chunk; + chunk.wire = wire; + chunk.width = wire->width; + chunk.offset = 0; + + RTLIL::SigSpec sig; + sig.chunks.push_back(chunk); + sig.width = chunk.width; + + for (auto &attr : that->attributes) { + if (attr.second->type != AST_CONSTANT) + log_error("Attribute `%s' with non-constant value at %s:%d!\n", + attr.first.c_str(), that->filename.c_str(), that->linenum); + cell->attributes[attr.first].str = attr.second->str; + cell->attributes[attr.first].bits = attr.second->bits; + } + + cell->parameters["\\A_SIGNED"] = RTLIL::Const(that->children[0]->is_signed); + cell->parameters["\\B_SIGNED"] = RTLIL::Const(that->children[1]->is_signed); + + cell->parameters["\\A_WIDTH"] = RTLIL::Const(left.width); + cell->parameters["\\B_WIDTH"] = RTLIL::Const(right.width); + + cell->connections["\\A"] = left; + cell->connections["\\B"] = right; + + cell->parameters["\\Y_WIDTH"] = result_width; + cell->connections["\\Y"] = sig; + return sig; +} + +// helper function for creating RTLIL code for multiplexers +static RTLIL::SigSpec mux2rtlil(AstNode *that, const RTLIL::SigSpec &cond, const RTLIL::SigSpec &left, const RTLIL::SigSpec &right) +{ + assert(cond.width == 1); + + std::stringstream sstr; + sstr << "$ternary$" << that->filename << ":" << that->linenum << "$" << (RTLIL::autoidx++); + + RTLIL::Cell *cell = new RTLIL::Cell; + cell->attributes["\\src"] = stringf("%s:%d", that->filename.c_str(), that->linenum); + cell->name = sstr.str(); + cell->type = "$mux"; + current_module->cells[cell->name] = cell; + + RTLIL::Wire *wire = new RTLIL::Wire; + wire->attributes["\\src"] = stringf("%s:%d", that->filename.c_str(), that->linenum); + wire->name = cell->name + "_Y"; + wire->width = left.width; + current_module->wires[wire->name] = wire; + + RTLIL::SigChunk chunk; + chunk.wire = wire; + chunk.width = wire->width; + chunk.offset = 0; + + RTLIL::SigSpec sig; + sig.chunks.push_back(chunk); + sig.width = chunk.width; + + for (auto &attr : that->attributes) { + if (attr.second->type != AST_CONSTANT) + log_error("Attribute `%s' with non-constant value at %s:%d!\n", + attr.first.c_str(), that->filename.c_str(), that->linenum); + cell->attributes[attr.first].str = attr.second->str; + cell->attributes[attr.first].bits = attr.second->bits; + } + + cell->parameters["\\WIDTH"] = RTLIL::Const(left.width); + + cell->connections["\\A"] = right; + cell->connections["\\B"] = left; + cell->connections["\\S"] = cond; + cell->connections["\\Y"] = sig; + + return sig; +} + +// helper class for converting AST always nodes to RTLIL processes +struct AST_INTERNAL::ProcessGenerator +{ + // input and output structures + AstNode *always; + RTLIL::Process *proc; + + // This always points to the RTLIL::CaseRule beeing filled at the moment + RTLIL::CaseRule *current_case; + + // This two variables contain the replacement pattern to be used in the right hand side + // of an assignment. E.g. in the code "foo = bar; foo = func(foo);" the foo in the right + // hand side of the 2nd assignment needs to be replace with the temporary signal holding + // the value assigned in the first assignment. So when the first assignement is processed + // the according information is appended to subst_rvalue_from and subst_rvalue_to. + RTLIL::SigSpec subst_rvalue_from, subst_rvalue_to; + + // This two variables contain the replacement pattern to be used in the left hand side + // of an assignment. E.g. in the code "always @(posedge clk) foo <= bar" the signal bar + // should not be connected to the signal foo. Instead it must be connected to the temporary + // signal that is used as input for the register that drives the signal foo. + RTLIL::SigSpec subst_lvalue_from, subst_lvalue_to; + + // The code here generates a number of temprorary signal for each output register. This + // map helps generating nice numbered names for all this temporary signals. + std::map new_temp_count; + + ProcessGenerator(AstNode *always) : always(always) + { + // generate process and simple root case + proc = new RTLIL::Process; + proc->name = stringf("$proc$%s:%d$%d", always->filename.c_str(), always->linenum, RTLIL::autoidx++); + for (auto &attr : always->attributes) { + if (attr.second->type != AST_CONSTANT) + log_error("Attribute `%s' with non-constant value at %s:%d!\n", + attr.first.c_str(), always->filename.c_str(), always->linenum); + proc->attributes[attr.first].str = attr.second->str; + proc->attributes[attr.first].bits = attr.second->bits; + } + current_module->processes[proc->name] = proc; + current_case = &proc->root_case; + + // create initial temporary signal for all output registers + collect_lvalues(subst_lvalue_from, always, true, true); + subst_lvalue_to = new_temp_signal(subst_lvalue_from); + + bool found_anyedge_syncs = false; + for (auto child : always->children) + if (child->type == AST_EDGE) + found_anyedge_syncs = true; + + if (found_anyedge_syncs) { + log("Note: Assuming pure combinatorial block at %s:%d in\n", always->filename.c_str(), always->linenum); + log("compliance with IEC 62142(E):2005 / IEEE Std. 1364.1(E):2002. Recommending\n"); + log("use of @* instead of @(...) for better match of synthesis and simulation.\n"); + } + + // create syncs for the process + bool found_clocked_sync = false; + for (auto child : always->children) + if (child->type == AST_POSEDGE || child->type == AST_NEGEDGE) { + found_clocked_sync = true; + if (found_anyedge_syncs) + log_error("Found non-synthesizable event list at %s:%d!\n", always->filename.c_str(), always->linenum); + RTLIL::SyncRule *syncrule = new RTLIL::SyncRule; + syncrule->type = child->type == AST_POSEDGE ? RTLIL::STp : RTLIL::STn; + syncrule->signal = child->children[0]->genRTLIL(); + addChunkActions(syncrule->actions, subst_lvalue_from, subst_lvalue_to); + proc->syncs.push_back(syncrule); + } + if (proc->syncs.empty()) { + RTLIL::SyncRule *syncrule = new RTLIL::SyncRule; + syncrule->type = RTLIL::STa; + syncrule->signal = RTLIL::SigSpec(); + addChunkActions(syncrule->actions, subst_lvalue_from, subst_lvalue_to); + proc->syncs.push_back(syncrule); + } + + // create initial assignments for the temporary signals + if ((flag_nolatches || always->attributes.count("\\nolatches") > 0 || current_module->attributes.count("\\nolatches")) && !found_clocked_sync) { + subst_rvalue_from = subst_lvalue_from; + subst_rvalue_to = RTLIL::SigSpec(RTLIL::State::Sx, subst_rvalue_from.width); + } else { + addChunkActions(current_case->actions, subst_lvalue_to, subst_lvalue_from); + } + + // process the AST + for (auto child : always->children) + if (child->type == AST_BLOCK) + processAst(child); + } + + // create new temporary signals + RTLIL::SigSpec new_temp_signal(RTLIL::SigSpec sig) + { + sig.optimize(); + for (size_t i = 0; i < sig.chunks.size(); i++) + { + RTLIL::SigChunk &chunk = sig.chunks[i]; + if (chunk.wire == NULL) + continue; + + RTLIL::Wire *wire = new RTLIL::Wire; + wire->attributes["\\src"] = stringf("%s:%d", always->filename.c_str(), always->linenum); + do { + wire->name = stringf("$%d%s[%d:%d]", new_temp_count[chunk.wire]++, + chunk.wire->name.c_str(), chunk.width+chunk.offset-1, chunk.offset);; + } while (current_module->wires.count(wire->name) > 0); + wire->width = chunk.width; + current_module->wires[wire->name] = wire; + + chunk.wire = wire; + chunk.offset = 0; + } + return sig; + } + + // recursively traverse the AST an collect all assigned signals + void collect_lvalues(RTLIL::SigSpec ®, AstNode *ast, bool type_eq, bool type_le, bool run_sort_and_unify = true) + { + switch (ast->type) + { + case AST_CASE: + for (auto child : ast->children) + if (child != ast->children[0]) { + assert(child->type == AST_COND); + collect_lvalues(reg, child, type_eq, type_le, false); + } + break; + + case AST_COND: + case AST_ALWAYS: + for (auto child : ast->children) + if (child->type == AST_BLOCK) + collect_lvalues(reg, child, type_eq, type_le, false); + break; + + case AST_BLOCK: + for (auto child : ast->children) { + if (child->type == AST_ASSIGN_EQ && type_eq) + reg.append(child->children[0]->genRTLIL()); + if (child->type == AST_ASSIGN_LE && type_le) + reg.append(child->children[0]->genRTLIL()); + if (child->type == AST_CASE || child->type == AST_BLOCK) + collect_lvalues(reg, child, type_eq, type_le, false); + } + break; + + default: + assert(0); + } + + if (run_sort_and_unify) + reg.sort_and_unify(); + } + + // remove all assignments to the given signal pattern in a case and all its children + // when the last statement in the code "a = 23; if (b) a = 42; a = 0;" is processed this + // function is acalled to clean up the first two assignments as they are overwritten by + // the third assignment. + void removeSignalFromCaseTree(RTLIL::SigSpec pattern, RTLIL::CaseRule *cs) + { + for (auto it = cs->actions.begin(); it != cs->actions.end(); it++) + it->first.remove2(pattern, &it->second); + + for (auto it = cs->switches.begin(); it != cs->switches.end(); it++) + for (auto it2 = (*it)->cases.begin(); it2 != (*it)->cases.end(); it2++) + removeSignalFromCaseTree(pattern, *it2); + } + + // add an assignment (aka "action") but split it up in chunks. this way huge assignments + // are avoided and the generated $mux cells have a more "natural" size. + void addChunkActions(std::vector &actions, RTLIL::SigSpec lvalue, RTLIL::SigSpec rvalue) + { + assert(lvalue.width == rvalue.width); + lvalue.optimize(); + rvalue.optimize(); + + int offset = 0; + for (size_t i = 0; i < lvalue.chunks.size(); i++) { + RTLIL::SigSpec lhs = lvalue.chunks[i]; + RTLIL::SigSpec rhs = rvalue.extract(offset, lvalue.chunks[i].width); + actions.push_back(RTLIL::SigSig(lhs, rhs)); + offset += lhs.width; + } + } + + // recursively process the AST and fill the RTLIL::Process + void processAst(AstNode *ast) + { + switch (ast->type) + { + case AST_BLOCK: + for (auto child : ast->children) + processAst(child); + break; + + case AST_ASSIGN_EQ: + case AST_ASSIGN_LE: + { + RTLIL::SigSpec unmapped_lvalue = ast->children[0]->genRTLIL(), lvalue = unmapped_lvalue; + RTLIL::SigSpec rvalue = ast->children[1]->genWidthRTLIL(lvalue.width, &subst_rvalue_from, &subst_rvalue_to); + lvalue.replace(subst_lvalue_from, subst_lvalue_to); + + if (ast->type == AST_ASSIGN_EQ) { + subst_rvalue_from.remove2(unmapped_lvalue, &subst_rvalue_to); + subst_rvalue_from.append(unmapped_lvalue); + subst_rvalue_from.optimize(); + subst_rvalue_to.append(rvalue); + subst_rvalue_to.optimize(); + } + + removeSignalFromCaseTree(lvalue, current_case); + current_case->actions.push_back(RTLIL::SigSig(lvalue, rvalue)); + } + break; + + case AST_CASE: + { + RTLIL::SwitchRule *sw = new RTLIL::SwitchRule; + sw->signal = ast->children[0]->genWidthRTLIL(-1, &subst_rvalue_from, &subst_rvalue_to); + current_case->switches.push_back(sw); + + for (auto &attr : ast->attributes) { + if (attr.second->type != AST_CONSTANT) + log_error("Attribute `%s' with non-constant value at %s:%d!\n", + attr.first.c_str(), ast->filename.c_str(), ast->linenum); + sw->attributes[attr.first].str = attr.second->str; + sw->attributes[attr.first].bits = attr.second->bits; + } + + RTLIL::SigSpec this_case_eq_lvalue; + collect_lvalues(this_case_eq_lvalue, ast, true, false); + + RTLIL::SigSpec this_case_eq_ltemp = new_temp_signal(this_case_eq_lvalue); + + RTLIL::SigSpec this_case_eq_rvalue = this_case_eq_lvalue; + this_case_eq_rvalue.replace(subst_rvalue_from, subst_rvalue_to); + + RTLIL::SigSpec backup_subst_lvalue_from = subst_lvalue_from; + RTLIL::SigSpec backup_subst_lvalue_to = subst_lvalue_to; + + RTLIL::SigSpec backup_subst_rvalue_from = subst_rvalue_from; + RTLIL::SigSpec backup_subst_rvalue_to = subst_rvalue_to; + + bool generated_default_case = false; + RTLIL::CaseRule *last_generated_case = NULL; + for (auto child : ast->children) + { + if (child == ast->children[0] || generated_default_case) + continue; + assert(child->type == AST_COND); + + subst_lvalue_from = backup_subst_lvalue_from; + subst_lvalue_to = backup_subst_lvalue_to; + + subst_rvalue_from = backup_subst_rvalue_from; + subst_rvalue_to = backup_subst_rvalue_to; + + subst_lvalue_from.remove2(this_case_eq_lvalue, &subst_lvalue_to); + subst_lvalue_from.append(this_case_eq_lvalue); + subst_lvalue_from.optimize(); + subst_lvalue_to.append(this_case_eq_ltemp); + subst_lvalue_to.optimize(); + + RTLIL::CaseRule *backup_case = current_case; + current_case = new RTLIL::CaseRule; + last_generated_case = current_case; + addChunkActions(current_case->actions, this_case_eq_ltemp, this_case_eq_rvalue); + for (auto node : child->children) { + if (node->type == AST_DEFAULT) { + generated_default_case = true; + current_case->compare.clear(); + } else if (node->type == AST_BLOCK) { + processAst(node); + } else if (!generated_default_case) + current_case->compare.push_back(node->genWidthRTLIL(sw->signal.width)); + } + sw->cases.push_back(current_case); + current_case = backup_case; + } + + if (last_generated_case != NULL && ast->attributes.count("\\full_case") > 0) { + last_generated_case->compare.clear(); + } else if (!generated_default_case) { + RTLIL::CaseRule *default_case = new RTLIL::CaseRule; + addChunkActions(default_case->actions, this_case_eq_ltemp, this_case_eq_rvalue); + sw->cases.push_back(default_case); + } + + subst_lvalue_from = backup_subst_lvalue_from; + subst_lvalue_to = backup_subst_lvalue_to; + + subst_rvalue_from = backup_subst_rvalue_from; + subst_rvalue_to = backup_subst_rvalue_to; + + subst_rvalue_from.remove2(this_case_eq_lvalue, &subst_rvalue_to); + subst_rvalue_from.append(this_case_eq_lvalue); + subst_rvalue_from.optimize(); + subst_rvalue_to.append(this_case_eq_ltemp); + subst_rvalue_to.optimize(); + + this_case_eq_lvalue.replace(subst_lvalue_from, subst_lvalue_to); + removeSignalFromCaseTree(this_case_eq_lvalue, current_case); + addChunkActions(current_case->actions, this_case_eq_lvalue, this_case_eq_ltemp); + } + break; + + case AST_TCALL: + case AST_FOR: + break; + + default: + assert(0); + } + } +}; + +// create RTLIL from an AST node +// all generated cells, wires and processes are added to the module pointed to by 'current_module' +// when the AST node is an expression (AST_ADD, AST_BIT_XOR, etc.), the result signal is returned. +// +// note that this function is influenced by a number of global variables that might be set when +// called from genWidthRTLIL(). also note that this function recursively calls itself to transform +// larger expressions into a netlist of cells. +RTLIL::SigSpec AstNode::genRTLIL(int width_hint) +{ + // in the following big switch() statement there are some uses of + // Clifford's Device (http://www.clifford.at/cfun/cliffdev/). In this + // cases this variable is used to hold the type of the cell that should + // be instanciated for this type of AST node. + std::string type_name; + + current_filename = filename; + set_line_num(linenum); + + switch (type) + { + // simply ignore this nodes. + // they are eighter leftovers from simplify() or are referenced by other nodes + // and are only accessed here thru this references + case AST_TASK: + case AST_FUNCTION: + case AST_AUTOWIRE: + case AST_PARAMETER: + case AST_LOCALPARAM: + case AST_GENVAR: + case AST_GENFOR: + case AST_GENIF: + break; + + // create an RTLIL::Wire for an AST_WIRE node + case AST_WIRE: { + if (current_module->wires.count(str) != 0) + log_error("Re-definition of signal `%s' at %s:%d!\n", + str.c_str(), filename.c_str(), linenum); + if (!range_valid) + log_error("Signal `%s' with non-constant width at %s:%d!\n", + str.c_str(), filename.c_str(), linenum); + + if (range_left < range_right && (range_left != -1 || range_right != 0)) { + int tmp = range_left; + range_left = range_right; + range_right = tmp; + } + + RTLIL::Wire *wire = new RTLIL::Wire; + wire->attributes["\\src"] = stringf("%s:%d", filename.c_str(), linenum); + wire->name = str; + wire->width = range_left - range_right + 1; + wire->start_offset = range_right; + wire->port_id = port_id; + wire->port_input = is_input; + wire->port_output = is_output; + current_module->wires[wire->name] = wire; + + for (auto &attr : attributes) { + if (attr.second->type != AST_CONSTANT) + log_error("Attribute `%s' with non-constant value at %s:%d!\n", + attr.first.c_str(), filename.c_str(), linenum); + wire->attributes[attr.first].str = attr.second->str; + wire->attributes[attr.first].bits = attr.second->bits; + } + } + break; + + // create an RTLIL::Memory for an AST_MEMORY node + case AST_MEMORY: { + if (current_module->memories.count(str) != 0) + log_error("Re-definition of memory `%s' at %s:%d!\n", + str.c_str(), filename.c_str(), linenum); + + assert(children.size() >= 2); + assert(children[0]->type == AST_RANGE); + assert(children[1]->type == AST_RANGE); + + if (!children[0]->range_valid || !children[1]->range_valid) + log_error("Memory `%s' with non-constant width or size at %s:%d!\n", + str.c_str(), filename.c_str(), linenum); + + RTLIL::Memory *memory = new RTLIL::Memory; + memory->attributes["\\src"] = stringf("%s:%d", filename.c_str(), linenum); + memory->name = str; + memory->width = children[0]->range_left - children[0]->range_right + 1; + memory->start_offset = children[0]->range_right; + memory->size = children[1]->range_left - children[1]->range_right; + current_module->memories[memory->name] = memory; + + if (memory->size < 0) + memory->size *= -1; + memory->size += std::min(children[1]->range_left, children[1]->range_right) + 1; + + for (auto &attr : attributes) { + if (attr.second->type != AST_CONSTANT) + log_error("Attribute `%s' with non-constant value at %s:%d!\n", + attr.first.c_str(), filename.c_str(), linenum); + memory->attributes[attr.first].str = attr.second->str; + memory->attributes[attr.first].bits = attr.second->bits; + } + } + break; + + // simply return the corresponding RTLIL::SigSpec for an AST_CONSTANT node + case AST_CONSTANT: + { + RTLIL::SigChunk chunk; + chunk.wire = NULL; + chunk.data.bits = bits; + chunk.width = bits.size(); + chunk.offset = 0; + + RTLIL::SigSpec sig; + sig.chunks.push_back(chunk); + sig.width = chunk.width; + return sig; + } + + // simply return the corresponding RTLIL::SigSpec for an AST_IDENTIFIER node + // for identifiers with dynamic bit ranges (e.g. "foo[bar]" or "foo[bar+3:bar]") a + // shifter cell is created and the output signal of this cell is returned + case AST_IDENTIFIER: + { + if (id2ast && id2ast->type == AST_AUTOWIRE && current_module->wires.count(str) == 0) { + RTLIL::Wire *wire = new RTLIL::Wire; + wire->attributes["\\src"] = stringf("%s:%d", filename.c_str(), linenum); + wire->name = str; + if (width_hint >= 0) { + wire->width = width_hint; + log("Warning: Identifier `%s' is implicitly declared with width %d at %s:%d.\n", + str.c_str(), width_hint, filename.c_str(), linenum); + } else { + log("Warning: Identifier `%s' is implicitly declared at %s:%d.\n", + str.c_str(), filename.c_str(), linenum); + } + wire->auto_width = true; + current_module->wires[str] = wire; + } + else if (!id2ast || (id2ast->type != AST_WIRE && id2ast->type != AST_AUTOWIRE && + id2ast->type != AST_MEMORY) || current_module->wires.count(str) == 0) + log_error("Identifier `%s' doesn't map to any signal at %s:%d!\n", + str.c_str(), filename.c_str(), linenum); + + if (id2ast->type == AST_MEMORY) + log_error("Identifier `%s' does map to an unexpanded memory at %s:%d!\n", + str.c_str(), filename.c_str(), linenum); + + RTLIL::Wire *wire = current_module->wires[str]; + + RTLIL::SigChunk chunk; + chunk.wire = wire; + chunk.width = wire->width; + chunk.offset = 0; + + if (children.size() != 0) { + assert(children[0]->type == AST_RANGE); + if (!children[0]->range_valid) { + AstNode *left_at_zero_ast = children[0]->children[0]->clone(); + AstNode *right_at_zero_ast = children[0]->children.size() >= 2 ? children[0]->children[1]->clone() : left_at_zero_ast->clone(); + while (left_at_zero_ast->simplify(true, true, false, 1)) { } + while (right_at_zero_ast->simplify(true, true, false, 1)) { } + 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; + AstNode *fake_ast = new AstNode(AST_NONE, clone(), children[0]->children.size() >= 2 ? + children[0]->children[1]->clone() : children[0]->children[0]->clone()); + fake_ast->children[0]->delete_children(); + RTLIL::SigSpec sig = binop2rtlil(fake_ast, "$shr", width, + fake_ast->children[0]->genRTLIL(), fake_ast->children[1]->genRTLIL()); + delete left_at_zero_ast; + delete right_at_zero_ast; + delete fake_ast; + return sig; + } else { + chunk.offset = children[0]->range_right - id2ast->range_right; + chunk.width = children[0]->range_left - children[0]->range_right + 1; + if (children[0]->range_left > id2ast->range_left || id2ast->range_right > children[0]->range_right) + log_error("Range select out of bounds on signal `%s' at %s:%d!\n", + str.c_str(), filename.c_str(), linenum); + } + } + + RTLIL::SigSpec sig; + sig.chunks.push_back(chunk); + sig.width = chunk.width; + + if (genRTLIL_subst_from && genRTLIL_subst_to) + sig.replace(*genRTLIL_subst_from, *genRTLIL_subst_to); + + is_signed = id2ast->is_signed; + if (children.size() != 0) + is_signed = false; + + return sig; + } + + // just pass thru the signal. the parent will evaluated the is_signed property and inperpret the SigSpec accordingly + case AST_TO_SIGNED: + case AST_TO_UNSIGNED: { + RTLIL::SigSpec sig = children[0]->genRTLIL(width_hint); + is_signed = type == AST_TO_SIGNED; + return sig; + } + + // concatenation of signals can be done directly using RTLIL::SigSpec + case AST_CONCAT: { + RTLIL::SigSpec sig; + sig.width = 0; + for (auto it = children.begin(); it != children.end(); it++) { + RTLIL::SigSpec s = (*it)->genRTLIL(); + for (size_t i = 0; i < s.chunks.size(); i++) { + sig.chunks.push_back(s.chunks[i]); + sig.width += s.chunks[i].width; + } + } + return sig; + } + + // replication of signals can be done directly using RTLIL::SigSpec + case AST_REPLICATE: { + RTLIL::SigSpec left = children[0]->genRTLIL(); + RTLIL::SigSpec right = children[1]->genRTLIL(); + if (!left.is_fully_const()) + log_error("Left operand of replicate expression is not constant at %s:%d!\n", filename.c_str(), linenum); + int count = left.as_int(); + RTLIL::SigSpec sig; + for (int i = 0; i < count; i++) + sig.append(right); + is_signed = false; + return sig; + } + + // generate cells for unary operations: $not, $pos, $neg + if (0) { case AST_BIT_NOT: type_name = "$not"; } + if (0) { case AST_POS: type_name = "$pos"; } + if (0) { case AST_NEG: type_name = "$neg"; } + { + RTLIL::SigSpec arg = children[0]->genRTLIL(width_hint); + is_signed = type == AST_NEG || (type == AST_POS && children[0]->is_signed); + int width = type == AST_NEG && arg.width < width_hint ? arg.width+1 : arg.width; + if (width > width_hint && width_hint > 0) + width = width_hint; + return uniop2rtlil(this, type_name, width, arg); + } + + // generate cells for binary operations: $and, $or, $xor, $xnor + if (0) { case AST_BIT_AND: type_name = "$and"; } + if (0) { case AST_BIT_OR: type_name = "$or"; } + if (0) { case AST_BIT_XOR: type_name = "$xor"; } + if (0) { case AST_BIT_XNOR: type_name = "$xnor"; } + { + RTLIL::SigSpec left = children[0]->genRTLIL(width_hint); + RTLIL::SigSpec right = children[1]->genRTLIL(width_hint); + int width = std::max(left.width, right.width); + if (width > width_hint && width_hint > 0) + width = width_hint; + return binop2rtlil(this, type_name, width, left, right); + } + + // generate cells for unary operations: $reduce_and, $reduce_or, $reduce_xor, $reduce_xnor + if (0) { case AST_REDUCE_AND: type_name = "$reduce_and"; } + if (0) { case AST_REDUCE_OR: type_name = "$reduce_or"; } + if (0) { case AST_REDUCE_XOR: type_name = "$reduce_xor"; } + if (0) { case AST_REDUCE_XNOR: type_name = "$reduce_xnor"; } + { + RTLIL::SigSpec arg = children[0]->genRTLIL(); + RTLIL::SigSpec sig = uniop2rtlil(this, type_name, 1, arg); + return sig; + } + + // generate cells for unary operations: $reduce_bool + // (this is actually just an $reduce_or, but for clearity a different cell type is used) + if (0) { case AST_REDUCE_BOOL: type_name = "$reduce_bool"; } + { + RTLIL::SigSpec arg = children[0]->genRTLIL(); + RTLIL::SigSpec sig = arg.width > 1 ? uniop2rtlil(this, type_name, 1, arg) : arg; + return sig; + } + + // generate cells for binary operations: $shl, $shr, $sshl, $sshr + if (0) { case AST_SHIFT_LEFT: type_name = "$shl"; } + if (0) { case AST_SHIFT_RIGHT: type_name = "$shr"; } + if (0) { case AST_SHIFT_SLEFT: type_name = "$sshl"; is_signed = true; } + if (0) { case AST_SHIFT_SRIGHT: type_name = "$sshr"; is_signed = true; } + { + RTLIL::SigSpec left = children[0]->genRTLIL(width_hint); + RTLIL::SigSpec right = children[1]->genRTLIL(width_hint); + int width = width_hint > 0 ? width_hint : left.width; + return binop2rtlil(this, type_name, width, left, right); + } + + // generate cells for binary operations: $lt, $le, $eq, $ne, $ge, $gt + if (0) { case AST_LT: type_name = "$lt"; } + if (0) { case AST_LE: type_name = "$le"; } + if (0) { case AST_EQ: type_name = "$eq"; } + if (0) { case AST_NE: type_name = "$ne"; } + if (0) { case AST_GE: type_name = "$ge"; } + if (0) { case AST_GT: type_name = "$gt"; } + { + RTLIL::SigSpec left = children[0]->genRTLIL(); + RTLIL::SigSpec right = children[1]->genRTLIL(); + RTLIL::SigSpec sig = binop2rtlil(this, type_name, 1, left, right); + return sig; + } + + // generate cells for binary operations: $add, $sub, $mul, $div, $mod, $pow + if (0) { case AST_ADD: type_name = "$add"; } + if (0) { case AST_SUB: type_name = "$sub"; } + if (0) { case AST_MUL: type_name = "$mul"; } + if (0) { case AST_DIV: type_name = "$div"; } + if (0) { case AST_MOD: type_name = "$mod"; } + if (0) { case AST_POW: type_name = "$pow"; } + { + RTLIL::SigSpec left = children[0]->genRTLIL(width_hint); + RTLIL::SigSpec right = children[1]->genRTLIL(width_hint); + int width = std::max(left.width, right.width); + if (width > width_hint && width_hint > 0) + width = width_hint; + if (width < width_hint) { + if (type == AST_ADD || type == AST_SUB) { + width++; + if (width < width_hint && children[0]->is_signed != children[1]->is_signed) + width++; + } + if (type == AST_SUB && !children[0]->is_signed && !children[1]->is_signed) + width = width_hint; + if (type == AST_MUL) + width = std::min(left.width + right.width, width_hint); + } + is_signed = children[0]->is_signed || children[1]->is_signed; + return binop2rtlil(this, type_name, width, left, right); + } + + // generate cells for binary operations: $logic_and, $logic_or + if (0) { case AST_LOGIC_AND: type_name = "$logic_and"; } + if (0) { case AST_LOGIC_OR: type_name = "$logic_or"; } + { + RTLIL::SigSpec left = children[0]->genRTLIL(); + RTLIL::SigSpec right = children[1]->genRTLIL(); + return binop2rtlil(this, type_name, 1, left, right); + } + + // generate cells for unary operations: $logic_not + case AST_LOGIC_NOT: + { + RTLIL::SigSpec arg = children[0]->genRTLIL(); + return uniop2rtlil(this, "$logic_not", 1, arg); + } + + // generate multiplexer for ternary operator (aka ?:-operator) + case AST_TERNARY: + { + RTLIL::SigSpec cond = children[0]->genRTLIL(); + RTLIL::SigSpec val1 = children[1]->genRTLIL(); + RTLIL::SigSpec val2 = children[2]->genRTLIL(); + + if (cond.width > 1) + cond = uniop2rtlil(this, "$reduce_bool", 1, cond, false); + + int width = std::max(val1.width, val2.width); + if (children[1]->is_signed && children[2]->is_signed) { + is_signed = true; + val1.extend(width, children[1]->is_signed); + val2.extend(width, children[2]->is_signed); + } else { + is_signed = false; + val1.extend(width); + val2.extend(width); + } + + return mux2rtlil(this, cond, val1, val2); + } + + // generate $memrd cells for memory read ports + case AST_MEMRD: + { + std::stringstream sstr; + sstr << "$memrd$" << str << "$" << filename << ":" << linenum << "$" << (RTLIL::autoidx++); + + RTLIL::Cell *cell = new RTLIL::Cell; + cell->attributes["\\src"] = stringf("%s:%d", filename.c_str(), linenum); + cell->name = sstr.str(); + cell->type = "$memrd"; + current_module->cells[cell->name] = cell; + + RTLIL::Wire *wire = new RTLIL::Wire; + wire->attributes["\\src"] = stringf("%s:%d", filename.c_str(), linenum); + wire->name = cell->name + "_DATA"; + wire->width = current_module->memories[str]->width; + current_module->wires[wire->name] = wire; + + int addr_bits = 1; + while ((1 << addr_bits) < current_module->memories[str]->size) + addr_bits++; + + cell->connections["\\CLK"] = RTLIL::SigSpec(RTLIL::State::Sx, 1); + cell->connections["\\ADDR"] = children[0]->genRTLIL(); + cell->connections["\\DATA"] = RTLIL::SigSpec(wire); + + cell->parameters["\\MEMID"] = RTLIL::Const(str); + cell->parameters["\\ABITS"] = RTLIL::Const(addr_bits); + cell->parameters["\\WIDTH"] = RTLIL::Const(wire->width); + + cell->parameters["\\CLK_ENABLE"] = RTLIL::Const(0); + cell->parameters["\\CLK_POLARITY"] = RTLIL::Const(0); + + return RTLIL::SigSpec(wire); + } + + // generate $memwr cells for memory write ports + case AST_MEMWR: + { + std::stringstream sstr; + sstr << "$memwr$" << str << "$" << filename << ":" << linenum << "$" << (RTLIL::autoidx++); + + RTLIL::Cell *cell = new RTLIL::Cell; + cell->attributes["\\src"] = stringf("%s:%d", filename.c_str(), linenum); + cell->name = sstr.str(); + cell->type = "$memwr"; + current_module->cells[cell->name] = cell; + + int addr_bits = 1; + while ((1 << addr_bits) < current_module->memories[str]->size) + addr_bits++; + + cell->connections["\\CLK"] = RTLIL::SigSpec(RTLIL::State::Sx, 1); + cell->connections["\\ADDR"] = children[0]->genRTLIL(); + cell->connections["\\DATA"] = children[1]->genRTLIL(); + cell->connections["\\EN"] = children[2]->genRTLIL(); + + cell->parameters["\\MEMID"] = RTLIL::Const(str); + cell->parameters["\\ABITS"] = RTLIL::Const(addr_bits); + cell->parameters["\\WIDTH"] = RTLIL::Const(current_module->memories[str]->width); + + cell->parameters["\\CLK_ENABLE"] = RTLIL::Const(0); + cell->parameters["\\CLK_POLARITY"] = RTLIL::Const(0); + } + break; + + // add entries to current_module->connections for assignments (outside of always blocks) + case AST_ASSIGN: + { + if (children[0]->type == AST_IDENTIFIER && children[0]->id2ast && children[0]->id2ast->type == AST_AUTOWIRE) { + RTLIL::SigSpec right = children[1]->genRTLIL(); + RTLIL::SigSpec left = children[0]->genWidthRTLIL(right.width); + current_module->connections.push_back(RTLIL::SigSig(left, right)); + } else { + RTLIL::SigSpec left = children[0]->genRTLIL(); + RTLIL::SigSpec right = children[1]->genWidthRTLIL(left.width); + current_module->connections.push_back(RTLIL::SigSig(left, right)); + } + } + break; + + // create an RTLIL::Cell for an AST_CELL + case AST_CELL: + { + int port_counter = 0, para_counter = 0; + RTLIL::Cell *cell = new RTLIL::Cell; + cell->attributes["\\src"] = stringf("%s:%d", filename.c_str(), linenum); + cell->name = str; + for (auto it = children.begin(); it != children.end(); it++) { + AstNode *child = *it; + if (child->type == AST_CELLTYPE) { + cell->type = child->str; + continue; + } + if (child->type == AST_PARASET) { + if (child->children[0]->type != AST_CONSTANT) + log_error("Parameter `%s' with non-constant value at %s:%d!\n", + child->str.c_str(), filename.c_str(), linenum); + if (child->str.size() == 0) { + char buf[100]; + snprintf(buf, 100, "$%d", ++para_counter); + cell->parameters[buf].str = child->children[0]->str; + cell->parameters[buf].bits = child->children[0]->bits; + } else { + cell->parameters[child->str].str = child->children[0]->str; + cell->parameters[child->str].bits = child->children[0]->bits; + } + continue; + } + if (child->type == AST_ARGUMENT) { + RTLIL::SigSpec sig; + if (child->children.size() > 0) + sig = child->children[0]->genRTLIL(); + if (child->str.size() == 0) { + char buf[100]; + snprintf(buf, 100, "$%d", ++port_counter); + cell->connections[buf] = sig; + } else { + cell->connections[child->str] = sig; + } + continue; + } + assert(0); + } + for (auto &attr : attributes) { + if (attr.second->type != AST_CONSTANT) + log_error("Attribute `%s' with non-constant value at %s:%d!\n", + attr.first.c_str(), filename.c_str(), linenum); + cell->attributes[attr.first].str = attr.second->str; + cell->attributes[attr.first].bits = attr.second->bits; + } + if (current_module->cells.count(cell->name) != 0) + log_error("Re-definition of cell `%s' at %s:%d!\n", + str.c_str(), filename.c_str(), linenum); + current_module->cells[str] = cell; + } + break; + + // use ProcessGenerator for always blocks + case AST_ALWAYS: { + AstNode *always = this->clone(); + ProcessGenerator generator(always); + delete always; + } break; + + // everything should have been handled above -> print error if not. + default: + for (auto f : log_files) + current_ast->dumpAst(f, "verilog-ast> "); + type_name = type2str(type); + log_error("Don't know how to generate RTLIL code for %s node at %s:%d!\n", + type_name.c_str(), filename.c_str(), linenum); + } + + return RTLIL::SigSpec(); +} + +// this is a wrapper for AstNode::genRTLIL() when a specific signal width is requested and/or +// signals must be substituted before beeing used as input values (used by ProcessGenerator) +// note that this is using some global variables to communicate this special settings to AstNode::genRTLIL(). +RTLIL::SigSpec AstNode::genWidthRTLIL(int width, RTLIL::SigSpec *subst_from, RTLIL::SigSpec *subst_to) +{ + RTLIL::SigSpec *backup_subst_from = genRTLIL_subst_from; + RTLIL::SigSpec *backup_subst_to = genRTLIL_subst_to; + + if (subst_from) + genRTLIL_subst_from = subst_from; + if (subst_to) + genRTLIL_subst_to = subst_to; + + RTLIL::SigSpec sig = genRTLIL(width); + + genRTLIL_subst_from = backup_subst_from; + genRTLIL_subst_to = backup_subst_to; + + if (width >= 0) + sig.extend(width, is_signed); + + return sig; +} + diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc new file mode 100644 index 00000000..cb8b1043 --- /dev/null +++ b/frontends/ast/simplify.cc @@ -0,0 +1,1081 @@ +/* + * 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 "kernel/sha1.h" +#include "ast.h" + +#include +#include +#include + +using namespace AST; +using namespace AST_INTERNAL; + +// convert the AST into a simpler AST that has all parameters subsitited 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) +{ + AstNode *newNode = NULL; + bool did_something = false; + + if (stage == 0) + { + assert(type == AST_MODULE); + + while (simplify(const_fold, at_zero, in_lvalue, 1)) { } + + if (!flag_nomem2reg && attributes.count("\\nomem2reg") == 0) + { + std::set mem2reg_set, mem2reg_candidates; + mem2reg_as_needed_pass1(mem2reg_set, mem2reg_candidates, false, false); + + 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); + } + } + + mem2reg_as_needed_pass2(mem2reg_set, this, NULL); + + for (size_t i = 0; i < children.size(); i++) { + if (mem2reg_set.count(children[i]) > 0) { + delete children[i]; + children.erase(children.begin() + (i--)); + } + } + } + + while (simplify(const_fold, at_zero, in_lvalue, 2)) { } + return false; + } + + current_filename = filename; + set_line_num(linenum); + + // we do not look inside a task or function + // (but as soon as a task of function is instanciated we process the generated AST as usual) + if (type == AST_FUNCTION || type == AST_TASK) + return false; + + // deactivate all calls non-synthesis system taks + if ((type == AST_FCALL || type == AST_TCALL) && (str == "$display" || str == "$stop" || str == "$finish")) { + 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_PARASET || type == AST_RANGE) + 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; + + 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 (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; + 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; + } + this_wire_scope[node->str] = node; + } + wires_are_incompatible: + 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) { + backup_scope[node->str] = current_scope[node->str]; + current_scope[node->str] = node; + } + } + } + + auto backup_current_block = current_block; + auto backup_current_block_child = current_block_child; + auto backup_current_top_block = current_top_block; + + // 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 && i >= 1) + break; + while (did_something_here && i < children.size()) { + bool const_fold_here = const_fold, in_lvalue_here = in_lvalue; + if (i == 0 && type == AST_REPLICATE) + 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 && children[i]->type == AST_BLOCK) + current_top_block = children[i]; + did_something_here = children[i]->simplify(const_fold_here, at_zero, in_lvalue_here, stage); + if (did_something_here) + did_something = true; + } + } + for (auto &attr : attributes) { + while (attr.second->simplify(true, false, false, stage)) { } + } + + current_block = backup_current_block; + current_block_child = backup_current_block_child; + current_top_block = backup_current_top_block; + + 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(); + + // annotate constant ranges + if (type == AST_RANGE) { + bool old_range_valid = range_valid; + range_valid = false; + range_left = -1; + range_right = 0; + 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; + } + } + + // 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_left = children[0]->range_left; + range_right = children[0]->range_right; + } + } else { + if (!range_valid) + did_something = true; + range_valid = true; + range_left = 0; + range_right = 0; + } + } + + // 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) && 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; + } + id2ast = current_scope[str]; + } + + // 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]; + + 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)) { } + + 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)) { } + + 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 << "$" << (RTLIL::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++) + 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)) { } + + 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; + } + + // simplify generate-if blocks + if (type == AST_GENIF && children.size() != 0) + { + AstNode *buf = children[0]->clone(); + while (buf->simplify(true, false, false, stage)) { } + 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->integer != 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++) + current_ast_mod->children.push_back(buf->children[i]); + + buf->children.clear(); + delete buf; + } + + delete_children(); + did_something = true; + } + + // replace primitives with assignmens + 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) { + assert(child->type == AST_ARGUMENT); + assert(child->children.size() == 1); + children_list.push_back(child->children[0]); + child->children.clear(); + delete child; + } + children.clear(); + + 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; + 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)) { } + while (right_at_zero_ast->simplify(true, true, false, stage)) { } + 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 = 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:; + + // 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[0]->type == AST_RANGE && children[0]->children.size() == 1) { + newNode = new AstNode(AST_MEMRD, children[0]->children[0]->clone()); + newNode->str = str; + 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]->children.size() == 1 && 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) + { + std::stringstream sstr; + sstr << "$memwr$" << children[0]->str << "$" << filename << ":" << linenum << "$" << (RTLIL::autoidx++); + std::string id_addr = sstr.str() + "_ADDR", id_data = sstr.str() + "_DATA", id_en = sstr.str() + "_EN"; + + if (type == AST_ASSIGN_EQ) + log("Warining: Blocking assignment to memory in line %s:%d is handled like a non-blocking assignment.\n", + filename.c_str(), linenum); + + 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; + current_ast_mod->children.push_back(wire_addr); + current_scope[wire_addr->str] = wire_addr; + + 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; + + AstNode *wire_en = new AstNode(AST_WIRE); + wire_en->str = id_en; + current_ast_mod->children.push_back(wire_en); + current_scope[wire_en->str] = wire_en; + + std::vector x_bits; + x_bits.push_back(RTLIL::State::Sx); + + AstNode *assign_addr = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), mkconst_bits(x_bits, false)); + assign_addr->children[0]->str = id_addr; + + AstNode *assign_data = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), mkconst_bits(x_bits, false)); + assign_data->children[0]->str = id_data; + + 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_addr); + default_signals->children.push_back(assign_data); + 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; + + assign_data = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), children[1]->clone()); + assign_data->children[0]->str = id_data; + + assign_en = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), mkconst_int(1, false, 1)); + assign_en->children[0]->str = id_en; + + newNode = new AstNode(AST_BLOCK); + newNode->children.push_back(assign_addr); + newNode->children.push_back(assign_data); + newNode->children.push_back(assign_en); + + AstNode *wrnode = new AstNode(AST_MEMWR); + wrnode->children.push_back(new AstNode(AST_IDENTIFIER)); + wrnode->children.push_back(new AstNode(AST_IDENTIFIER)); + wrnode->children.push_back(new AstNode(AST_IDENTIFIER)); + wrnode->str = children[0]->str; + wrnode->children[0]->str = id_addr; + wrnode->children[1]->str = id_data; + 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 (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 (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 << "$" << (RTLIL::autoidx++) << "$"; + std::string prefix = sstr.str(); + + size_t arg_count = 0; + std::map replace_rules; + + if (current_block == NULL) + { + assert(type == AST_FCALL); + + AstNode *wire = NULL; + for (auto child : decl->children) + if (child->type == AST_WIRE && child->str == str) + wire = child->clone(); + 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); + + 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; + } + + for (auto child : decl->children) + { + if (child->type == AST_WIRE) + { + 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); + + replace_rules[child->str] = wire->str; + + if (child->is_input && arg_count < children.size()) + { + AstNode *arg = children[arg_count++]->clone(); + AstNode *wire_id = new AstNode(AST_IDENTIFIER); + wire_id->str = wire->str; + AstNode *assign = new AstNode(AST_ASSIGN_EQ, wire_id, arg); + + 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; + } + } + } + else + { + AstNode *stmt = child->clone(); + stmt->replace_ids(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 && newNode == NULL) + { + 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 (children.size() != 0 && children[0]->type == AST_RANGE && children[0]->range_valid) { + if (current_scope[str]->children[0]->type == AST_CONSTANT) { + std::vector data; + for (int i = children[0]->range_right; i <= children[0]->range_left; i++) + data.push_back(current_scope[str]->children[0]->bits[i]); + newNode = mkconst_bits(data, false); + } + } else + if (children.size() == 0) + newNode = current_scope[str]->children[0]->clone(); + } + else if (at_zero && current_module->wires.count(str) > 0) { + assert(current_scope.count(str) > 0 && (current_scope[str]->type == AST_WIRE || current_scope[str]->type == AST_AUTOWIRE)); + if (children.size() != 0 && children[0]->type == AST_RANGE && children[0]->range_valid) + newNode = mkconst_int(0, false, children[0]->range_left - children[0]->range_right + 1); + else + if (children.size() == 0) + newNode = mkconst_int(0, current_scope[str]->is_signed, current_module->wires[str]->width); + } + break; + case AST_BIT_NOT: + if (children[0]->type == AST_CONSTANT) { + RTLIL::Const y = RTLIL::const_not(RTLIL::Const(children[0]->bits), dummy_arg, children[0]->is_signed, false, -1); + newNode = mkconst_bits(y.bits, false); + } + 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(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); + } + 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 && children[1]->type == AST_CONSTANT) { + RTLIL::Const y = const_func(RTLIL::Const(children[0]->bits), dummy_arg, children[0]->is_signed, 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); + } + 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); + } + 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 (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, false, -1); + newNode = mkconst_bits(y.bits, children[0]->is_signed); + } + 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_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) { + 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); + } + 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 (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(RTLIL::Const(children[0]->bits), RTLIL::Const(children[1]->bits), + children[0]->is_signed, children[1]->is_signed, -1); + newNode = mkconst_bits(y.bits, children[0]->is_signed && children[1]->is_signed); + } + 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(RTLIL::Const(children[0]->bits), dummy_arg, children[0]->is_signed, false, -1); + newNode = mkconst_bits(y.bits, children[0]->is_signed); + } + break; + case AST_TERNARY: + if (children[0]->type == AST_CONSTANT) { + if (children[0]->integer) + newNode = children[1]->clone(); + else + newNode = children[2]->clone(); + } + break; + default: + 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, "+ "); + assert(newNode != NULL); + newNode->filename = filename; + newNode->linenum = linenum; + newNode->cloneInto(this); + delete newNode; + did_something = true; + } + + return did_something; +} + +// 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]; + return; + } + + 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; + child->str = new_name; + } + } + + for (size_t i = 0; i < children.size(); i++) { + AstNode *child = children[i]; + if (child->type != AST_FUNCTION && child->type != AST_TASK) + 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 instanciated) +void AstNode::replace_ids(std::map &rules) +{ + if (type == AST_IDENTIFIER && rules.count(str) > 0) + str = rules[str]; + for (auto child : children) + child->replace_ids(rules); +} + +// find memories that should be replaced by registers +void AstNode::mem2reg_as_needed_pass1(std::set &mem2reg_set, std::set &mem2reg_candidates, bool sync_proc, bool async_proc) +{ + if ((type == AST_ASSIGN_LE && async_proc) || (type == AST_ASSIGN_EQ && (sync_proc || async_proc))) + if (children[0]->type == AST_IDENTIFIER && children[0]->id2ast && children[0]->id2ast->type == AST_MEMORY && + children[0]->id2ast->attributes.count("\\nomem2reg") == 0) { + if (async_proc || mem2reg_candidates.count(children[0]->id2ast) > 0) { + if (mem2reg_set.count(children[0]->id2ast) == 0) + log("Warning: Replacing memory %s with list of registers because of assignment in line %s:%d.\n", + children[0]->str.c_str(), filename.c_str(), linenum); + mem2reg_set.insert(children[0]->id2ast); + } + mem2reg_candidates.insert(children[0]->id2ast); + } + + if (type == AST_ALWAYS) { + for (auto child : children) { + if (child->type == AST_POSEDGE || child->type == AST_NEGEDGE) + sync_proc = true; + } + async_proc = !sync_proc; + } + + for (auto child : children) + child->mem2reg_as_needed_pass1(mem2reg_set, mem2reg_candidates, sync_proc, async_proc); +} + +// actually replace memories with registers +void AstNode::mem2reg_as_needed_pass2(std::set &mem2reg_set, AstNode *mod, AstNode *block) +{ + if (type == AST_BLOCK) + block = this; + + if ((type == AST_ASSIGN_LE || type == AST_ASSIGN_EQ) && block != NULL && + children[0]->id2ast && mem2reg_set.count(children[0]->id2ast) > 0) + { + std::stringstream sstr; + sstr << "$mem2reg_wr$" << children[0]->str << "$" << filename << ":" << linenum << "$" << (RTLIL::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; + mod->children.push_back(wire_addr); + + 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; + mod->children.push_back(wire_data); + + assert(block != NULL); + size_t assign_idx = 0; + while (assign_idx < block->children.size() && block->children[assign_idx] != this) + assign_idx++; + 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(AST_ASSIGN_EQ, new AstNode(AST_IDENTIFIER), new AstNode(AST_IDENTIFIER)); + 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; + } + + if (type == AST_IDENTIFIER && id2ast && mem2reg_set.count(id2ast) > 0) + { + std::stringstream sstr; + sstr << "$mem2reg_rd$" << children[0]->str << "$" << filename << ":" << linenum << "$" << (RTLIL::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; + mod->children.push_back(wire_addr); + + 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; + mod->children.push_back(wire_data); + + AstNode *assign_addr = new AstNode(AST_ASSIGN_EQ, 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; + 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++; + 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); + wire_addr->is_reg = true; + wire_data->is_reg = true; + } + 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; + } + + assert(id2ast == NULL || mem2reg_set.count(id2ast) == 0); + + for (size_t i = 0; i < children.size(); i++) + children[i]->mem2reg_as_needed_pass2(mem2reg_set, mod, block); +} + +// calulate memory dimensions +void AstNode::meminfo(int &mem_width, int &mem_size, int &addr_bits) +{ + 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 += std::min(children[1]->range_left, children[1]->range_right) + 1; + + addr_bits = 1; + while ((1 << addr_bits) < mem_size) + addr_bits++; +} + -- cgit v1.2.3