diff --git a/.travis/build-and-test.sh b/.travis/build-and-test.sh index 096dde64f..b8c35041d 100755 --- a/.travis/build-and-test.sh +++ b/.travis/build-and-test.sh @@ -36,6 +36,8 @@ echo ########################################################################## +./yosys tests/simple/fiedler-cooley.v + echo echo 'Testing...' && echo -en 'travis_fold:start:script.test\\r' echo diff --git a/.travis/setup.sh b/.travis/setup.sh index d689cd2bd..4af0b8ee9 100755 --- a/.travis/setup.sh +++ b/.travis/setup.sh @@ -6,48 +6,15 @@ source .travis/common.sh ########################################################################## -# Fixing Travis's git clone -echo -echo 'Fixing git setup...' && echo -en 'travis_fold:start:before_install.git\\r' -echo -git fetch --unshallow && git fetch --tags - -# For pull requests, we get more info about the git source. -if [ z"$TRAVIS_PULL_REQUEST_SLUG" != z ]; then - echo "- Fetching from pull request source" - git remote add source https://github.com/$TRAVIS_PULL_REQUEST_SLUG.git - git fetch source && git fetch --tags - - echo "- Fetching the actual pull request" - git fetch origin pull/$TRAVIS_PULL_REQUEST/head:pull-$TRAVIS_PULL_REQUEST-head - git fetch origin pull/$TRAVIS_PULL_REQUEST/merge:pull-$TRAVIS_PULL_REQUEST-merge - - git log -n 5 --graph pull-$TRAVIS_PULL_REQUEST-merge -fi - -# For building branches we need to fix the "detached head" state. -if [ z"$TRAVIS_BRANCH" != z ]; then - TRAVIS_COMMIT_ACTUAL=$(git log --pretty=format:'%H' -n 1) - echo "- Fixing detached head (current $TRAVIS_COMMIT_ACTUAL -> $TRAVIS_COMMIT)" - git remote -v - git branch -v - if [ x"$(git show-ref -s HEAD)" = x"$TRAVIS_COMMIT" ]; then - echo "Checked out at $TRAVIS_COMMIT" - else - if [ z"$TRAVIS_PULL_REQUEST_SLUG" != z ]; then - git fetch source $TRAVIS_COMMIT || echo "Unable to fetch $TRAVIS_COMMIT from source" - fi - git fetch origin $TRAVIS_COMMIT || echo "Unable to fetch $TRAVIS_COMMIT from origin" - fi - git branch -D $TRAVIS_BRANCH || true - git checkout $TRAVIS_COMMIT -b $TRAVIS_BRANCH - git branch -v -fi - # Output status information. -git status -git describe --tags -git log -n 5 --graph +( + set +e + set -x + git status + git branch -v + git log -n 5 --graph + git log --format=oneline -n 20 --graph +) echo echo -en 'travis_fold:end:before_install.git\\r' echo diff --git a/Makefile b/Makefile index d83a71256..8e93cd285 100644 --- a/Makefile +++ b/Makefile @@ -100,7 +100,7 @@ LDFLAGS += -rdynamic LDLIBS += -lrt endif -YOSYS_VER := 0.8+$(shell cd $(YOSYS_SRC) && test -e .git && { git log --author=clifford@clifford.at --oneline 4d4665b.. | wc -l; }) +YOSYS_VER := 0.8+$(shell cd $(YOSYS_SRC) && test -e .git && { git log --author=clifford@clifford.at --oneline 4d4665b.. 2> /dev/null | wc -l; }) GIT_REV := $(shell cd $(YOSYS_SRC) && git rev-parse --short HEAD 2> /dev/null || echo UNKNOWN) OBJS = kernel/version_$(GIT_REV).o diff --git a/backends/firrtl/firrtl.cc b/backends/firrtl/firrtl.cc index 32410a651..0917ecba6 100644 --- a/backends/firrtl/firrtl.cc +++ b/backends/firrtl/firrtl.cc @@ -23,7 +23,11 @@ #include "kernel/celltypes.h" #include "kernel/cellaigs.h" #include "kernel/log.h" +#include #include +#include +#include +#include USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN @@ -37,6 +41,7 @@ static const FDirection FD_NODIRECTION = 0x0; static const FDirection FD_IN = 0x1; static const FDirection FD_OUT = 0x2; static const FDirection FD_INOUT = 0x3; +static const int FIRRTL_MAX_DSH_WIDTH_ERROR = 20; // For historic reasons, this is actually one greater than the maximum allowed shift width // Get a port direction with respect to a specific module. FDirection getPortFDirection(IdString id, Module *module) @@ -173,6 +178,26 @@ struct FirrtlWorker void process_instance(RTLIL::Cell *cell, vector &wire_exprs) { std::string cell_type = fid(cell->type); + std::string instanceOf; + // If this is a parameterized module, its parent module is encoded in the cell type + if (cell->type.substr(0, 8) == "$paramod") + { + std::string::iterator it; + for (it = cell_type.begin(); it < cell_type.end(); it++) + { + switch (*it) { + case '\\': /* FALL_THROUGH */ + case '=': /* FALL_THROUGH */ + case '\'': /* FALL_THROUGH */ + case '$': instanceOf.append("_"); break; + default: instanceOf.append(1, *it); break; + } + } + } + else + { + instanceOf = cell_type; + } std::string cell_name = cellname(cell); std::string cell_name_comment; @@ -182,7 +207,13 @@ struct FirrtlWorker cell_name_comment = ""; // Find the module corresponding to this instance. auto instModule = design->module(cell->type); - wire_exprs.push_back(stringf("%s" "inst %s%s of %s", indent.c_str(), cell_name.c_str(), cell_name_comment.c_str(), cell_type.c_str())); + // If there is no instance for this, just return. + if (instModule == NULL) + { + log_warning("No instance for %s.%s\n", cell_type.c_str(), cell_name.c_str()); + return; + } + wire_exprs.push_back(stringf("%s" "inst %s%s of %s", indent.c_str(), cell_name.c_str(), cell_name_comment.c_str(), instanceOf.c_str())); for (auto it = cell->connections().begin(); it != cell->connections().end(); ++it) { if (it->second.size() > 0) { @@ -194,20 +225,20 @@ struct FirrtlWorker std::string source, sink; switch (dir) { case FD_INOUT: - log_warning("Instance port connection %s.%s is INOUT; treating as OUT\n", log_id(cell_type), log_signal(it->second)); + log_warning("Instance port connection %s.%s is INOUT; treating as OUT\n", cell_type.c_str(), log_signal(it->second)); case FD_OUT: source = firstName; sink = secondName; break; case FD_NODIRECTION: - log_warning("Instance port connection %s.%s is NODIRECTION; treating as IN\n", log_id(cell_type), log_signal(it->second)); + log_warning("Instance port connection %s.%s is NODIRECTION; treating as IN\n", cell_type.c_str(), log_signal(it->second)); /* FALL_THROUGH */ case FD_IN: source = secondName; sink = firstName; break; default: - log_error("Instance port %s.%s unrecognized connection direction 0x%x !\n", log_id(cell_type), log_signal(it->second), dir); + log_error("Instance port %s.%s unrecognized connection direction 0x%x !\n", cell_type.c_str(), log_signal(it->second), dir); break; } wire_exprs.push_back(stringf("\n%s%s <= %s", indent.c_str(), sink.c_str(), source.c_str())); @@ -217,6 +248,20 @@ struct FirrtlWorker } + // Given an expression for a shift amount, and a maximum width, + // generate the FIRRTL expression for equivalent dynamic shift taking into account FIRRTL shift semantics. + std::string gen_dshl(const string b_expr, const int b_padded_width) + { + string result = b_expr; + if (b_padded_width >= FIRRTL_MAX_DSH_WIDTH_ERROR) { + int max_shift_width_bits = FIRRTL_MAX_DSH_WIDTH_ERROR - 1; + string max_shift_string = stringf("UInt<%d>(%d)", max_shift_width_bits, (1<name)); @@ -225,6 +270,12 @@ struct FirrtlWorker for (auto wire : module->wires()) { const auto wireName = make_id(wire->name); + // If a wire has initial data, issue a warning since FIRRTL doesn't currently support it. + if (wire->attributes.count("\\init")) { + log_warning("Initial value (%s) for (%s.%s) not supported\n", + wire->attributes.at("\\init").as_string().c_str(), + log_id(module), log_id(wire)); + } if (wire->port_id) { if (wire->port_input && wire->port_output) @@ -240,7 +291,8 @@ struct FirrtlWorker for (auto cell : module->cells()) { - // Is this cell is a module instance? + bool extract_y_bits = false; // Assume no extraction of final bits will be required. + // Is this cell is a module instance? if (cell->type[0] != '$') { process_instance(cell, wire_exprs); @@ -264,21 +316,21 @@ struct FirrtlWorker } string primop; - bool always_uint = false; + bool always_uint = false; if (cell->type == "$not") primop = "not"; - if (cell->type == "$neg") primop = "neg"; - if (cell->type == "$logic_not") { + else if (cell->type == "$neg") primop = "neg"; + else if (cell->type == "$logic_not") { primop = "eq"; a_expr = stringf("%s, UInt(0)", a_expr.c_str()); } - if (cell->type == "$reduce_and") primop = "andr"; - if (cell->type == "$reduce_or") primop = "orr"; - if (cell->type == "$reduce_xor") primop = "xorr"; - if (cell->type == "$reduce_xnor") { + else if (cell->type == "$reduce_and") primop = "andr"; + else if (cell->type == "$reduce_or") primop = "orr"; + else if (cell->type == "$reduce_xor") primop = "xorr"; + else if (cell->type == "$reduce_xnor") { primop = "not"; a_expr = stringf("xorr(%s)", a_expr.c_str()); } - if (cell->type == "$reduce_bool") { + else if (cell->type == "$reduce_bool") { primop = "neq"; // Use the sign of the a_expr and its width as the type (UInt/SInt) and width of the comparand. bool a_signed = cell->parameters.at("\\A_SIGNED").as_bool(); @@ -297,14 +349,15 @@ struct FirrtlWorker continue; } if (cell->type.in("$add", "$sub", "$mul", "$div", "$mod", "$xor", "$and", "$or", "$eq", "$eqx", - "$gt", "$ge", "$lt", "$le", "$ne", "$nex", "$shr", "$sshr", "$sshl", "$shl", - "$logic_and", "$logic_or")) + "$gt", "$ge", "$lt", "$le", "$ne", "$nex", "$shr", "$sshr", "$sshl", "$shl", + "$logic_and", "$logic_or")) { string y_id = make_id(cell->name); bool is_signed = cell->parameters.at("\\A_SIGNED").as_bool(); int y_width = cell->parameters.at("\\Y_WIDTH").as_int(); string a_expr = make_expr(cell->getPort("\\A")); string b_expr = make_expr(cell->getPort("\\B")); + int b_padded_width = cell->parameters.at("\\B_WIDTH").as_int(); wire_decls.push_back(stringf(" wire %s: UInt<%d>\n", y_id.c_str(), y_width)); if (cell->parameters.at("\\A_SIGNED").as_bool()) { @@ -315,67 +368,93 @@ struct FirrtlWorker if (cell->parameters.at("\\B_SIGNED").as_bool()) { b_expr = "asSInt(" + b_expr + ")"; } - b_expr = stringf("pad(%s, %d)", b_expr.c_str(), y_width); + if (b_padded_width < y_width) { + auto b_sig = cell->getPort("\\B"); + b_padded_width = y_width; + } } - a_expr = stringf("pad(%s, %d)", a_expr.c_str(), y_width); + auto a_sig = cell->getPort("\\A"); if (cell->parameters.at("\\A_SIGNED").as_bool() & (cell->type == "$shr")) { a_expr = "asUInt(" + a_expr + ")"; } string primop; - bool always_uint = false; + bool always_uint = false; if (cell->type == "$add") primop = "add"; - if (cell->type == "$sub") primop = "sub"; - if (cell->type == "$mul") primop = "mul"; - if (cell->type == "$div") primop = "div"; - if (cell->type == "$mod") primop = "rem"; - if (cell->type == "$and") { + else if (cell->type == "$sub") primop = "sub"; + else if (cell->type == "$mul") primop = "mul"; + else if (cell->type == "$div") primop = "div"; + else if (cell->type == "$mod") primop = "rem"; + else if (cell->type == "$and") { primop = "and"; always_uint = true; } - if (cell->type == "$or" ) { + else if (cell->type == "$or" ) { primop = "or"; always_uint = true; } - if (cell->type == "$xor") { + else if (cell->type == "$xor") { primop = "xor"; always_uint = true; } - if ((cell->type == "$eq") | (cell->type == "$eqx")) { + else if ((cell->type == "$eq") | (cell->type == "$eqx")) { primop = "eq"; always_uint = true; } - if ((cell->type == "$ne") | (cell->type == "$nex")) { + else if ((cell->type == "$ne") | (cell->type == "$nex")) { primop = "neq"; always_uint = true; } - if (cell->type == "$gt") { + else if (cell->type == "$gt") { primop = "gt"; always_uint = true; } - if (cell->type == "$ge") { + else if (cell->type == "$ge") { primop = "geq"; always_uint = true; } - if (cell->type == "$lt") { + else if (cell->type == "$lt") { primop = "lt"; always_uint = true; } - if (cell->type == "$le") { + else if (cell->type == "$le") { primop = "leq"; always_uint = true; } - if ((cell->type == "$shl") | (cell->type == "$sshl")) primop = "dshl"; - if ((cell->type == "$shr") | (cell->type == "$sshr")) primop = "dshr"; - if ((cell->type == "$logic_and")) { + else if ((cell->type == "$shl") | (cell->type == "$sshl")) { + // FIRRTL will widen the result (y) by the amount of the shift. + // We'll need to offset this by extracting the un-widened portion as Verilog would do. + extract_y_bits = true; + // Is the shift amount constant? + auto b_sig = cell->getPort("\\B"); + if (b_sig.is_fully_const()) { + primop = "shl"; + } else { + primop = "dshl"; + // Convert from FIRRTL left shift semantics. + b_expr = gen_dshl(b_expr, b_padded_width); + } + } + else if ((cell->type == "$shr") | (cell->type == "$sshr")) { + // We don't need to extract a specific range of bits. + extract_y_bits = false; + // Is the shift amount constant? + auto b_sig = cell->getPort("\\B"); + if (b_sig.is_fully_const()) { + primop = "shr"; + } else { + primop = "dshr"; + } + } + else if ((cell->type == "$logic_and")) { primop = "and"; a_expr = "neq(" + a_expr + ", UInt(0))"; b_expr = "neq(" + b_expr + ", UInt(0))"; always_uint = true; } - if ((cell->type == "$logic_or")) { + else if ((cell->type == "$logic_or")) { primop = "or"; a_expr = "neq(" + a_expr + ", UInt(0))"; b_expr = "neq(" + b_expr + ", UInt(0))"; @@ -388,6 +467,11 @@ struct FirrtlWorker string expr = stringf("%s(%s, %s)", primop.c_str(), a_expr.c_str(), b_expr.c_str()); + // Deal with FIRRTL's "shift widens" semantics + if (extract_y_bits) { + expr = stringf("bits(%s, %d, 0)", expr.c_str(), y_width - 1); + } + if ((is_signed && !always_uint) || cell->type.in("$sub")) expr = stringf("asUInt(%s)", expr.c_str()); @@ -513,7 +597,65 @@ struct FirrtlWorker continue; } - log_error("Cell type not supported: %s (%s.%s)\n", log_id(cell->type), log_id(module), log_id(cell)); + // This may be a parameterized module - paramod. + if (cell->type.substr(0, 8) == "$paramod") + { + process_instance(cell, wire_exprs); + continue; + } + if (cell->type == "$shiftx") { + // assign y = a[b +: y_width]; + // We'll extract the correct bits as part of the primop. + + string y_id = make_id(cell->name); + int y_width = cell->parameters.at("\\Y_WIDTH").as_int(); + string a_expr = make_expr(cell->getPort("\\A")); + // Get the initial bit selector + string b_expr = make_expr(cell->getPort("\\B")); + wire_decls.push_back(stringf(" wire %s: UInt<%d>\n", y_id.c_str(), y_width)); + + if (cell->getParam("\\B_SIGNED").as_bool()) { + // Use validif to constrain the selection (test the sign bit) + auto b_string = b_expr.c_str(); + int b_sign = cell->parameters.at("\\B_WIDTH").as_int() - 1; + b_expr = stringf("validif(not(bits(%s, %d, %d)), %s)", b_string, b_sign, b_sign, b_string); + } + string expr = stringf("dshr(%s, %s)", a_expr.c_str(), b_expr.c_str()); + + cell_exprs.push_back(stringf(" %s <= %s\n", y_id.c_str(), expr.c_str())); + register_reverse_wire_map(y_id, cell->getPort("\\Y")); + continue; + } + if (cell->type == "$shift") { + // assign y = a >> b; + // where b may be negative + + string y_id = make_id(cell->name); + int y_width = cell->parameters.at("\\Y_WIDTH").as_int(); + string a_expr = make_expr(cell->getPort("\\A")); + string b_expr = make_expr(cell->getPort("\\B")); + auto b_string = b_expr.c_str(); + int b_padded_width = cell->parameters.at("\\B_WIDTH").as_int(); + string expr; + wire_decls.push_back(stringf(" wire %s: UInt<%d>\n", y_id.c_str(), y_width)); + + if (cell->getParam("\\B_SIGNED").as_bool()) { + // We generate a left or right shift based on the sign of b. + std::string dshl = stringf("bits(dshl(%s, %s), 0, %d)", a_expr.c_str(), gen_dshl(b_expr, b_padded_width).c_str(), y_width); + std::string dshr = stringf("dshr(%s, %s)", a_expr.c_str(), b_string); + expr = stringf("mux(%s < 0, %s, %s)", + b_string, + dshl.c_str(), + dshr.c_str() + ); + } else { + expr = stringf("dshr(%s, %s)", a_expr.c_str(), b_string); + } + cell_exprs.push_back(stringf(" %s <= %s\n", y_id.c_str(), expr.c_str())); + register_reverse_wire_map(y_id, cell->getPort("\\Y")); + continue; + } + log_warning("Cell type not supported: %s (%s.%s)\n", log_id(cell->type), log_id(module), log_id(cell)); } for (auto conn : module->connections()) @@ -629,38 +771,53 @@ struct FirrtlBackend : public Backend { log(" write_firrtl [options] [filename]\n"); log("\n"); log("Write a FIRRTL netlist of the current design.\n"); + log("The following commands are executed by this command:\n"); + log(" pmuxtree\n"); log("\n"); } void execute(std::ostream *&f, std::string filename, std::vector args, RTLIL::Design *design) YS_OVERRIDE { - size_t argidx; - for (argidx = 1; argidx < args.size(); argidx++) - { - // if (args[argidx] == "-aig") { - // aig_mode = true; - // continue; - // } - break; + size_t argidx = args.size(); // We aren't expecting any arguments. + + // If we weren't explicitly passed a filename, use the last argument (if it isn't a flag). + if (filename == "") { + if (argidx > 0 && args[argidx - 1][0] != '-') { + // extra_args and friends need to see this argument. + argidx -= 1; + filename = args[argidx]; + } } extra_args(f, filename, args, argidx); + if (!design->full_selection()) + log_cmd_error("This command only operates on fully selected designs!\n"); + log_header(design, "Executing FIRRTL backend.\n"); + log_push(); - Module *top = design->top_module(); - - if (top == nullptr) - log_error("No top module found!\n"); + Pass::call(design, stringf("pmuxtree")); namecache.clear(); autoid_counter = 0; + // Get the top module, or a reasonable facsimile - we need something for the circuit name. + Module *top = design->top_module(); + Module *last = nullptr; + // Generate module and wire names. for (auto module : design->modules()) { make_id(module->name); + last = module; + if (top == nullptr && module->get_bool_attribute("\\top")) { + top = module; + } for (auto wire : module->wires()) if (wire->port_id) make_id(wire->name); } + if (top == nullptr) + top = last; + *f << stringf("circuit %s:\n", make_id(top->name)); for (auto module : design->modules()) diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index 60668f1f0..d351a6266 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -1310,6 +1310,15 @@ void dump_cell(std::ostream &f, std::string indent, RTLIL::Cell *cell) } } + if (reg_ct.count(cell->type) && cell->hasPort("\\Q")) { + std::stringstream ss; + dump_reg_init(ss, cell->getPort("\\Q")); + if (!ss.str().empty()) { + f << stringf("%sinitial %s.Q", indent.c_str(), cell_name.c_str()); + f << ss.str(); + f << ";\n"; + } + } } void dump_conn(std::ostream &f, std::string indent, const RTLIL::SigSpec &left, const RTLIL::SigSpec &right) diff --git a/frontends/ast/genrtlil.cc b/frontends/ast/genrtlil.cc index e66625228..2d591b29d 100644 --- a/frontends/ast/genrtlil.cc +++ b/frontends/ast/genrtlil.cc @@ -644,7 +644,7 @@ void AstNode::detectSignWidthWorker(int &width_hint, bool &sign_hint, bool *foun while (right_at_zero_ast->simplify(true, true, false, 1, -1, false, false)) { } if (left_at_zero_ast->type != AST_CONSTANT || right_at_zero_ast->type != AST_CONSTANT) log_file_error(filename, linenum, "Unsupported expression on dynamic range select on signal `%s'!\n", str.c_str()); - this_width = left_at_zero_ast->integer - right_at_zero_ast->integer + 1; + this_width = abs(int(left_at_zero_ast->integer - right_at_zero_ast->integer)) + 1; delete left_at_zero_ast; delete right_at_zero_ast; } else @@ -792,7 +792,7 @@ void AstNode::detectSignWidthWorker(int &width_hint, bool &sign_hint, bool *foun // everything should have been handled above -> print error if not. default: for (auto f : log_files) - current_ast->dumpAst(f, "verilog-ast> "); + current_ast_mod->dumpAst(f, "verilog-ast> "); log_file_error(filename, linenum, "Don't know how to detect sign and width for %s node!\n", type2str(type).c_str()); } @@ -1034,7 +1034,7 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint) while (right_at_zero_ast->simplify(true, true, false, 1, -1, false, false)) { } if (left_at_zero_ast->type != AST_CONSTANT || right_at_zero_ast->type != AST_CONSTANT) log_file_error(filename, linenum, "Unsupported expression on dynamic range select on signal `%s'!\n", str.c_str()); - int width = left_at_zero_ast->integer - right_at_zero_ast->integer + 1; + int width = abs(int(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(); @@ -1565,7 +1565,7 @@ RTLIL::SigSpec AstNode::genRTLIL(int width_hint, bool sign_hint) // everything should have been handled above -> print error if not. default: for (auto f : log_files) - current_ast->dumpAst(f, "verilog-ast> "); + current_ast_mod->dumpAst(f, "verilog-ast> "); type_name = type2str(type); log_file_error(filename, linenum, "Don't know how to generate RTLIL code for %s node!\n", type_name.c_str()); } diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index 83714f897..46013544b 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -642,6 +642,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, // (iterate by index as e.g. auto wires can add new children in the process) for (size_t i = 0; i < children.size(); i++) { bool did_something_here = true; + bool backup_flag_autowire = flag_autowire; if ((type == AST_GENFOR || type == AST_FOR) && i >= 3) break; if ((type == AST_GENIF || type == AST_GENCASE) && i >= 1) @@ -652,6 +653,8 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, break; if (type == AST_PREFIX && i >= 1) break; + if (type == AST_DEFPARAM && i == 0) + flag_autowire = true; while (did_something_here && i < children.size()) { bool const_fold_here = const_fold, in_lvalue_here = in_lvalue; int width_hint_here = width_hint; @@ -686,6 +689,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, children.erase(children.begin() + (i--)); did_something = true; } + flag_autowire = backup_flag_autowire; } for (auto &attr : attributes) { while (attr.second->simplify(true, false, false, stage, -1, false, true)) @@ -934,12 +938,15 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage, } } if (current_scope.count(str) == 0) { - // log_warning("Creating auto-wire `%s' in module `%s'.\n", str.c_str(), current_ast_mod->str.c_str()); - AstNode *auto_wire = new AstNode(AST_AUTOWIRE); - auto_wire->str = str; - current_ast_mod->children.push_back(auto_wire); - current_scope[str] = auto_wire; - did_something = true; + if (flag_autowire) { + AstNode *auto_wire = new AstNode(AST_AUTOWIRE); + auto_wire->str = str; + current_ast_mod->children.push_back(auto_wire); + current_scope[str] = auto_wire; + did_something = true; + } else { + log_file_error(filename, linenum, "Identifier `%s' is implicitly declared and `default_nettype is set to none.\n", str.c_str()); + } } if (id2ast != current_scope[str]) { id2ast = current_scope[str]; @@ -1689,7 +1696,7 @@ skip_dynamic_range_lvalue_expansion:; while (right_at_zero_ast->simplify(true, true, false, 1, -1, false, false)) { } if (left_at_zero_ast->type != AST_CONSTANT || right_at_zero_ast->type != AST_CONSTANT) log_file_error(filename, linenum, "Unsupported expression on dynamic range select on signal `%s'!\n", str.c_str()); - int width = left_at_zero_ast->integer - right_at_zero_ast->integer + 1; + int width = abs(int(left_at_zero_ast->integer - right_at_zero_ast->integer)) + 1; assign_data = new AstNode(AST_ASSIGN_LE, new AstNode(AST_IDENTIFIER), new AstNode(AST_SHIFT_LEFT, children[1]->clone(), offset_ast->clone())); @@ -1778,7 +1785,7 @@ skip_dynamic_range_lvalue_expansion:; if (str == "\\$past") { - if (width_hint <= 0) + if (width_hint < 0) goto replace_fcall_later; int num_steps = 1; diff --git a/frontends/verific/verific.cc b/frontends/verific/verific.cc index 94138cdd6..8ee951d20 100644 --- a/frontends/verific/verific.cc +++ b/frontends/verific/verific.cc @@ -1920,6 +1920,10 @@ struct VerificPass : public Pass { // WARNING: instantiating unknown module 'XYZ' (VERI-1063) Message::SetMessageType("VERI-1063", VERIFIC_ERROR); +#ifndef DB_PRESERVE_INITIAL_VALUE +# warning Verific was built without DB_PRESERVE_INITIAL_VALUE. +#endif + set_verific_global_flags = false; } diff --git a/kernel/driver.cc b/kernel/driver.cc index 178641101..a0bb7e60a 100644 --- a/kernel/driver.cc +++ b/kernel/driver.cc @@ -179,6 +179,7 @@ int main(int argc, char **argv) { std::string frontend_command = "auto"; std::string backend_command = "auto"; + std::vector vlog_defines; std::vector passes_commands; std::vector plugin_filenames; std::string output_filename = ""; @@ -268,7 +269,10 @@ int main(int argc, char **argv) printf(" -A\n"); printf(" will call abort() at the end of the script. for debugging\n"); printf("\n"); - printf(" -D [:]\n"); + printf(" -D [=]\n"); + printf(" set the specified Verilog define (via \"read -define\")\n"); + printf("\n"); + printf(" -P [:]\n"); printf(" dump the design when printing the specified log header to a file.\n"); printf(" yosys_dump_.il is used as filename if none is specified.\n"); printf(" Use 'ALL' as to dump at every header.\n"); @@ -307,7 +311,7 @@ int main(int argc, char **argv) } int opt; - while ((opt = getopt(argc, argv, "MXAQTVSm:f:Hh:b:o:p:l:L:qv:tds:c:W:w:e:D:E:")) != -1) + while ((opt = getopt(argc, argv, "MXAQTVSm:f:Hh:b:o:p:l:L:qv:tds:c:W:w:e:D:P:E:")) != -1) { switch (opt) { @@ -408,6 +412,9 @@ int main(int argc, char **argv) std::regex_constants::egrep)); break; case 'D': + vlog_defines.push_back(optarg); + break; + case 'P': { auto args = split_tokens(optarg, ":"); if (!args.empty() && args[0] == "ALL") { @@ -473,6 +480,13 @@ int main(int argc, char **argv) shell(yosys_design); } + if (!vlog_defines.empty()) { + std::string vdef_cmd = "read -define"; + for (auto vdef : vlog_defines) + vdef_cmd += " " + vdef; + run_pass(vdef_cmd); + } + while (optind < argc) run_frontend(argv[optind++], frontend_command, output_filename == "-" ? &backend_command : NULL); diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 8404db5e9..d4aebcda9 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -2410,6 +2410,9 @@ void RTLIL::Cell::fixup_parameters(bool set_a_signed, bool set_b_signed) if (connections_.count("\\Y")) parameters["\\Y_WIDTH"] = GetSize(connections_["\\Y"]); + if (connections_.count("\\Q")) + parameters["\\WIDTH"] = GetSize(connections_["\\Q"]); + check(); } diff --git a/passes/cmds/setundef.cc b/passes/cmds/setundef.cc index 56ef2d125..f6949c820 100644 --- a/passes/cmds/setundef.cc +++ b/passes/cmds/setundef.cc @@ -143,6 +143,9 @@ struct SetundefPass : public Pass { log(" -init\n"); log(" also create/update init values for flip-flops\n"); log("\n"); + log(" -params\n"); + log(" replace undef in cell parameters\n"); + log("\n"); } void execute(std::vector args, RTLIL::Design *design) YS_OVERRIDE { @@ -150,6 +153,7 @@ struct SetundefPass : public Pass { bool undriven_mode = false; bool expose_mode = false; bool init_mode = false; + bool params_mode = false; SetundefWorker worker; log_header(design, "Executing SETUNDEF pass (replace undef values with defined constants).\n"); @@ -199,6 +203,10 @@ struct SetundefPass : public Pass { init_mode = true; continue; } + if (args[argidx] == "-params") { + params_mode = true; + continue; + } if (args[argidx] == "-random" && !got_value && argidx+1 < args.size()) { got_value = true; worker.next_bit_mode = MODE_RANDOM; @@ -228,6 +236,18 @@ struct SetundefPass : public Pass { for (auto module : design->selected_modules()) { + if (params_mode) + { + for (auto *cell : module->selected_cells()) { + for (auto ¶meter : cell->parameters) { + for (auto &bit : parameter.second.bits) { + if (bit > RTLIL::State::S1) + bit = worker.next_bit(); + } + } + } + } + if (undriven_mode) { if (!module->processes.empty()) diff --git a/passes/hierarchy/hierarchy.cc b/passes/hierarchy/hierarchy.cc index 0e28dbca2..2d8edebb5 100644 --- a/passes/hierarchy/hierarchy.cc +++ b/passes/hierarchy/hierarchy.cc @@ -140,6 +140,23 @@ void generate(RTLIL::Design *design, const std::vector &celltypes, } } +// Return the "basic" type for an array item. +std::string basic_cell_type(const std::string celltype, int pos[3] = nullptr) { + std::string basicType = celltype; + if (celltype.substr(0, 7) == "$array:") { + int pos_idx = celltype.find_first_of(':'); + int pos_num = celltype.find_first_of(':', pos_idx + 1); + int pos_type = celltype.find_first_of(':', pos_num + 1); + basicType = celltype.substr(pos_type + 1); + if (pos != nullptr) { + pos[0] = pos_idx; + pos[1] = pos_num; + pos[2] = pos_type; + } + } + return basicType; +} + bool expand_module(RTLIL::Design *design, RTLIL::Module *module, bool flag_check, bool flag_simcheck, std::vector &libdirs) { bool did_something = false; @@ -178,9 +195,11 @@ bool expand_module(RTLIL::Design *design, RTLIL::Module *module, bool flag_check std::vector connections_to_add_signal; if (cell->type.substr(0, 7) == "$array:") { - int pos_idx = cell->type.str().find_first_of(':'); - int pos_num = cell->type.str().find_first_of(':', pos_idx + 1); - int pos_type = cell->type.str().find_first_of(':', pos_num + 1); + int pos[3]; + basic_cell_type(cell->type.str(), pos); + int pos_idx = pos[0]; + int pos_num = pos[1]; + int pos_type = pos[2]; int idx = atoi(cell->type.str().substr(pos_idx + 1, pos_num).c_str()); int num = atoi(cell->type.str().substr(pos_num + 1, pos_type).c_str()); array_cells[cell] = std::pair(idx, num); @@ -439,10 +458,7 @@ void hierarchy_worker(RTLIL::Design *design, std::setcells()) { std::string celltype = cell->type.str(); if (celltype.substr(0, 7) == "$array:") { - int pos_idx = celltype.find_first_of(':'); - int pos_num = celltype.find_first_of(':', pos_idx + 1); - int pos_type = celltype.find_first_of(':', pos_num + 1); - celltype = celltype.substr(pos_type + 1); + celltype = basic_cell_type(celltype); } if (design->module(celltype)) hierarchy_worker(design, used, design->module(celltype), indent+4); @@ -502,9 +518,19 @@ int find_top_mod_score(Design *design, Module *module, dict &db) if (db.count(module) == 0) { int score = 0; db[module] = 0; - for (auto cell : module->cells()) - if (design->module(cell->type)) - score = max(score, find_top_mod_score(design, design->module(cell->type), db) + 1); + for (auto cell : module->cells()) { + std::string celltype = cell->type.str(); + // Is this an array instance + if (celltype.substr(0, 7) == "$array:") { + celltype = basic_cell_type(celltype); + } + // Is this cell a module instance? + auto instModule = design->module(celltype); + // If there is no instance for this, issue a warning. + if (instModule != nullptr) { + score = max(score, find_top_mod_score(design, instModule, db) + 1); + } + } db[module] = score; } return db.at(module); diff --git a/passes/opt/opt_rmdff.cc b/passes/opt/opt_rmdff.cc index 5880254c1..e8570f0eb 100644 --- a/passes/opt/opt_rmdff.cc +++ b/passes/opt/opt_rmdff.cc @@ -174,8 +174,6 @@ bool handle_dffsr(RTLIL::Module *mod, RTLIL::Cell *cell) cell->unsetParam("\\CLR_POLARITY"); cell->unsetPort("\\SET"); cell->unsetPort("\\CLR"); - - return true; } else { @@ -186,11 +184,12 @@ bool handle_dffsr(RTLIL::Module *mod, RTLIL::Cell *cell) cell->unsetParam("\\CLR_POLARITY"); cell->unsetPort("\\SET"); cell->unsetPort("\\CLR"); - - return true; } + + return true; } - else + + if (!hasreset) { IdString new_type; @@ -207,8 +206,10 @@ bool handle_dffsr(RTLIL::Module *mod, RTLIL::Cell *cell) cell->unsetPort("\\S"); cell->unsetPort("\\R"); - return did_something; + return true; } + + return did_something; } bool handle_dlatch(RTLIL::Module *mod, RTLIL::Cell *dlatch) diff --git a/passes/opt/wreduce.cc b/passes/opt/wreduce.cc index 8063b86a6..09983bc67 100644 --- a/passes/opt/wreduce.cc +++ b/passes/opt/wreduce.cc @@ -38,7 +38,8 @@ struct WreduceConfig "$shl", "$shr", "$sshl", "$sshr", "$shift", "$shiftx", "$lt", "$le", "$eq", "$ne", "$eqx", "$nex", "$ge", "$gt", "$add", "$sub", "$mul", // "$div", "$mod", "$pow", - "$mux", "$pmux" + "$mux", "$pmux", + "$dff", "$adff" }); } }; @@ -134,6 +135,78 @@ struct WreduceWorker module->connect(sig_y.extract(n_kept, n_removed), sig_removed); } + void run_cell_dff(Cell *cell) + { + // Reduce size of FF if inputs are just sign/zero extended or output bit is not used + + SigSpec sig_d = mi.sigmap(cell->getPort("\\D")); + SigSpec sig_q = mi.sigmap(cell->getPort("\\Q")); + + int width_before = GetSize(sig_q); + + if (width_before == 0) + return; + + bool zero_ext = sig_d[GetSize(sig_d)-1] == State::S0; + bool sign_ext = !zero_ext; + + for (int i = GetSize(sig_q)-1; i >= 0; i--) + { + if (zero_ext && sig_d[i] == State::S0) { + module->connect(sig_q[i], State::S0); + sig_d.remove(i); + sig_q.remove(i); + continue; + } + + if (sign_ext && i > 0 && sig_d[i] == sig_d[i-1]) { + module->connect(sig_q[i], sig_q[i-1]); + sig_d.remove(i); + sig_q.remove(i); + continue; + } + + auto info = mi.query(sig_q[i]); + if (!info->is_output && GetSize(info->ports) <= 1 && !keep_bits.count(mi.sigmap(sig_q[i]))) { + sig_d.remove(i); + sig_q.remove(i); + zero_ext = false; + sign_ext = false; + continue; + } + + break; + } + + if (width_before == GetSize(sig_q)) + return; + + if (GetSize(sig_q) == 0) { + log("Removed cell %s.%s (%s).\n", log_id(module), log_id(cell), log_id(cell->type)); + return; + } + + log("Removed top %d bits (of %d) from mux cell %s.%s (%s).\n", width_before - GetSize(sig_q), width_before, + log_id(module), log_id(cell), log_id(cell->type)); + + for (auto bit : sig_d) + work_queue_bits.insert(bit); + + for (auto bit : sig_q) + work_queue_bits.insert(bit); + + // Narrow ARST_VALUE parameter to new size. + if (cell->parameters.count("\\ARST_VALUE")) { + Const arst_value = cell->getParam("\\ARST_VALUE"); + arst_value.bits.resize(GetSize(sig_q)); + cell->setParam("\\ARST_VALUE", arst_value); + } + + cell->setPort("\\D", sig_d); + cell->setPort("\\Q", sig_q); + cell->fixup_parameters(); + } + void run_reduce_inport(Cell *cell, char port, int max_port_size, bool &port_signed, bool &did_something) { port_signed = cell->getParam(stringf("\\%c_SIGNED", port)).as_bool(); @@ -176,6 +249,9 @@ struct WreduceWorker if (cell->type.in("$mux", "$pmux")) return run_cell_mux(cell); + if (cell->type.in("$dff", "$adff")) + return run_cell_dff(cell); + SigSpec sig = mi.sigmap(cell->getPort("\\Y")); if (sig.has_const()) diff --git a/passes/pmgen/.gitignore b/passes/pmgen/.gitignore new file mode 100644 index 000000000..c9263057e --- /dev/null +++ b/passes/pmgen/.gitignore @@ -0,0 +1 @@ +/ice40_dsp_pm.h diff --git a/passes/pmgen/Makefile.inc b/passes/pmgen/Makefile.inc new file mode 100644 index 000000000..33baaca30 --- /dev/null +++ b/passes/pmgen/Makefile.inc @@ -0,0 +1,8 @@ +OBJS += passes/pmgen/ice40_dsp.o + +passes/pmgen/ice40_dsp.o: passes/pmgen/ice40_dsp_pm.h +EXTRA_OBJS += passes/pmgen/ice40_dsp_pm.h +.SECONDARY: passes/pmgen/ice40_dsp_pm.h + +passes/pmgen/ice40_dsp_pm.h: passes/pmgen/ice40_dsp.pmg passes/pmgen/pmgen.py + $(P) cd passes/pmgen && python3 pmgen.py ice40_dsp diff --git a/passes/pmgen/README.md b/passes/pmgen/README.md new file mode 100644 index 000000000..a1007dc62 --- /dev/null +++ b/passes/pmgen/README.md @@ -0,0 +1,224 @@ +Pattern Matcher Generator +========================= + +The program `pmgen.py` reads a `.pmg` (Pattern Matcher Generator) file and +writes a header-only C++ library that implements that pattern matcher. + +The "patterns" in this context are subgraphs in a Yosys RTLIL netlist. + +The algorithm used in the generated pattern matcher is a simple recursive +search with backtracking. It is left to the author of the `.pmg` file to +determine an efficient cell order for the search that allows for maximum +use of indices and early backtracking. + + +API of Generated Matcher +======================== + +When `pmgen.py` reads a `foobar.pmg` file, it writes `foobar_pm.h` containing +a class `foobar_pm`. That class is instanciated with an RTLIL module and a +list of cells from that module: + + foobar_pm pm(module, module->selected_cells()); + +The caller must make sure that none of the cells in the 2nd argument are +deleted for as long as the patter matcher instance is used. + +At any time it is possible to disable cells, preventing them from showing +up in any future matches: + + pm.blacklist(some_cell); + +The `.run(callback_function)` method searches for all matches and calls the +callback function for each found match: + + pm.run([&](){ + log("found matching 'foo' cell: %s\n", log_id(pm.st.foo)); + log(" with 'bar' cell: %s\n", log_id(pm.st.bar)); + }); + +The `.pmg` file declares matcher state variables that are accessible via the +`.st.` members. (The `.st` member is of type `foobar_pm::state_t`.) + +Similarly the `.pmg` file declares user data variables that become members of +`.ud`, a struct of type `foobar_pm::udata_t`. + + +The .pmg File Format +==================== + +The `.pmg` file format is a simple line-based file format. For the most part +lines consist of whitespace-separated tokens. + +Lines in `.pmg` files starting with `//` are comments. + +Declaring state variables +------------------------- + +One or more state variables can be declared using the `state` statement, +followed by a C++ type in angle brackets, followed by a whitespace separated +list of variable names. For example: + + state flag1 flag2 happy big + state sigA sigB sigY + +State variables are automatically managed by the generated backtracking algorithm +and saved and restored as needed. + +They are automatically initialized to the default constructed value of their type +when `.run(callback_function)` is called. + +Declaring udata variables +------------------------- + +Udata (user-data) variables can be used for example to configure the matcher or +the callback function used to perform actions on found matches. + +There is no automatic management of udata variables. For this reason it is +recommended that the user-supplied matcher code treats them as read-only +variables. + +They are declared like state variables, just using the `udata` statement: + + udata min_data_width max_data_width + udata data_port_name + +They are atomatically initialzed to the default constructed value of their type +when ther pattern matcher object is constructed. + +Embedded C++ code +----------------- + +Many statements in a `.pmg` file contain C++ code. However, there are some +slight additions to regular C++/Yosys/RTLIL code that make it a bit easier to +write matchers: + +- Identifiers starting with a dollar sign or backslash are automatically + converted to special IdString variables that are initialized when the + matcher object is constructed. + +- The `port(, )` function is a handy alias for + `sigmap(->getPort())`. + +- Similarly `param(, )` looks up a parameter on a cell. + +- The function `nusers()` returns the number of different cells + connected to any of the given signal bits, plus one if any of the signal + bits is also a primary input or primary output. + +- In `code..endcode` blocks there exist `accept`, `reject`, and `branch` + statements. + +- In `index` statements there is a special `===` operator for the index + lookup. + +Matching cells +-------------- + +Cells are matched using `match..endmatch` blocks. For example: + + match mul + if ff + select mul->type == $mul + select nusers(port(mul, \Y) == 2 + index port(mul, \Y) === port(ff, \D) + filter some_weird_function(mul) < other_weird_function(ff) + optional + endmatch + +A `match` block starts with `match ` and implicitly generates +a state variable `` of type `RTLIL::Cell*`. + +All statements in the match block are optional. (An empty match block +would simply match each and every cell in the module.) + +The `if ` statement makes the match block conditional. If +`` evaluates to `false` then the match block will be ignored +and the corresponding state variable is set to `nullptr`. In our example +we only try to match the `mul` cell if the `ff` state variable points +to a cell. (Presumably `ff` is provided by a prior `match` block.) + +The `select` lines are evaluated once for each cell when the matcher is +initialized. A `match` block will only consider cells for which all `select` +expressions evaluated to `true`. Note that the state variable corresponding to +the match (in the example `mul`) is the only state variable that may be used +`select` lines. + +Index lines are using the `index expr1 === expr2` syntax. `expr1` is +evaluated during matcher initialization and the same restrictions apply as for +`select` expressions. `expr2` is evaluated when the match is calulated. It is a +function of any state variables assigned to by previous blocks. Both expression +are converted to the given type and compared for equality. Only cells for which +all `index` statements in the block pass are considered by the match. + +Note that `select` and `index` are fast operations. Thus `select` and `index` +should be used whenever possible to create efficient matchers. + +Finally, `filter ` narrows down the remaining list of cells. For +performance reasons `filter` statements should only be used for things that +can't be done using `select` and `index`. + +The `optional` statement marks optional matches. I.e. the matcher will also +explore the case where `mul` is set to `nullptr`. Without the `optional` +statement a match may only be assigned nullptr when one of the `if` expressions +evaluates to `false`. + +Additional code +--------------- + +Interleaved with `match..endmatch` blocks there may be `code..endcode` blocks. +Such a block starts with the keyword `code` followed by a list of state variables +that the block may modify. For example: + + code addAB sigS + if (addA) { + addAB = addA; + sigS = port(addA, \B); + } + if (addB) { + addAB = addB; + sigS = port(addB, \A); + } + endcode + +The special keyword `reject` can be used to reject the current state and +backtrack. For example: + + code + if (ffA && ffB) { + if (port(ffA, \CLK) != port(ffB, \CLK)) + reject; + if (param(ffA, \CLK_POLARITY) != param(ffB, \CLK_POLARITY)) + reject; + } + endcode + +Similarly, the special keyword `accept` can be used to accept the current +state. (`accept` will not backtrack. This means it continues with the current +branch and may accept a larger match later.) + +The special keyword `branch` can be used to explore different cases. Note that +each code block has an implicit `branch` at the end. So most use-cases of the +`branch` keyword need to end the block with `reject` to avoid the implicit +branch at the end. For example: + + state mode + + code mode + for (mode = 0; mode < 8; mode++) + branch; + reject; + endcode + +But in some cases it is more natural to utilize the implicit branch statement: + + state portAB + + code portAB + portAB = \A; + branch; + portAB = \B; + endcode + +There is an implicit `code..endcode` block at the end of each `.pgm` file +that just accepts everything that gets all the way there. diff --git a/passes/pmgen/ice40_dsp.cc b/passes/pmgen/ice40_dsp.cc new file mode 100644 index 000000000..3a054a463 --- /dev/null +++ b/passes/pmgen/ice40_dsp.cc @@ -0,0 +1,237 @@ +/* + * 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. + * + */ + +#include "kernel/yosys.h" +#include "kernel/sigtools.h" +#include "passes/pmgen/ice40_dsp_pm.h" + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +void create_ice40_dsp(ice40_dsp_pm &pm) +{ +#if 0 + log("\n"); + log("ffA: %s\n", log_id(pm.st.ffA, "--")); + log("ffB: %s\n", log_id(pm.st.ffB, "--")); + log("mul: %s\n", log_id(pm.st.mul, "--")); + log("ffY: %s\n", log_id(pm.st.ffY, "--")); + log("addAB: %s\n", log_id(pm.st.addAB, "--")); + log("muxAB: %s\n", log_id(pm.st.muxAB, "--")); + log("ffS: %s\n", log_id(pm.st.ffS, "--")); +#endif + + log("Checking %s.%s for iCE40 DSP inference.\n", log_id(pm.module), log_id(pm.st.mul)); + + if (GetSize(pm.st.sigA) > 16) { + log(" input A (%s) is too large (%d > 16).\n", log_signal(pm.st.sigA), GetSize(pm.st.sigA)); + return; + } + + if (GetSize(pm.st.sigB) > 16) { + log(" input B (%s) is too large (%d > 16).\n", log_signal(pm.st.sigB), GetSize(pm.st.sigB)); + return; + } + + if (GetSize(pm.st.sigS) > 32) { + log(" accumulator (%s) is too large (%d > 32).\n", log_signal(pm.st.sigS), GetSize(pm.st.sigS)); + return; + } + + if (GetSize(pm.st.sigY) > 32) { + log(" output (%s) is too large (%d > 32).\n", log_signal(pm.st.sigY), GetSize(pm.st.sigY)); + return; + } + + bool mul_signed = pm.st.mul->getParam("\\A_SIGNED").as_bool(); + + if (mul_signed) { + log(" inference of signed iCE40 DSP arithmetic is currently not supported.\n"); + return; + } + + log(" replacing $mul with SB_MAC16 cell.\n"); + + Cell *cell = pm.module->addCell(NEW_ID, "\\SB_MAC16"); + pm.module->swap_names(cell, pm.st.mul); + + // SB_MAC16 Input Interface + + SigSpec A = pm.st.sigA; + A.extend_u0(16, mul_signed); + + SigSpec B = pm.st.sigB; + B.extend_u0(16, mul_signed); + + SigSpec CD; + if (pm.st.muxA) + CD = pm.st.muxA->getPort("\\B"); + if (pm.st.muxB) + CD = pm.st.muxB->getPort("\\A"); + CD.extend_u0(32, mul_signed); + + cell->setPort("\\A", A); + cell->setPort("\\B", B); + cell->setPort("\\C", CD.extract(0, 16)); + cell->setPort("\\D", CD.extract(16, 16)); + + cell->setParam("\\A_REG", pm.st.ffA ? State::S1 : State::S0); + cell->setParam("\\B_REG", pm.st.ffB ? State::S1 : State::S0); + + cell->setPort("\\AHOLD", State::S0); + cell->setPort("\\BHOLD", State::S0); + cell->setPort("\\CHOLD", State::S0); + cell->setPort("\\DHOLD", State::S0); + + cell->setPort("\\IRSTTOP", State::S0); + cell->setPort("\\IRSTBOT", State::S0); + + if (pm.st.clock_vld) + { + cell->setPort("\\CLK", pm.st.clock); + cell->setPort("\\CE", State::S1); + cell->setParam("\\NEG_TRIGGER", pm.st.clock_pol ? State::S0 : State::S1); + + log(" clock: %s (%s)", log_signal(pm.st.clock), pm.st.clock_pol ? "posedge" : "negedge"); + + if (pm.st.ffA) + log(" ffA:%s", log_id(pm.st.ffA)); + + if (pm.st.ffB) + log(" ffB:%s", log_id(pm.st.ffB)); + + if (pm.st.ffY) + log(" ffY:%s", log_id(pm.st.ffY)); + + if (pm.st.ffS) + log(" ffS:%s", log_id(pm.st.ffS)); + + log("\n"); + } + else + { + cell->setPort("\\CLK", State::S0); + cell->setPort("\\CE", State::S0); + cell->setParam("\\NEG_TRIGGER", State::S0); + } + + // SB_MAC16 Cascade Interface + + cell->setPort("\\SIGNEXTIN", State::Sx); + cell->setPort("\\SIGNEXTOUT", pm.module->addWire(NEW_ID)); + + cell->setPort("\\CI", State::Sx); + cell->setPort("\\CO", pm.module->addWire(NEW_ID)); + + cell->setPort("\\ACCUMCI", State::Sx); + cell->setPort("\\ACCUMCO", pm.module->addWire(NEW_ID)); + + // SB_MAC16 Output Interface + + SigSpec O = pm.st.ffS ? pm.st.sigS : pm.st.sigY; + if (GetSize(O) < 32) + O.append(pm.module->addWire(NEW_ID, 32-GetSize(O))); + + cell->setPort("\\O", O); + + if (pm.st.addAB) { + log(" accumulator %s (%s)\n", log_id(pm.st.addAB), log_id(pm.st.addAB->type)); + cell->setPort("\\ADDSUBTOP", pm.st.addAB->type == "$add" ? State::S0 : State::S1); + cell->setPort("\\ADDSUBBOT", pm.st.addAB->type == "$add" ? State::S0 : State::S1); + } else { + cell->setPort("\\ADDSUBTOP", State::S0); + cell->setPort("\\ADDSUBBOT", State::S0); + } + + cell->setPort("\\ORSTTOP", State::S0); + cell->setPort("\\ORSTBOT", State::S0); + + cell->setPort("\\OHOLDTOP", State::S0); + cell->setPort("\\OHOLDBOT", State::S0); + + SigSpec acc_reset = State::S0; + if (pm.st.muxA) + acc_reset = pm.st.muxA->getPort("\\S"); + if (pm.st.muxB) + acc_reset = pm.module->Not(NEW_ID, pm.st.muxB->getPort("\\S")); + + cell->setPort("\\OLOADTOP", acc_reset); + cell->setPort("\\OLOADBOT", acc_reset); + + // SB_MAC16 Remaining Parameters + + cell->setParam("\\C_REG", State::S0); + cell->setParam("\\D_REG", State::S0); + + cell->setParam("\\TOP_8x8_MULT_REG", pm.st.ffY ? State::S1 : State::S0); + cell->setParam("\\BOT_8x8_MULT_REG", pm.st.ffY ? State::S1 : State::S0); + cell->setParam("\\PIPELINE_16x16_MULT_REG1", pm.st.ffY ? State::S1 : State::S0); + cell->setParam("\\PIPELINE_16x16_MULT_REG2", State::S0); + + cell->setParam("\\TOPOUTPUT_SELECT", Const(pm.st.ffS ? 1 : 3, 2)); + cell->setParam("\\TOPADDSUB_LOWERINPUT", Const(2, 2)); + cell->setParam("\\TOPADDSUB_UPPERINPUT", State::S0); + cell->setParam("\\TOPADDSUB_CARRYSELECT", Const(3, 2)); + + cell->setParam("\\BOTOUTPUT_SELECT", Const(pm.st.ffS ? 1 : 3, 2)); + cell->setParam("\\BOTADDSUB_LOWERINPUT", Const(2, 2)); + cell->setParam("\\BOTADDSUB_UPPERINPUT", State::S0); + cell->setParam("\\BOTADDSUB_CARRYSELECT", Const(0, 2)); + + cell->setParam("\\MODE_8x8", State::S0); + cell->setParam("\\A_SIGNED", mul_signed ? State::S1 : State::S0); + cell->setParam("\\B_SIGNED", mul_signed ? State::S1 : State::S0); + + pm.autoremove(pm.st.mul); + pm.autoremove(pm.st.ffY); + pm.autoremove(pm.st.ffS); +} + +struct Ice40DspPass : public Pass { + Ice40DspPass() : Pass("ice40_dsp", "iCE40: map multipliers") { } + void help() YS_OVERRIDE + { + // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| + log("\n"); + log(" ice40_dsp [options] [selection]\n"); + log("\n"); + log("Map multipliers and multiply-accumulate blocks to iCE40 DSP resources.\n"); + log("\n"); + } + void execute(std::vector args, RTLIL::Design *design) YS_OVERRIDE + { + log_header(design, "Executing ICE40_DSP pass (map multipliers).\n"); + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) + { + // if (args[argidx] == "-singleton") { + // singleton_mode = true; + // continue; + // } + break; + } + extra_args(args, argidx, design); + + for (auto module : design->selected_modules()) + ice40_dsp_pm(module, module->selected_cells()).run(create_ice40_dsp); + } +} Ice40DspPass; + +PRIVATE_NAMESPACE_END diff --git a/passes/pmgen/ice40_dsp.pmg b/passes/pmgen/ice40_dsp.pmg new file mode 100644 index 000000000..96c62e313 --- /dev/null +++ b/passes/pmgen/ice40_dsp.pmg @@ -0,0 +1,160 @@ +state clock +state clock_pol clock_vld +state sigA sigB sigY sigS +state addAB muxAB + +match mul + select mul->type.in($mul) + select GetSize(mul->getPort(\A)) + GetSize(mul->getPort(\B)) > 10 + select GetSize(mul->getPort(\Y)) > 10 +endmatch + +match ffA + select ffA->type.in($dff) + // select nusers(port(ffA, \Q)) == 2 + index port(ffA, \Q) === port(mul, \A) + optional +endmatch + +code sigA clock clock_pol clock_vld + sigA = port(mul, \A); + + if (ffA) { + sigA = port(ffA, \D); + + clock = port(ffA, \CLK).as_bit(); + clock_pol = param(ffA, \CLK_POLARITY).as_bool(); + clock_vld = true; + } +endcode + +match ffB + select ffB->type.in($dff) + // select nusers(port(ffB, \Q)) == 2 + index port(ffB, \Q) === port(mul, \B) + optional +endmatch + +code sigB clock clock_pol clock_vld + sigB = port(mul, \B); + + if (ffB) { + sigB = port(ffB, \D); + SigBit c = port(ffB, \CLK).as_bit(); + bool cp = param(ffB, \CLK_POLARITY).as_bool(); + + if (clock_vld && (c != clock || cp != clock_pol)) + reject; + + clock = c; + clock_pol = cp; + clock_vld = true; + } +endcode + +match ffY + select ffY->type.in($dff) + select nusers(port(ffY, \D)) == 2 + index port(ffY, \D) === port(mul, \Y) + optional +endmatch + +code sigY clock clock_pol clock_vld + sigY = port(mul, \Y); + + if (ffY) { + sigY = port(ffY, \Q); + SigBit c = port(ffY, \CLK).as_bit(); + bool cp = param(ffY, \CLK_POLARITY).as_bool(); + + if (clock_vld && (c != clock || cp != clock_pol)) + reject; + + clock = c; + clock_pol = cp; + clock_vld = true; + } +endcode + +match addA + select addA->type.in($add) + select nusers(port(addA, \A)) == 2 + index port(addA, \A) === sigY + optional +endmatch + +match addB + if !addA + select addB->type.in($add, $sub) + select nusers(port(addB, \B)) == 2 + index port(addB, \B) === sigY + optional +endmatch + +code addAB sigS + if (addA) { + addAB = addA; + sigS = port(addA, \B); + } + if (addB) { + addAB = addB; + sigS = port(addB, \A); + } + if (addAB) { + int natural_mul_width = GetSize(sigA) + GetSize(sigB); + int actual_mul_width = GetSize(sigY); + int actual_acc_width = GetSize(sigS); + + if ((actual_acc_width > actual_mul_width) && (natural_mul_width > actual_mul_width)) + reject; + if ((actual_acc_width != actual_mul_width) && (param(mul, \A_SIGNED).as_bool() != param(addAB, \A_SIGNED).as_bool())) + reject; + } +endcode + +match muxA + if addAB + select muxA->type.in($mux) + select nusers(port(muxA, \A)) == 2 + index port(muxA, \A) === port(addAB, \Y) + optional +endmatch + +match muxB + if addAB + if !muxA + select muxB->type.in($mux) + select nusers(port(muxB, \B)) == 2 + index port(muxB, \B) === port(addAB, \Y) + optional +endmatch + +code muxAB + muxAB = addAB; + if (muxA) + muxAB = muxA; + if (muxB) + muxAB = muxB; +endcode + +match ffS + if muxAB + select ffS->type.in($dff) + select nusers(port(ffS, \D)) == 2 + index port(ffS, \D) === port(muxAB, \Y) + index port(ffS, \Q) === sigS +endmatch + +code clock clock_pol clock_vld + if (ffS) { + SigBit c = port(ffS, \CLK).as_bit(); + bool cp = param(ffS, \CLK_POLARITY).as_bool(); + + if (clock_vld && (c != clock || cp != clock_pol)) + reject; + + clock = c; + clock_pol = cp; + clock_vld = true; + } +endcode diff --git a/passes/pmgen/pmgen.py b/passes/pmgen/pmgen.py new file mode 100644 index 000000000..e688a4567 --- /dev/null +++ b/passes/pmgen/pmgen.py @@ -0,0 +1,482 @@ +#!/usr/bin/env python3 + +import re +import sys +import pprint + +pp = pprint.PrettyPrinter(indent=4) + +prefix = sys.argv[1] + +state_types = dict() +udata_types = dict() +blocks = list() +ids = dict() + +def rewrite_cpp(s): + t = list() + i = 0 + while i < len(s): + if s[i] in ("'", '"') and i + 1 < len(s): + j = i + 1 + while j + 1 < len(s) and s[j] != s[i]: + if s[j] == '\\' and j + 1 < len(s): + j += 1 + j += 1 + t.append(s[i:j+1]) + i = j + 1 + continue + + if s[i] in ('$', '\\') and i + 1 < len(s): + j = i + 1 + while True: + if j == len(s): + j -= 1 + break + if ord('a') <= ord(s[j]) <= ord('z'): + j += 1 + continue + if ord('A') <= ord(s[j]) <= ord('Z'): + j += 1 + continue + if ord('0') <= ord(s[j]) <= ord('9'): + j += 1 + continue + if s[j] == '_': + j += 1 + continue + j -= 1 + break + + n = s[i:j+1] + i = j + 1 + + if n[0] == '$': + v = "id_d_" + n[1:] + else: + v = "id_b_" + n[1:] + + if v not in ids: + ids[v] = n + else: + assert ids[v] == n + + t.append(v) + continue + + if s[i] == "\t": + t.append(" ") + else: + t.append(s[i]) + + i += 1 + + return "".join(t) + +with open("%s.pmg" % prefix, "r") as f: + while True: + line = f.readline() + if line == "": break + line = line.strip() + + cmd = line.split() + if len(cmd) == 0 or cmd[0].startswith("//"): continue + cmd = cmd[0] + + if cmd == "state": + m = re.match(r"^state\s+<(.*?)>\s+(([A-Za-z_][A-Za-z_0-9]*\s+)*[A-Za-z_][A-Za-z_0-9]*)\s*$", line) + assert m + type_str = m.group(1) + states_str = m.group(2) + for s in re.split(r"\s+", states_str): + assert s not in state_types + state_types[s] = type_str + continue + + if cmd == "udata": + m = re.match(r"^udata\s+<(.*?)>\s+(([A-Za-z_][A-Za-z_0-9]*\s+)*[A-Za-z_][A-Za-z_0-9]*)\s*$", line) + assert m + type_str = m.group(1) + udatas_str = m.group(2) + for s in re.split(r"\s+", udatas_str): + assert s not in udata_types + udata_types[s] = type_str + continue + + if cmd == "match": + block = dict() + block["type"] = "match" + + line = line.split() + assert len(line) == 2 + assert line[1] not in state_types + block["cell"] = line[1] + state_types[line[1]] = "Cell*"; + + block["if"] = list() + block["select"] = list() + block["index"] = list() + block["filter"] = list() + block["optional"] = False + + while True: + l = f.readline() + assert l != "" + a = l.split() + if len(a) == 0 or a[0].startswith("//"): continue + if a[0] == "endmatch": break + + if a[0] == "if": + b = l.lstrip()[2:] + block["if"].append(rewrite_cpp(b.strip())) + continue + + if a[0] == "select": + b = l.lstrip()[6:] + block["select"].append(rewrite_cpp(b.strip())) + continue + + if a[0] == "index": + m = re.match(r"^\s*index\s+<(.*?)>\s+(.*?)\s*===\s*(.*?)\s*$", l) + assert m + block["index"].append((m.group(1), rewrite_cpp(m.group(2)), rewrite_cpp(m.group(3)))) + continue + + if a[0] == "filter": + b = l.lstrip()[6:] + block["filter"].append(rewrite_cpp(b.strip())) + continue + + if a[0] == "optional": + block["optional"] = True + continue + + assert False + + blocks.append(block) + + if cmd == "code": + block = dict() + block["type"] = "code" + block["code"] = list() + block["states"] = set() + + for s in line.split()[1:]: + assert s in state_types + block["states"].add(s) + + while True: + l = f.readline() + assert l != "" + a = l.split() + if len(a) == 0: continue + if a[0] == "endcode": break + + block["code"].append(rewrite_cpp(l.rstrip())) + + blocks.append(block) + +with open("%s_pm.h" % prefix, "w") as f: + print("// Generated by pmgen.py from {}.pgm".format(prefix), file=f) + print("", file=f) + + print("#include \"kernel/yosys.h\"", file=f) + print("#include \"kernel/sigtools.h\"", file=f) + print("", file=f) + + print("YOSYS_NAMESPACE_BEGIN", file=f) + print("", file=f) + + print("struct {}_pm {{".format(prefix), file=f) + print(" Module *module;", file=f) + print(" SigMap sigmap;", file=f) + print(" std::function on_accept;".format(prefix), file=f) + print("", file=f) + + for index in range(len(blocks)): + block = blocks[index] + if block["type"] == "match": + index_types = list() + for entry in block["index"]: + index_types.append(entry[0]) + print(" typedef std::tuple<{}> index_{}_key_type;".format(", ".join(index_types), index), file=f) + print(" dict> index_{};".format(index, index), file=f) + print(" dict> sigusers;", file=f) + print(" pool blacklist_cells;", file=f) + print(" pool autoremove_cells;", file=f) + print(" bool blacklist_dirty;", file=f) + print(" int rollback;", file=f) + print("", file=f) + + print(" struct state_t {", file=f) + for s, t in sorted(state_types.items()): + print(" {} {};".format(t, s), file=f) + print(" } st;", file=f) + print("", file=f) + + print(" struct udata_t {", file=f) + for s, t in sorted(udata_types.items()): + print(" {} {};".format(t, s), file=f) + print(" } ud;", file=f) + print("", file=f) + + for v, n in sorted(ids.items()): + if n[0] == "\\": + print(" IdString {}{{\"\\{}\"}};".format(v, n), file=f) + else: + print(" IdString {}{{\"{}\"}};".format(v, n), file=f) + print("", file=f) + + print(" void add_siguser(const SigSpec &sig, Cell *cell) {", file=f) + print(" for (auto bit : sigmap(sig)) {", file=f) + print(" if (bit.wire == nullptr) continue;", file=f) + print(" if (sigusers.count(bit) == 0 && bit.wire->port_id)", file=f) + print(" sigusers[bit].insert(nullptr);", file=f) + print(" sigusers[bit].insert(cell);", file=f) + print(" }", file=f) + print(" }", file=f) + print("", file=f) + + print(" void blacklist(Cell *cell) {", file=f) + print(" if (cell != nullptr) {", file=f) + print(" if (blacklist_cells.insert(cell).second)", file=f) + print(" blacklist_dirty = true;", file=f) + print(" }", file=f) + print(" }", file=f) + print("", file=f) + + print(" void autoremove(Cell *cell) {", file=f) + print(" if (cell != nullptr) {", file=f) + print(" if (blacklist_cells.insert(cell).second)", file=f) + print(" blacklist_dirty = true;", file=f) + print(" autoremove_cells.insert(cell);", file=f) + print(" }", file=f) + print(" }", file=f) + print("", file=f) + + print(" void check_blacklist() {", file=f) + print(" if (!blacklist_dirty)", file=f) + print(" return;", file=f) + print(" blacklist_dirty = false;", file=f) + for index in range(len(blocks)): + block = blocks[index] + if block["type"] == "match": + print(" if (st.{} != nullptr && blacklist_cells.count(st.{})) {{".format(block["cell"], block["cell"]), file=f) + print(" rollback = {};".format(index+1), file=f) + print(" return;", file=f) + print(" }", file=f) + print(" rollback = 0;", file=f) + print(" }", file=f) + print("", file=f) + + print(" SigSpec port(Cell *cell, IdString portname) {", file=f) + print(" return sigmap(cell->getPort(portname));", file=f) + print(" }", file=f) + print("", file=f) + + print(" Const param(Cell *cell, IdString paramname) {", file=f) + print(" return cell->getParam(paramname);", file=f) + print(" }", file=f) + print("", file=f) + + print(" int nusers(const SigSpec &sig) {", file=f) + print(" pool users;", file=f) + print(" for (auto bit : sigmap(sig))", file=f) + print(" for (auto user : sigusers[bit])", file=f) + print(" users.insert(user);", file=f) + print(" return GetSize(users);", file=f) + print(" }", file=f) + print("", file=f) + + print(" {}_pm(Module *module, const vector &cells) :".format(prefix), file=f) + print(" module(module), sigmap(module) {", file=f) + for s, t in sorted(udata_types.items()): + if t.endswith("*"): + print(" ud.{} = nullptr;".format(s), file=f) + else: + print(" ud.{} = {}();".format(s, t), file=f) + print(" for (auto cell : module->cells()) {", file=f) + print(" for (auto &conn : cell->connections())", file=f) + print(" add_siguser(conn.second, cell);", file=f) + print(" }", file=f) + print(" for (auto cell : cells) {", file=f) + + for index in range(len(blocks)): + block = blocks[index] + if block["type"] == "match": + print(" do {", file=f) + print(" Cell *{} = cell;".format(block["cell"]), file=f) + for expr in block["select"]: + print(" if (!({})) break;".format(expr), file=f) + print(" index_{}_key_type key;".format(index), file=f) + for field, entry in enumerate(block["index"]): + print(" std::get<{}>(key) = {};".format(field, entry[1]), file=f) + print(" index_{}[key].push_back(cell);".format(index), file=f) + print(" } while (0);", file=f) + + print(" }", file=f) + print(" }", file=f) + print("", file=f) + + print(" ~{}_pm() {{".format(prefix), file=f) + print(" for (auto cell : autoremove_cells)", file=f) + print(" module->remove(cell);", file=f) + print(" }", file=f) + print("", file=f) + + print(" void run(std::function on_accept_f) {", file=f) + print(" on_accept = on_accept_f;", file=f) + print(" rollback = 0;", file=f) + print(" blacklist_dirty = false;", file=f) + for s, t in sorted(state_types.items()): + if t.endswith("*"): + print(" st.{} = nullptr;".format(s), file=f) + else: + print(" st.{} = {}();".format(s, t), file=f) + print(" block_0();", file=f) + print(" }", file=f) + print("", file=f) + + print(" void run(std::function on_accept_f) {{".format(prefix), file=f) + print(" run([&](){on_accept_f(*this);});", file=f) + print(" }", file=f) + print("", file=f) + + for index in range(len(blocks)): + block = blocks[index] + + print(" void block_{}() {{".format(index), file=f) + + const_st = set() + nonconst_st = set() + restore_st = set() + + for i in range(index): + if blocks[i]["type"] == "code": + for s in blocks[i]["states"]: + const_st.add(s) + elif blocks[i]["type"] == "match": + const_st.add(blocks[i]["cell"]) + else: + assert False + + if block["type"] == "code": + for s in block["states"]: + if s in const_st: + const_st.remove(s) + restore_st.add(s) + nonconst_st.add(s) + elif block["type"] == "match": + s = block["cell"] + assert s not in const_st + nonconst_st.add(s) + else: + assert False + + for s in sorted(const_st): + t = state_types[s] + if t.endswith("*"): + print(" {} const &{} YS_ATTRIBUTE(unused) = st.{};".format(t, s, s), file=f) + else: + print(" const {} &{} YS_ATTRIBUTE(unused) = st.{};".format(t, s, s), file=f) + + for s in sorted(nonconst_st): + t = state_types[s] + print(" {} &{} YS_ATTRIBUTE(unused) = st.{};".format(t, s, s), file=f) + + if len(restore_st): + print("", file=f) + for s in sorted(restore_st): + t = state_types[s] + print(" {} backup_{} = {};".format(t, s, s), file=f) + + if block["type"] == "code": + print("", file=f) + print(" do {", file=f) + print("#define reject do { check_blacklist(); goto rollback_label; } while(0)", file=f) + print("#define accept do { on_accept(); check_blacklist(); if (rollback) goto rollback_label; } while(0)", file=f) + print("#define branch do {{ block_{}(); if (rollback) goto rollback_label; }} while(0)".format(index+1), file=f) + + for line in block["code"]: + print(" " + line, file=f) + + print("", file=f) + print(" block_{}();".format(index+1), file=f) + print("#undef reject", file=f) + print("#undef accept", file=f) + print("#undef branch", file=f) + print(" } while (0);", file=f) + print("", file=f) + print("rollback_label:", file=f) + print(" YS_ATTRIBUTE(unused);", file=f) + + if len(restore_st) or len(nonconst_st): + print("", file=f) + for s in sorted(restore_st): + t = state_types[s] + print(" {} = backup_{};".format(s, s), file=f) + for s in sorted(nonconst_st): + if s not in restore_st: + t = state_types[s] + if t.endswith("*"): + print(" {} = nullptr;".format(s), file=f) + else: + print(" {} = {}();".format(s, t), file=f) + + elif block["type"] == "match": + assert len(restore_st) == 0 + + if len(block["if"]): + for expr in block["if"]: + print("", file=f) + print(" if (!({})) {{".format(expr), file=f) + print(" {} = nullptr;".format(block["cell"]), file=f) + print(" block_{}();".format(index+1), file=f) + print(" return;", file=f) + print(" }", file=f) + + print("", file=f) + print(" index_{}_key_type key;".format(index), file=f) + for field, entry in enumerate(block["index"]): + print(" std::get<{}>(key) = {};".format(field, entry[2]), file=f) + print(" const vector &cells = index_{}[key];".format(index), file=f) + + print("", file=f) + print(" for (int idx = 0; idx < GetSize(cells); idx++) {", file=f) + print(" {} = cells[idx];".format(block["cell"]), file=f) + print(" if (blacklist_cells.count({})) continue;".format(block["cell"]), file=f) + for expr in block["filter"]: + print(" if (!({})) continue;".format(expr), file=f) + print(" block_{}();".format(index+1), file=f) + print(" if (rollback) {", file=f) + print(" if (rollback != {}) {{".format(index+1), file=f) + print(" {} = nullptr;".format(block["cell"]), file=f) + print(" return;", file=f) + print(" }", file=f) + print(" rollback = 0;", file=f) + print(" }", file=f) + print(" }", file=f) + + print("", file=f) + print(" {} = nullptr;".format(block["cell"]), file=f) + + if block["optional"]: + print(" block_{}();".format(index+1), file=f) + + else: + assert False + + + print(" }", file=f) + print("", file=f) + + print(" void block_{}() {{".format(len(blocks)), file=f) + print(" on_accept();", file=f) + print(" check_blacklist();", file=f) + print(" }", file=f) + print("};", file=f) + + print("", file=f) + print("YOSYS_NAMESPACE_END", file=f) + +# pp.pprint(blocks) diff --git a/passes/techmap/abc.cc b/passes/techmap/abc.cc index d2d15a4a9..21b70f492 100644 --- a/passes/techmap/abc.cc +++ b/passes/techmap/abc.cc @@ -327,8 +327,26 @@ void extract_cell(RTLIL::Cell *cell, bool keepff) } } -std::string remap_name(RTLIL::IdString abc_name) +std::string remap_name(RTLIL::IdString abc_name, RTLIL::Wire **orig_wire = nullptr) { + std::string abc_sname = abc_name.substr(1); + if (abc_sname.substr(0, 5) == "ys__n") { + int sid = std::stoi(abc_sname.substr(5)); + bool inv = abc_sname.back() == 'v'; + for (auto sig : signal_list) { + if (sig.id == sid && sig.bit.wire != nullptr) { + std::stringstream sstr; + sstr << "$abc$" << map_autoidx << "$" << sig.bit.wire->name.substr(1); + if (sig.bit.wire->width != 1) + sstr << "[" << sig.bit.offset << "]"; + if (inv) + sstr << "_inv"; + if (orig_wire != nullptr) + *orig_wire = sig.bit.wire; + return sstr.str(); + } + } + } std::stringstream sstr; sstr << "$abc$" << map_autoidx << "$" << abc_name.substr(1); return sstr.str(); @@ -353,12 +371,12 @@ void dump_loop_graph(FILE *f, int &nr, std::map> &edges, std: } for (auto n : nodes) - fprintf(f, " n%d [label=\"%s\\nid=%d, count=%d\"%s];\n", n, log_signal(signal_list[n].bit), + fprintf(f, " ys__n%d [label=\"%s\\nid=%d, count=%d\"%s];\n", n, log_signal(signal_list[n].bit), n, in_counts[n], workpool.count(n) ? ", shape=box" : ""); for (auto &e : edges) for (auto n : e.second) - fprintf(f, " n%d -> n%d;\n", e.first, n); + fprintf(f, " ys__n%d -> ys__n%d;\n", e.first, n); fprintf(f, "}\n"); } @@ -624,7 +642,7 @@ struct abc_output_filter void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::string script_file, std::string exe_file, std::string liberty_file, std::string constr_file, bool cleanup, vector lut_costs, bool dff_mode, std::string clk_str, bool keepff, std::string delay_target, std::string sop_inputs, std::string sop_products, std::string lutin_shared, bool fast_mode, - const std::vector &cells, bool show_tempdir, bool sop_mode) + const std::vector &cells, bool show_tempdir, bool sop_mode, bool abc_dress) { module = current_module; map_autoidx = autoidx++; @@ -728,7 +746,8 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin for (size_t pos = abc_script.find("{S}"); pos != std::string::npos; pos = abc_script.find("{S}", pos)) abc_script = abc_script.substr(0, pos) + lutin_shared + abc_script.substr(pos+3); - + if (abc_dress) + abc_script += "; dress"; abc_script += stringf("; write_blif %s/output.blif", tempdir_name.c_str()); abc_script = add_echos_to_abc_cmd(abc_script); @@ -784,7 +803,7 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin for (auto &si : signal_list) { if (!si.is_port || si.type != G(NONE)) continue; - fprintf(f, " n%d", si.id); + fprintf(f, " ys__n%d", si.id); pi_map[count_input++] = log_signal(si.bit); } if (count_input == 0) @@ -796,17 +815,17 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin for (auto &si : signal_list) { if (!si.is_port || si.type == G(NONE)) continue; - fprintf(f, " n%d", si.id); + fprintf(f, " ys__n%d", si.id); po_map[count_output++] = log_signal(si.bit); } fprintf(f, "\n"); for (auto &si : signal_list) - fprintf(f, "# n%-5d %s\n", si.id, log_signal(si.bit)); + fprintf(f, "# ys__n%-5d %s\n", si.id, log_signal(si.bit)); for (auto &si : signal_list) { if (si.bit.wire == NULL) { - fprintf(f, ".names n%d\n", si.id); + fprintf(f, ".names ys__n%d\n", si.id); if (si.bit == RTLIL::State::S1) fprintf(f, "1\n"); } @@ -815,68 +834,68 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin int count_gates = 0; for (auto &si : signal_list) { if (si.type == G(BUF)) { - fprintf(f, ".names n%d n%d\n", si.in1, si.id); + fprintf(f, ".names ys__n%d ys__n%d\n", si.in1, si.id); fprintf(f, "1 1\n"); } else if (si.type == G(NOT)) { - fprintf(f, ".names n%d n%d\n", si.in1, si.id); + fprintf(f, ".names ys__n%d ys__n%d\n", si.in1, si.id); fprintf(f, "0 1\n"); } else if (si.type == G(AND)) { - fprintf(f, ".names n%d n%d n%d\n", si.in1, si.in2, si.id); + fprintf(f, ".names ys__n%d ys__n%d ys__n%d\n", si.in1, si.in2, si.id); fprintf(f, "11 1\n"); } else if (si.type == G(NAND)) { - fprintf(f, ".names n%d n%d n%d\n", si.in1, si.in2, si.id); + fprintf(f, ".names ys__n%d ys__n%d ys__n%d\n", si.in1, si.in2, si.id); fprintf(f, "0- 1\n"); fprintf(f, "-0 1\n"); } else if (si.type == G(OR)) { - fprintf(f, ".names n%d n%d n%d\n", si.in1, si.in2, si.id); + fprintf(f, ".names ys__n%d ys__n%d ys__n%d\n", si.in1, si.in2, si.id); fprintf(f, "-1 1\n"); fprintf(f, "1- 1\n"); } else if (si.type == G(NOR)) { - fprintf(f, ".names n%d n%d n%d\n", si.in1, si.in2, si.id); + fprintf(f, ".names ys__n%d ys__n%d ys__n%d\n", si.in1, si.in2, si.id); fprintf(f, "00 1\n"); } else if (si.type == G(XOR)) { - fprintf(f, ".names n%d n%d n%d\n", si.in1, si.in2, si.id); + fprintf(f, ".names ys__n%d ys__n%d ys__n%d\n", si.in1, si.in2, si.id); fprintf(f, "01 1\n"); fprintf(f, "10 1\n"); } else if (si.type == G(XNOR)) { - fprintf(f, ".names n%d n%d n%d\n", si.in1, si.in2, si.id); + fprintf(f, ".names ys__n%d ys__n%d ys__n%d\n", si.in1, si.in2, si.id); fprintf(f, "00 1\n"); fprintf(f, "11 1\n"); } else if (si.type == G(ANDNOT)) { - fprintf(f, ".names n%d n%d n%d\n", si.in1, si.in2, si.id); + fprintf(f, ".names ys__n%d ys__n%d ys__n%d\n", si.in1, si.in2, si.id); fprintf(f, "10 1\n"); } else if (si.type == G(ORNOT)) { - fprintf(f, ".names n%d n%d n%d\n", si.in1, si.in2, si.id); + fprintf(f, ".names ys__n%d ys__n%d ys__n%d\n", si.in1, si.in2, si.id); fprintf(f, "1- 1\n"); fprintf(f, "-0 1\n"); } else if (si.type == G(MUX)) { - fprintf(f, ".names n%d n%d n%d n%d\n", si.in1, si.in2, si.in3, si.id); + fprintf(f, ".names ys__n%d ys__n%d ys__n%d ys__n%d\n", si.in1, si.in2, si.in3, si.id); fprintf(f, "1-0 1\n"); fprintf(f, "-11 1\n"); } else if (si.type == G(AOI3)) { - fprintf(f, ".names n%d n%d n%d n%d\n", si.in1, si.in2, si.in3, si.id); + fprintf(f, ".names ys__n%d ys__n%d ys__n%d ys__n%d\n", si.in1, si.in2, si.in3, si.id); fprintf(f, "-00 1\n"); fprintf(f, "0-0 1\n"); } else if (si.type == G(OAI3)) { - fprintf(f, ".names n%d n%d n%d n%d\n", si.in1, si.in2, si.in3, si.id); + fprintf(f, ".names ys__n%d ys__n%d ys__n%d ys__n%d\n", si.in1, si.in2, si.in3, si.id); fprintf(f, "00- 1\n"); fprintf(f, "--0 1\n"); } else if (si.type == G(AOI4)) { - fprintf(f, ".names n%d n%d n%d n%d n%d\n", si.in1, si.in2, si.in3, si.in4, si.id); + fprintf(f, ".names ys__n%d ys__n%d ys__n%d ys__n%d ys__n%d\n", si.in1, si.in2, si.in3, si.in4, si.id); fprintf(f, "-0-0 1\n"); fprintf(f, "-00- 1\n"); fprintf(f, "0--0 1\n"); fprintf(f, "0-0- 1\n"); } else if (si.type == G(OAI4)) { - fprintf(f, ".names n%d n%d n%d n%d n%d\n", si.in1, si.in2, si.in3, si.in4, si.id); + fprintf(f, ".names ys__n%d ys__n%d ys__n%d ys__n%d ys__n%d\n", si.in1, si.in2, si.in3, si.in4, si.id); fprintf(f, "00-- 1\n"); fprintf(f, "--00 1\n"); } else if (si.type == G(FF)) { if (si.init == State::S0 || si.init == State::S1) { - fprintf(f, ".latch n%d n%d %d\n", si.in1, si.id, si.init == State::S1 ? 1 : 0); + fprintf(f, ".latch ys__n%d ys__n%d %d\n", si.in1, si.id, si.init == State::S1 ? 1 : 0); recover_init = true; } else - fprintf(f, ".latch n%d n%d 2\n", si.in1, si.id); + fprintf(f, ".latch ys__n%d ys__n%d 2\n", si.in1, si.id); } else if (si.type != G(NONE)) log_abort(); if (si.type != G(NONE)) @@ -889,7 +908,6 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin log("Extracted %d gates and %d wires to a netlist network with %d inputs and %d outputs.\n", count_gates, GetSize(signal_list), count_input, count_output); log_push(); - if (count_output > 0) { log_header(design, "Executing ABC.\n"); @@ -988,7 +1006,10 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin log_error("ABC output file does not contain a module `netlist'.\n"); for (auto &it : mapped_mod->wires_) { RTLIL::Wire *w = it.second; - RTLIL::Wire *wire = module->addWire(remap_name(w->name)); + RTLIL::Wire *orig_wire = nullptr; + RTLIL::Wire *wire = module->addWire(remap_name(w->name, &orig_wire)); + if (orig_wire != nullptr && orig_wire->attributes.count("\\src")) + wire->attributes["\\src"] = orig_wire->attributes["\\src"]; if (markgroups) wire->attributes["\\abcgroup"] = map_autoidx; design->select(module, wire); } @@ -1213,7 +1234,7 @@ void abc_module(RTLIL::Design *design, RTLIL::Module *current_module, std::strin for (auto &si : signal_list) if (si.is_port) { char buffer[100]; - snprintf(buffer, 100, "\\n%d", si.id); + snprintf(buffer, 100, "\\ys__n%d", si.id); RTLIL::SigSig conn; if (si.type != G(NONE)) { conn.first = si.bit; @@ -1407,6 +1428,11 @@ struct AbcPass : public Pass { log(" this attribute is a unique integer for each ABC process started. This\n"); log(" is useful for debugging the partitioning of clock domains.\n"); log("\n"); + log(" -dress\n"); + log(" run the 'dress' command after all other ABC commands. This aims to\n"); + log(" preserve naming by an equivalence check between the original and post-ABC\n"); + log(" netlists (experimental).\n"); + log("\n"); log("When neither -liberty nor -lut is used, the Yosys standard cell library is\n"); log("loaded into ABC before the ABC script is executed.\n"); log("\n"); @@ -1441,6 +1467,7 @@ struct AbcPass : public Pass { std::string delay_target, sop_inputs, sop_products, lutin_shared = "-S 1"; bool fast_mode = false, dff_mode = false, keepff = false, cleanup = true; bool show_tempdir = false, sop_mode = false; + bool abc_dress = false; vector lut_costs; markgroups = false; @@ -1555,6 +1582,10 @@ struct AbcPass : public Pass { map_mux16 = true; continue; } + if (arg == "-dress") { + abc_dress = true; + continue; + } if (arg == "-g" && argidx+1 < args.size()) { for (auto g : split_tokens(args[++argidx], ",")) { vector gate_list; @@ -1704,7 +1735,7 @@ struct AbcPass : public Pass { if (!dff_mode || !clk_str.empty()) { abc_module(design, mod, script_file, exe_file, liberty_file, constr_file, cleanup, lut_costs, dff_mode, clk_str, keepff, - delay_target, sop_inputs, sop_products, lutin_shared, fast_mode, mod->selected_cells(), show_tempdir, sop_mode); + delay_target, sop_inputs, sop_products, lutin_shared, fast_mode, mod->selected_cells(), show_tempdir, sop_mode, abc_dress); continue; } @@ -1849,7 +1880,7 @@ struct AbcPass : public Pass { en_polarity = std::get<2>(it.first); en_sig = assign_map(std::get<3>(it.first)); abc_module(design, mod, script_file, exe_file, liberty_file, constr_file, cleanup, lut_costs, !clk_sig.empty(), "$", - keepff, delay_target, sop_inputs, sop_products, lutin_shared, fast_mode, it.second, show_tempdir, sop_mode); + keepff, delay_target, sop_inputs, sop_products, lutin_shared, fast_mode, it.second, show_tempdir, sop_mode, abc_dress); assign_map.set(mod); } } diff --git a/techlibs/ecp5/synth_ecp5.cc b/techlibs/ecp5/synth_ecp5.cc index 2e9176a84..bda03d251 100644 --- a/techlibs/ecp5/synth_ecp5.cc +++ b/techlibs/ecp5/synth_ecp5.cc @@ -268,9 +268,9 @@ struct SynthEcp5Pass : public ScriptPass } run("techmap -map +/ecp5/latches_map.v"); if (nomux) - run("abc -lut 4"); + run("abc -lut 4 -dress"); else - run("abc -lut 4:7"); + run("abc -lut 4:7 -dress"); run("clean"); } diff --git a/techlibs/ice40/cells_sim.v b/techlibs/ice40/cells_sim.v index 38ed45981..2041693cc 100644 --- a/techlibs/ice40/cells_sim.v +++ b/techlibs/ice40/cells_sim.v @@ -886,59 +886,6 @@ module SB_WARMBOOT ( ); endmodule -// UltraPlus feature cells -(* blackbox *) -module SB_MAC16 ( - input CLK, - input CE, - input [15:0] C, - input [15:0] A, - input [15:0] B, - input [15:0] D, - input AHOLD, - input BHOLD, - input CHOLD, - input DHOLD, - input IRSTTOP, - input IRSTBOT, - input ORSTTOP, - input ORSTBOT, - input OLOADTOP, - input OLOADBOT, - input ADDSUBTOP, - input ADDSUBBOT, - input OHOLDTOP, - input OHOLDBOT, - input CI, - input ACCUMCI, - input SIGNEXTIN, - output [31:0] O, - output CO, - output ACCUMCO, - output SIGNEXTOUT -); -parameter NEG_TRIGGER = 1'b0; -parameter C_REG = 1'b0; -parameter A_REG = 1'b0; -parameter B_REG = 1'b0; -parameter D_REG = 1'b0; -parameter TOP_8x8_MULT_REG = 1'b0; -parameter BOT_8x8_MULT_REG = 1'b0; -parameter PIPELINE_16x16_MULT_REG1 = 1'b0; -parameter PIPELINE_16x16_MULT_REG2 = 1'b0; -parameter TOPOUTPUT_SELECT = 2'b00; -parameter TOPADDSUB_LOWERINPUT = 2'b00; -parameter TOPADDSUB_UPPERINPUT = 1'b0; -parameter TOPADDSUB_CARRYSELECT = 2'b00; -parameter BOTOUTPUT_SELECT = 2'b00; -parameter BOTADDSUB_LOWERINPUT = 2'b00; -parameter BOTADDSUB_UPPERINPUT = 1'b0; -parameter BOTADDSUB_CARRYSELECT = 2'b00; -parameter MODE_8x8 = 1'b0; -parameter A_SIGNED = 1'b0; -parameter B_SIGNED = 1'b0; -endmodule - module SB_SPRAM256KA ( input [13:0] ADDRESS, input [15:0] DATAIN, @@ -1273,3 +1220,171 @@ module SB_IO_OD ( endgenerate `endif endmodule + +module SB_MAC16 ( + input CLK, CE, + input [15:0] C, A, B, D, + input AHOLD, BHOLD, CHOLD, DHOLD, + input IRSTTOP, IRSTBOT, + input ORSTTOP, ORSTBOT, + input OLOADTOP, OLOADBOT, + input ADDSUBTOP, ADDSUBBOT, + input OHOLDTOP, OHOLDBOT, + input CI, ACCUMCI, SIGNEXTIN, + output [31:0] O, + output CO, ACCUMCO, SIGNEXTOUT +); + parameter [0:0] NEG_TRIGGER = 0; + parameter [0:0] C_REG = 0; + parameter [0:0] A_REG = 0; + parameter [0:0] B_REG = 0; + parameter [0:0] D_REG = 0; + parameter [0:0] TOP_8x8_MULT_REG = 0; + parameter [0:0] BOT_8x8_MULT_REG = 0; + parameter [0:0] PIPELINE_16x16_MULT_REG1 = 0; + parameter [0:0] PIPELINE_16x16_MULT_REG2 = 0; + parameter [1:0] TOPOUTPUT_SELECT = 0; + parameter [1:0] TOPADDSUB_LOWERINPUT = 0; + parameter [0:0] TOPADDSUB_UPPERINPUT = 0; + parameter [1:0] TOPADDSUB_CARRYSELECT = 0; + parameter [1:0] BOTOUTPUT_SELECT = 0; + parameter [1:0] BOTADDSUB_LOWERINPUT = 0; + parameter [0:0] BOTADDSUB_UPPERINPUT = 0; + parameter [1:0] BOTADDSUB_CARRYSELECT = 0; + parameter [0:0] MODE_8x8 = 0; + parameter [0:0] A_SIGNED = 0; + parameter [0:0] B_SIGNED = 0; + + wire clock = CLK ^ NEG_TRIGGER; + + // internal wires, compare Figure on page 133 of ICE Technology Library 3.0 and Fig 2 on page 2 of Lattice TN1295-DSP + // http://www.latticesemi.com/~/media/LatticeSemi/Documents/TechnicalBriefs/SBTICETechnologyLibrary201608.pdf + // https://www.latticesemi.com/-/media/LatticeSemi/Documents/ApplicationNotes/AD/DSPFunctionUsageGuideforICE40Devices.ashx + wire [15:0] iA, iB, iC, iD; + wire [15:0] iF, iJ, iK, iG; + wire [31:0] iL, iH; + wire [15:0] iW, iX, iP, iQ; + wire [15:0] iY, iZ, iR, iS; + wire HCI, LCI, LCO; + + // Regs C and A + reg [15:0] rC, rA; + always @(posedge clock, posedge IRSTTOP) begin + if (IRSTTOP) begin + rC <= 0; + rA <= 0; + end else if (CE) begin + if (!CHOLD) rC <= C; + if (!AHOLD) rA <= A; + end + end + assign iC = C_REG ? rC : C; + assign iA = A_REG ? rA : A; + + // Regs B and D + reg [15:0] rB, rD; + always @(posedge clock, posedge IRSTBOT) begin + if (IRSTBOT) begin + rB <= 0; + rD <= 0; + end else if (CE) begin + if (!BHOLD) rB <= B; + if (!DHOLD) rD <= D; + end + end + assign iB = B_REG ? rB : B; + assign iD = D_REG ? rD : D; + + // Multiplier Stage + wire [15:0] p_Ah_Bh, p_Al_Bh, p_Ah_Bl, p_Al_Bl; + wire [15:0] Ah, Al, Bh, Bl; + assign Ah = {A_SIGNED ? {8{iA[15]}} : 8'b0, iA[15: 8]}; + assign Al = {A_SIGNED ? {8{iA[ 7]}} : 8'b0, iA[ 7: 0]}; + assign Bh = {B_SIGNED ? {8{iB[15]}} : 8'b0, iB[15: 8]}; + assign Bl = {B_SIGNED ? {8{iB[ 7]}} : 8'b0, iB[ 7: 0]}; + assign p_Ah_Bh = Ah * Bh; + assign p_Al_Bh = Al * Bh; + assign p_Ah_Bl = Ah * Bl; + assign p_Al_Bl = Al * Bl; + + // Regs F and J + reg [15:0] rF, rJ; + always @(posedge clock, posedge IRSTTOP) begin + if (IRSTTOP) begin + rF <= 0; + rJ <= 0; + end else if (CE) begin + rF <= p_Ah_Bh; + if (!MODE_8x8) rJ <= p_Al_Bh; + end + end + assign iF = TOP_8x8_MULT_REG ? rF : p_Ah_Bh; + assign iJ = PIPELINE_16x16_MULT_REG1 ? rJ : p_Al_Bh; + + // Regs K and G + reg [15:0] rK, rG; + always @(posedge clock, posedge IRSTBOT) begin + if (IRSTBOT) begin + rK <= 0; + rG <= 0; + end else if (CE) begin + if (!MODE_8x8) rK <= p_Ah_Bl; + rG <= p_Al_Bl; + end + end + assign iK = PIPELINE_16x16_MULT_REG1 ? rK : p_Ah_Bl; + assign iG = BOT_8x8_MULT_REG ? rG : p_Al_Bl; + + // Adder Stage + assign iL = iG + (iK << 8) + (iJ << 8) + (iF << 16); + + // Reg H + reg [31:0] rH; + always @(posedge clock, posedge IRSTBOT) begin + if (IRSTBOT) begin + rH <= 0; + end else if (CE) begin + if (!MODE_8x8) rH <= iL; + end + end + assign iH = PIPELINE_16x16_MULT_REG2 ? rH : iL; + + // Hi Output Stage + wire [15:0] XW, Oh; + reg [15:0] rQ; + assign iW = TOPADDSUB_UPPERINPUT ? iC : iQ; + assign iX = (TOPADDSUB_LOWERINPUT == 0) ? iA : (TOPADDSUB_LOWERINPUT == 1) ? iF : (TOPADDSUB_LOWERINPUT == 2) ? iH[31:16] : {16{iZ[15]}}; + assign {ACCUMCO, XW} = iX + (iW ^ {16{ADDSUBTOP}}) + HCI; + assign CO = ACCUMCO ^ ADDSUBTOP; + assign iP = OLOADTOP ? iC : XW ^ {16{ADDSUBTOP}}; + always @(posedge clock, posedge ORSTTOP) begin + if (ORSTTOP) begin + rQ <= 0; + end else if (CE) begin + if (!OHOLDTOP) rQ <= iP; + end + end + assign iQ = rQ; + assign Oh = (TOPOUTPUT_SELECT == 0) ? iP : (TOPOUTPUT_SELECT == 1) ? iQ : (TOPOUTPUT_SELECT == 2) ? iF : iH[31:16]; + assign HCI = (TOPADDSUB_CARRYSELECT == 0) ? 1'b0 : (TOPADDSUB_CARRYSELECT == 1) ? 1'b1 : (TOPADDSUB_CARRYSELECT == 2) ? LCO : LCO ^ ADDSUBBOT; + assign SIGNEXTOUT = iX[15]; + + // Lo Output Stage + wire [15:0] YZ, Ol; + reg [15:0] rS; + assign iY = BOTADDSUB_UPPERINPUT ? iD : iS; + assign iZ = (BOTADDSUB_LOWERINPUT == 0) ? iB : (BOTADDSUB_LOWERINPUT == 1) ? iG : (BOTADDSUB_LOWERINPUT == 2) ? iH[15:0] : {16{SIGNEXTIN}}; + assign {LCO, YZ} = iZ + (iY ^ {16{ADDSUBBOT}}) + LCI; + assign iR = OLOADBOT ? iD : YZ ^ {16{ADDSUBBOT}}; + always @(posedge clock, posedge ORSTBOT) begin + if (ORSTBOT) begin + rS <= 0; + end else if (CE) begin + if (!OHOLDBOT) rS <= iR; + end + end + assign iS = rS; + assign Ol = (BOTOUTPUT_SELECT == 0) ? iR : (BOTOUTPUT_SELECT == 1) ? iS : (BOTOUTPUT_SELECT == 2) ? iG : iH[15:0]; + assign LCI = (BOTADDSUB_CARRYSELECT == 0) ? 1'b0 : (BOTADDSUB_CARRYSELECT == 1) ? 1'b1 : (BOTADDSUB_CARRYSELECT == 2) ? ACCUMCI : CI; + assign O = {Oh, Ol}; +endmodule diff --git a/techlibs/ice40/synth_ice40.cc b/techlibs/ice40/synth_ice40.cc index f900453e8..34501b335 100644 --- a/techlibs/ice40/synth_ice40.cc +++ b/techlibs/ice40/synth_ice40.cc @@ -79,6 +79,9 @@ struct SynthIce40Pass : public ScriptPass log(" -nobram\n"); log(" do not use SB_RAM40_4K* cells in output netlist\n"); log("\n"); + log(" -dsp\n"); + log(" use iCE40 UltraPlus DSP cells for large arithmetic\n"); + log("\n"); log(" -noabc\n"); log(" use built-in Yosys LUT techmapping instead of abc\n"); log("\n"); @@ -96,7 +99,7 @@ struct SynthIce40Pass : public ScriptPass } string top_opt, blif_file, edif_file, json_file; - bool nocarry, nodffe, nobram, flatten, retime, relut, noabc, abc2, vpr; + bool nocarry, nodffe, nobram, dsp, flatten, retime, relut, noabc, abc2, vpr; int min_ce_use; void clear_flags() YS_OVERRIDE @@ -109,6 +112,7 @@ struct SynthIce40Pass : public ScriptPass nodffe = false; min_ce_use = -1; nobram = false; + dsp = false; flatten = true; retime = false; relut = false; @@ -181,6 +185,10 @@ struct SynthIce40Pass : public ScriptPass nobram = true; continue; } + if (args[argidx] == "-dsp") { + dsp = true; + continue; + } if (args[argidx] == "-noabc") { noabc = true; continue; @@ -214,11 +222,11 @@ struct SynthIce40Pass : public ScriptPass { run("read_verilog -lib +/ice40/cells_sim.v"); run(stringf("hierarchy -check %s", help_mode ? "-top " : top_opt.c_str())); + run("proc"); } if (flatten && check_label("flatten", "(unless -noflatten)")) { - run("proc"); run("flatten"); run("tribuf -logic"); run("deminout"); @@ -226,7 +234,23 @@ struct SynthIce40Pass : public ScriptPass if (check_label("coarse")) { - run("synth -lut 4 -run coarse"); + run("opt_expr"); + run("opt_clean"); + run("check"); + run("opt"); + run("wreduce"); + run("share"); + run("techmap -map +/cmp2lut.v -D LUT_WIDTH=4"); + run("opt_expr"); + run("opt_clean"); + if (help_mode || dsp) + run("ice40_dsp", "(if -dsp)"); + run("alumacc"); + run("opt"); + run("fsm"); + run("opt -fast"); + run("memory -nomap"); + run("opt_clean"); } if (!nobram && check_label("bram", "(skip if -nobram)")) @@ -282,7 +306,7 @@ struct SynthIce40Pass : public ScriptPass run("techmap -map +/gate2lut.v -D LUT_WIDTH=4", "(only if -noabc)"); } if (!noabc) { - run("abc -lut 4", "(skip if -noabc)"); + run("abc -dress -lut 4", "(skip if -noabc)"); } run("clean"); if (relut || help_mode) { diff --git a/techlibs/ice40/tests/.gitignore b/techlibs/ice40/tests/.gitignore index b58f9ad4a..120286550 100644 --- a/techlibs/ice40/tests/.gitignore +++ b/techlibs/ice40/tests/.gitignore @@ -1,2 +1,11 @@ -test_ffs_[01][01][01][01][01]_* -test_bram_[0-9]* +/test_ffs_[01][01][01][01][01]_* +/test_bram_[0-9]* +/test_dsp_model +/test_dsp_model.vcd +/test_dsp_model_ref.v +/test_dsp_model_uut.v +/test_dsp_map +/test_dsp_map.vcd +/test_dsp_map_tb.v +/test_dsp_map_top.v +/test_dsp_map_syn.v diff --git a/techlibs/ice40/tests/test_dsp_map.sh b/techlibs/ice40/tests/test_dsp_map.sh new file mode 100644 index 000000000..3f7f134e4 --- /dev/null +++ b/techlibs/ice40/tests/test_dsp_map.sh @@ -0,0 +1,107 @@ +#!/bin/bash +set -ex + +for iter in {1..100} +do + SZA=$(( 3 + $RANDOM % 13 )) + SZB=$(( 3 + $RANDOM % 13 )) + SZO=$(( 3 + $RANDOM % 29 )) + + C0=clk$(( $RANDOM & 1)) + C1=clk$(( $RANDOM & 1)) + C2=clk$(( $RANDOM & 1)) + C3=clk$(( $RANDOM & 1)) + + E0=$( test $(( $RANDOM & 1 )) -eq 0 && echo posedge || echo negedge ) + E1=$( test $(( $RANDOM & 1 )) -eq 0 && echo posedge || echo negedge ) + E2=$( test $(( $RANDOM & 1 )) -eq 0 && echo posedge || echo negedge ) + E3=$( test $(( $RANDOM & 1 )) -eq 0 && echo posedge || echo negedge ) + + SP=$( test $(( $RANDOM & 1 )) -eq 0 && echo S || echo P ) + + RC=$( test $(( $RANDOM & 1 )) -eq 0 && echo "reset" || echo "!reset" ) + RV="32'h$( echo $RANDOM | md5sum | cut -c1-8 )" + + cat > test_dsp_map_top.v << EOT +module top ( + input clk0, clk1, reset, + input [$SZA:0] A, + input [$SZB:0] B, + output [$SZO:0] O +); + reg [15:0] AA, BB; + reg [31:0] P, S; + + always @($E0 $C0) AA <= A; + always @($E1 $C1) BB <= B; + always @($E2 $C2) P <= AA * BB; + always @($E3 $C3) S <= $RC ? $RV : S + P; + assign O = $SP; +endmodule +EOT + + cat > test_dsp_map_tb.v << EOT +\`timescale 1ns / 1ps +module testbench; + reg clk1, clk0, reset; + reg [$SZA:0] A; + reg [$SZB:0] B; + + wire [$SZO:0] O_top, O_syn; + + top top_inst (.clk0(clk0), .clk1(clk1), .reset(reset), .A(A), .B(B), .O(O_top)); + syn syn_inst (.clk0(clk0), .clk1(clk1), .reset(reset), .A(A), .B(B), .O(O_syn)); + + initial begin + // \$dumpfile("test_dsp_map.vcd"); + // \$dumpvars(0, testbench); + + #2; + clk0 = 0; + clk1 = 0; + reset = 1; + reset = $RC; + A = 0; + B = 0; + + repeat (3) begin + #2; clk0 = ~clk0; + #2; clk0 = ~clk0; + #2; clk1 = ~clk1; + #2; clk1 = ~clk1; + end + + repeat (100) begin + #2; + A = \$urandom; + B = \$urandom; + reset = \$urandom & \$urandom & \$urandom & \$urandom; + if (\$urandom & 1) begin + #2; clk0 = ~clk0; + #2; clk0 = ~clk0; + end else begin + #2; clk1 = ~clk1; + #2; clk1 = ~clk1; + end + #2; + if (O_top !== O_syn) begin + \$display("ERROR: O_top=%b O_syn=%b", O_top, O_syn); + \$stop; + end + // \$display("OK O_top=O_syn=%b", O_top); + end + + \$display("Test passed."); + \$finish; + end +endmodule +EOT + + ../../../yosys -p 'read_verilog test_dsp_map_top.v; synth_ice40 -dsp; rename top syn; write_verilog test_dsp_map_syn.v' + iverilog -o test_dsp_map -s testbench test_dsp_map_tb.v test_dsp_map_top.v test_dsp_map_syn.v ../cells_sim.v + vvp -N test_dsp_map +done + +: "" +: "#### All tests passed. ####" +: "" diff --git a/techlibs/ice40/tests/test_dsp_model.sh b/techlibs/ice40/tests/test_dsp_model.sh new file mode 100644 index 000000000..1bc0cc688 --- /dev/null +++ b/techlibs/ice40/tests/test_dsp_model.sh @@ -0,0 +1,11 @@ +#!/bin/bash +set -ex +sed 's/SB_MAC16/SB_MAC16_UUT/; /SB_MAC16_UUT/,/endmodule/ p; d;' < ../cells_sim.v > test_dsp_model_uut.v +cat /opt/lscc/iCEcube2.2017.01/verilog/sb_ice_syn.v > test_dsp_model_ref.v +for tb in testbench \ + testbench_comb_8x8_A testbench_comb_8x8_B testbench_comb_16x16 \ + testbench_seq_16x16_A testbench_seq_16x16_B +do + iverilog -s $tb -o test_dsp_model test_dsp_model.v test_dsp_model_uut.v test_dsp_model_ref.v + vvp -N ./test_dsp_model +done diff --git a/techlibs/ice40/tests/test_dsp_model.v b/techlibs/ice40/tests/test_dsp_model.v new file mode 100644 index 000000000..594bd4ad3 --- /dev/null +++ b/techlibs/ice40/tests/test_dsp_model.v @@ -0,0 +1,342 @@ +`timescale 1ns / 1ps + +module testbench; + parameter [0:0] NEG_TRIGGER = 0; + parameter [0:0] C_REG = 0; + parameter [0:0] A_REG = 0; + parameter [0:0] B_REG = 0; + parameter [0:0] D_REG = 0; + parameter [0:0] TOP_8x8_MULT_REG = 0; + parameter [0:0] BOT_8x8_MULT_REG = 0; + parameter [0:0] PIPELINE_16x16_MULT_REG1 = 0; + parameter [0:0] PIPELINE_16x16_MULT_REG2 = 0; + parameter [1:0] TOPOUTPUT_SELECT = 0; + parameter [1:0] TOPADDSUB_LOWERINPUT = 0; + parameter [0:0] TOPADDSUB_UPPERINPUT = 1; + parameter [1:0] TOPADDSUB_CARRYSELECT = 0; + parameter [1:0] BOTOUTPUT_SELECT = 0; + parameter [1:0] BOTADDSUB_LOWERINPUT = 0; + parameter [0:0] BOTADDSUB_UPPERINPUT = 1; + parameter [1:0] BOTADDSUB_CARRYSELECT = 0; + parameter [0:0] MODE_8x8 = 0; + parameter [0:0] A_SIGNED = 0; + parameter [0:0] B_SIGNED = 0; + + reg CLK, CE; + reg [15:0] C, A, B, D; + reg AHOLD, BHOLD, CHOLD, DHOLD; + reg IRSTTOP, IRSTBOT; + reg ORSTTOP, ORSTBOT; + reg OLOADTOP, OLOADBOT; + reg ADDSUBTOP, ADDSUBBOT; + reg OHOLDTOP, OHOLDBOT; + reg CI, ACCUMCI, SIGNEXTIN; + + output [31:0] REF_O, UUT_O; + output REF_CO, REF_ACCUMCO, REF_SIGNEXTOUT; + output UUT_CO, UUT_ACCUMCO, UUT_SIGNEXTOUT; + + integer errcount = 0; + + task clkcycle; + begin + #5; + CLK = ~CLK; + #10; + CLK = ~CLK; + #2; + if (REF_O !== UUT_O) begin + $display("ERROR at %1t: REF_O=%b UUT_O=%b DIFF=%b", $time, REF_O, UUT_O, REF_O ^ UUT_O); + errcount = errcount + 1; + end + if (REF_CO !== UUT_CO) begin + $display("ERROR at %1t: REF_CO=%b UUT_CO=%b", $time, REF_CO, UUT_CO); + errcount = errcount + 1; + end + if (REF_ACCUMCO !== UUT_ACCUMCO) begin + $display("ERROR at %1t: REF_ACCUMCO=%b UUT_ACCUMCO=%b", $time, REF_ACCUMCO, UUT_ACCUMCO); + errcount = errcount + 1; + end + if (REF_SIGNEXTOUT !== UUT_SIGNEXTOUT) begin + $display("ERROR at %1t: REF_SIGNEXTOUT=%b UUT_SIGNEXTOUT=%b", $time, REF_SIGNEXTOUT, UUT_SIGNEXTOUT); + errcount = errcount + 1; + end + #3; + end + endtask + + initial begin + $dumpfile("test_dsp_model.vcd"); + $dumpvars(0, testbench); + + #2; + CLK = NEG_TRIGGER; + CE = 1; + {C, A, B, D} = 0; + {AHOLD, BHOLD, CHOLD, DHOLD} = 0; + {OLOADTOP, OLOADBOT} = 0; + {ADDSUBTOP, ADDSUBBOT} = 0; + {OHOLDTOP, OHOLDBOT} = 0; + {CI, ACCUMCI, SIGNEXTIN} = 0; + + {IRSTTOP, IRSTBOT} = ~0; + {ORSTTOP, ORSTBOT} = ~0; + + #3; + {IRSTTOP, IRSTBOT} = 0; + {ORSTTOP, ORSTBOT} = 0; + + repeat (300) begin + clkcycle; + + A = $urandom; + B = $urandom; + C = $urandom; + D = $urandom; + + {AHOLD, BHOLD, CHOLD, DHOLD} = $urandom & $urandom & $urandom; + {OLOADTOP, OLOADBOT} = $urandom & $urandom & $urandom; + {ADDSUBTOP, ADDSUBBOT} = $urandom & $urandom & $urandom; + {OHOLDTOP, OHOLDBOT} = $urandom & $urandom & $urandom; + {CI, ACCUMCI, SIGNEXTIN} = $urandom & $urandom & $urandom; + + {IRSTTOP, IRSTBOT} = $urandom & $urandom & $urandom; + {ORSTTOP, ORSTBOT} = $urandom & $urandom & $urandom; + end + + if (errcount == 0) begin + $display("All tests passed."); + $finish; + end else begin + $display("Caught %1d errors.", errcount); + $stop; + end + end + + SB_MAC16 #( + .NEG_TRIGGER (NEG_TRIGGER ), + .C_REG (C_REG ), + .A_REG (A_REG ), + .B_REG (B_REG ), + .D_REG (D_REG ), + .TOP_8x8_MULT_REG (TOP_8x8_MULT_REG ), + .BOT_8x8_MULT_REG (BOT_8x8_MULT_REG ), + .PIPELINE_16x16_MULT_REG1 (PIPELINE_16x16_MULT_REG1), + .PIPELINE_16x16_MULT_REG2 (PIPELINE_16x16_MULT_REG2), + .TOPOUTPUT_SELECT (TOPOUTPUT_SELECT ), + .TOPADDSUB_LOWERINPUT (TOPADDSUB_LOWERINPUT ), + .TOPADDSUB_UPPERINPUT (TOPADDSUB_UPPERINPUT ), + .TOPADDSUB_CARRYSELECT (TOPADDSUB_CARRYSELECT ), + .BOTOUTPUT_SELECT (BOTOUTPUT_SELECT ), + .BOTADDSUB_LOWERINPUT (BOTADDSUB_LOWERINPUT ), + .BOTADDSUB_UPPERINPUT (BOTADDSUB_UPPERINPUT ), + .BOTADDSUB_CARRYSELECT (BOTADDSUB_CARRYSELECT ), + .MODE_8x8 (MODE_8x8 ), + .A_SIGNED (A_SIGNED ), + .B_SIGNED (B_SIGNED ) + ) ref ( + .CLK (CLK ), + .CE (CE ), + .C (C ), + .A (A ), + .B (B ), + .D (D ), + .AHOLD (AHOLD ), + .BHOLD (BHOLD ), + .CHOLD (CHOLD ), + .DHOLD (DHOLD ), + .IRSTTOP (IRSTTOP ), + .IRSTBOT (IRSTBOT ), + .ORSTTOP (ORSTTOP ), + .ORSTBOT (ORSTBOT ), + .OLOADTOP (OLOADTOP ), + .OLOADBOT (OLOADBOT ), + .ADDSUBTOP (ADDSUBTOP ), + .ADDSUBBOT (ADDSUBBOT ), + .OHOLDTOP (OHOLDTOP ), + .OHOLDBOT (OHOLDBOT ), + .CI (CI ), + .ACCUMCI (ACCUMCI ), + .SIGNEXTIN (SIGNEXTIN ), + .O (REF_O ), + .CO (REF_CO ), + .ACCUMCO (REF_ACCUMCO ), + .SIGNEXTOUT (REF_SIGNEXTOUT) + ); + + SB_MAC16_UUT #( + .NEG_TRIGGER (NEG_TRIGGER ), + .C_REG (C_REG ), + .A_REG (A_REG ), + .B_REG (B_REG ), + .D_REG (D_REG ), + .TOP_8x8_MULT_REG (TOP_8x8_MULT_REG ), + .BOT_8x8_MULT_REG (BOT_8x8_MULT_REG ), + .PIPELINE_16x16_MULT_REG1 (PIPELINE_16x16_MULT_REG1), + .PIPELINE_16x16_MULT_REG2 (PIPELINE_16x16_MULT_REG2), + .TOPOUTPUT_SELECT (TOPOUTPUT_SELECT ), + .TOPADDSUB_LOWERINPUT (TOPADDSUB_LOWERINPUT ), + .TOPADDSUB_UPPERINPUT (TOPADDSUB_UPPERINPUT ), + .TOPADDSUB_CARRYSELECT (TOPADDSUB_CARRYSELECT ), + .BOTOUTPUT_SELECT (BOTOUTPUT_SELECT ), + .BOTADDSUB_LOWERINPUT (BOTADDSUB_LOWERINPUT ), + .BOTADDSUB_UPPERINPUT (BOTADDSUB_UPPERINPUT ), + .BOTADDSUB_CARRYSELECT (BOTADDSUB_CARRYSELECT ), + .MODE_8x8 (MODE_8x8 ), + .A_SIGNED (A_SIGNED ), + .B_SIGNED (B_SIGNED ) + ) uut ( + .CLK (CLK ), + .CE (CE ), + .C (C ), + .A (A ), + .B (B ), + .D (D ), + .AHOLD (AHOLD ), + .BHOLD (BHOLD ), + .CHOLD (CHOLD ), + .DHOLD (DHOLD ), + .IRSTTOP (IRSTTOP ), + .IRSTBOT (IRSTBOT ), + .ORSTTOP (ORSTTOP ), + .ORSTBOT (ORSTBOT ), + .OLOADTOP (OLOADTOP ), + .OLOADBOT (OLOADBOT ), + .ADDSUBTOP (ADDSUBTOP ), + .ADDSUBBOT (ADDSUBBOT ), + .OHOLDTOP (OHOLDTOP ), + .OHOLDBOT (OHOLDBOT ), + .CI (CI ), + .ACCUMCI (ACCUMCI ), + .SIGNEXTIN (SIGNEXTIN ), + .O (UUT_O ), + .CO (UUT_CO ), + .ACCUMCO (UUT_ACCUMCO ), + .SIGNEXTOUT (UUT_SIGNEXTOUT) + ); +endmodule + +module testbench_comb_8x8_A; + testbench #( + .NEG_TRIGGER (0), + .C_REG (0), + .A_REG (0), + .B_REG (0), + .D_REG (0), + .TOP_8x8_MULT_REG (0), + .BOT_8x8_MULT_REG (0), + .PIPELINE_16x16_MULT_REG1 (0), + .PIPELINE_16x16_MULT_REG2 (0), + .TOPOUTPUT_SELECT (2), // 0=P, 1=Q, 2=8x8, 3=16x16 + .TOPADDSUB_LOWERINPUT (0), // 0=A, 1=8x8, 2=16x16, 3=S-EXT + .TOPADDSUB_UPPERINPUT (0), // 0=Q, 1=C + .TOPADDSUB_CARRYSELECT (0), // 0=0, 1=1, 2=ACI, 3=CI + .BOTOUTPUT_SELECT (2), // 0=R, 1=S, 2=8x8, 3=16x16 + .BOTADDSUB_LOWERINPUT (0), // 0=B, 1=8x8, 2=16x16, 3=S-EXT + .BOTADDSUB_UPPERINPUT (0), // 0=S, 1=D + .BOTADDSUB_CARRYSELECT (0), // 0=0, 1=1, 2=ACI, 3=CI + .MODE_8x8 (0), + .A_SIGNED (0), + .B_SIGNED (0) + ) testbench (); +endmodule + +module testbench_comb_8x8_B; + testbench #( + .NEG_TRIGGER (0), + .C_REG (0), + .A_REG (0), + .B_REG (0), + .D_REG (0), + .TOP_8x8_MULT_REG (0), + .BOT_8x8_MULT_REG (0), + .PIPELINE_16x16_MULT_REG1 (0), + .PIPELINE_16x16_MULT_REG2 (0), + .TOPOUTPUT_SELECT (0), // 0=P, 1=Q, 2=8x8, 3=16x16 + .TOPADDSUB_LOWERINPUT (1), // 0=A, 1=8x8, 2=16x16, 3=S-EXT + .TOPADDSUB_UPPERINPUT (1), // 0=Q, 1=C + .TOPADDSUB_CARRYSELECT (0), // 0=0, 1=1, 2=ACI, 3=CI + .BOTOUTPUT_SELECT (0), // 0=R, 1=S, 2=8x8, 3=16x16 + .BOTADDSUB_LOWERINPUT (1), // 0=B, 1=8x8, 2=16x16, 3=S-EXT + .BOTADDSUB_UPPERINPUT (1), // 0=S, 1=D + .BOTADDSUB_CARRYSELECT (0), // 0=0, 1=1, 2=ACI, 3=CI + .MODE_8x8 (0), + .A_SIGNED (0), + .B_SIGNED (0) + ) testbench (); +endmodule + +module testbench_comb_16x16; + testbench #( + .NEG_TRIGGER (0), + .C_REG (0), + .A_REG (0), + .B_REG (0), + .D_REG (0), + .TOP_8x8_MULT_REG (0), + .BOT_8x8_MULT_REG (0), + .PIPELINE_16x16_MULT_REG1 (0), + .PIPELINE_16x16_MULT_REG2 (0), + .TOPOUTPUT_SELECT (0), // 0=P, 1=Q, 2=8x8, 3=16x16 + .TOPADDSUB_LOWERINPUT (2), // 0=A, 1=8x8, 2=16x16, 3=S-EXT + .TOPADDSUB_UPPERINPUT (1), // 0=Q, 1=C + .TOPADDSUB_CARRYSELECT (2), // 0=0, 1=1, 2=ACI, 3=CI + .BOTOUTPUT_SELECT (0), // 0=R, 1=S, 2=8x8, 3=16x16 + .BOTADDSUB_LOWERINPUT (2), // 0=B, 1=8x8, 2=16x16, 3=S-EXT + .BOTADDSUB_UPPERINPUT (1), // 0=S, 1=D + .BOTADDSUB_CARRYSELECT (2), // 0=0, 1=1, 2=ACI, 3=CI + .MODE_8x8 (0), + .A_SIGNED (0), + .B_SIGNED (0) + ) testbench (); +endmodule + +module testbench_seq_16x16_A; + testbench #( + .NEG_TRIGGER (0), + .C_REG (1), + .A_REG (1), + .B_REG (1), + .D_REG (1), + .TOP_8x8_MULT_REG (1), + .BOT_8x8_MULT_REG (1), + .PIPELINE_16x16_MULT_REG1 (1), + .PIPELINE_16x16_MULT_REG2 (1), + .TOPOUTPUT_SELECT (0), // 0=P, 1=Q, 2=8x8, 3=16x16 + .TOPADDSUB_LOWERINPUT (2), // 0=A, 1=8x8, 2=16x16, 3=S-EXT + .TOPADDSUB_UPPERINPUT (1), // 0=Q, 1=C + .TOPADDSUB_CARRYSELECT (2), // 0=0, 1=1, 2=ACI, 3=CI + .BOTOUTPUT_SELECT (0), // 0=R, 1=S, 2=8x8, 3=16x16 + .BOTADDSUB_LOWERINPUT (2), // 0=B, 1=8x8, 2=16x16, 3=S-EXT + .BOTADDSUB_UPPERINPUT (1), // 0=S, 1=D + .BOTADDSUB_CARRYSELECT (2), // 0=0, 1=1, 2=ACI, 3=CI + .MODE_8x8 (0), + .A_SIGNED (0), + .B_SIGNED (0) + ) testbench (); +endmodule + +module testbench_seq_16x16_B; + testbench #( + .NEG_TRIGGER (0), + .C_REG (1), + .A_REG (1), + .B_REG (1), + .D_REG (1), + .TOP_8x8_MULT_REG (1), + .BOT_8x8_MULT_REG (1), + .PIPELINE_16x16_MULT_REG1 (1), + .PIPELINE_16x16_MULT_REG2 (0), + .TOPOUTPUT_SELECT (1), // 0=P, 1=Q, 2=8x8, 3=16x16 + .TOPADDSUB_LOWERINPUT (2), // 0=A, 1=8x8, 2=16x16, 3=S-EXT + .TOPADDSUB_UPPERINPUT (0), // 0=Q, 1=C + .TOPADDSUB_CARRYSELECT (2), // 0=0, 1=1, 2=ACI, 3=CI + .BOTOUTPUT_SELECT (1), // 0=R, 1=S, 2=8x8, 3=16x16 + .BOTADDSUB_LOWERINPUT (2), // 0=B, 1=8x8, 2=16x16, 3=S-EXT + .BOTADDSUB_UPPERINPUT (0), // 0=S, 1=D + .BOTADDSUB_CARRYSELECT (2), // 0=0, 1=1, 2=ACI, 3=CI + .MODE_8x8 (0), + .A_SIGNED (0), + .B_SIGNED (0) + ) testbench (); +endmodule diff --git a/tests/asicworld/run-test.sh b/tests/asicworld/run-test.sh index d5708c456..c22ab6928 100755 --- a/tests/asicworld/run-test.sh +++ b/tests/asicworld/run-test.sh @@ -11,4 +11,4 @@ do done shift "$((OPTIND-1))" -exec ${MAKE:-make} -f ../tools/autotest.mk $seed EXTRA_FLAGS="-e" *.v +exec ${MAKE:-make} -f ../tools/autotest.mk $seed EXTRA_FLAGS+="-e" *.v diff --git a/tests/asicworld/xfirrtl b/tests/asicworld/xfirrtl new file mode 100644 index 000000000..c782a2bd6 --- /dev/null +++ b/tests/asicworld/xfirrtl @@ -0,0 +1,24 @@ +# This file contains the names of verilog files to exclude from verilog to FIRRTL regression tests due to known failures. +code_hdl_models_arbiter.v error: reg rst; cannot be driven by primitives or continuous assignment. +code_hdl_models_clk_div_45.v yosys issue: 2nd PMUXTREE pass yields: ERROR: Negative edge clock on FF clk_div_45.$procdff$49. +code_hdl_models_d_ff_gates.v combinational loop +code_hdl_models_d_latch_gates.v combinational loop +code_hdl_models_dff_async_reset.v $adff +code_hdl_models_tff_async_reset.v $adff +code_hdl_models_uart.v $adff +code_specman_switch_fabric.v subfield assignment (bits() <= ...) +code_tidbits_asyn_reset.v $adff +code_tidbits_reg_seq_example.v $adff +code_verilog_tutorial_always_example.v empty module +code_verilog_tutorial_escape_id.v make_id issues (name begins with a digit) +code_verilog_tutorial_explicit.v firrtl backend bug (empty module) +code_verilog_tutorial_first_counter.v error: reg rst; cannot be driven by primitives or continuous assignment. +code_verilog_tutorial_fsm_full.v error: reg reset; cannot be driven by primitives or continuous assignment. +code_verilog_tutorial_if_else.v empty module (everything is under 'always @ (posedge clk)') +[code_verilog_tutorial_n_out_primitive.v empty module +code_verilog_tutorial_parallel_if.v empty module (everything is under 'always @ (posedge clk)') +code_verilog_tutorial_simple_function.v empty module (no hardware) +code_verilog_tutorial_simple_if.v empty module (everything is under 'always @ (posedge clk)') +code_verilog_tutorial_task_global.v empty module (everything is under 'always @ (posedge clk)') +code_verilog_tutorial_v2k_reg.v empty module +code_verilog_tutorial_which_clock.v $adff diff --git a/tests/opt/opt_ff.v b/tests/opt/opt_ff.v new file mode 100644 index 000000000..a01b64b61 --- /dev/null +++ b/tests/opt/opt_ff.v @@ -0,0 +1,21 @@ +module top( + input clk, + input rst, + input [2:0] a, + output [1:0] b +); + reg [2:0] b_reg; + initial begin + b_reg <= 3'b0; + end + + assign b = b_reg[1:0]; + always @(posedge clk or posedge rst) begin + if(rst) begin + b_reg <= 3'b0; + end else begin + b_reg <= a; + end + end +endmodule + diff --git a/tests/opt/opt_ff.ys b/tests/opt/opt_ff.ys new file mode 100644 index 000000000..704c7acf3 --- /dev/null +++ b/tests/opt/opt_ff.ys @@ -0,0 +1,3 @@ +read_verilog opt_ff.v +synth_ice40 +ice40_unlut diff --git a/tests/simple/dff_init.v b/tests/simple/dff_init.v new file mode 100644 index 000000000..be947042e --- /dev/null +++ b/tests/simple/dff_init.v @@ -0,0 +1,42 @@ +module dff0_test(n1, n1_inv, clk); + input clk; + output n1; + reg n1 = 32'd0; + output n1_inv; + always @(posedge clk) + n1 <= n1_inv; + assign n1_inv = ~n1; +endmodule + +module dff1_test(n1, n1_inv, clk); + input clk; + (* init = 32'd1 *) + output n1; + reg n1 = 32'd1; + output n1_inv; + always @(posedge clk) + n1 <= n1_inv; + assign n1_inv = ~n1; +endmodule + +module dff0a_test(n1, n1_inv, clk); + input clk; + (* init = 32'd0 *) // Must be consistent with reg initialiser below + output n1; + reg n1 = 32'd0; + output n1_inv; + always @(posedge clk) + n1 <= n1_inv; + assign n1_inv = ~n1; +endmodule + +module dff1a_test(n1, n1_inv, clk); + input clk; + (* init = 32'd1 *) // Must be consistent with reg initialiser below + output n1; + reg n1 = 32'd1; + output n1_inv; + always @(posedge clk) + n1 <= n1_inv; + assign n1_inv = ~n1; +endmodule diff --git a/tests/simple/hierdefparam.v b/tests/simple/hierdefparam.v index ff92c38bd..c9368ca7a 100644 --- a/tests/simple/hierdefparam.v +++ b/tests/simple/hierdefparam.v @@ -1,3 +1,5 @@ +`default_nettype none + module hierdefparam_top(input [7:0] A, output [7:0] Y); generate begin:foo hierdefparam_a mod_a(.A(A), .Y(Y)); diff --git a/tests/simple/xfirrtl b/tests/simple/xfirrtl new file mode 100644 index 000000000..00e89b389 --- /dev/null +++ b/tests/simple/xfirrtl @@ -0,0 +1,26 @@ +# This file contains the names of verilog files to exclude from verilog to FIRRTL regression tests due to known failures. +arraycells.v inst id[0] of +dff_different_styles.v +generate.v combinational loop +hierdefparam.v inst id[0] of +i2c_master_tests.v $adff +macros.v drops modules +mem2reg.v drops modules +mem_arst.v $adff +memory.v $adff +multiplier.v inst id[0] of +muxtree.v drops modules +omsp_dbg_uart.v $adff +operators.v $pow +paramods.v subfield assignment (bits() <= ...) +partsel.v drops modules +process.v drops modules +realexpr.v drops modules +scopes.v original verilog issues ( -x where x isn't declared signed) +sincos.v $adff +specify.v no code (empty module generates error +subbytes.v $adff +task_func.v drops modules +values.v combinational loop +vloghammer.v combinational loop +wreduce.v original verilog issues ( -x where x isn't declared signed) diff --git a/tests/tools/autotest.mk b/tests/tools/autotest.mk index c68678929..e0f2bcdc1 100644 --- a/tests/tools/autotest.mk +++ b/tests/tools/autotest.mk @@ -1,7 +1,7 @@ -EXTRA_FLAGS= -SEED= - +# Don't bother defining default values for SEED and EXTRA_FLAGS. +# Their "natural" default values should be sufficient, +# and they may be overridden in the environment. ifneq ($(strip $(SEED)),) SEEDOPT=-S$(SEED) endif diff --git a/tests/tools/autotest.sh b/tests/tools/autotest.sh index d6216244f..218edf931 100755 --- a/tests/tools/autotest.sh +++ b/tests/tools/autotest.sh @@ -17,12 +17,18 @@ scriptfiles="" scriptopt="" toolsdir="$(cd $(dirname $0); pwd)" warn_iverilog_git=false +# The following are used in verilog to firrtl regression tests. +# Typically these will be passed as environment variables: +#EXTRA_FLAGS="--firrtl2verilog 'java -cp /.../firrtl/utils/bin/firrtl.jar firrtl.Driver'" +# The tests are skipped if firrtl2verilog is the empty string (the default). +firrtl2verilog="" +xfirrtl="../xfirrtl" if [ ! -f $toolsdir/cmp_tbdata -o $toolsdir/cmp_tbdata.c -nt $toolsdir/cmp_tbdata ]; then ( set -ex; ${CC:-gcc} -Wall -o $toolsdir/cmp_tbdata $toolsdir/cmp_tbdata.c; ) || exit 1 fi -while getopts xmGl:wkjvref:s:p:n:S:I: opt; do +while getopts xmGl:wkjvref:s:p:n:S:I:-: opt; do case "$opt" in x) use_xsim=true ;; @@ -59,8 +65,24 @@ while getopts xmGl:wkjvref:s:p:n:S:I: opt; do include_opts="$include_opts -I $OPTARG" xinclude_opts="$xinclude_opts -i $OPTARG" minclude_opts="$minclude_opts +incdir+$OPTARG" ;; + -) + case "${OPTARG}" in + xfirrtl) + xfirrtl="${!OPTIND}" + OPTIND=$(( $OPTIND + 1 )) + ;; + firrtl2verilog) + firrtl2verilog="${!OPTIND}" + OPTIND=$(( $OPTIND + 1 )) + ;; + *) + if [ "$OPTERR" == 1 ] && [ "${optspec:0:1}" != ":" ]; then + echo "Unknown option --${OPTARG}" >&2 + fi + ;; + esac;; *) - echo "Usage: $0 [-x|-m] [-G] [-w] [-k] [-j] [-v] [-r] [-e] [-l libs] [-f frontend] [-s script] [-p cmdstring] [-n iters] [-S seed] [-I incdir] verilog-files\n" >&2 + echo "Usage: $0 [-x|-m] [-G] [-w] [-k] [-j] [-v] [-r] [-e] [-l libs] [-f frontend] [-s script] [-p cmdstring] [-n iters] [-S seed] [-I incdir] [--xfirrtl FIRRTL test exclude file] [--firrtl2verilog command to generate verilog from firrtl] verilog-files\n" >&2 exit 1 esac done @@ -109,6 +131,8 @@ do fn=$(basename $fn) bn=$(basename $bn) + rm -f ${bn}_ref.fir + egrep -v '^\s*`timescale' ../$fn > ${bn}_ref.v if [ ! -f ../${bn}_tb.v ]; then @@ -148,6 +172,13 @@ do else test_passes -f "$frontend $include_opts" -p "hierarchy; proc; opt; memory; opt; fsm; opt -full -fine" ${bn}_ref.v test_passes -f "$frontend $include_opts" -p "hierarchy; synth -run coarse; techmap; opt; abc -dff" ${bn}_ref.v + if [ -n "$firrtl2verilog" ]; then + if test -z "$xfirrtl" || ! grep "$fn" "$xfirrtl" ; then + "$toolsdir"/../../yosys -b "firrtl" -o ${bn}_ref.fir -f "$frontend $include_opts" -p "prep -nordff; proc; opt; memory; opt; fsm; opt -full -fine; pmuxtree" ${bn}_ref.v + $firrtl2verilog -i ${bn}_ref.fir -o ${bn}_ref.fir.v -X verilog + test_passes -f "$frontend $include_opts" -p "hierarchy; proc; opt; memory; opt; fsm; opt -full -fine" ${bn}_ref.fir.v + fi + fi fi touch ../${bn}.log } @@ -160,14 +191,18 @@ do ( set -ex; body; ) > ${bn}.err 2>&1 fi + did_firrtl="" + if [ -f ${bn}.out/${bn}_ref.fir ]; then + did_firrtl="+FIRRTL " + fi if [ -f ${bn}.log ]; then mv ${bn}.err ${bn}.log - echo "${status_prefix}-> ok" + echo "${status_prefix}${did_firrtl}-> ok" elif [ -f ${bn}.skip ]; then mv ${bn}.err ${bn}.skip echo "${status_prefix}-> skip" else - echo "${status_prefix}-> ERROR!" + echo "${status_prefix}${did_firrtl}-> ERROR!" if $warn_iverilog_git; then echo "Note: Make sure that 'iverilog' is an up-to-date git checkout of Icarus Verilog." fi diff --git a/tests/various/hierarchy.sh b/tests/various/hierarchy.sh new file mode 100644 index 000000000..d33a247be --- /dev/null +++ b/tests/various/hierarchy.sh @@ -0,0 +1,59 @@ +#!/usr/bin/env bash +# Simple test of hierarchy -auto-top. + +set -e + +echo -n " TOP first - " +../../yosys -s - <<- EOY | grep "Automatically selected TOP as design top module" + read_verilog << EOV + module TOP(a, y); + input a; + output [31:0] y; + + aoi12 p [31:0] (a, y); + endmodule + + module aoi12(a, y); + input a; + output y; + assign y = ~a; + endmodule + EOV + hierarchy -auto-top +EOY + +echo -n " TOP last - " +../../yosys -s - <<- EOY | grep "Automatically selected TOP as design top module" + read_verilog << EOV + module aoi12(a, y); + input a; + output y; + assign y = ~a; + endmodule + + module TOP(a, y); + input a; + output [31:0] y; + + aoi12 foo (a, y); + endmodule + EOV + hierarchy -auto-top +EOY + +echo -n " no explicit top - " +../../yosys -s - <<- EOY | grep "Automatically selected noTop as design top module." + read_verilog << EOV + module aoi12(a, y); + input a; + output y; + assign y = ~a; + endmodule + + module noTop(a, y); + input a; + output [31:0] y; + endmodule + EOV + hierarchy -auto-top +EOY diff --git a/tests/various/run-test.sh b/tests/various/run-test.sh index 67e1beb23..d49553ede 100755 --- a/tests/various/run-test.sh +++ b/tests/various/run-test.sh @@ -1,6 +1,14 @@ -#!/bin/bash +#!/usr/bin/env bash set -e for x in *.ys; do echo "Running $x.." ../../yosys -ql ${x%.ys}.log $x done +# Run any .sh files in this directory (with the exception of the file - run-test.sh +shell_tests=$(echo *.sh | sed -e 's/run-test.sh//') +if [ "$shell_tests" ]; then + for s in $shell_tests; do + echo "Running $s.." + bash $s + done +fi