diff options
45 files changed, 1062 insertions, 509 deletions
diff --git a/.editorconfig b/.editorconfig index 4d6f5ef7a..f5444d81a 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,3 +5,8 @@ indent_style = tab indent_size = tab trim_trailing_whitespace = true insert_final_newline = true + +[abc/**] +indent_style = space +indent_size = 2 +trim_trailing_whitespace = false @@ -8,7 +8,7 @@ Yosys 0.9 .. Yosys 0.9-dev * Various - Added "write_xaiger" backend - - Added "abc9" pass for timing-aware techmapping (experimental, FPGA only, no FFs) + - Added "abc9" pass for timing-aware techmapping (experimental, FPGA only) - Added "synth_xilinx -abc9" (experimental) - Added "synth_ice40 -abc9" (experimental) - Added "synth -abc9" (experimental) @@ -58,7 +58,6 @@ Yosys 0.9 .. Yosys 0.9-dev - Added support for SystemVerilog wildcard port connections (.*) - Added "xilinx_dffopt" pass - Added "scratchpad" pass - - Added "abc9 -dff" - Added "synth_xilinx -dff" - Improved support of $readmem[hb] Memory Content File inclusion - Added "opt_lut_ins" pass @@ -66,6 +65,7 @@ Yosys 0.9 .. Yosys 0.9-dev - Removed "dffsr2dff" (use opt_rmdff instead) - Added "design -delete" - Added "select -unset" + - Use YosysHQ/abc instead of upstream berkeley-abc/abc Yosys 0.8 .. Yosys 0.9 ---------------------- @@ -133,9 +133,9 @@ bumpversion: # is just a symlink to your actual ABC working directory, as 'make mrproper' # will remove the 'abc' directory and you do not want to accidentally # delete your work on ABC.. -ABCREV = ed90ce2 +ABCREV = d14acd8 ABCPULL = 1 -ABCURL ?= https://github.com/berkeley-abc/abc +ABCURL ?= https://github.com/YosysHQ/abc ABCMKARGS = CC="$(CXX)" CXX="$(CXX)" ABC_USE_LIBSTDCXX=1 # set ABCEXTERNAL = <abc-command> to use an external ABC instance diff --git a/backends/cxxrtl/cxxrtl.cc b/backends/cxxrtl/cxxrtl.cc index ef8335e50..e7711962f 100644 --- a/backends/cxxrtl/cxxrtl.cc +++ b/backends/cxxrtl/cxxrtl.cc @@ -171,6 +171,11 @@ struct Scheduler { } }; +bool is_input_wire(const RTLIL::Wire *wire) +{ + return wire->port_input && !wire->port_output; +} + bool is_unary_cell(RTLIL::IdString type) { return type.in( @@ -207,14 +212,57 @@ bool is_ff_cell(RTLIL::IdString type) bool is_internal_cell(RTLIL::IdString type) { - return type[0] == '$' && !type.begins_with("$paramod\\"); + return type[0] == '$' && !type.begins_with("$paramod"); +} + +bool is_cxxrtl_blackbox_cell(const RTLIL::Cell *cell) +{ + RTLIL::Module *cell_module = cell->module->design->module(cell->type); + log_assert(cell_module != nullptr); + return cell_module->get_bool_attribute(ID(cxxrtl_blackbox)); +} + +enum class CxxrtlPortType { + UNKNOWN = 0, // or mixed comb/sync + COMB = 1, + SYNC = 2, +}; + +CxxrtlPortType cxxrtl_port_type(const RTLIL::Cell *cell, RTLIL::IdString port) +{ + RTLIL::Module *cell_module = cell->module->design->module(cell->type); + if (cell_module == nullptr || !cell_module->get_bool_attribute(ID(cxxrtl_blackbox))) + return CxxrtlPortType::UNKNOWN; + RTLIL::Wire *cell_output_wire = cell_module->wire(port); + log_assert(cell_output_wire != nullptr); + bool is_comb = cell_output_wire->get_bool_attribute(ID(cxxrtl_comb)); + bool is_sync = cell_output_wire->get_bool_attribute(ID(cxxrtl_sync)); + if (is_comb && is_sync) + log_cmd_error("Port `%s.%s' is marked as both `cxxrtl_comb` and `cxxrtl_sync`.\n", + log_id(cell_module), log_signal(cell_output_wire)); + else if (is_comb) + return CxxrtlPortType::COMB; + else if (is_sync) + return CxxrtlPortType::SYNC; + return CxxrtlPortType::UNKNOWN; +} + +bool is_cxxrtl_comb_port(const RTLIL::Cell *cell, RTLIL::IdString port) +{ + return cxxrtl_port_type(cell, port) == CxxrtlPortType::COMB; +} + +bool is_cxxrtl_sync_port(const RTLIL::Cell *cell, RTLIL::IdString port) +{ + return cxxrtl_port_type(cell, port) == CxxrtlPortType::SYNC; } struct FlowGraph { struct Node { enum class Type { CONNECT, - CELL, + CELL_SYNC, + CELL_EVAL, PROCESS }; @@ -225,7 +273,7 @@ struct FlowGraph { }; std::vector<Node*> nodes; - dict<const RTLIL::Wire*, pool<Node*, hash_ptr_ops>> wire_defs, wire_uses; + dict<const RTLIL::Wire*, pool<Node*, hash_ptr_ops>> wire_comb_defs, wire_sync_defs, wire_uses; dict<const RTLIL::Wire*, bool> wire_def_elidable, wire_use_elidable; ~FlowGraph() @@ -234,13 +282,17 @@ struct FlowGraph { delete node; } - void add_defs(Node *node, const RTLIL::SigSpec &sig, bool elidable) + void add_defs(Node *node, const RTLIL::SigSpec &sig, bool fully_sync, bool elidable) { for (auto chunk : sig.chunks()) - if (chunk.wire) - wire_defs[chunk.wire].insert(node); - // Only defs of an entire wire in the right order can be elided. - if (sig.is_wire()) + if (chunk.wire) { + if (fully_sync) + wire_sync_defs[chunk.wire].insert(node); + else + wire_comb_defs[chunk.wire].insert(node); + } + // Only comb defs of an entire wire in the right order can be elided. + if (!fully_sync && sig.is_wire()) wire_def_elidable[sig.as_wire()] = elidable; } @@ -268,7 +320,7 @@ struct FlowGraph { // Connections void add_connect_defs_uses(Node *node, const RTLIL::SigSig &conn) { - add_defs(node, conn.first, /*elidable=*/true); + add_defs(node, conn.first, /*fully_sync=*/false, /*elidable=*/true); add_uses(node, conn.second); } @@ -283,21 +335,59 @@ struct FlowGraph { } // Cells - void add_cell_defs_uses(Node *node, const RTLIL::Cell *cell) + void add_cell_sync_defs(Node *node, const RTLIL::Cell *cell) + { + // To understand why this node type is necessary and why it produces comb defs, consider a cell + // with input \i and sync output \o, used in a design such that \i is connected to \o. This does + // not result in a feedback arc because the output is synchronous. However, a naive implementation + // of code generation for cells that assigns to inputs, evaluates cells, assigns from outputs + // would not be able to immediately converge... + // + // wire<1> i_tmp; + // cell->p_i = i_tmp.curr; + // cell->eval(); + // i_tmp.next = cell->p_o.curr; + // + // ... since the wire connecting the input and output ports would not be localizable. To solve + // this, the cell is split into two scheduling nodes; one exclusively for sync outputs, and + // another for inputs and all non-sync outputs. This way the generated code can be rearranged... + // + // value<1> i_tmp; + // i_tmp = cell->p_o.curr; + // cell->p_i = i_tmp; + // cell->eval(); + // + // eliminating the unnecessary delta cycle. Conceptually, the CELL_SYNC node type is a series of + // connections of the form `connect \lhs \cell.\sync_output`; the right-hand side of these is not + // as a wire in RTLIL. If it was expressible, then `\cell.\sync_output` would have a sync def, + // and this node would be an ordinary CONNECT node, with `\lhs` having a comb def. Because it isn't, + // a special node type is used, the right-hand side does not appear anywhere, and the left-hand + // side has a comb def. + for (auto conn : cell->connections()) + if (cell->output(conn.first)) + if (is_cxxrtl_sync_port(cell, conn.first)) { + // See note regarding elidability below. + add_defs(node, conn.second, /*fully_sync=*/false, /*elidable=*/false); + } + } + + void add_cell_eval_defs_uses(Node *node, const RTLIL::Cell *cell) { - log_assert(cell->known()); for (auto conn : cell->connections()) { if (cell->output(conn.first)) { - if (is_sync_ff_cell(cell->type) || (cell->type == ID($memrd) && cell->getParam(ID::CLK_ENABLE).as_bool())) - /* non-combinatorial outputs do not introduce defs */; - else if (is_elidable_cell(cell->type)) - add_defs(node, conn.second, /*elidable=*/true); + if (is_elidable_cell(cell->type)) + add_defs(node, conn.second, /*fully_sync=*/false, /*elidable=*/true); + else if (is_sync_ff_cell(cell->type) || (cell->type == ID($memrd) && cell->getParam(ID::CLK_ENABLE).as_bool())) + add_defs(node, conn.second, /*fully_sync=*/true, /*elidable=*/false); else if (is_internal_cell(cell->type)) - add_defs(node, conn.second, /*elidable=*/false); - else { - // Unlike outputs of internal cells (which generate code that depends on the ability to set the output - // wire bits), outputs of user cells are normal wires, and the wires connected to them can be elided. - add_defs(node, conn.second, /*elidable=*/true); + add_defs(node, conn.second, /*fully_sync=*/false, /*elidable=*/false); + else if (!is_cxxrtl_sync_port(cell, conn.first)) { + // Although at first it looks like outputs of user-defined cells may always be elided, the reality is + // more complex. Fully sync outputs produce no defs and so don't participate in elision. Fully comb + // outputs are assigned in a different way depending on whether the cell's eval() immediately converged. + // Unknown/mixed outputs could be elided, but should be rare in practical designs and don't justify + // the infrastructure required to elide outputs of cells with many of them. + add_defs(node, conn.second, /*fully_sync=*/false, /*elidable=*/false); } } if (cell->input(conn.first)) @@ -307,11 +397,27 @@ struct FlowGraph { Node *add_node(const RTLIL::Cell *cell) { + log_assert(cell->known()); + + bool has_fully_sync_outputs = false; + for (auto conn : cell->connections()) + if (cell->output(conn.first) && is_cxxrtl_sync_port(cell, conn.first)) { + has_fully_sync_outputs = true; + break; + } + if (has_fully_sync_outputs) { + Node *node = new Node; + node->type = Node::Type::CELL_SYNC; + node->cell = cell; + nodes.push_back(node); + add_cell_sync_defs(node, cell); + } + Node *node = new Node; - node->type = Node::Type::CELL; + node->type = Node::Type::CELL_EVAL; node->cell = cell; nodes.push_back(node); - add_cell_defs_uses(node, cell); + add_cell_eval_defs_uses(node, cell); return node; } @@ -319,7 +425,7 @@ struct FlowGraph { void add_case_defs_uses(Node *node, const RTLIL::CaseRule *case_) { for (auto &action : case_->actions) { - add_defs(node, action.first, /*elidable=*/false); + add_defs(node, action.first, /*is_sync=*/false, /*elidable=*/false); add_uses(node, action.second); } for (auto sub_switch : case_->switches) { @@ -338,9 +444,9 @@ struct FlowGraph { for (auto sync : process->syncs) for (auto action : sync->actions) { if (sync->type == RTLIL::STp || sync->type == RTLIL::STn || sync->type == RTLIL::STe) - /* sync actions do not introduce feedback */; + add_defs(node, action.first, /*is_sync=*/true, /*elidable=*/false); else - add_defs(node, action.first, /*elidable=*/false); + add_defs(node, action.first, /*is_sync=*/false, /*elidable=*/false); add_uses(node, action.second); } } @@ -356,13 +462,6 @@ struct FlowGraph { } }; -bool is_cxxrtl_blackbox_cell(const RTLIL::Cell *cell) -{ - RTLIL::Module *cell_module = cell->module->design->module(cell->type); - log_assert(cell_module != nullptr); - return cell_module->get_bool_attribute(ID(cxxrtl.blackbox)); -} - std::vector<std::string> split_by(const std::string &str, const std::string &sep) { std::vector<std::string> result; @@ -414,22 +513,24 @@ struct CxxrtlWorker { bool elide_public = false; bool localize_internal = false; bool localize_public = false; - bool run_splitnets = false; + bool run_opt_clean_purge = false; + bool run_proc_flatten = false; + bool max_opt_level = false; std::ostringstream f; std::string indent; int temporary = 0; dict<const RTLIL::Module*, SigMap> sigmaps; - pool<const RTLIL::Wire*> sync_wires; - dict<RTLIL::SigBit, RTLIL::SyncType> sync_types; + pool<const RTLIL::Wire*> edge_wires; + dict<RTLIL::SigBit, RTLIL::SyncType> edge_types; pool<const RTLIL::Memory*> writable_memories; dict<const RTLIL::Cell*, pool<const RTLIL::Cell*>> transparent_for; - dict<const RTLIL::Cell*, dict<RTLIL::Wire*, RTLIL::IdString>> cell_wire_defs; dict<const RTLIL::Wire*, FlowGraph::Node> elided_wires; dict<const RTLIL::Module*, std::vector<FlowGraph::Node>> schedule; pool<const RTLIL::Wire*> localized_wires; dict<const RTLIL::Module*, pool<std::string>> blackbox_specializations; + dict<const RTLIL::Module*, bool> eval_converges; void inc_indent() { indent += "\t"; @@ -505,7 +606,7 @@ struct CxxrtlWorker { std::string mangle(const RTLIL::Module *module) { - return mangle_module_name(module->name, /*is_blackbox=*/module->get_bool_attribute(ID(cxxrtl.blackbox))); + return mangle_module_name(module->name, /*is_blackbox=*/module->get_bool_attribute(ID(cxxrtl_blackbox))); } std::string mangle(const RTLIL::Memory *memory) @@ -533,19 +634,19 @@ struct CxxrtlWorker { std::vector<std::string> template_param_names(const RTLIL::Module *module) { - if (!module->has_attribute(ID(cxxrtl.template))) + if (!module->has_attribute(ID(cxxrtl_template))) return {}; - if (module->attributes.at(ID(cxxrtl.template)).flags != RTLIL::CONST_FLAG_STRING) - log_cmd_error("Attribute `cxxrtl.template' of module `%s' is not a string.\n", log_id(module)); + if (module->attributes.at(ID(cxxrtl_template)).flags != RTLIL::CONST_FLAG_STRING) + log_cmd_error("Attribute `cxxrtl_template' of module `%s' is not a string.\n", log_id(module)); - std::vector<std::string> param_names = split_by(module->get_string_attribute(ID(cxxrtl.template)), " \t"); + std::vector<std::string> param_names = split_by(module->get_string_attribute(ID(cxxrtl_template)), " \t"); for (const auto ¶m_name : param_names) { // Various lowercase prefixes (p_, i_, cell_, ...) are used for member variables, so require // parameters to start with an uppercase letter to avoid name conflicts. (This is the convention // in both Verilog and C++, anyway.) if (!isupper(param_name[0])) - log_cmd_error("Attribute `cxxrtl.template' of module `%s' includes a parameter `%s', " + log_cmd_error("Attribute `cxxrtl_template' of module `%s' includes a parameter `%s', " "which does not start with an uppercase letter.\n", log_id(module), param_name.c_str()); } @@ -576,7 +677,7 @@ struct CxxrtlWorker { { RTLIL::Module *cell_module = cell->module->design->module(cell->type); log_assert(cell_module != nullptr); - if (!cell_module->get_bool_attribute(ID(cxxrtl.blackbox))) + if (!cell_module->get_bool_attribute(ID(cxxrtl_blackbox))) return ""; std::vector<std::string> param_names = template_param_names(cell_module); @@ -625,12 +726,13 @@ struct CxxrtlWorker { void dump_const_init(const RTLIL::Const &data, int width, int offset = 0, bool fixed_width = false) { + const int CHUNK_SIZE = 32; f << "{"; while (width > 0) { - const int CHUNK_SIZE = 32; - uint32_t chunk = data.extract(offset, width > CHUNK_SIZE ? CHUNK_SIZE : width).as_int(); + int chunk_width = min(width, CHUNK_SIZE); + uint32_t chunk = data.extract(offset, chunk_width).as_int(); if (fixed_width) - f << stringf("0x%08xu", chunk); + f << stringf("0x%.*xu", (3 + chunk_width) / 4, chunk); else f << stringf("%#xu", chunk); if (width > CHUNK_SIZE) @@ -669,18 +771,14 @@ struct CxxrtlWorker { case FlowGraph::Node::Type::CONNECT: dump_connect_elided(node.connect); break; - case FlowGraph::Node::Type::CELL: - if (is_elidable_cell(node.cell->type)) { - dump_cell_elided(node.cell); - } else { - const char *access = is_cxxrtl_blackbox_cell(node.cell) ? "->" : "."; - f << mangle(node.cell) << access << mangle_wire_name(cell_wire_defs[node.cell][chunk.wire]) << ".curr"; - } + case FlowGraph::Node::Type::CELL_EVAL: + log_assert(is_elidable_cell(node.cell->type)); + dump_cell_elided(node.cell); break; default: log_assert(false); } - } else if (localized_wires[chunk.wire]) { + } else if (localized_wires[chunk.wire] || is_input_wire(chunk.wire)) { f << mangle(chunk.wire); } else { f << mangle(chunk.wire) << (is_lhs ? ".next" : ".curr"); @@ -740,8 +838,8 @@ struct CxxrtlWorker { case FlowGraph::Node::Type::CONNECT: collect_connect(node.connect, cells); break; - case FlowGraph::Node::Type::CELL: - collect_cell(node.cell, cells); + case FlowGraph::Node::Type::CELL_EVAL: + collect_cell_eval(node.cell, cells); break; default: log_assert(false); @@ -780,6 +878,19 @@ struct CxxrtlWorker { f << ";\n"; } + void dump_cell_sync(const RTLIL::Cell *cell) + { + const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : "."; + f << indent << "// cell " << cell->name.str() << " syncs\n"; + for (auto conn : cell->connections()) + if (cell->output(conn.first)) + if (is_cxxrtl_sync_port(cell, conn.first)) { + f << indent; + dump_sigspec_lhs(conn.second); + f << " = " << mangle(cell) << access << mangle_wire_name(conn.first) << ".curr;\n"; + } + } + void dump_cell_elided(const RTLIL::Cell *cell) { // Unary cells @@ -833,7 +944,7 @@ struct CxxrtlWorker { elided_wires.count(cell->getPort(ID::Y).as_wire()); } - void collect_cell(const RTLIL::Cell *cell, std::vector<RTLIL::IdString> &cells) + void collect_cell_eval(const RTLIL::Cell *cell, std::vector<RTLIL::IdString> &cells) { if (!is_cell_elided(cell)) return; @@ -844,7 +955,7 @@ struct CxxrtlWorker { collect_sigspec_rhs(port.second, cells); } - void dump_cell(const RTLIL::Cell *cell) + void dump_cell_eval(const RTLIL::Cell *cell) { if (is_cell_elided(cell)) return; @@ -1088,26 +1199,69 @@ struct CxxrtlWorker { log_assert(cell->known()); const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : "."; for (auto conn : cell->connections()) - if (cell->input(conn.first)) { + if (cell->input(conn.first) && !cell->output(conn.first)) { + f << indent << mangle(cell) << access << mangle_wire_name(conn.first) << " = "; + dump_sigspec_rhs(conn.second); + f << ";\n"; + if (getenv("CXXRTL_VOID_MY_WARRANTY")) { + // Until we have proper clock tree detection, this really awful hack that opportunistically + // propagates prev_* values for clocks can be used to estimate how much faster a design could + // be if only one clock edge was simulated by replacing: + // top.p_clk = value<1>{0u}; top.step(); + // top.p_clk = value<1>{1u}; top.step(); + // with: + // top.prev_p_clk = value<1>{0u}; top.p_clk = value<1>{1u}; top.step(); + // Don't rely on this; it will be removed without warning. + RTLIL::Module *cell_module = cell->module->design->module(cell->type); + if (cell_module != nullptr && cell_module->wire(conn.first) && conn.second.is_wire()) { + RTLIL::Wire *cell_module_wire = cell_module->wire(conn.first); + if (edge_wires[conn.second.as_wire()] && edge_wires[cell_module_wire]) { + f << indent << mangle(cell) << access << "prev_" << mangle(cell_module_wire) << " = "; + f << "prev_" << mangle(conn.second.as_wire()) << ";\n"; + } + } + } + } else if (cell->input(conn.first)) { f << indent << mangle(cell) << access << mangle_wire_name(conn.first) << ".next = "; dump_sigspec_rhs(conn.second); f << ";\n"; } - f << indent << mangle(cell) << access << "eval();\n"; - for (auto conn : cell->connections()) { - if (conn.second.is_wire()) { - RTLIL::Wire *wire = conn.second.as_wire(); - if (elided_wires.count(wire) && cell_wire_defs[cell].count(wire)) - continue; - } - if (cell->output(conn.first)) { - if (conn.second.empty()) - continue; // ignore disconnected ports - f << indent; - dump_sigspec_lhs(conn.second); - f << " = " << mangle(cell) << access << mangle_wire_name(conn.first) << ".curr;\n"; + auto assign_from_outputs = [&](bool cell_converged) { + for (auto conn : cell->connections()) { + if (cell->output(conn.first)) { + if (conn.second.empty()) + continue; // ignore disconnected ports + if (is_cxxrtl_sync_port(cell, conn.first)) + continue; // fully sync ports are handled in CELL_SYNC nodes + f << indent; + dump_sigspec_lhs(conn.second); + f << " = " << mangle(cell) << access << mangle_wire_name(conn.first); + // Similarly to how there is no purpose to buffering cell inputs, there is also no purpose to buffering + // combinatorial cell outputs in case the cell converges within one cycle. (To convince yourself that + // this optimization is valid, consider that, since the cell converged within one cycle, it would not + // have any buffered wires if they were not output ports. Imagine inlining the cell's eval() function, + // and consider the fate of the localized wires that used to be output ports.) + // + // Unlike cell inputs (which are never buffered), it is not possible to know apriori whether the cell + // (which may be late bound) will converge immediately. Because of this, the choice between using .curr + // (appropriate for buffered outputs) and .next (appropriate for unbuffered outputs) is made at runtime. + if (cell_converged && is_cxxrtl_comb_port(cell, conn.first)) + f << ".next;\n"; + else + f << ".curr;\n"; + } } - } + }; + f << indent << "if (" << mangle(cell) << access << "eval()) {\n"; + inc_indent(); + assign_from_outputs(/*cell_converged=*/true); + dec_indent(); + f << indent << "} else {\n"; + inc_indent(); + f << indent << "converged = false;\n"; + assign_from_outputs(/*cell_converged=*/false); + dec_indent(); + f << indent << "}\n"; } } @@ -1253,42 +1407,66 @@ struct CxxrtlWorker { } } - void dump_wire(const RTLIL::Wire *wire, bool is_local) + void dump_wire(const RTLIL::Wire *wire, bool is_local_context) { if (elided_wires.count(wire)) return; + if (localized_wires.count(wire) != is_local_context) + return; - if (is_local) { - if (!localized_wires.count(wire)) - return; - + if (is_local_context) { dump_attrs(wire); f << indent << "value<" << wire->width << "> " << mangle(wire) << ";\n"; } else { - if (localized_wires.count(wire)) - return; - std::string width; - if (wire->module->has_attribute(ID(cxxrtl.blackbox)) && wire->has_attribute(ID(cxxrtl.width))) { - width = wire->get_string_attribute(ID(cxxrtl.width)); + if (wire->module->has_attribute(ID(cxxrtl_blackbox)) && wire->has_attribute(ID(cxxrtl_width))) { + width = wire->get_string_attribute(ID(cxxrtl_width)); } else { width = std::to_string(wire->width); } dump_attrs(wire); - f << indent << "wire<" << width << "> " << mangle(wire); + f << indent << (is_input_wire(wire) ? "value" : "wire") << "<" << width << "> " << mangle(wire); if (wire->has_attribute(ID::init)) { f << " "; dump_const_init(wire->attributes.at(ID::init)); } f << ";\n"; - if (sync_wires[wire]) { - for (auto sync_type : sync_types) { - if (sync_type.first.wire == wire) { - if (sync_type.second != RTLIL::STn) - f << indent << "bool posedge_" << mangle(sync_type.first) << " = false;\n"; - if (sync_type.second != RTLIL::STp) - f << indent << "bool negedge_" << mangle(sync_type.first) << " = false;\n"; + if (edge_wires[wire]) { + if (is_input_wire(wire)) { + f << indent << "value<" << width << "> prev_" << mangle(wire); + if (wire->has_attribute(ID::init)) { + f << " "; + dump_const_init(wire->attributes.at(ID::init)); + } + f << ";\n"; + } + for (auto edge_type : edge_types) { + if (edge_type.first.wire == wire) { + std::string prev, next; + if (is_input_wire(wire)) { + prev = "prev_" + mangle(edge_type.first.wire); + next = mangle(edge_type.first.wire); + } else { + prev = mangle(edge_type.first.wire) + ".curr"; + next = mangle(edge_type.first.wire) + ".next"; + } + prev += ".slice<" + std::to_string(edge_type.first.offset) + ">().val()"; + next += ".slice<" + std::to_string(edge_type.first.offset) + ">().val()"; + if (edge_type.second != RTLIL::STn) { + f << indent << "bool posedge_" << mangle(edge_type.first) << "() const {\n"; + inc_indent(); + f << indent << "return !" << prev << " && " << next << ";\n"; + dec_indent(); + f << indent << "}\n"; + } + if (edge_type.second != RTLIL::STp) { + f << indent << "bool negedge_" << mangle(edge_type.first) << "() const {\n"; + inc_indent(); + f << indent << "return " << prev << " && !" << next << ";\n"; + dec_indent(); + f << indent << "}\n"; + } } } } @@ -1343,16 +1521,36 @@ struct CxxrtlWorker { void dump_eval_method(RTLIL::Module *module) { inc_indent(); - if (!module->get_bool_attribute(ID(cxxrtl.blackbox))) { + f << indent << "bool converged = " << (eval_converges.at(module) ? "true" : "false") << ";\n"; + if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) { + for (auto wire : module->wires()) { + if (edge_wires[wire]) { + for (auto edge_type : edge_types) { + if (edge_type.first.wire == wire) { + if (edge_type.second != RTLIL::STn) { + f << indent << "bool posedge_" << mangle(edge_type.first) << " = "; + f << "this->posedge_" << mangle(edge_type.first) << "();\n"; + } + if (edge_type.second != RTLIL::STp) { + f << indent << "bool negedge_" << mangle(edge_type.first) << " = "; + f << "this->negedge_" << mangle(edge_type.first) << "();\n"; + } + } + } + } + } for (auto wire : module->wires()) - dump_wire(wire, /*is_local=*/true); + dump_wire(wire, /*is_local_context=*/true); for (auto node : schedule[module]) { switch (node.type) { case FlowGraph::Node::Type::CONNECT: dump_connect(node.connect); break; - case FlowGraph::Node::Type::CELL: - dump_cell(node.cell); + case FlowGraph::Node::Type::CELL_SYNC: + dump_cell_sync(node.cell); + break; + case FlowGraph::Node::Type::CELL_EVAL: + dump_cell_eval(node.cell); break; case FlowGraph::Node::Type::PROCESS: dump_process(node.process); @@ -1360,14 +1558,7 @@ struct CxxrtlWorker { } } } - for (auto sync_type : sync_types) { - if (sync_type.first.wire->module == module) { - if (sync_type.second != RTLIL::STn) - f << indent << "posedge_" << mangle(sync_type.first) << " = false;\n"; - if (sync_type.second != RTLIL::STp) - f << indent << "negedge_" << mangle(sync_type.first) << " = false;\n"; - } - } + f << indent << "return converged;\n"; dec_indent(); } @@ -1378,41 +1569,15 @@ struct CxxrtlWorker { for (auto wire : module->wires()) { if (elided_wires.count(wire) || localized_wires.count(wire)) continue; - if (sync_wires[wire]) { - std::string wire_prev = mangle(wire) + "_prev"; - std::string wire_curr = mangle(wire) + ".curr"; - std::string wire_edge = mangle(wire) + "_edge"; - f << indent << "value<" << wire->width << "> " << wire_prev << " = " << wire_curr << ";\n"; - f << indent << "if (" << mangle(wire) << ".commit()) {\n"; - inc_indent(); - f << indent << "value<" << wire->width << "> " << wire_edge << " = " - << wire_prev << ".bit_xor(" << wire_curr << ");\n"; - for (auto sync_type : sync_types) { - if (sync_type.first.wire != wire) - continue; - if (sync_type.second != RTLIL::STn) { - f << indent << "if (" << wire_edge << ".slice<" << sync_type.first.offset << ">().val() && " - << wire_curr << ".slice<" << sync_type.first.offset << ">().val())\n"; - inc_indent(); - f << indent << "posedge_" << mangle(sync_type.first) << " = true;\n"; - dec_indent(); - } - if (sync_type.second != RTLIL::STp) { - f << indent << "if (" << wire_edge << ".slice<" << sync_type.first.offset << ">().val() && " - << "!" << wire_curr << ".slice<" << sync_type.first.offset << ">().val())\n"; - inc_indent(); - f << indent << "negedge_" << mangle(sync_type.first) << " = true;\n"; - dec_indent(); - } - f << indent << "changed = true;\n"; - } - dec_indent(); - f << indent << "}\n"; - } else if (!module->get_bool_attribute(ID(cxxrtl.blackbox)) || wire->port_id != 0) { - f << indent << "changed |= " << mangle(wire) << ".commit();\n"; + if (is_input_wire(wire)) { + if (edge_wires[wire]) + f << indent << "prev_" << mangle(wire) << " = " << mangle(wire) << ";\n"; + continue; } + if (!module->get_bool_attribute(ID(cxxrtl_blackbox)) || wire->port_id != 0) + f << indent << "changed |= " << mangle(wire) << ".commit();\n"; } - if (!module->get_bool_attribute(ID(cxxrtl.blackbox))) { + if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) { for (auto memory : module->memories) { if (!writable_memories[memory.second]) continue; @@ -1459,17 +1624,17 @@ struct CxxrtlWorker { void dump_module_intf(RTLIL::Module *module) { dump_attrs(module); - if (module->get_bool_attribute(ID(cxxrtl.blackbox))) { - if (module->has_attribute(ID(cxxrtl.template))) + if (module->get_bool_attribute(ID(cxxrtl_blackbox))) { + if (module->has_attribute(ID(cxxrtl_template))) f << indent << "template" << template_params(module, /*is_decl=*/true) << "\n"; f << indent << "struct " << mangle(module) << " : public module {\n"; inc_indent(); for (auto wire : module->wires()) { if (wire->port_id != 0) - dump_wire(wire, /*is_local=*/false); + dump_wire(wire, /*is_local_context=*/false); } f << "\n"; - f << indent << "void eval() override {\n"; + f << indent << "bool eval() override {\n"; dump_eval_method(module); f << indent << "}\n"; f << "\n"; @@ -1506,7 +1671,7 @@ struct CxxrtlWorker { f << indent << "struct " << mangle(module) << " : public module {\n"; inc_indent(); for (auto wire : module->wires()) - dump_wire(wire, /*is_local=*/false); + dump_wire(wire, /*is_local_context=*/false); f << "\n"; bool has_memories = false; for (auto memory : module->memories) { @@ -1522,7 +1687,7 @@ struct CxxrtlWorker { dump_attrs(cell); RTLIL::Module *cell_module = module->design->module(cell->type); log_assert(cell_module != nullptr); - if (cell_module->get_bool_attribute(ID(cxxrtl.blackbox))) { + if (cell_module->get_bool_attribute(ID(cxxrtl_blackbox))) { f << indent << "std::unique_ptr<" << mangle(cell_module) << template_args(cell) << "> "; f << mangle(cell) << " = " << mangle(cell_module) << template_args(cell); f << "::create(" << escape_cxx_string(cell->name.str()) << ", "; @@ -1537,7 +1702,7 @@ struct CxxrtlWorker { } if (has_cells) f << "\n"; - f << indent << "void eval() override;\n"; + f << indent << "bool eval() override;\n"; f << indent << "bool commit() override;\n"; dec_indent(); f << indent << "}; // struct " << mangle(module) << "\n"; @@ -1547,9 +1712,9 @@ struct CxxrtlWorker { void dump_module_impl(RTLIL::Module *module) { - if (module->get_bool_attribute(ID(cxxrtl.blackbox))) + if (module->get_bool_attribute(ID(cxxrtl_blackbox))) return; - f << indent << "void " << mangle(module) << "::eval() {\n"; + f << indent << "bool " << mangle(module) << "::eval() {\n"; dump_eval_method(module); f << indent << "}\n"; f << "\n"; @@ -1566,9 +1731,9 @@ struct CxxrtlWorker { for (auto module : design->modules()) { if (!design->selected_module(module)) continue; - if (module->get_bool_attribute(ID(cxxrtl.blackbox))) + if (module->get_bool_attribute(ID(cxxrtl_blackbox))) modules.push_back(module); // cxxrtl blackboxes first - if (module->get_blackbox_attribute() || module->get_bool_attribute(ID(cxxrtl.blackbox))) + if (module->get_blackbox_attribute() || module->get_bool_attribute(ID(cxxrtl_blackbox))) continue; topo_design.node(module); @@ -1638,16 +1803,18 @@ struct CxxrtlWorker { log_assert(type == RTLIL::STp || type == RTLIL::STn || type == RTLIL::STe); RTLIL::SigBit sigbit = signal[0]; - if (!sync_types.count(sigbit)) - sync_types[sigbit] = type; - else if (sync_types[sigbit] != type) - sync_types[sigbit] = RTLIL::STe; - sync_wires.insert(signal.as_wire()); + if (!edge_types.count(sigbit)) + edge_types[sigbit] = type; + else if (edge_types[sigbit] != type) + edge_types[sigbit] = RTLIL::STe; + edge_wires.insert(signal.as_wire()); } void analyze_design(RTLIL::Design *design) { bool has_feedback_arcs = false; + bool has_buffered_wires = false; + for (auto module : design->modules()) { if (!design->selected_module(module)) continue; @@ -1655,16 +1822,16 @@ struct CxxrtlWorker { SigMap &sigmap = sigmaps[module]; sigmap.set(module); - if (module->get_bool_attribute(ID(cxxrtl.blackbox))) { + if (module->get_bool_attribute(ID(cxxrtl_blackbox))) { for (auto port : module->ports) { RTLIL::Wire *wire = module->wire(port); - if (wire->has_attribute(ID(cxxrtl.edge))) { - RTLIL::Const edge_attr = wire->attributes[ID(cxxrtl.edge)]; + if (wire->has_attribute(ID(cxxrtl_edge))) { + RTLIL::Const edge_attr = wire->attributes[ID(cxxrtl_edge)]; if (!(edge_attr.flags & RTLIL::CONST_FLAG_STRING) || (int)edge_attr.decode_string().size() != GetSize(wire)) - log_cmd_error("Attribute `cxxrtl.edge' of port `%s.%s' is not a string with one character per bit.\n", + log_cmd_error("Attribute `cxxrtl_edge' of port `%s.%s' is not a string with one character per bit.\n", log_id(module), log_signal(wire)); - std::string edges = wire->get_string_attribute(ID(cxxrtl.edge)); + std::string edges = wire->get_string_attribute(ID(cxxrtl_edge)); for (int i = 0; i < GetSize(wire); i++) { RTLIL::SigSpec wire_sig = wire; switch (edges[i]) { @@ -1673,13 +1840,17 @@ struct CxxrtlWorker { case 'n': register_edge_signal(sigmap, wire_sig[i], RTLIL::STn); break; case 'a': register_edge_signal(sigmap, wire_sig[i], RTLIL::STe); break; default: - log_cmd_error("Attribute `cxxrtl.edge' of port `%s.%s' contains specifiers " + log_cmd_error("Attribute `cxxrtl_edge' of port `%s.%s' contains specifiers " "other than '-', 'p', 'n', or 'a'.\n", log_id(module), log_signal(wire)); } } } } + + // Black boxes converge by default, since their implementations are quite unlikely to require + // internal propagation of comb signals. + eval_converges[module] = true; continue; } @@ -1698,12 +1869,12 @@ struct CxxrtlWorker { RTLIL::Module *cell_module = design->module(cell->type); if (cell_module && cell_module->get_blackbox_attribute() && - !cell_module->get_bool_attribute(ID(cxxrtl.blackbox))) + !cell_module->get_bool_attribute(ID(cxxrtl_blackbox))) log_cmd_error("External blackbox cell `%s' is not marked as a CXXRTL blackbox.\n", log_id(cell->type)); if (cell_module && - cell_module->get_bool_attribute(ID(cxxrtl.blackbox)) && - cell_module->get_bool_attribute(ID(cxxrtl.template))) + cell_module->get_bool_attribute(ID(cxxrtl_blackbox)) && + cell_module->get_bool_attribute(ID(cxxrtl_template))) blackbox_specializations[cell_module].insert(template_args(cell)); FlowGraph::Node *node = flow.add_node(cell); @@ -1788,23 +1959,15 @@ struct CxxrtlWorker { if (wire->get_bool_attribute(ID::keep)) continue; if (wire->name.begins_with("$") && !elide_internal) continue; if (wire->name.begins_with("\\") && !elide_public) continue; - if (sync_wires[wire]) continue; - log_assert(flow.wire_defs[wire].size() == 1); - elided_wires[wire] = **flow.wire_defs[wire].begin(); + if (edge_wires[wire]) continue; + log_assert(flow.wire_comb_defs[wire].size() == 1); + elided_wires[wire] = **flow.wire_comb_defs[wire].begin(); } - // Elided wires that are outputs of internal cells are always connected to a well known port (Y). - // For user cells, there could be multiple of them, and we need a way to look up the port name - // knowing only the wire. - for (auto cell : module->cells()) - for (auto conn : cell->connections()) - if (conn.second.is_wire() && elided_wires.count(conn.second.as_wire())) - cell_wire_defs[cell][conn.second.as_wire()] = conn.first; - dict<FlowGraph::Node*, pool<const RTLIL::Wire*>, hash_ptr_ops> node_defs; - for (auto wire_def : flow.wire_defs) - for (auto node : wire_def.second) - node_defs[node].insert(wire_def.first); + for (auto wire_comb_def : flow.wire_comb_defs) + for (auto node : wire_comb_def.second) + node_defs[node].insert(wire_comb_def.first); Scheduler<FlowGraph::Node> scheduler; dict<FlowGraph::Node*, Scheduler<FlowGraph::Node>::Vertex*, hash_ptr_ops> node_map; @@ -1843,10 +2006,9 @@ struct CxxrtlWorker { if (!feedback_wires.empty()) { has_feedback_arcs = true; - log("Module `%s' contains feedback arcs through wires:\n", module->name.c_str()); - for (auto wire : feedback_wires) { - log(" %s\n", wire->name.c_str()); - } + log("Module `%s' contains feedback arcs through wires:\n", log_id(module)); + for (auto wire : feedback_wires) + log(" %s\n", log_id(wire)); } for (auto wire : module->wires()) { @@ -1855,14 +2017,46 @@ struct CxxrtlWorker { if (wire->get_bool_attribute(ID::keep)) continue; if (wire->name.begins_with("$") && !localize_internal) continue; if (wire->name.begins_with("\\") && !localize_public) continue; - if (sync_wires[wire]) continue; - // Outputs of FF/$memrd cells and LHS of sync actions do not end up in defs. - if (flow.wire_defs[wire].size() != 1) continue; + if (edge_wires[wire]) continue; + if (flow.wire_sync_defs.count(wire) > 0) continue; localized_wires.insert(wire); } + + // For maximum performance, the state of the simulation (which is the same as the set of its double buffered + // wires, since using a singly buffered wire for any kind of state introduces a race condition) should contain + // no wires attached to combinatorial outputs. Feedback wires, by definition, make that impossible. However, + // it is possible that a design with no feedback arcs would end up with doubly buffered wires in such cases + // as a wire with multiple drivers where one of them is combinatorial and the other is synchronous. Such designs + // also require more than one delta cycle to converge. + pool<const RTLIL::Wire*> buffered_wires; + for (auto wire : module->wires()) { + if (flow.wire_comb_defs[wire].size() > 0 && !elided_wires.count(wire) && !localized_wires[wire]) { + if (!feedback_wires[wire]) + buffered_wires.insert(wire); + } + } + if (!buffered_wires.empty()) { + has_buffered_wires = true; + log("Module `%s' contains buffered combinatorial wires:\n", log_id(module)); + for (auto wire : buffered_wires) + log(" %s\n", log_id(wire)); + } + + eval_converges[module] = feedback_wires.empty() && buffered_wires.empty(); } - if (has_feedback_arcs) { - log("Feedback arcs require delta cycles during evaluation.\n"); + if (has_feedback_arcs || has_buffered_wires) { + // Although both non-feedback buffered combinatorial wires and apparent feedback wires may be eliminated + // by optimizing the design, if after `opt_clean -purge` there are any feedback wires remaining, it is very + // likely that these feedback wires are indicative of a true logic loop, so they get emphasized in the message. + const char *why_pessimistic = nullptr; + if (has_feedback_arcs) + why_pessimistic = "feedback wires"; + else if (has_buffered_wires) + why_pessimistic = "buffered combinatorial wires"; + log("\n"); + log_warning("Design contains %s, which require delta cycles during evaluation.\n", why_pessimistic); + if (!max_opt_level) + log("Increasing the optimization level may eliminate %s from the design.\n", why_pessimistic); } } @@ -1871,7 +2065,7 @@ struct CxxrtlWorker { has_sync_init = has_packed_mem = false; for (auto module : design->modules()) { - if (module->get_blackbox_attribute() && !module->has_attribute(ID(cxxrtl.blackbox))) + if (module->get_blackbox_attribute() && !module->has_attribute(ID(cxxrtl_blackbox))) continue; if (!design->selected_whole_module(module)) @@ -1894,8 +2088,12 @@ struct CxxrtlWorker { void prepare_design(RTLIL::Design *design) { bool has_sync_init, has_packed_mem; + log_push(); check_design(design, has_sync_init, has_packed_mem); - if (has_sync_init) { + if (run_proc_flatten) { + Pass::call(design, "proc"); + Pass::call(design, "flatten"); + } else if (has_sync_init) { // We're only interested in proc_init, but it depends on proc_prune and proc_clean, so call those // in case they weren't already. (This allows `yosys foo.v -o foo.cc` to work.) Pass::call(design, "proc_prune"); @@ -1908,18 +2106,15 @@ struct CxxrtlWorker { if (has_sync_init || has_packed_mem) check_design(design, has_sync_init, has_packed_mem); log_assert(!(has_sync_init || has_packed_mem)); - - if (run_splitnets) { - Pass::call(design, "splitnets -driver"); + if (run_opt_clean_purge) Pass::call(design, "opt_clean -purge"); - } - log("\n"); + log_pop(); analyze_design(design); } }; struct CxxrtlBackend : public Backend { - static const int DEFAULT_OPT_LEVEL = 5; + static const int DEFAULT_OPT_LEVEL = 6; CxxrtlBackend() : Backend("cxxrtl", "convert design to C++ RTL simulation") { } void help() YS_OVERRIDE @@ -1941,9 +2136,9 @@ struct CxxrtlBackend : public Backend { log(" top.step();\n"); log(" while (1) {\n"); log(" /* user logic */\n"); - log(" top.p_clk.next = value<1> {0u};\n"); + log(" top.p_clk = value<1> {0u};\n"); log(" top.step();\n"); - log(" top.p_clk.next = value<1> {1u};\n"); + log(" top.p_clk = value<1> {1u};\n"); log(" top.step();\n"); log(" }\n"); log(" }\n"); @@ -1961,22 +2156,24 @@ struct CxxrtlBackend : public Backend { log("For example, the following Verilog code defines a CXXRTL black box interface for\n"); log("a synchronous debug sink:\n"); log("\n"); - log(" (* cxxrtl.blackbox *)\n"); + log(" (* cxxrtl_blackbox *)\n"); log(" module debug(...);\n"); - log(" (* cxxrtl.edge = \"p\" *) input clk;\n"); + log(" (* cxxrtl_edge = \"p\" *) input clk;\n"); log(" input en;\n"); - log(" input [7:0] data;\n"); + log(" input [7:0] i_data;\n"); + log(" (* cxxrtl_sync *) output [7:0] o_data;\n"); log(" endmodule\n"); log("\n"); log("For this HDL interface, this backend will generate the following C++ interface:\n"); log("\n"); log(" struct bb_p_debug : public module {\n"); - log(" wire<1> p_clk;\n"); - log(" bool posedge_p_clk = false;\n"); - log(" wire<1> p_en;\n"); - log(" wire<8> p_data;\n"); + log(" value<1> p_clk;\n"); + log(" bool posedge_p_clk() const { /* ... */ }\n"); + log(" value<1> p_en;\n"); + log(" value<8> p_i_data;\n"); + log(" wire<8> p_o_data;\n"); log("\n"); - log(" void eval() override;\n"); + log(" bool eval() override;\n"); log(" bool commit() override;\n"); log("\n"); log(" static std::unique_ptr<bb_p_debug>\n"); @@ -1989,10 +2186,11 @@ struct CxxrtlBackend : public Backend { log(" namespace cxxrtl_design {\n"); log("\n"); log(" struct stderr_debug : public bb_p_debug {\n"); - log(" void eval() override {\n"); - log(" if (posedge_p_clk && p_en.curr)\n"); - log(" fprintf(stderr, \"debug: %%02x\\n\", p_data.curr.data[0]);\n"); - log(" bb_p_debug::eval();\n"); + log(" bool eval() override {\n"); + log(" if (posedge_p_clk() && p_en)\n"); + log(" fprintf(stderr, \"debug: %%02x\\n\", p_i_data.data[0]);\n"); + log(" p_o_data.next = p_i_data;\n"); + log(" return bb_p_debug::eval();\n"); log(" }\n"); log(" };\n"); log("\n"); @@ -2008,12 +2206,13 @@ struct CxxrtlBackend : public Backend { log("port widths. For example, the following Verilog code defines a CXXRTL black box\n"); log("interface for a configurable width debug sink:\n"); log("\n"); - log(" (* cxxrtl.blackbox, cxxrtl.template = \"WIDTH\" *)\n"); + log(" (* cxxrtl_blackbox, cxxrtl_template = \"WIDTH\" *)\n"); log(" module debug(...);\n"); log(" parameter WIDTH = 8;\n"); - log(" (* cxxrtl.edge = \"p\" *) input clk;\n"); + log(" (* cxxrtl_edge = \"p\" *) input clk;\n"); log(" input en;\n"); - log(" (* cxxrtl.width = \"WIDTH\" *) input [WIDTH - 1:0] data;\n"); + log(" (* cxxrtl_width = \"WIDTH\" *) input [WIDTH - 1:0] i_data;\n"); + log(" (* cxxrtl_width = \"WIDTH\" *) output [WIDTH - 1:0] o_data;\n"); log(" endmodule\n"); log("\n"); log("For this parametric HDL interface, this backend will generate the following C++\n"); @@ -2022,7 +2221,8 @@ struct CxxrtlBackend : public Backend { log(" template<size_t WIDTH>\n"); log(" struct bb_p_debug : public module {\n"); log(" // ...\n"); - log(" wire<WIDTH> p_data;\n"); + log(" value<WIDTH> p_i_data;\n"); + log(" wire<WIDTH> p_o_data;\n"); log(" // ...\n"); log(" static std::unique_ptr<bb_p_debug<WIDTH>>\n"); log(" create(std::string name, metadata_map parameters, metadata_map attributes);\n"); @@ -2046,27 +2246,33 @@ struct CxxrtlBackend : public Backend { log("\n"); log("The following attributes are recognized by this backend:\n"); log("\n"); - log(" cxxrtl.blackbox\n"); + log(" cxxrtl_blackbox\n"); log(" only valid on modules. if specified, the module contents are ignored,\n"); log(" and the generated code includes only the module interface and a factory\n"); log(" function, which will be called to instantiate the module.\n"); log("\n"); - log(" cxxrtl.edge\n"); + log(" cxxrtl_edge\n"); log(" only valid on inputs of black boxes. must be one of \"p\", \"n\", \"a\".\n"); - log(" if specified on signal `clk`, the generated code includes boolean fields\n"); - log(" `posedge_p_clk` (if \"p\"), `negedge_p_clk` (if \"n\"), or both (if \"a\"),\n"); - log(" as well as edge detection logic, simplifying implementation of clocked\n"); - log(" black boxes.\n"); + log(" if specified on signal `clk`, the generated code includes edge detectors\n"); + log(" `posedge_p_clk()` (if \"p\"), `negedge_p_clk()` (if \"n\"), or both (if\n"); + log(" \"a\"), simplifying implementation of clocked black boxes.\n"); log("\n"); - log(" cxxrtl.template\n"); + log(" cxxrtl_template\n"); log(" only valid on black boxes. must contain a space separated sequence of\n"); log(" identifiers that have a corresponding black box parameters. for each\n"); log(" of them, the generated code includes a `size_t` template parameter.\n"); log("\n"); - log(" cxxrtl.width\n"); + log(" cxxrtl_width\n"); log(" only valid on ports of black boxes. must be a constant expression, which\n"); log(" is directly inserted into generated code.\n"); log("\n"); + log(" cxxrtl_comb, cxxrtl_sync\n"); + log(" only valid on outputs of black boxes. if specified, indicates that every\n"); + log(" bit of the output port is driven, correspondingly, by combinatorial or\n"); + log(" synchronous logic. this knowledge is used for scheduling optimizations.\n"); + log(" if neither is specified, the output will be pessimistically treated as\n"); + log(" driven by both combinatorial and synchronous logic.\n"); + log("\n"); log("The following options are supported by this backend:\n"); log("\n"); log(" -header\n"); @@ -2100,7 +2306,10 @@ struct CxxrtlBackend : public Backend { log(" like -O3, and localize public wires not marked (*keep*) if possible.\n"); log("\n"); log(" -O5\n"); - log(" like -O4, and run `splitnets -driver; opt_clean -purge` first.\n"); + log(" like -O4, and run `opt_clean -purge` first.\n"); + log("\n"); + log(" -O6\n"); + log(" like -O5, and run `proc; flatten` first.\n"); log("\n"); } void execute(std::ostream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) YS_OVERRIDE @@ -2134,8 +2343,11 @@ struct CxxrtlBackend : public Backend { extra_args(f, filename, args, argidx); switch (opt_level) { + case 6: + worker.max_opt_level = true; + worker.run_proc_flatten = true; case 5: - worker.run_splitnets = true; + worker.run_opt_clean_purge = true; case 4: worker.localize_public = true; case 3: diff --git a/backends/cxxrtl/cxxrtl.h b/backends/cxxrtl/cxxrtl.h index 41e6290d1..701510b7f 100644 --- a/backends/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/cxxrtl.h @@ -641,13 +641,15 @@ struct memory { void update(size_t index, const value<Width> &val, const value<Width> &mask, int priority = 0) { assert(index < data.size()); - write_queue.emplace_back(write { index, val, mask, priority }); + // Queue up the write while keeping the queue sorted by priority. + write_queue.insert( + std::upper_bound(write_queue.begin(), write_queue.end(), priority, + [](const int a, const write& b) { return a < b.priority; }), + write { index, val, mask, priority }); } bool commit() { bool changed = false; - std::sort(write_queue.begin(), write_queue.end(), - [](const write &a, const write &b) { return a.priority < b.priority; }); for (const write &entry : write_queue) { value<Width> elem = data[entry.index]; elem = elem.update(entry.val, entry.mask); @@ -717,15 +719,16 @@ struct module { module(const module &) = delete; module &operator=(const module &) = delete; - virtual void eval() = 0; + virtual bool eval() = 0; virtual bool commit() = 0; size_t step() { size_t deltas = 0; + bool converged = false; do { - eval(); + converged = eval(); deltas++; - } while (commit()); + } while (commit() && !converged); return deltas; } }; diff --git a/frontends/verific/verific.cc b/frontends/verific/verific.cc index 519151310..fe4bda68e 100644 --- a/frontends/verific/verific.cc +++ b/frontends/verific/verific.cc @@ -149,7 +149,7 @@ RTLIL::IdString VerificImporter::new_verific_id(Verific::DesignObj *obj) return s; } -void VerificImporter::import_attributes(dict<RTLIL::IdString, RTLIL::Const> &attributes, DesignObj *obj) +void VerificImporter::import_attributes(dict<RTLIL::IdString, RTLIL::Const> &attributes, DesignObj *obj, Netlist *nl) { MapIter mi; Att *attr; @@ -163,6 +163,68 @@ void VerificImporter::import_attributes(dict<RTLIL::IdString, RTLIL::Const> &att continue; attributes[RTLIL::escape_id(attr->Key())] = RTLIL::Const(std::string(attr->Value())); } + + if (nl) { + auto type_range = nl->GetTypeRange(obj->Name()); + if (!type_range) + return; + if (!type_range->IsTypeEnum()) + return; + if (nl->IsFromVhdl() && strcmp(type_range->GetTypeName(), "STD_LOGIC") == 0) + return; + auto type_name = type_range->GetTypeName(); + if (!type_name) + return; + attributes.emplace(ID::wiretype, RTLIL::escape_id(type_name)); + + MapIter mi; + const char *k, *v; + FOREACH_MAP_ITEM(type_range->GetEnumIdMap(), mi, &k, &v) { + if (nl->IsFromVerilog()) { + // Expect <decimal>'b<binary> + auto p = strchr(v, '\''); + if (p) { + if (*(p+1) != 'b') + p = nullptr; + else + for (auto q = p+2; *q != '\0'; q++) + if (*q != '0' && *q != '1') { + p = nullptr; + break; + } + } + if (p == nullptr) + log_error("Expected TypeRange value '%s' to be of form <decimal>'b<binary>.\n", v); + attributes.emplace(stringf("\\enum_value_%s", p+2), RTLIL::escape_id(k)); + } + else if (nl->IsFromVhdl()) { + // Expect "<binary>" + auto p = v; + if (p) { + if (*p != '"') + p = nullptr; + else { + auto *q = p+1; + for (; *q != '"'; q++) + if (*q != '0' && *q != '1') { + p = nullptr; + break; + } + if (p && *(q+1) != '\0') + p = nullptr; + } + } + if (p == nullptr) + log_error("Expected TypeRange value '%s' to be of form \"<binary>\".\n", v); + auto l = strlen(p); + auto q = (char*)malloc(l+1-2); + strncpy(q, p+1, l-2); + q[l-2] = '\0'; + attributes.emplace(stringf("\\enum_value_%s", q), RTLIL::escape_id(k)); + free(q); + } + } + } } RTLIL::SigSpec VerificImporter::operatorInput(Instance *inst) @@ -845,7 +907,7 @@ void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::se log(" importing port %s.\n", port->Name()); RTLIL::Wire *wire = module->addWire(RTLIL::escape_id(port->Name())); - import_attributes(wire->attributes, port); + import_attributes(wire->attributes, port, nl); wire->port_id = nl->IndexOf(port) + 1; @@ -872,7 +934,7 @@ void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::se RTLIL::Wire *wire = module->addWire(RTLIL::escape_id(portbus->Name()), portbus->Size()); wire->start_offset = min(portbus->LeftIndex(), portbus->RightIndex()); - import_attributes(wire->attributes, portbus); + import_attributes(wire->attributes, portbus, nl); if (portbus->GetDir() == DIR_INOUT || portbus->GetDir() == DIR_IN) wire->port_input = true; @@ -1021,7 +1083,7 @@ void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::se log(" importing net %s as %s.\n", net->Name(), log_id(wire_name)); RTLIL::Wire *wire = module->addWire(wire_name); - import_attributes(wire->attributes, net); + import_attributes(wire->attributes, net, nl); net_map[net] = wire; } @@ -1046,7 +1108,7 @@ void VerificImporter::import_netlist(RTLIL::Design *design, Netlist *nl, std::se RTLIL::Wire *wire = module->addWire(wire_name, netbus->Size()); wire->start_offset = min(netbus->LeftIndex(), netbus->RightIndex()); - import_attributes(wire->attributes, netbus); + import_attributes(wire->attributes, netbus, nl); RTLIL::Const initval = Const(State::Sx, GetSize(wire)); bool initval_valid = false; diff --git a/frontends/verific/verific.h b/frontends/verific/verific.h index 2ccfcd42c..f168a2588 100644 --- a/frontends/verific/verific.h +++ b/frontends/verific/verific.h @@ -79,7 +79,7 @@ struct VerificImporter RTLIL::SigBit net_map_at(Verific::Net *net); RTLIL::IdString new_verific_id(Verific::DesignObj *obj); - void import_attributes(dict<RTLIL::IdString, RTLIL::Const> &attributes, Verific::DesignObj *obj); + void import_attributes(dict<RTLIL::IdString, RTLIL::Const> &attributes, Verific::DesignObj *obj, Verific::Netlist *nl = nullptr); RTLIL::SigSpec operatorInput(Verific::Instance *inst); RTLIL::SigSpec operatorInput1(Verific::Instance *inst); diff --git a/kernel/constids.inc b/kernel/constids.inc index 68a5782fd..27b652e24 100644 --- a/kernel/constids.inc +++ b/kernel/constids.inc @@ -29,6 +29,7 @@ X(B) X(BI) X(blackbox) X(B_SIGNED) +X(bugpoint_keep) X(B_WIDTH) X(C) X(cells_not_processed) @@ -199,6 +200,7 @@ X(wand) X(whitebox) X(WIDTH) X(wildcard_port_conns) +X(wiretype) X(wor) X(WORDS) X(WR_ADDR) diff --git a/kernel/hashlib.h b/kernel/hashlib.h index 97fadea0e..592d6e577 100644 --- a/kernel/hashlib.h +++ b/kernel/hashlib.h @@ -642,6 +642,7 @@ protected: entry_t() { } entry_t(const K &udata, int next) : udata(udata), next(next) { } + entry_t(K &&udata, int next) : udata(std::move(udata)), next(next) { } }; std::vector<int> hashtable; @@ -745,11 +746,24 @@ protected: int do_insert(const K &value, int &hash) { if (hashtable.empty()) { - entries.push_back(entry_t(value, -1)); + entries.emplace_back(value, -1); do_rehash(); hash = do_hash(value); } else { - entries.push_back(entry_t(value, hashtable[hash])); + entries.emplace_back(value, hashtable[hash]); + hashtable[hash] = entries.size() - 1; + } + return entries.size() - 1; + } + + int do_insert(K &&rvalue, int &hash) + { + if (hashtable.empty()) { + entries.emplace_back(std::forward<K>(rvalue), -1); + do_rehash(); + hash = do_hash(rvalue); + } else { + entries.emplace_back(std::forward<K>(rvalue), hashtable[hash]); hashtable[hash] = entries.size() - 1; } return entries.size() - 1; @@ -847,6 +861,22 @@ public: return std::pair<iterator, bool>(iterator(this, i), true); } + std::pair<iterator, bool> insert(K &&rvalue) + { + int hash = do_hash(rvalue); + int i = do_lookup(rvalue, hash); + if (i >= 0) + return std::pair<iterator, bool>(iterator(this, i), false); + i = do_insert(std::forward<K>(rvalue), hash); + return std::pair<iterator, bool>(iterator(this, i), true); + } + + template<typename... Args> + std::pair<iterator, bool> emplace(Args&&... args) + { + return insert(K(std::forward<Args>(args)...)); + } + int erase(const K &key) { int hash = do_hash(key); diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index 2aefe30b1..196e301b6 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -2619,16 +2619,15 @@ void RTLIL::Cell::setParam(RTLIL::IdString paramname, RTLIL::Const value) const RTLIL::Const &RTLIL::Cell::getParam(RTLIL::IdString paramname) const { - static const RTLIL::Const empty; const auto &it = parameters.find(paramname); if (it != parameters.end()) return it->second; if (module && module->design) { RTLIL::Module *m = module->design->module(type); if (m) - return m->parameter_default_values.at(paramname, empty); + return m->parameter_default_values.at(paramname); } - return empty; + throw std::out_of_range("Cell::getParam()"); } void RTLIL::Cell::sort() diff --git a/manual/CHAPTER_Auxprogs.tex b/manual/CHAPTER_Auxprogs.tex index 724d37f0b..f09b18f76 100644 --- a/manual/CHAPTER_Auxprogs.tex +++ b/manual/CHAPTER_Auxprogs.tex @@ -19,7 +19,8 @@ for details. \section{yosys-abc} -This is a unmodified copy of ABC \citeweblink{ABC}. Not all versions of Yosys -work with all versions of ABC. So Yosys comes with its own yosys-abc to avoid +This is a fork of ABC \citeweblink{ABC} with a small set of custom modifications +that have not yet been accepted upstream. Not all versions of Yosys work with +all versions of ABC. So Yosys comes with its own yosys-abc to avoid compatibility issues between the two. diff --git a/misc/yosys-config.in b/misc/yosys-config.in index 370835f8f..a31ef38c2 100644 --- a/misc/yosys-config.in +++ b/misc/yosys-config.in @@ -6,7 +6,7 @@ help() { echo "Usage: $0 [--exec] [--prefix pf] args.." echo " $0 --build modname.so cppsources.." echo "" - echo "Replecement args:" + echo "Replacement args:" echo " --cxx @CXX@" echo " --cxxflags $( echo '@CXXFLAGS@' | fmt -w60 | sed ':a;N;$!ba;s/\n/ \\\n /g' )" echo " --ldflags @LDFLAGS@" diff --git a/passes/cmds/bugpoint.cc b/passes/cmds/bugpoint.cc index ad6a07fa0..00aac596f 100644 --- a/passes/cmds/bugpoint.cc +++ b/passes/cmds/bugpoint.cc @@ -30,23 +30,21 @@ struct BugpointPass : public Pass { { // |---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---|---v---| log("\n"); - log(" bugpoint [options]\n"); + log(" bugpoint [options] -script <filename>\n"); log("\n"); - log("This command minimizes testcases that crash Yosys. It removes an arbitrary part\n"); - log("of the design and recursively invokes Yosys with a given script, repeating these\n"); - log("steps while it can find a smaller design that still causes a crash. Once this\n"); - log("command finishes, it replaces the current design with the smallest testcase it\n"); - log("was able to produce.\n"); + log("This command minimizes the current design that is known to crash Yosys with the\n"); + log("given script into a smaller testcase. It does this by removing an arbitrary part\n"); + log("of the design and recursively invokes a new Yosys process with this modified design\n"); + log("and the same script, repeating these steps while it can find a smaller design that\n"); + log("still causes a crash. Once this command finishes, it replaces the current design\n"); + log("with the smallest testcase it was able to produce.\n"); log("\n"); - log("It is possible to specify the kinds of design part that will be removed. If none\n"); - log("are specified, all parts of design will be removed.\n"); + log(" -script <filename>\n"); + log(" use this script to crash Yosys. required.\n"); log("\n"); log(" -yosys <filename>\n"); log(" use this Yosys binary. if not specified, `yosys` is used.\n"); log("\n"); - log(" -script <filename>\n"); - log(" use this script to crash Yosys. required.\n"); - log("\n"); log(" -grep <string>\n"); log(" only consider crashes that place this string in the log file.\n"); log("\n"); @@ -60,14 +58,21 @@ struct BugpointPass : public Pass { log(" finishing. produces smaller and more useful testcases, but may fail to\n"); log(" produce any testcase at all if the crash is related to dangling wires.\n"); log("\n"); + log("It is possible to constrain which parts of the design will be considered for\n"); + log("removal. Unless one or more of the following options are specified, all parts\n"); + log("will be considered.\n"); + log("\n"); log(" -modules\n"); - log(" try to remove modules.\n"); + log(" try to remove modules. modules with a (* bugpoint_keep *) attribute\n"); + log(" will be skipped.\n"); log("\n"); log(" -ports\n"); - log(" try to remove module ports.\n"); + log(" try to remove module ports. ports with a (* bugpoint_keep *) attribute\n"); + log(" will be skipped (useful for clocks, resets, etc.)\n"); log("\n"); log(" -cells\n"); - log(" try to remove cells.\n"); + log(" try to remove cells. cells with a (* bugpoint_keep *) attribute will\n"); + log(" be skipped.\n"); log("\n"); log(" -connections\n"); log(" try to reconnect ports to 'x.\n"); @@ -133,18 +138,26 @@ struct BugpointPass : public Pass { int index = 0; if (modules) { + Module *removed_module = nullptr; for (auto module : design_copy->modules()) { if (module->get_blackbox_attribute()) continue; + if (module->get_bool_attribute(ID::bugpoint_keep)) + continue; + if (index++ == seed) { - log("Trying to remove module %s.\n", module->name.c_str()); - design_copy->remove(module); - return design_copy; + log_header(design, "Trying to remove module %s.\n", log_id(module)); + removed_module = module; + break; } } + if (removed_module) { + design_copy->remove(removed_module); + return design_copy; + } } if (ports) { @@ -155,18 +168,21 @@ struct BugpointPass : public Pass { for (auto wire : mod->wires()) { + if (!wire->port_id) + continue; + if (!stage2 && wire->get_bool_attribute(ID($bugpoint))) continue; - if (wire->port_input || wire->port_output) + if (wire->get_bool_attribute(ID::bugpoint_keep)) + continue; + + if (index++ == seed) { - if (index++ == seed) - { - log("Trying to remove module port %s.\n", log_signal(wire)); - wire->port_input = wire->port_output = false; - mod->fixup_ports(); - return design_copy; - } + log_header(design, "Trying to remove module port %s.\n", log_id(wire)); + wire->port_input = wire->port_output = false; + mod->fixup_ports(); + return design_copy; } } } @@ -178,15 +194,24 @@ struct BugpointPass : public Pass { if (mod->get_blackbox_attribute()) continue; + + Cell *removed_cell = nullptr; for (auto cell : mod->cells()) { + if (cell->get_bool_attribute(ID::bugpoint_keep)) + continue; + if (index++ == seed) { - log("Trying to remove cell %s.%s.\n", mod->name.c_str(), cell->name.c_str()); - mod->remove(cell); - return design_copy; + log_header(design, "Trying to remove cell %s.%s.\n", log_id(mod), log_id(cell)); + removed_cell = cell; + break; } } + if (removed_cell) { + mod->remove(removed_cell); + return design_copy; + } } } if (connections) @@ -209,7 +234,7 @@ struct BugpointPass : public Pass { if (index++ == seed) { - log("Trying to remove cell port %s.%s.%s.\n", mod->name.c_str(), cell->name.c_str(), it.first.c_str()); + log_header(design, "Trying to remove cell port %s.%s.%s.\n", log_id(mod), log_id(cell), log_id(it.first)); RTLIL::SigSpec port_x(State::Sx, port.size()); cell->unsetPort(it.first); cell->setPort(it.first, port_x); @@ -218,7 +243,7 @@ struct BugpointPass : public Pass { if (!stage2 && (cell->input(it.first) || cell->output(it.first)) && index++ == seed) { - log("Trying to expose cell port %s.%s.%s as module port.\n", mod->name.c_str(), cell->name.c_str(), it.first.c_str()); + log_header(design, "Trying to expose cell port %s.%s.%s as module port.\n", log_id(mod), log_id(cell), log_id(it.first)); RTLIL::Wire *wire = mod->addWire(NEW_ID, port.size()); wire->set_bool_attribute(ID($bugpoint)); wire->port_input = cell->input(it.first); @@ -250,7 +275,7 @@ struct BugpointPass : public Pass { { if (index++ == seed) { - log("Trying to remove assign %s %s in %s.%s.\n", log_signal((*it).first), log_signal((*it).second), mod->name.c_str(), pr.first.c_str()); + log_header(design, "Trying to remove assign %s %s in %s.%s.\n", log_signal(it->first), log_signal(it->second), log_id(mod), log_id(pr.first)); cs->actions.erase(it); return design_copy; } @@ -276,7 +301,7 @@ struct BugpointPass : public Pass { { if (index++ == seed) { - log("Trying to remove sync %s update %s %s in %s.%s.\n", log_signal(sy->signal), log_signal((*it).first), log_signal((*it).second), mod->name.c_str(), pr.first.c_str()); + log_header(design, "Trying to remove sync %s update %s %s in %s.%s.\n", log_signal(sy->signal), log_signal(it->first), log_signal(it->second), log_id(mod), log_id(pr.first)); sy->actions.erase(it); return design_copy; } @@ -294,6 +319,9 @@ struct BugpointPass : public Pass { bool fast = false, clean = false; bool modules = false, ports = false, cells = false, connections = false, assigns = false, updates = false, has_part = false; + log_header(design, "Executing BUGPOINT pass (minimize testcases).\n"); + log_push(); + size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { @@ -437,6 +465,8 @@ struct BugpointPass : public Pass { design->add(module->clone()); delete crashing_design; } + + log_pop(); } } BugpointPass; diff --git a/passes/cmds/design.cc b/passes/cmds/design.cc index cfe97067d..421defe0c 100644 --- a/passes/cmds/design.cc +++ b/passes/cmds/design.cc @@ -228,14 +228,20 @@ struct DesignPass : public Pass { } if (import_mode) { + std::vector<RTLIL::Module*> candidates; for (auto module : copy_src_modules) { if (module->get_bool_attribute(ID::top)) { - copy_src_modules.clear(); - copy_src_modules.push_back(module); + candidates.clear(); + candidates.push_back(module); break; } + if (!module->get_blackbox_attribute()) + candidates.push_back(module); } + + if (GetSize(candidates) == 1) + copy_src_modules = std::move(candidates); } } diff --git a/passes/cmds/select.cc b/passes/cmds/select.cc index c04ff438a..6e728c16f 100644 --- a/passes/cmds/select.cc +++ b/passes/cmds/select.cc @@ -630,8 +630,10 @@ static void select_stmt(RTLIL::Design *design, std::string arg, bool disable_emp std::string arg_mod, arg_memb; std::unordered_map<std::string, bool> arg_mod_found; std::unordered_map<std::string, bool> arg_memb_found; - auto isalpha = [](const char &x) { return ((x >= 'a' && x <= 'z') || (x >= 'A' && x <= 'Z')); }; - bool prefixed = GetSize(arg) >= 2 && isalpha(arg[0]) && arg[1] == ':'; + + auto isprefixed = [](const string &s) { + return GetSize(s) >= 2 && ((s[0] >= 'a' && s[0] <= 'z') || (s[0] >= 'A' && s[0] <= 'Z')) && s[1] == ':'; + }; if (arg.size() == 0) return; @@ -759,31 +761,40 @@ static void select_stmt(RTLIL::Design *design, std::string arg, bool disable_emp return; } + bool select_blackboxes = false; + if (arg.substr(0, 1) == "=") { + arg = arg.substr(1); + select_blackboxes = true; + } + if (!design->selected_active_module.empty()) { arg_mod = design->selected_active_module; arg_memb = arg; - if (!prefixed) arg_memb_found[arg_memb] = false; + if (!isprefixed(arg_memb)) + arg_memb_found[arg_memb] = false; } else - if (prefixed && arg[0] >= 'a' && arg[0] <= 'z') { + if (isprefixed(arg) && arg[0] >= 'a' && arg[0] <= 'z') { arg_mod = "*", arg_memb = arg; } else { size_t pos = arg.find('/'); if (pos == std::string::npos) { arg_mod = arg; - if (!prefixed) arg_mod_found[arg_mod] = false; + if (!isprefixed(arg_mod)) + arg_mod_found[arg_mod] = false; } else { arg_mod = arg.substr(0, pos); - if (!prefixed) arg_mod_found[arg_mod] = false; + if (!isprefixed(arg_mod)) + arg_mod_found[arg_mod] = false; arg_memb = arg.substr(pos+1); - bool arg_memb_prefixed = GetSize(arg_memb) >= 2 && isalpha(arg_memb[0]) && arg_memb[1] == ':'; - if (!arg_memb_prefixed) arg_memb_found[arg_memb] = false; + if (!isprefixed(arg_memb)) + arg_memb_found[arg_memb] = false; } } work_stack.push_back(RTLIL::Selection()); RTLIL::Selection &sel = work_stack.back(); - if (arg == "*" && arg_mod == "*") { + if (arg == "*" && arg_mod == "*" && select_blackboxes) { select_filter_active_mod(design, work_stack.back()); return; } @@ -791,6 +802,9 @@ static void select_stmt(RTLIL::Design *design, std::string arg, bool disable_emp sel.full_selection = false; for (auto mod : design->modules()) { + if (!select_blackboxes && mod->get_blackbox_attribute()) + continue; + if (arg_mod.compare(0, 2, "A:") == 0) { if (!match_attr(mod->attributes, arg_mod.substr(2))) continue; @@ -1104,6 +1118,9 @@ struct SelectPass : public Pass { log(" <obj_pattern>\n"); log(" select the specified object(s) from the current module\n"); log("\n"); + log("By default, patterns will not match black/white-box modules or their"); + log("contents. To include such objects, prefix the pattern with '='.\n"); + log("\n"); log("A <mod_pattern> can be a module name, wildcard expression (*, ?, [..])\n"); log("matching module names, or one of the following:\n"); log("\n"); diff --git a/passes/pmgen/ice40_dsp.cc b/passes/pmgen/ice40_dsp.cc index bfddfd0eb..f16cc4a0b 100644 --- a/passes/pmgen/ice40_dsp.cc +++ b/passes/pmgen/ice40_dsp.cc @@ -73,11 +73,11 @@ void create_ice40_dsp(ice40_dsp_pm &pm) // SB_MAC16 Input Interface SigSpec A = st.sigA; - A.extend_u0(16, st.mul->parameters.at(ID::A_SIGNED, State::S0).as_bool()); + A.extend_u0(16, st.mul->getParam(ID::A_SIGNED).as_bool()); log_assert(GetSize(A) == 16); SigSpec B = st.sigB; - B.extend_u0(16, st.mul->parameters.at(ID::B_SIGNED, State::S0).as_bool()); + B.extend_u0(16, st.mul->getParam(ID::B_SIGNED).as_bool()); log_assert(GetSize(B) == 16); SigSpec CD = st.sigCD; @@ -248,8 +248,8 @@ void create_ice40_dsp(ice40_dsp_pm &pm) cell->setParam(ID(BOTADDSUB_CARRYSELECT), Const(0, 2)); cell->setParam(ID(MODE_8x8), State::S0); - cell->setParam(ID::A_SIGNED, st.mul->parameters.at(ID::A_SIGNED, State::S0).as_bool()); - cell->setParam(ID::B_SIGNED, st.mul->parameters.at(ID::B_SIGNED, State::S0).as_bool()); + cell->setParam(ID::A_SIGNED, st.mul->getParam(ID::A_SIGNED).as_bool()); + cell->setParam(ID::B_SIGNED, st.mul->getParam(ID::B_SIGNED).as_bool()); if (st.ffO) { if (st.o_lo) diff --git a/passes/pmgen/ice40_dsp.pmg b/passes/pmgen/ice40_dsp.pmg index 9d649cb98..2456a49dc 100644 --- a/passes/pmgen/ice40_dsp.pmg +++ b/passes/pmgen/ice40_dsp.pmg @@ -65,7 +65,7 @@ code sigA sigB sigH endcode code argQ ffA ffAholdmux ffArstmux ffAholdpol ffArstpol sigA clock clock_pol - if (mul->type != \SB_MAC16 || !param(mul, \A_REG, State::S0).as_bool()) { + if (mul->type != \SB_MAC16 || !param(mul, \A_REG).as_bool()) { argQ = sigA; subpattern(in_dffe); if (dff) { @@ -86,7 +86,7 @@ code argQ ffA ffAholdmux ffArstmux ffAholdpol ffArstpol sigA clock clock_pol endcode code argQ ffB ffBholdmux ffBrstmux ffBholdpol ffBrstpol sigB clock clock_pol - if (mul->type != \SB_MAC16 || !param(mul, \B_REG, State::S0).as_bool()) { + if (mul->type != \SB_MAC16 || !param(mul, \B_REG).as_bool()) { argQ = sigB; subpattern(in_dffe); if (dff) { @@ -109,7 +109,7 @@ endcode code argD ffFJKG sigH clock clock_pol if (nusers(sigH) == 2 && (mul->type != \SB_MAC16 || - (!param(mul, \TOP_8x8_MULT_REG, State::S0).as_bool() && !param(mul, \BOT_8x8_MULT_REG, State::S0).as_bool() && !param(mul, \PIPELINE_16x16_MULT_REG1, State::S0).as_bool() && !param(mul, \PIPELINE_16x16_MULT_REG1, State::S0).as_bool()))) { + (!param(mul, \TOP_8x8_MULT_REG).as_bool() && !param(mul, \BOT_8x8_MULT_REG).as_bool() && !param(mul, \PIPELINE_16x16_MULT_REG1).as_bool() && !param(mul, \PIPELINE_16x16_MULT_REG1).as_bool()))) { argD = sigH; subpattern(out_dffe); if (dff) { @@ -148,7 +148,7 @@ endcode code argD ffH sigH sigO clock clock_pol if (ffFJKG && nusers(sigH) == 2 && - (mul->type != \SB_MAC16 || !param(mul, \PIPELINE_16x16_MULT_REG2, State::S0).as_bool())) { + (mul->type != \SB_MAC16 || !param(mul, \PIPELINE_16x16_MULT_REG2).as_bool())) { argD = sigH; subpattern(out_dffe); if (dff) { @@ -179,7 +179,7 @@ reject_ffH: ; endcode match add - if mul->type != \SB_MAC16 || (param(mul, \TOPOUTPUT_SELECT, State::S0).as_int() == 3 && param(mul, \BOTOUTPUT_SELECT, State::S0).as_int() == 3) + if mul->type != \SB_MAC16 || (param(mul, \TOPOUTPUT_SELECT).as_int() == 3 && param(mul, \BOTOUTPUT_SELECT).as_int() == 3) select add->type.in($add) choice <IdString> AB {\A, \B} @@ -205,7 +205,7 @@ code sigCD sigO cd_signed if ((actual_acc_width > actual_mul_width) && (natural_mul_width > actual_mul_width)) reject; // If accumulator, check adder width and signedness - if (sigCD == sigH && (actual_acc_width != actual_mul_width) && (param(mul, \A_SIGNED, State::S0).as_bool() != param(add, \A_SIGNED).as_bool())) + if (sigCD == sigH && (actual_acc_width != actual_mul_width) && (param(mul, \A_SIGNED).as_bool() != param(add, \A_SIGNED).as_bool())) reject; sigO = port(add, \Y); @@ -229,7 +229,7 @@ endcode code argD ffO ffOholdmux ffOrstmux ffOholdpol ffOrstpol sigO sigCD clock clock_pol cd_signed o_lo if (mul->type != \SB_MAC16 || // Ensure that register is not already used - ((param(mul, \TOPOUTPUT_SELECT, 0).as_int() != 1 && param(mul, \BOTOUTPUT_SELECT, 0).as_int() != 1) && + ((param(mul, \TOPOUTPUT_SELECT).as_int() != 1 && param(mul, \BOTOUTPUT_SELECT).as_int() != 1) && // Ensure that OLOADTOP/OLOADBOT is unused or zero (port(mul, \OLOADTOP, State::S0).is_fully_zero() && port(mul, \OLOADBOT, State::S0).is_fully_zero()))) { @@ -280,7 +280,7 @@ endcode code argQ ffCD ffCDholdmux ffCDholdpol ffCDrstpol sigCD clock clock_pol if (!sigCD.empty() && sigCD != sigO && - (mul->type != \SB_MAC16 || (!param(mul, \C_REG, State::S0).as_bool() && !param(mul, \D_REG, State::S0).as_bool()))) { + (mul->type != \SB_MAC16 || (!param(mul, \C_REG).as_bool() && !param(mul, \D_REG).as_bool()))) { argQ = sigCD; subpattern(in_dffe); if (dff) { @@ -532,7 +532,7 @@ endcode match ff select ff->type.in($dff) - // DSP48E1 does not support clock inversion + // SB_MAC16 does not support clock inversion select param(ff, \CLK_POLARITY).as_bool() slice offset GetSize(port(ff, \D)) diff --git a/passes/pmgen/xilinx_dsp.pmg b/passes/pmgen/xilinx_dsp.pmg index af47ab111..d40f073c9 100644 --- a/passes/pmgen/xilinx_dsp.pmg +++ b/passes/pmgen/xilinx_dsp.pmg @@ -95,7 +95,7 @@ code sigA sigB sigC sigD sigM clock sigD = port(dsp, \D, SigSpec()); SigSpec P = port(dsp, \P); - if (param(dsp, \USE_MULT, Const("MULTIPLY")).decode_string() == "MULTIPLY") { + if (param(dsp, \USE_MULT).decode_string() == "MULTIPLY") { // Only care about those bits that are used int i; for (i = GetSize(P)-1; i >= 0; i--) @@ -120,7 +120,7 @@ endcode // reset functionality, using a subpattern discussed above) // If matched, treat 'A' input as input of ADREG code argQ ffAD ffADcemux ffADrstmux ffADcepol ffADrstpol sigA clock - if (param(dsp, \ADREG, 1).as_int() == 0) { + if (param(dsp, \ADREG).as_int() == 0) { argQ = sigA; subpattern(in_dffe); if (dff) { @@ -144,7 +144,7 @@ endcode match preAdd if sigD.empty() || sigD.is_fully_zero() // Ensure that preAdder not already used - if param(dsp, \USE_DPORT, Const("FALSE")).decode_string() == "FALSE" + if param(dsp, \USE_DPORT).decode_string() == "FALSE" if port(dsp, \INMODE, Const(0, 5)).is_fully_zero() select preAdd->type.in($add) @@ -176,7 +176,7 @@ code argQ ffAD ffADcemux ffADrstmux ffADcepol ffADrstpol sigA clock ffA2 ffA2cem // Only search for ffA2 if there was a pre-adder // (otherwise ffA2 would have been matched as ffAD) if (preAdd) { - if (param(dsp, \AREG, 1).as_int() == 0) { + if (param(dsp, \AREG).as_int() == 0) { argQ = sigA; subpattern(in_dffe); if (dff) { @@ -237,7 +237,7 @@ endcode // (5) Match 'B' input for B2REG // If B2REG, then match 'B' input for B1REG code argQ ffB2 ffB2cemux ffB2rstmux ffB2cepol ffBrstpol sigB clock ffB1 ffB1cemux ffB1rstmux ffB1cepol - if (param(dsp, \BREG, 1).as_int() == 0) { + if (param(dsp, \BREG).as_int() == 0) { argQ = sigB; subpattern(in_dffe); if (dff) { @@ -287,7 +287,7 @@ endcode // (6) Match 'D' input for DREG code argQ ffD ffDcemux ffDrstmux ffDcepol ffDrstpol sigD clock - if (param(dsp, \DREG, 1).as_int() == 0) { + if (param(dsp, \DREG).as_int() == 0) { argQ = sigD; subpattern(in_dffe); if (dff) { @@ -308,7 +308,7 @@ endcode // (7) Match 'P' output that exclusively drives an MREG code argD ffM ffMcemux ffMrstmux ffMcepol ffMrstpol sigM sigP clock - if (param(dsp, \MREG, 1).as_int() == 0 && nusers(sigM) == 2) { + if (param(dsp, \MREG).as_int() == 0 && nusers(sigM) == 2) { argD = sigM; subpattern(out_dffe); if (dff) { @@ -363,7 +363,7 @@ endcode // (9) Match 'P' output that exclusively drives a PREG code argD ffP ffPcemux ffPrstmux ffPcepol ffPrstpol sigP clock - if (param(dsp, \PREG, 1).as_int() == 0) { + if (param(dsp, \PREG).as_int() == 0) { int users = 2; // If ffMcemux and no postAdd new-value net must have three users: ffMcemux, ffM and ffPcemux if (ffMcemux && !postAdd) users++; @@ -424,7 +424,7 @@ endcode // to implement this function match overflow if ffP - if param(dsp, \USE_PATTERN_DETECT, Const("NO_PATDET")).decode_string() == "NO_PATDET" + if param(dsp, \USE_PATTERN_DETECT).decode_string() == "NO_PATDET" select overflow->type.in($ge) select GetSize(port(overflow, \Y)) <= 48 select port(overflow, \B).is_fully_const() diff --git a/passes/pmgen/xilinx_dsp_CREG.pmg b/passes/pmgen/xilinx_dsp_CREG.pmg index b20e4f458..42d4d1b9b 100644 --- a/passes/pmgen/xilinx_dsp_CREG.pmg +++ b/passes/pmgen/xilinx_dsp_CREG.pmg @@ -42,7 +42,7 @@ udata <bool> dffcepol dffrstpol // and (b) uses the 'C' port match dsp select dsp->type.in(\DSP48A, \DSP48A1, \DSP48E1) - select param(dsp, \CREG, 1).as_int() == 0 + select param(dsp, \CREG).as_int() == 0 select nusers(port(dsp, \C, SigSpec())) > 1 endmatch @@ -61,7 +61,7 @@ code sigC sigP clock SigSpec P = port(dsp, \P); if (!dsp->type.in(\DSP48E1) || - param(dsp, \USE_MULT, Const("MULTIPLY")).decode_string() == "MULTIPLY") { + param(dsp, \USE_MULT).decode_string() == "MULTIPLY") { // Only care about those bits that are used int i; for (i = GetSize(P)-1; i >= 0; i--) diff --git a/passes/pmgen/xilinx_dsp_cascade.pmg b/passes/pmgen/xilinx_dsp_cascade.pmg index b14a1ee0a..8babb88e6 100644 --- a/passes/pmgen/xilinx_dsp_cascade.pmg +++ b/passes/pmgen/xilinx_dsp_cascade.pmg @@ -188,7 +188,7 @@ arg next // driven by the 'P' output of the previous DSP cell, and (c) has its // 'PCIN' port unused match nextP - select !param(nextP, \CREG, State::S1).as_bool() + select !nextP->type.in(\DSP48E1) || !param(nextP, \CREG).as_bool() select (nextP->type.in(\DSP48A, \DSP48A1) && port(nextP, \OPMODE, Const(0, 8)).extract(2,2) == Const::from_string("11")) || (nextP->type.in(\DSP48E1) && port(nextP, \OPMODE, Const(0, 7)).extract(4,3) == Const::from_string("011")) select nusers(port(nextP, \C, SigSpec())) > 1 select nusers(port(nextP, \PCIN, SigSpec())) == 0 @@ -201,7 +201,7 @@ endmatch match nextP_shift17 if !nextP select nextP_shift17->type.in(\DSP48E1) - select !param(nextP_shift17, \CREG, State::S1).as_bool() + select !param(nextP_shift17, \CREG).as_bool() select port(nextP_shift17, \OPMODE, Const(0, 7)).extract(4,3) == Const::from_string("011") select nusers(port(nextP_shift17, \C, SigSpec())) > 1 select nusers(port(nextP_shift17, \PCIN, SigSpec())) == 0 @@ -242,10 +242,10 @@ code argQ clock AREG if (next && next->type.in(\DSP48E1)) { Cell *prev = std::get<0>(chain.back()); - if (param(next, \A_INPUT, Const("DIRECT")).decode_string() == "DIRECT" && + if (param(next, \A_INPUT).decode_string() == "DIRECT" && port(next, \ACIN, SigSpec()).is_fully_zero() && nusers(port(prev, \ACOUT, SigSpec())) <= 1) { - if (param(prev, \AREG, 2) == 0) { + if (param(prev, \AREG) == 0) { if (port(prev, \A) == port(next, \A)) AREG = 0; } @@ -259,9 +259,9 @@ code argQ clock AREG if (dffrstmux && port(dffrstmux, \S) != port(prev, \RSTA, State::S0)) goto reject_AREG; IdString CEA; - if (param(prev, \AREG, 2) == 1) + if (param(prev, \AREG) == 1) CEA = \CEA2; - else if (param(prev, \AREG, 2) == 2) + else if (param(prev, \AREG) == 2) CEA = \CEA1; else log_abort(); if (!dffcemux && port(prev, CEA, State::S0) != State::S1) @@ -282,11 +282,11 @@ code argQ clock BREG BREG = -1; if (next) { Cell *prev = std::get<0>(chain.back()); - if (param(next, \B_INPUT, Const("DIRECT")).decode_string() == "DIRECT" && + if ((next->type != \DSP48E1 || param(next, \B_INPUT).decode_string() == "DIRECT") && port(next, \BCIN, SigSpec()).is_fully_zero() && nusers(port(prev, \BCOUT, SigSpec())) <= 1) { - if ((next->type.in(\DSP48A, \DSP48A1) && param(prev, \B0REG, 0) == 0 && param(prev, \B1REG, 1) == 0) || - (next->type.in(\DSP48E1) && param(prev, \BREG, 2) == 0)) { + if ((next->type.in(\DSP48A, \DSP48A1) && param(prev, \B0REG) == 0 && param(prev, \B1REG) == 0) || + (next->type.in(\DSP48E1) && param(prev, \BREG) == 0)) { if (port(prev, \B) == port(next, \B)) BREG = 0; } @@ -303,9 +303,9 @@ code argQ clock BREG if (next->type.in(\DSP48A, \DSP48A1)) CEB = \CEB; else if (next->type.in(\DSP48E1)) { - if (param(prev, \BREG, 2) == 1) + if (param(prev, \BREG) == 1) CEB = \CEB2; - else if (param(prev, \BREG, 2) == 2) + else if (param(prev, \BREG) == 2) CEB = \CEB1; else log_abort(); } @@ -315,7 +315,7 @@ code argQ clock BREG if (dffcemux && port(dffcemux, \S) != port(prev, CEB, State::S0)) goto reject_BREG; if (dffD == unextend(port(prev, \B))) { - if (next->type.in(\DSP48A, \DSP48A1) && param(prev, \B0REG, 0) != 0) + if (next->type.in(\DSP48A, \DSP48A1) && param(prev, \B0REG) != 0) goto reject_BREG; BREG = 1; } diff --git a/passes/pmgen/xilinx_srl.cc b/passes/pmgen/xilinx_srl.cc index 24b525b93..b99653fb3 100644 --- a/passes/pmgen/xilinx_srl.cc +++ b/passes/pmgen/xilinx_srl.cc @@ -48,7 +48,7 @@ void run_fixed(xilinx_srl_pm &pm) initval.append(State::Sx); } else if (cell->type.in(ID(FDRE), ID(FDRE_1))) { - if (cell->parameters.at(ID::INIT, State::S0).as_bool()) + if (cell->getParam(ID::INIT).as_bool()) initval.append(State::S1); else initval.append(State::S0); @@ -71,7 +71,7 @@ void run_fixed(xilinx_srl_pm &pm) else if (first_cell->type.in(ID($_DFF_N_), ID($_DFFE_NN_), ID($_DFFE_NP_), ID(FDRE_1))) c->setParam(ID(CLKPOL), 0); else if (first_cell->type.in(ID(FDRE))) { - if (!first_cell->parameters.at(ID(IS_C_INVERTED), State::S0).as_bool()) + if (!first_cell->getParam(ID(IS_C_INVERTED)).as_bool()) c->setParam(ID(CLKPOL), 1); else c->setParam(ID(CLKPOL), 0); diff --git a/passes/pmgen/xilinx_srl.pmg b/passes/pmgen/xilinx_srl.pmg index 535b3dfdc..80f0a27c2 100644 --- a/passes/pmgen/xilinx_srl.pmg +++ b/passes/pmgen/xilinx_srl.pmg @@ -13,8 +13,8 @@ endcode match first select first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1) select !first->has_keep_attr() - select !first->type.in(\FDRE) || !param(first, \IS_R_INVERTED, State::S0).as_bool() - select !first->type.in(\FDRE) || !param(first, \IS_D_INVERTED, State::S0).as_bool() + select !first->type.in(\FDRE) || !param(first, \IS_R_INVERTED).as_bool() + select !first->type.in(\FDRE) || !param(first, \IS_D_INVERTED).as_bool() select !first->type.in(\FDRE, \FDRE_1) || port(first, \R, State::S0).is_fully_zero() filter !non_first_cells.count(first) generate @@ -84,8 +84,8 @@ arg en_port match first select first->type.in($_DFF_N_, $_DFF_P_, $_DFFE_NN_, $_DFFE_NP_, $_DFFE_PN_, $_DFFE_PP_, \FDRE, \FDRE_1) select !first->has_keep_attr() - select !first->type.in(\FDRE) || !param(first, \IS_R_INVERTED, State::S0).as_bool() - select !first->type.in(\FDRE) || !param(first, \IS_D_INVERTED, State::S0).as_bool() + select !first->type.in(\FDRE) || !param(first, \IS_R_INVERTED).as_bool() + select !first->type.in(\FDRE) || !param(first, \IS_D_INVERTED).as_bool() select !first->type.in(\FDRE, \FDRE_1) || port(first, \R, State::S0).is_fully_zero() endmatch @@ -111,9 +111,9 @@ match next index <SigBit> port(next, \Q) === port(first, \D) filter port(next, clk_port) == port(first, clk_port) filter en_port == IdString() || port(next, en_port) == port(first, en_port) - filter !first->type.in(\FDRE) || param(next, \IS_C_INVERTED, State::S0).as_bool() == param(first, \IS_C_INVERTED, State::S0).as_bool() - filter !first->type.in(\FDRE) || param(next, \IS_D_INVERTED, State::S0).as_bool() == param(first, \IS_D_INVERTED, State::S0).as_bool() - filter !first->type.in(\FDRE) || param(next, \IS_R_INVERTED, State::S0).as_bool() == param(first, \IS_R_INVERTED, State::S0).as_bool() + filter !first->type.in(\FDRE) || param(next, \IS_C_INVERTED).as_bool() == param(first, \IS_C_INVERTED).as_bool() + filter !first->type.in(\FDRE) || param(next, \IS_D_INVERTED).as_bool() == param(first, \IS_D_INVERTED).as_bool() + filter !first->type.in(\FDRE) || param(next, \IS_R_INVERTED).as_bool() == param(first, \IS_R_INVERTED).as_bool() filter !first->type.in(\FDRE, \FDRE_1) || port(next, \R, State::S0).is_fully_zero() endmatch @@ -138,9 +138,9 @@ match next index <SigBit> port(next, \Q) === port(chain.back(), \D) filter port(next, clk_port) == port(first, clk_port) filter en_port == IdString() || port(next, en_port) == port(first, en_port) - filter !first->type.in(\FDRE) || param(next, \IS_C_INVERTED, State::S0).as_bool() == param(first, \IS_C_INVERTED, State::S0).as_bool() - filter !first->type.in(\FDRE) || param(next, \IS_D_INVERTED, State::S0).as_bool() == param(first, \IS_D_INVERTED, State::S0).as_bool() - filter !first->type.in(\FDRE) || param(next, \IS_R_INVERTED, State::S0).as_bool() == param(first, \IS_R_INVERTED, State::S0).as_bool() + filter !first->type.in(\FDRE) || param(next, \IS_C_INVERTED).as_bool() == param(first, \IS_C_INVERTED).as_bool() + filter !first->type.in(\FDRE) || param(next, \IS_D_INVERTED).as_bool() == param(first, \IS_D_INVERTED).as_bool() + filter !first->type.in(\FDRE) || param(next, \IS_R_INVERTED).as_bool() == param(first, \IS_R_INVERTED).as_bool() filter !first->type.in(\FDRE, \FDRE_1) || port(next, \R, State::S0).is_fully_zero() generate Cell *cell = module->addCell(NEW_ID, chain.back()->type); diff --git a/passes/sat/qbfsat.cc b/passes/sat/qbfsat.cc index 981271770..d99ca1b53 100644 --- a/passes/sat/qbfsat.cc +++ b/passes/sat/qbfsat.cc @@ -49,15 +49,15 @@ struct QbfSolutionType { }; struct QbfSolveOptions { - bool specialize, specialize_from_file, write_solution, nocleanup, dump_final_smt2, assume_outputs; + bool specialize, specialize_from_file, write_solution, nocleanup, dump_final_smt2, assume_outputs, assume_neg; bool sat, unsat, show_smtbmc; std::string specialize_soln_file; std::string write_soln_soln_file; std::string dump_final_smt2_file; size_t argidx; QbfSolveOptions() : specialize(false), specialize_from_file(false), write_solution(false), - nocleanup(false), dump_final_smt2(false), assume_outputs(false), sat(false), unsat(false), - show_smtbmc(false), argidx(0) {}; + nocleanup(false), dump_final_smt2(false), assume_outputs(false), assume_neg(false), + sat(false), unsat(false), show_smtbmc(false), argidx(0) {}; }; void recover_solution(QbfSolutionType &sol) { @@ -98,11 +98,8 @@ dict<std::string, std::string> get_hole_loc_name_map(RTLIL::Module *module, cons for (auto cell : module->cells()) { std::string cell_src = cell->get_src_attribute(); auto pos = sol.hole_to_value.find(cell_src); - if (pos != sol.hole_to_value.end()) { -#ifndef NDEBUG - log_assert(cell->type.in("$anyconst", "$anyseq")); - log_assert(cell->getPort(ID::Y).is_wire()); -#endif + if (pos != sol.hole_to_value.end() && cell->type.in("$anyconst", "$anyseq")) { + log_assert(hole_loc_to_name.find(pos->first) == hole_loc_to_name.end()); hole_loc_to_name[pos->first] = cell->getPort(ID::Y).as_wire()->name.str(); } } @@ -242,7 +239,7 @@ void allconstify_inputs(RTLIL::Module *module, const pool<std::string> &input_wi module->fixup_ports(); } -void assume_miter_outputs(RTLIL::Module *module) { +void assume_miter_outputs(RTLIL::Module *module, const QbfSolveOptions &opt) { std::vector<RTLIL::Wire *> wires_to_assume; for (auto w : module->wires()) if (w->port_output && w->width == 1) @@ -257,7 +254,14 @@ void assume_miter_outputs(RTLIL::Module *module) { log("\n"); } - for(auto i = 0; wires_to_assume.size() > 1; ++i) { + if (opt.assume_neg) { + for (unsigned int i = 0; i < wires_to_assume.size(); ++i) { + RTLIL::SigSpec n_wire = module->LogicNot(wires_to_assume[i]->name.str() + "__n__qbfsat", wires_to_assume[i], false, wires_to_assume[i]->get_src_attribute()); + wires_to_assume[i] = n_wire.as_wire(); + } + } + + for (auto i = 0; wires_to_assume.size() > 1; ++i) { std::vector<RTLIL::Wire *> buf; for (auto j = 0; j + 1 < GetSize(wires_to_assume); j += 2) { std::stringstream strstr; strstr << i << "_" << j; @@ -371,6 +375,10 @@ QbfSolveOptions parse_args(const std::vector<std::string> &args) { opt.assume_outputs = true; continue; } + else if (args[opt.argidx] == "-assume-negative-polarity") { + opt.assume_neg = true; + continue; + } else if (args[opt.argidx] == "-sat") { opt.sat = true; continue; @@ -464,6 +472,11 @@ struct QbfSatPass : public Pass { log(" -assume-outputs\n"); log(" Add an $assume cell for the conjunction of all one-bit module output wires.\n"); log("\n"); + log(" -assume-negative-polarity\n"); + log(" When adding $assume cells for one-bit module output wires, assume they are\n"); + log(" negative polarity signals and should always be low, for example like the\n"); + log(" miters created with the `miter` command.\n"); + log("\n"); log(" -sat\n"); log(" Generate an error if the solver does not return \"sat\".\n"); log("\n"); @@ -512,7 +525,7 @@ struct QbfSatPass : public Pass { pool<std::string> input_wires = validate_design_and_get_inputs(module, opt); allconstify_inputs(module, input_wires); if (opt.assume_outputs) - assume_miter_outputs(module); + assume_miter_outputs(module, opt); QbfSolutionType ret = qbf_solve(module, opt); Pass::call(design, "design -pop"); diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index 59bf5a712..03ca42cf3 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -128,8 +128,12 @@ struct SimInstance for (auto &port : cell->connections()) { if (cell->input(port.first)) - for (auto bit : sigmap(port.second)) + for (auto bit : sigmap(port.second)) { upd_cells[bit].insert(cell); + // Make sure cell inputs connected to constants are updated in the first cycle + if (bit.wire == nullptr) + dirty_bits.insert(bit); + } } if (cell->type.in(ID($dff))) { diff --git a/passes/techmap/abc9_exe.cc b/passes/techmap/abc9_exe.cc index 18618ff91..bad91a224 100644 --- a/passes/techmap/abc9_exe.cc +++ b/passes/techmap/abc9_exe.cc @@ -219,6 +219,17 @@ void abc9_module(RTLIL::Design *design, std::string script_file, std::string exe for (size_t pos = abc9_script.find("{R}"); pos != std::string::npos; pos = abc9_script.find("{R}", pos)) abc9_script = abc9_script.substr(0, pos) + R + abc9_script.substr(pos+3); + if (design->scratchpad_get_bool("abc9.nomfs")) + for (size_t pos = abc9_script.find("&mfs"); pos != std::string::npos; pos = abc9_script.find("&mfs", pos)) + abc9_script = abc9_script.erase(pos, strlen("&mfs")); + else { + auto s = stringf("&write -n %s/output.aig; ", tempdir_name.c_str()); + for (size_t pos = abc9_script.find("&mfs"); pos != std::string::npos; pos = abc9_script.find("&mfs", pos)) { + abc9_script = abc9_script.insert(pos, s); + pos += GetSize(s) + strlen("&mfs"); + } + } + abc9_script += stringf("; &ps -l; &write -n %s/output.aig", tempdir_name.c_str()); if (design->scratchpad_get_bool("abc9.verify")) { if (dff_mode) @@ -272,8 +283,12 @@ void abc9_module(RTLIL::Design *design, std::string script_file, std::string exe free(abc9_argv[2]); free(abc9_argv[3]); #endif - if (ret != 0) - log_error("ABC: execution of command \"%s\" failed: return code %d.\n", buffer.c_str(), ret); + if (ret != 0) { + if (check_file_exists(stringf("%s/output.aig", tempdir_name.c_str()))) + log_warning("ABC: execution of command \"%s\" failed: return code %d.\n", buffer.c_str(), ret); + else + log_error("ABC: execution of command \"%s\" failed: return code %d.\n", buffer.c_str(), ret); + } } struct Abc9ExePass : public Pass { diff --git a/techlibs/ecp5/ecp5_ffinit.cc b/techlibs/ecp5/ecp5_ffinit.cc index e85bee64e..ba72bd0c6 100644 --- a/techlibs/ecp5/ecp5_ffinit.cc +++ b/techlibs/ecp5/ecp5_ffinit.cc @@ -106,9 +106,7 @@ struct Ecp5FfinitPass : public Pass { SigBit bit_d = sigmap(sig_d[0]); SigBit bit_q = sigmap(sig_q[0]); - std::string regset = "RESET"; - if (cell->hasParam(ID(REGSET))) - regset = cell->getParam(ID(REGSET)).decode_string(); + std::string regset = cell->getParam(ID(REGSET)).decode_string(); State resetState; if (regset == "SET") resetState = State::S1; @@ -135,9 +133,7 @@ struct Ecp5FfinitPass : public Pass { } if (GetSize(sig_lsr) >= 1 && sig_lsr[0] != State::S0) { - std::string srmode = "LSR_OVER_CE"; - if (cell->hasParam(ID(SRMODE))) - srmode = cell->getParam(ID(SRMODE)).decode_string(); + std::string srmode = cell->getParam(ID(SRMODE)).decode_string(); if (srmode == "ASYNC") { log("Async reset value %c for FF cell %s inconsistent with init value %c.\n", resetState != State::S0 ? '1' : '0', log_id(cell), val != State::S0 ? '1' : '0'); @@ -154,9 +150,7 @@ struct Ecp5FfinitPass : public Pass { cell->setPort(ID(LSR), State::S0); if(cell->hasPort(ID(CE))) { - std::string cemux = "CE"; - if (cell->hasParam(ID(CEMUX))) - cemux = cell->getParam(ID(CEMUX)).decode_string(); + std::string cemux = cell->getParam(ID(CEMUX)).decode_string(); SigSpec sig_ce = cell->getPort(ID(CE)); if (GetSize(sig_ce) >= 1) { SigBit bit_ce = sigmap(sig_ce[0]); diff --git a/techlibs/ecp5/ecp5_gsr.cc b/techlibs/ecp5/ecp5_gsr.cc index d1503f71f..3d3f8e1c1 100644 --- a/techlibs/ecp5/ecp5_gsr.cc +++ b/techlibs/ecp5/ecp5_gsr.cc @@ -114,9 +114,9 @@ struct Ecp5GsrPass : public Pass { { if (cell->type != ID(TRELLIS_FF)) continue; - if (!cell->hasParam(ID(GSR)) || cell->getParam(ID(GSR)).decode_string() != "ENABLED") + if (cell->getParam(ID(GSR)).decode_string() != "ENABLED") continue; - if (!cell->hasParam(ID(SRMODE)) || cell->getParam(ID(SRMODE)).decode_string() != "ASYNC") + if (cell->getParam(ID(SRMODE)).decode_string() != "ASYNC") continue; SigSpec sig_lsr = cell->getPort(ID(LSR)); if (GetSize(sig_lsr) < 1) diff --git a/techlibs/intel_alm/Makefile.inc b/techlibs/intel_alm/Makefile.inc index 66204c8fc..bbf233aeb 100644 --- a/techlibs/intel_alm/Makefile.inc +++ b/techlibs/intel_alm/Makefile.inc @@ -15,9 +15,6 @@ $(foreach bramtype, $(bramtypes), $(eval $(call add_share_file,share/intel_alm/c $(eval $(call add_share_file,share/intel_alm/common,techlibs/intel_alm/common/lutram_mlab.txt)) $(eval $(call add_share_file,share/intel_alm/common,techlibs/intel_alm/common/lutram_mlab_map.v)) -families := cyclonev cyclone10gx - # Miscellaneous $(eval $(call add_share_file,share/intel_alm/common,techlibs/intel_alm/common/megafunction_bb.v)) $(eval $(call add_share_file,share/intel_alm/common,techlibs/intel_alm/common/quartus_rename.v)) -$(foreach family, $(families), $(eval $(call add_share_file,share/intel_alm/$(family),techlibs/intel_alm/$(family)/quartus_rename.v))) diff --git a/techlibs/intel_alm/common/alm_sim.v b/techlibs/intel_alm/common/alm_sim.v index 69768d9f7..979c51132 100644 --- a/techlibs/intel_alm/common/alm_sim.v +++ b/techlibs/intel_alm/common/alm_sim.v @@ -1,3 +1,72 @@ +// The core logic primitive of the Cyclone V/10GX is the Adaptive Logic Module +// (ALM). Each ALM is made up of an 8-input, 2-output look-up table, covered +// in this file, connected to combinational outputs, a carry chain, and four +// D flip-flops (which are covered as MISTRAL_FF in dff_sim.v). +// +// The ALM is vertically symmetric, so I find it helps to think in terms of +// half-ALMs, as that's predominantly the unit that synth_intel_alm uses. +// +// ALMs are quite flexible, having multiple modes. +// +// Normal (combinational) mode +// --------------------------- +// The ALM can implement: +// - a single 6-input function (with the other inputs usable for flip-flop access) +// - two 5-input functions that share two inputs +// - a 5-input and a 4-input function that share one input +// - a 5-input and a 3-or-less-input function that share no inputs +// - two 4-or-less-input functions that share no inputs +// +// Normal-mode functions are represented as MISTRAL_ALUTN cells with N inputs. +// It would be possible to represent a normal mode function as a single cell - +// the vendor cyclone{v,10gx}_lcell_comb cell does exactly that - but I felt +// it was more user-friendly to print out the specific function sizes +// separately. +// +// With the exception of MISTRAL_ALUT6, you can think of two normal-mode cells +// fitting inside a single ALM. +// +// Extended (7-input) mode +// ----------------------- +// The ALM can also fit a 7-input function made of two 5-input functions that +// share four inputs, multiplexed by another input. +// +// Because this can't accept arbitrary 7-input functions, Yosys can't handle +// it, so it doesn't have a cell, but I would likely call it MISTRAL_ALUT7(E?) +// if it did, and it would take up a full ALM. +// +// It might be possible to add an extraction pass to examine all ALUT5 cells +// that feed into ALUT3 cells to see if they can be combined into an extended +// ALM, but I don't think it will be worth it. +// +// Arithmetic mode +// --------------- +// In arithmetic mode, each half-ALM uses its carry chain to perform fast addition +// of two four-input functions that share three inputs. Oddly, the result of +// one of the functions is inverted before being added (you can see this as +// the dot on a full-adder input of Figure 1-8 in the Handbook). +// +// The cell for an arithmetic-mode half-ALM is MISTRAL_ALM_ARITH. One idea +// I've had (or rather was suggested by mwk) is that functions that feed into +// arithmetic-mode cells could be packed directly into the arithmetic-mode +// cell as a function, which reduces the number of ALMs needed. +// +// Shared arithmetic mode +// ---------------------- +// Shared arithmetic mode looks a lot like arithmetic mode, but here the +// output of every other four-input function goes to the input of the adder +// the next bit along. What this means is that adding three bits together can +// be done in an ALM, because functions can be used to implement addition that +// then feeds into the carry chain. This means that three bits can be added per +// ALM, as opposed to two in the arithmetic mode. +// +// Shared arithmetic mode doesn't currently have a cell, but I intend to add +// it as MISTRAL_ALM_SHARED, and have it occupy a full ALM. Because it adds +// three bits per cell, it makes addition shorter and use less ALMs, but +// I don't know enough to tell whether it's more efficient to use shared +// arithmetic mode to shorten the carry chain, or plain arithmetic mode with +// the functions packed in. + `default_nettype none (* abc9_lut=2, lib_whitebox *) diff --git a/techlibs/intel_alm/common/dff_map.v b/techlibs/intel_alm/common/dff_map.v index f7f2fe3c3..962be670c 100644 --- a/techlibs/intel_alm/common/dff_map.v +++ b/techlibs/intel_alm/common/dff_map.v @@ -6,7 +6,7 @@ parameter _TECHMAP_WIREINIT_Q_ = 1'b0; if (_TECHMAP_WIREINIT_Q_ !== 1'b1) begin wire _TECHMAP_REMOVEINIT_Q_ = 1'b1; MISTRAL_FF _TECHMAP_REPLACE_(.DATAIN(D), .CLK(C), .ACLR(1'b1), .ENA(1'b1), .SCLR(1'b0), .SLOAD(1'b0), .SDATA(1'b0), .Q(Q)); -end else $error("Unsupported flop: $_DFF_P_ with INIT=1"); +end else $error("Cannot implement a flip-flop that initialises to one"); endmodule module \$_DFF_N_ (input D, C, output Q); @@ -14,7 +14,7 @@ parameter _TECHMAP_WIREINIT_Q_ = 1'b0; if (_TECHMAP_WIREINIT_Q_ !== 1'b1) begin wire _TECHMAP_REMOVEINIT_Q_ = 1'b1; MISTRAL_FF _TECHMAP_REPLACE_(.DATAIN(D), .CLK(~C), .ACLR(1'b1), .ENA(1'b1), .SCLR(1'b0), .SLOAD(1'b0), .SDATA(1'b0), .Q(Q)); -end else $error("Unsupported flop: $_DFF_N_ with INIT=1"); +end else $error("Cannot implement a flip-flop that initialises to one"); endmodule // D flip-flops with reset @@ -23,7 +23,7 @@ parameter _TECHMAP_WIREINIT_Q_ = 1'b0; if (_TECHMAP_WIREINIT_Q_ !== 1'b1) begin wire _TECHMAP_REMOVEINIT_Q_ = 1'b1; MISTRAL_FF _TECHMAP_REPLACE_(.DATAIN(D), .CLK(C), .ACLR(~R), .ENA(1'b1), .SCLR(1'b0), .SLOAD(1'b0), .SDATA(1'b0), .Q(Q)); -end else $error("Unsupported flop: $_DFF_PP0_ with INIT=1"); +end else $error("Cannot implement a flip-flop with reset that initialises to one"); endmodule module \$_DFF_PN0_ (input D, C, R, output Q); @@ -31,7 +31,7 @@ parameter _TECHMAP_WIREINIT_Q_ = 1'b0; if (_TECHMAP_WIREINIT_Q_ !== 1'b1) begin wire _TECHMAP_REMOVEINIT_Q_ = 1'b1; MISTRAL_FF _TECHMAP_REPLACE_(.DATAIN(D), .CLK(C), .ACLR(R), .ENA(1'b1), .SCLR(1'b0), .SLOAD(1'b0), .SDATA(1'b0), .Q(Q)); -end else $error("Unsupported flop: $_DFF_PN0_ with INIT=1"); +end else $error("Cannot implement a flip-flop with reset that initialises to one"); endmodule module \$_DFF_NP0_ (input D, C, R, output Q); @@ -39,7 +39,7 @@ parameter _TECHMAP_WIREINIT_Q_ = 1'b0; if (_TECHMAP_WIREINIT_Q_ !== 1'b1) begin wire _TECHMAP_REMOVEINIT_Q_ = 1'b1; MISTRAL_FF _TECHMAP_REPLACE_(.DATAIN(D), .CLK(~C), .ACLR(~R), .ENA(1'b1), .SCLR(1'b0), .SLOAD(1'b0), .SDATA(1'b0), .Q(Q)); -end else $error("Unsupported flop: $_DFF_NP0_ with INIT=1"); +end else $error("Cannot implement a flip-flop with reset that initialises to one"); endmodule module \$_DFF_NN0_ (input D, C, R, output Q); @@ -47,7 +47,7 @@ parameter _TECHMAP_WIREINIT_Q_ = 1'b0; if (_TECHMAP_WIREINIT_Q_ !== 1'b1) begin wire _TECHMAP_REMOVEINIT_Q_ = 1'b1; MISTRAL_FF _TECHMAP_REPLACE_(.DATAIN(D), .CLK(~C), .ACLR(R), .ENA(1'b1), .SCLR(1'b0), .SLOAD(1'b0), .SDATA(1'b0), .Q(Q)); -end else $error("Unsupported flop: $_DFF_NN0_ with INIT=1"); +end else $error("Cannot implement a flip-flop with reset that initialises to one"); endmodule // D flip-flops with set @@ -58,7 +58,7 @@ if (_TECHMAP_WIREINIT_Q_ !== 1'b0) begin wire Q_tmp; MISTRAL_FF _TECHMAP_REPLACE_(.DATAIN(~D), .CLK(C), .ACLR(~R), .ENA(1'b1), .SCLR(1'b0), .SLOAD(1'b0), .SDATA(1'b0), .Q(Q_tmp)); assign Q = ~Q_tmp; -end else $error("Unsupported flop: $_DFF_PP1_ with INIT=0"); +end else $error("Cannot implement a flip-flop with set that initialises to zero"); endmodule module \$_DFF_PN1_ (input D, C, R, output Q); @@ -67,7 +67,7 @@ if (_TECHMAP_WIREINIT_Q_ !== 1'b0) begin wire _TECHMAP_REMOVEINIT_Q_ = 1'b1; wire Q_tmp; MISTRAL_FF _TECHMAP_REPLACE_(.DATAIN(~D), .CLK(C), .ACLR(R), .ENA(1'b1), .SCLR(1'b0), .SLOAD(1'b0), .SDATA(1'b0), .Q(Q_tmp)); -end else $error("Unsupported flop: $_DFF_PN1_ with INIT=0"); +end else $error("Cannot implement a flip-flop with set that initialises to zero"); endmodule module \$_DFF_NP1_ (input D, C, R, output Q); @@ -77,7 +77,7 @@ if (_TECHMAP_WIREINIT_Q_ !== 1'b0) begin wire Q_tmp; MISTRAL_FF _TECHMAP_REPLACE_(.DATAIN(~D), .CLK(~C), .ACLR(~R), .ENA(1'b1), .SCLR(1'b0), .SLOAD(1'b0), .SDATA(1'b0), .Q(Q_tmp)); assign Q = ~Q_tmp; -end else $error("Unsupported flop: $_DFF_NP1_ with INIT=0"); +end else $error("Cannot implement a flip-flop with set that initialises to zero"); endmodule module \$_DFF_NN1_ (input D, C, R, output Q); @@ -87,7 +87,7 @@ if (_TECHMAP_WIREINIT_Q_ !== 1'b0) begin wire Q_tmp; MISTRAL_FF _TECHMAP_REPLACE_(.DATAIN(~D), .CLK(~C), .ACLR(R), .ENA(1'b1), .SCLR(1'b0), .SLOAD(1'b0), .SDATA(1'b0), .Q(Q_tmp)); assign Q = ~Q_tmp; -end else $error("Unsupported flop: $_DFF_NN1_ with INIT=0"); +end else $error("Cannot implement a flip-flop with set that initialises to zero"); endmodule // D flip-flops with clock enable @@ -96,7 +96,7 @@ parameter _TECHMAP_WIREINIT_Q_ = 1'b0; if (_TECHMAP_WIREINIT_Q_ !== 1'b1) begin wire _TECHMAP_REMOVEINIT_Q_ = 1'b1; MISTRAL_FF _TECHMAP_REPLACE_(.DATAIN(D), .CLK(C), .ACLR(1'b1), .ENA(E), .SCLR(1'b0), .SLOAD(1'b0), .SDATA(1'b0), .Q(Q)); -end else $error("Unsupported flop: $_DFFE_PP_ with INIT=1"); +end else $error("Cannot implement a flip-flop with enable that initialises to one"); endmodule module \$_DFFE_PN_ (input D, C, E, output Q); @@ -104,7 +104,7 @@ parameter _TECHMAP_WIREINIT_Q_ = 1'b0; if (_TECHMAP_WIREINIT_Q_ !== 1'b1) begin wire _TECHMAP_REMOVEINIT_Q_ = 1'b1; MISTRAL_FF _TECHMAP_REPLACE_(.DATAIN(D), .CLK(C), .ACLR(1'b1), .ENA(~E), .SCLR(1'b0), .SLOAD(1'b0), .SDATA(1'b0), .Q(Q)); -end else $error("Unsupported flop: $_DFFE_PN_ with INIT=1"); +end else $error("Cannot implement a flip-flop with enable that initialises to one"); endmodule module \$_DFFE_NP_ (input D, C, E, output Q); @@ -112,7 +112,7 @@ parameter _TECHMAP_WIREINIT_Q_ = 1'b0; if (_TECHMAP_WIREINIT_Q_ !== 1'b1) begin wire _TECHMAP_REMOVEINIT_Q_ = 1'b1; MISTRAL_FF _TECHMAP_REPLACE_(.DATAIN(D), .CLK(~C), .ACLR(1'b1), .ENA(E), .SCLR(1'b0), .SLOAD(1'b0), .SDATA(1'b0), .Q(Q)); -end else $error("Unsupported flop: $_DFFE_NP_ with INIT=1"); +end else $error("Cannot implement a flip-flop with enable that initialises to one"); endmodule module \$_DFFE_NN_ (input D, C, E, output Q); @@ -120,5 +120,5 @@ parameter _TECHMAP_WIREINIT_Q_ = 1'b0; if (_TECHMAP_WIREINIT_Q_ !== 1'b1) begin wire _TECHMAP_REMOVEINIT_Q_ = 1'b1; MISTRAL_FF _TECHMAP_REPLACE_(.DATAIN(D), .CLK(~C), .ACLR(1'b1), .ENA(~E), .SCLR(1'b0), .SLOAD(1'b0), .SDATA(1'b0), .Q(Q)); -end else $error("Unsupported flop: $_DFFE_NN_ with INIT=1"); +end else $error("Cannot implement a flip-flop with enable that initialises to one"); endmodule diff --git a/techlibs/intel_alm/common/dff_sim.v b/techlibs/intel_alm/common/dff_sim.v index 07865905f..32444dd46 100644 --- a/techlibs/intel_alm/common/dff_sim.v +++ b/techlibs/intel_alm/common/dff_sim.v @@ -1,3 +1,47 @@ +// The four D flip-flops (DFFs) in a Cyclone V/10GX Adaptive Logic Module (ALM) +// act as one-bit memory cells that can be placed very flexibly (wherever there's +// an ALM); each flop is represented by a MISTRAL_FF cell. +// +// The flops in these chips are rather flexible in some ways, but in practice +// quite crippled by FPGA standards. +// +// What the flops can do +// --------------------- +// The core flop acts as a single-bit memory that initialises to zero at chip +// reset. It takes in data on the rising edge of CLK if ENA is high, +// and outputs it to Q. The ENA (clock enable) pin can therefore be used to +// capture the input only if a condition is true. +// +// The data itself is zero if SCLR (synchronous clear) is high, else it comes +// from SDATA (synchronous data) if SLOAD (synchronous load) is high, or DATAIN +// if SLOAD is low. +// +// If ACLR (asynchronous clear) is low then Q is forced to zero, regardless of +// the synchronous inputs or CLK edge. This is most often used for an FPGA-wide +// power-on reset. +// +// An asynchronous set that sets Q to one can be emulated by inverting the input +// and output of the flop, resulting in ACLR forcing Q to zero, which then gets +// inverted to produce one. Likewise, logic can operate on the falling edge of +// CLK if CLK is inverted before being passed as an input. +// +// What the flops *can't* do +// ------------------------- +// The trickiest part of the above capabilities is the lack of configurable +// initialisation state. For example, it isn't possible to implement a flop with +// asynchronous clear that initialises to one, because the hardware initialises +// to zero. Likewise, you can't emulate a flop with asynchronous set that +// initialises to zero, because the inverters mean the flop initialises to one. +// +// If the input design requires one of these cells (which appears to be rare +// in practice) then synth_intel_alm will fail to synthesize the design where +// other Yosys synthesis scripts might succeed. +// +// This stands in notable contrast to e.g. Xilinx flip-flops, which have +// configurable initialisation state and native synchronous/asynchronous +// set/clear (although not at the same time), which means they can generally +// implement a much wider variety of logic. + // DATAIN: synchronous data input // CLK: clock input (positive edge) // ACLR: asynchronous clear (negative-true) diff --git a/techlibs/intel_alm/common/quartus_rename.v b/techlibs/intel_alm/common/quartus_rename.v index d9961c174..ac0fe12aa 100644 --- a/techlibs/intel_alm/common/quartus_rename.v +++ b/techlibs/intel_alm/common/quartus_rename.v @@ -1,3 +1,10 @@ +`ifdef cyclonev +`define LCELL cyclonev_lcell_comb +`endif +`ifdef cyclone10gx +`define LCELL cyclone10gx_lcell_comb +`endif + module __MISTRAL_VCC(output Q); MISTRAL_ALUT2 #(.LUT(4'b1111)) _TECHMAP_REPLACE_ (.A(1'b1), .B(1'b1), .Q(Q)); @@ -17,3 +24,59 @@ module MISTRAL_FF(input DATAIN, CLK, ACLR, ENA, SCLR, SLOAD, SDATA, output reg Q dffeas #(.power_up("low"), .is_wysiwyg("true")) _TECHMAP_REPLACE_ (.d(DATAIN), .clk(CLK), .clrn(ACLR), .ena(ENA), .sclr(SCLR), .sload(SLOAD), .asdata(SDATA), .q(Q)); endmodule + + +module MISTRAL_ALUT6(input A, B, C, D, E, F, output Q); +parameter [63:0] LUT = 64'h0000_0000_0000_0000; + +`LCELL #(.lut_mask(LUT)) _TECHMAP_REPLACE_ (.dataa(A), .datab(B), .datac(C), .datad(D), .datae(E), .dataf(F), .combout(Q)); + +endmodule + + +module MISTRAL_ALUT5(input A, B, C, D, E, output Q); +parameter [31:0] LUT = 32'h0000_0000; + +`LCELL #(.lut_mask({2{LUT}})) _TECHMAP_REPLACE_ (.dataa(A), .datab(B), .datac(C), .datad(D), .datae(E), .combout(Q)); + +endmodule + + +module MISTRAL_ALUT4(input A, B, C, D, output Q); +parameter [15:0] LUT = 16'h0000; + +`LCELL #(.lut_mask({4{LUT}})) _TECHMAP_REPLACE_ (.dataa(A), .datab(B), .datac(C), .datad(D), .combout(Q)); + +endmodule + + +module MISTRAL_ALUT3(input A, B, C, output Q); +parameter [7:0] LUT = 8'h00; + +`LCELL #(.lut_mask({8{LUT}})) _TECHMAP_REPLACE_ (.dataa(A), .datab(B), .datac(C), .combout(Q)); + +endmodule + + +module MISTRAL_ALUT2(input A, B, output Q); +parameter [3:0] LUT = 4'h0; + +`LCELL #(.lut_mask({16{LUT}})) _TECHMAP_REPLACE_ (.dataa(A), .datab(B), .combout(Q)); + +endmodule + + +module MISTRAL_NOT(input A, output Q); + +NOT _TECHMAP_REPLACE_ (.IN(A), .OUT(Q)); + +endmodule + + +module MISTRAL_ALUT_ARITH(input A, B, C, D0, D1, CI, output SO, CO); +parameter LUT0 = 16'h0000; +parameter LUT1 = 16'h0000; + +`LCELL #(.lut_mask({16'h0, LUT1, 16'h0, LUT0})) _TECHMAP_REPLACE_ (.dataa(A), .datab(B), .datac(C), .datad(D0), .dataf(D1), .cin(CI), .sumout(SO), .cout(CO)); + +endmodule diff --git a/techlibs/intel_alm/cyclone10gx/quartus_rename.v b/techlibs/intel_alm/cyclone10gx/quartus_rename.v deleted file mode 100644 index 3fbc508ed..000000000 --- a/techlibs/intel_alm/cyclone10gx/quartus_rename.v +++ /dev/null @@ -1,54 +0,0 @@ -module MISTRAL_ALUT6(input A, B, C, D, E, F, output Q); -parameter LUT = 64'h0000_0000_0000_0000; - -cyclone10gx_lcell_comb #(.lut_mask(LUT)) _TECHMAP_REPLACE_ (.dataa(A), .datab(B), .datac(C), .datad(D), .datae(E), .dataf(F), .combout(Q)); - -endmodule - - -module MISTRAL_ALUT5(input A, B, C, D, E, output Q); -parameter LUT = 32'h0000_0000; - -cyclone10gx_lcell_comb #(.lut_mask({2{LUT}})) _TECHMAP_REPLACE_ (.dataa(A), .datab(B), .datac(C), .datad(D), .datae(E), .combout(Q)); - -endmodule - - -module MISTRAL_ALUT4(input A, B, C, D, output Q); -parameter LUT = 16'h0000; - -cyclone10gx_lcell_comb #(.lut_mask({4{LUT}})) _TECHMAP_REPLACE_ (.dataa(A), .datab(B), .datac(C), .datad(D), .combout(Q)); - -endmodule - - -module MISTRAL_ALUT3(input A, B, C, output Q); -parameter LUT = 8'h00; - -cyclone10gx_lcell_comb #(.lut_mask({8{LUT}})) _TECHMAP_REPLACE_ (.dataa(A), .datab(B), .datac(C), .combout(Q)); - -endmodule - - -module MISTRAL_ALUT2(input A, B, output Q); -parameter LUT = 4'h0; - -cyclone10gx_lcell_comb #(.lut_mask({16{LUT}})) _TECHMAP_REPLACE_ (.dataa(A), .datab(B), .combout(Q)); - -endmodule - - -module MISTRAL_NOT(input A, output Q); - -NOT _TECHMAP_REPLACE_ (.IN(A), .OUT(Q)); - -endmodule - - -module MISTRAL_ALUT_ARITH(input A, B, C, D0, D1, CI, output SO, CO); -parameter LUT0 = 16'h0000; -parameter LUT1 = 16'h0000; - -cyclone10gx_lcell_comb #(.lut_mask({16'h0, LUT1, 16'h0, LUT0})) _TECHMAP_REPLACE_ (.dataa(A), .datab(B), .datac(C), .datad(D0), .dataf(D1), .cin(CI), .sumout(SO), .cout(CO)); - -endmodule diff --git a/techlibs/intel_alm/cyclonev/quartus_rename.v b/techlibs/intel_alm/cyclonev/quartus_rename.v deleted file mode 100644 index 6eff375e1..000000000 --- a/techlibs/intel_alm/cyclonev/quartus_rename.v +++ /dev/null @@ -1,54 +0,0 @@ -module MISTRAL_ALUT6(input A, B, C, D, E, F, output Q); -parameter LUT = 64'h0000_0000_0000_0000; - -cyclonev_lcell_comb #(.lut_mask(LUT)) _TECHMAP_REPLACE_ (.dataa(A), .datab(B), .datac(C), .datad(D), .datae(E), .dataf(F), .combout(Q)); - -endmodule - - -module MISTRAL_ALUT5(input A, B, C, D, E, output Q); -parameter LUT = 32'h0000_0000; - -cyclonev_lcell_comb #(.lut_mask({2{LUT}})) _TECHMAP_REPLACE_ (.dataa(A), .datab(B), .datac(C), .datad(D), .datae(E), .combout(Q)); - -endmodule - - -module MISTRAL_ALUT4(input A, B, C, D, output Q); -parameter LUT = 16'h0000; - -cyclonev_lcell_comb #(.lut_mask({4{LUT}})) _TECHMAP_REPLACE_ (.dataa(A), .datab(B), .datac(C), .datad(D), .combout(Q)); - -endmodule - - -module MISTRAL_ALUT3(input A, B, C, output Q); -parameter LUT = 8'h00; - -cyclonev_lcell_comb #(.lut_mask({8{LUT}})) _TECHMAP_REPLACE_ (.dataa(A), .datab(B), .datac(C), .combout(Q)); - -endmodule - - -module MISTRAL_ALUT2(input A, B, output Q); -parameter LUT = 4'h0; - -cyclonev_lcell_comb #(.lut_mask({16{LUT}})) _TECHMAP_REPLACE_ (.dataa(A), .datab(B), .combout(Q)); - -endmodule - - -module MISTRAL_NOT(input A, output Q); - -NOT _TECHMAP_REPLACE_ (.IN(A), .OUT(Q)); - -endmodule - - -module MISTRAL_ALUT_ARITH(input A, B, C, D0, D1, CI, output SO, CO); -parameter LUT0 = 16'h0000; -parameter LUT1 = 16'h0000; - -cyclonev_lcell_comb #(.lut_mask({16'h0, LUT1, 16'h0, LUT0})) _TECHMAP_REPLACE_ (.dataa(A), .datab(B), .datac(C), .datad(D0), .dataf(D1), .cin(CI), .sumout(SO), .cout(CO)); - -endmodule diff --git a/techlibs/intel_alm/synth_intel_alm.cc b/techlibs/intel_alm/synth_intel_alm.cc index 47aa11500..200b0cdd1 100644 --- a/techlibs/intel_alm/synth_intel_alm.cc +++ b/techlibs/intel_alm/synth_intel_alm.cc @@ -200,6 +200,8 @@ struct SynthIntelALMPass : public ScriptPass { if (check_label("map_ffs")) { run("dff2dffe -direct-match $_DFF_*"); + // As mentioned in common/dff_sim.v, Intel flops power up to zero, + // so use `zinit` to add inverters where needed. run("zinit"); run("techmap -map +/techmap.v -map +/intel_alm/common/dff_map.v"); run("opt -full -undriven -mux_undef"); @@ -223,10 +225,17 @@ struct SynthIntelALMPass : public ScriptPass { if (check_label("quartus")) { if (quartus || help_mode) { + // Quartus ICEs if you have a wire which has `[]` in its name, + // which Yosys produces when building memories out of flops. + run("rename -hide w:*[* w:*]*"); + // VQM mode does not support 'x, so replace those with zero. run("setundef -zero"); + // VQM mode does not support multi-bit constant assignments + // (e.g. 2'b00 is an error), so as a workaround use references + // to constant driver cells, which Quartus accepts. run("hilomap -singleton -hicell __MISTRAL_VCC Q -locell __MISTRAL_GND Q"); - run("techmap -map +/intel_alm/common/quartus_rename.v"); - run(stringf("techmap -map +/intel_alm/%s/quartus_rename.v", family_opt.c_str())); + // Rename from Yosys-internal MISTRAL_* cells to Quartus cells. + run(stringf("techmap -D %s -map +/intel_alm/common/quartus_rename.v", family_opt.c_str())); } } diff --git a/techlibs/xilinx/xilinx_dffopt.cc b/techlibs/xilinx/xilinx_dffopt.cc index c608db883..c9d63c9f7 100644 --- a/techlibs/xilinx/xilinx_dffopt.cc +++ b/techlibs/xilinx/xilinx_dffopt.cc @@ -292,18 +292,21 @@ unmap: LutData final_lut; if (worthy_post_r) { final_lut = lut_d_post_r; - log(" Merging R LUT for %s/%s (%d -> %d)\n", log_id(cell), log_id(sig_Q.wire), GetSize(lut_d.second), GetSize(final_lut.second)); } else if (worthy_post_s) { final_lut = lut_d_post_s; - log(" Merging S LUT for %s/%s (%d -> %d)\n", log_id(cell), log_id(sig_Q.wire), GetSize(lut_d.second), GetSize(final_lut.second)); } else if (worthy_post_ce) { final_lut = lut_d_post_ce; - log(" Merging CE LUT for %s/%s (%d -> %d)\n", log_id(cell), log_id(sig_Q.wire), GetSize(lut_d.second), GetSize(final_lut.second)); } else { // Nothing to do here. continue; } + std::string ports; + if (worthy_post_r) ports += " + R"; + if (worthy_post_s) ports += " + S"; + if (worthy_post_ce) ports += " + CE"; + log(" Merging D%s LUTs for %s/%s (%d -> %d)\n", ports.c_str(), log_id(cell), log_id(sig_Q.wire), GetSize(lut_d.second), GetSize(final_lut.second)); + // Okay, we're doing it. Unmap ports. if (worthy_post_r) { cell->unsetParam(ID(IS_R_INVERTED)); diff --git a/tests/arch/ice40/ice40_dsp.ys b/tests/arch/ice40/ice40_dsp.ys index 250273859..b13e525fd 100644 --- a/tests/arch/ice40/ice40_dsp.ys +++ b/tests/arch/ice40/ice40_dsp.ys @@ -8,4 +8,5 @@ assign o4 = a * b; SB_MAC16 m3 (.A(a), .B(b), .O(o5)); endmodule EOT +read_verilog -lib +/ice40/cells_sim.v ice40_dsp diff --git a/tests/arch/intel_alm/quartus_ice.ys b/tests/arch/intel_alm/quartus_ice.ys new file mode 100644 index 000000000..4b9b54d10 --- /dev/null +++ b/tests/arch/intel_alm/quartus_ice.ys @@ -0,0 +1,12 @@ +read_verilog <<EOT +// Verilog has syntax for raw identifiers, where you start it with \ and end it with a space. +// This test crashes Quartus due to it parsing \a[10] as a wire slice and not a raw identifier. +module top(); + (* keep *) wire [31:0] \a[10] ; + (* keep *) wire b; + assign b = \a[10] [31]; +endmodule +EOT + +synth_intel_alm -family cyclonev -quartus +select -assert-none w:*[* w:*]* diff --git a/tests/arch/xilinx/xilinx_dffopt.ys b/tests/arch/xilinx/xilinx_dffopt.ys index dc036acfd..2c729832e 100644 --- a/tests/arch/xilinx/xilinx_dffopt.ys +++ b/tests/arch/xilinx/xilinx_dffopt.ys @@ -18,17 +18,17 @@ FDRE ff (.D(tmp[0]), .CE(tmp[1]), .R(tmp[2]), .Q(o[0])); endmodule EOT - +read_verilog -lib +/xilinx/cells_sim.v design -save t0 equiv_opt -blacklist xilinx_dffopt_blacklist.txt -assert -map +/xilinx/cells_sim.v xilinx_dffopt design -load postopt clean +cd t0 select -assert-count 1 t:FDRE select -assert-count 1 t:LUT6 -select -assert-count 3 t:LUT2 -select -assert-none t:FDRE t:LUT6 t:LUT2 %% t:* %D +select -assert-none t:FDRE t:LUT6 %% t:* %D design -load t0 @@ -36,9 +36,10 @@ equiv_opt -blacklist xilinx_dffopt_blacklist.txt -assert -map +/xilinx/cells_sim design -load postopt clean +cd t0 select -assert-count 1 t:FDRE select -assert-count 1 t:LUT4 -select -assert-count 3 t:LUT2 +select -assert-count 1 t:LUT2 select -assert-none t:FDRE t:LUT4 t:LUT2 %% t:* %D design -reset @@ -65,16 +66,17 @@ endmodule EOT +read_verilog -lib +/xilinx/cells_sim.v design -save t0 equiv_opt -blacklist xilinx_dffopt_blacklist.txt -assert -map +/xilinx/cells_sim.v xilinx_dffopt design -load postopt clean +cd t0 select -assert-count 1 t:FDSE select -assert-count 1 t:LUT6 -select -assert-count 3 t:LUT2 -select -assert-none t:FDSE t:LUT6 t:LUT2 %% t:* %D +select -assert-none t:FDSE t:LUT6 %% t:* %D design -load t0 @@ -82,9 +84,10 @@ equiv_opt -blacklist xilinx_dffopt_blacklist.txt -assert -map +/xilinx/cells_sim design -load postopt clean +cd t0 select -assert-count 1 t:FDSE select -assert-count 1 t:LUT4 -select -assert-count 3 t:LUT2 +select -assert-count 1 t:LUT2 select -assert-none t:FDSE t:LUT4 t:LUT2 %% t:* %D design -reset @@ -111,15 +114,17 @@ endmodule EOT +read_verilog -lib +/xilinx/cells_sim.v design -save t0 equiv_opt -async2sync -blacklist xilinx_dffopt_blacklist.txt -assert -map +/xilinx/cells_sim.v xilinx_dffopt design -load postopt clean +cd t0 select -assert-count 1 t:FDCE select -assert-count 1 t:LUT4 -select -assert-count 3 t:LUT2 +select -assert-count 1 t:LUT2 select -assert-none t:FDCE t:LUT4 t:LUT2 %% t:* %D design -reset @@ -145,16 +150,17 @@ endmodule EOT +read_verilog -lib +/xilinx/cells_sim.v design -save t0 equiv_opt -blacklist xilinx_dffopt_blacklist.txt -assert -map +/xilinx/cells_sim.v xilinx_dffopt design -load postopt clean +cd t0 select -assert-count 1 t:FDSE select -assert-count 1 t:LUT5 -select -assert-count 2 t:LUT2 -select -assert-none t:FDSE t:LUT5 t:LUT2 %% t:* %D +select -assert-none t:FDSE t:LUT5 %% t:* %D design -load t0 @@ -162,6 +168,7 @@ equiv_opt -blacklist xilinx_dffopt_blacklist.txt -assert -map +/xilinx/cells_sim design -load postopt clean +cd t0 select -assert-count 1 t:FDSE select -assert-count 2 t:LUT2 select -assert-none t:FDSE t:LUT2 %% t:* %D @@ -191,16 +198,17 @@ endmodule EOT +read_verilog -lib +/xilinx/cells_sim.v design -save t0 equiv_opt -blacklist xilinx_dffopt_blacklist.txt -assert -map +/xilinx/cells_sim.v xilinx_dffopt design -load postopt clean +cd t0 select -assert-count 1 t:FDRSE select -assert-count 1 t:LUT6 -select -assert-count 4 t:LUT2 -select -assert-none t:FDRSE t:LUT6 t:LUT2 %% t:* %D +select -assert-none t:FDRSE t:LUT6 %% t:* %D design -load t0 @@ -208,9 +216,10 @@ equiv_opt -blacklist xilinx_dffopt_blacklist.txt -assert -map +/xilinx/cells_sim design -load postopt clean +cd t0 select -assert-count 1 t:FDRSE select -assert-count 1 t:LUT4 -select -assert-count 4 t:LUT2 +select -assert-count 1 t:LUT2 select -assert-none t:FDRSE t:LUT4 t:LUT2 %% t:* %D design -reset diff --git a/tests/arch/xilinx/xilinx_dsp.ys b/tests/arch/xilinx/xilinx_dsp.ys index 3b9f52930..59d8296ab 100644 --- a/tests/arch/xilinx/xilinx_dsp.ys +++ b/tests/arch/xilinx/xilinx_dsp.ys @@ -8,4 +8,5 @@ assign o4 = a * b; DSP48E1 m3 (.A(a), .B(b), .P(o5)); endmodule EOT +read_verilog -lib +/xilinx/cells_sim.v xilinx_dsp diff --git a/tests/select/blackboxes.ys b/tests/select/blackboxes.ys new file mode 100644 index 000000000..9bfe92c6b --- /dev/null +++ b/tests/select/blackboxes.ys @@ -0,0 +1,28 @@ +read_verilog -specify <<EOT +module top(input a, b, output o); +assign o = a & b; +endmodule + +(* blackbox *) +module bb(input a, b, output o); +assign o = a | b; +specify + (a => o) = 1; +endspecify +endmodule + +(* whitebox *) +module wb(input a, b, output o); +assign o = a ^ b; +endmodule +EOT +clean + +select -assert-count 1 c:* +select -assert-none t:* t:$and %d +select -assert-count 3 w:* +select -assert-count 4 * + +select -assert-count 3 =c:* +select -assert-count 10 =w:* +select -assert-count 13 =* diff --git a/tests/various/design.ys b/tests/various/design.ys index f13ad8171..a64430dc7 100644 --- a/tests/various/design.ys +++ b/tests/various/design.ys @@ -1,9 +1,17 @@ read_verilog <<EOT +(* blackbox *) +module bb(input i, output o); +endmodule + +(* whitebox *) +module wb(input i, output o); +assign o = ~i; +endmodule + module top(input i, output o); -assign o = i; +assign o = ~i; endmodule EOT -design -stash foo -design -delete foo -logger -expect error "No saved design 'foo' found!" 1 -design -delete foo + +design -stash gate +design -import gate -as gate diff --git a/tests/various/design1.ys b/tests/various/design1.ys new file mode 100644 index 000000000..f13ad8171 --- /dev/null +++ b/tests/various/design1.ys @@ -0,0 +1,9 @@ +read_verilog <<EOT +module top(input i, output o); +assign o = i; +endmodule +EOT +design -stash foo +design -delete foo +logger -expect error "No saved design 'foo' found!" 1 +design -delete foo diff --git a/tests/various/plugin.sh b/tests/various/plugin.sh index d6d4aee59..2880c8c06 100644 --- a/tests/various/plugin.sh +++ b/tests/various/plugin.sh @@ -1,6 +1,8 @@ set -e rm -f plugin.so CXXFLAGS=$(../../yosys-config --cxxflags) -CXXFLAGS=${CXXFLAGS// -I\/usr\/local\/share\/yosys\/include/ -I..\/..\/share\/include} +DATDIR=$(../../yosys-config --datdir) +DATDIR=${DATDIR//\//\\\/} +CXXFLAGS=${CXXFLAGS//$DATDIR/..\/..\/share} ../../yosys-config --exec --cxx ${CXXFLAGS} --ldflags -shared -o plugin.so plugin.cc ../../yosys -m ./plugin.so -p "test" | grep -q "Plugin test passed!" diff --git a/tests/various/sim_const.ys b/tests/various/sim_const.ys new file mode 100644 index 000000000..d778b92cd --- /dev/null +++ b/tests/various/sim_const.ys @@ -0,0 +1,13 @@ +read_verilog <<EOT + +module top(input clk, output reg [1:0] q); + wire [1:0] x = 2'b10; + always @(posedge clk) + q <= x & 2'b11; +endmodule +EOT + +proc +sim -clock clk -n 1 -w top +select -assert-count 1 a:init=2'b10 top/q %i + |