diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 2a046703b..4a77d2234 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -14,10 +14,10 @@ jobs: run: sudo apt-get install bison flex libreadline-dev tcl-dev libffi-dev - name: Checkout repository - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Initialize CodeQL - uses: github/codeql-action/init@v2 + uses: github/codeql-action/init@v3 with: languages: cpp queries: security-extended,security-and-quality @@ -26,4 +26,4 @@ jobs: run: make yosys -j6 - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v2 + uses: github/codeql-action/analyze@v3 diff --git a/.github/workflows/emcc.yml b/.github/workflows/emcc.yml index 295d9554b..7c6409c1b 100644 --- a/.github/workflows/emcc.yml +++ b/.github/workflows/emcc.yml @@ -6,13 +6,13 @@ jobs: emcc: runs-on: ubuntu-latest steps: - - uses: mymindstorm/setup-emsdk@v11 - - uses: actions/checkout@v3 + - uses: mymindstorm/setup-emsdk@v14 + - uses: actions/checkout@v4 - name: Build run: | make config-emcc make YOSYS_VER=latest - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: yosysjs path: yosysjs-latest.zip diff --git a/.github/workflows/test-linux.yml b/.github/workflows/test-linux.yml index a16481726..5d929f581 100644 --- a/.github/workflows/test-linux.yml +++ b/.github/workflows/test-linux.yml @@ -79,19 +79,22 @@ jobs: $CXX --version - name: Checkout Yosys - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Get iverilog shell: bash run: | git clone https://github.com/steveicarus/iverilog.git + cd iverilog + git checkout 192b6aec96fde982e6ddcb28b346d5893aa8e874 + echo "IVERILOG_GIT=$(git rev-parse HEAD)" >> $GITHUB_ENV - name: Cache iverilog id: cache-iverilog - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: .local/ - key: ${{ matrix.os.id }}-${{ hashFiles('iverilog/.git/refs/heads/master') }} + key: ${{ matrix.os.id }}-${{ env.IVERILOG_GIT }} - name: Build iverilog if: steps.cache-iverilog.outputs.cache-hit != 'true' diff --git a/.github/workflows/test-macos.yml b/.github/workflows/test-macos.yml index 2b48b7252..17e2ae331 100644 --- a/.github/workflows/test-macos.yml +++ b/.github/workflows/test-macos.yml @@ -35,19 +35,22 @@ jobs: cc --version - name: Checkout Yosys - uses: actions/checkout@v3 + uses: actions/checkout@v4 - name: Get iverilog shell: bash run: | git clone https://github.com/steveicarus/iverilog.git + cd iverilog + git checkout 192b6aec96fde982e6ddcb28b346d5893aa8e874 + echo "IVERILOG_GIT=$(git rev-parse HEAD)" >> $GITHUB_ENV - name: Cache iverilog id: cache-iverilog - uses: actions/cache@v3 + uses: actions/cache@v4 with: path: .local/ - key: ${{ matrix.os.id }}-${{ hashFiles('iverilog/.git/refs/heads/master') }} + key: ${{ matrix.os.id }}-${{ env.IVERILOG_GIT }} - name: Build iverilog if: steps.cache-iverilog.outputs.cache-hit != 'true' diff --git a/.github/workflows/version.yml b/.github/workflows/version.yml index c2a1756e9..47a7fe1a3 100644 --- a/.github/workflows/version.yml +++ b/.github/workflows/version.yml @@ -10,7 +10,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v3 + uses: actions/checkout@v4 with: fetch-depth: 0 - name: Take last commit diff --git a/.github/workflows/vs.yml b/.github/workflows/vs.yml index 428770e72..799c5f259 100644 --- a/.github/workflows/vs.yml +++ b/.github/workflows/vs.yml @@ -6,10 +6,10 @@ jobs: yosys-vcxsrc: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build run: make vcxsrc YOSYS_VER=latest - - uses: actions/upload-artifact@v3 + - uses: actions/upload-artifact@v4 with: name: vcxsrc path: yosys-win32-vcxsrc-latest.zip @@ -18,7 +18,7 @@ jobs: runs-on: windows-2019 needs: yosys-vcxsrc steps: - - uses: actions/download-artifact@v3 + - uses: actions/download-artifact@v4 with: name: vcxsrc path: . diff --git a/.github/workflows/wasi.yml b/.github/workflows/wasi.yml index 74900c388..d6d200d92 100644 --- a/.github/workflows/wasi.yml +++ b/.github/workflows/wasi.yml @@ -6,7 +6,7 @@ jobs: wasi: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v4 - name: Build run: | WASI_SDK=wasi-sdk-19.0 diff --git a/CHANGELOG b/CHANGELOG index 4fb60904a..990f9e17d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -2,12 +2,26 @@ List of major changes and improvements between releases ======================================================= -Yosys 0.36 .. Yosys 0.37-dev +Yosys 0.37 .. Yosys 0.38-dev -------------------------- +Yosys 0.36 .. Yosys 0.37 +-------------------------- + * New commands and options + - Added option "-nodisplay" to read_verilog. + + * SystemVerilog + - Correct hierarchical path names for structs and unions. + + * Various + - Print hierarchy for failed assertions in "sim" pass. + - Add "--present-only" option to "yosys-witness" to omit unused signals. + - Implement a generic record/replay interface for CXXRTL. + - Improved readability of emitted code with "write_verilog". + Yosys 0.35 .. Yosys 0.36 -------------------------- -* New commands and options + * New commands and options - Added option "--" to pass arguments down to tcl when using -c option. - Added ability on MacOS and Windows to pass options after arguments on cli. - Added option "-cmp2softlogic" to synth_lattice. diff --git a/Makefile b/Makefile index 8bde98a1c..0ccdbe2c9 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.36+85 +YOSYS_VER := 0.37+52 # Note: We arrange for .gitcommit to contain the (short) commit hash in # tarballs generated with git-archive(1) using .gitattributes. The git repo @@ -157,7 +157,7 @@ endif OBJS = kernel/version_$(GIT_REV).o bumpversion: - sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline 8f07a0d.. | wc -l`/;" Makefile + sed -i "/^YOSYS_VER := / s/+[0-9][0-9]*$$/+`git log --oneline a5c7f69.. | wc -l`/;" Makefile # set 'ABCREV = default' to use abc/ as it is # @@ -679,12 +679,8 @@ OBJS += libs/bigint/BigUnsigned.o libs/bigint/BigUnsignedInABase.o OBJS += libs/sha1/sha1.o -ifneq ($(SMALL),1) - OBJS += libs/json11/json11.o -OBJS += libs/subcircuit/subcircuit.o - OBJS += libs/ezsat/ezsat.o OBJS += libs/ezsat/ezminisat.o @@ -699,6 +695,10 @@ OBJS += libs/fst/fastlz.o OBJS += libs/fst/lz4.o endif +ifneq ($(SMALL),1) + +OBJS += libs/subcircuit/subcircuit.o + include $(YOSYS_SRC)/frontends/*/Makefile.inc include $(YOSYS_SRC)/passes/*/Makefile.inc include $(YOSYS_SRC)/backends/*/Makefile.inc @@ -707,6 +707,9 @@ include $(YOSYS_SRC)/techlibs/*/Makefile.inc else include $(YOSYS_SRC)/frontends/verilog/Makefile.inc +ifeq ($(ENABLE_VERIFIC),1) +include $(YOSYS_SRC)/frontends/verific/Makefile.inc +endif include $(YOSYS_SRC)/frontends/rtlil/Makefile.inc include $(YOSYS_SRC)/frontends/ast/Makefile.inc include $(YOSYS_SRC)/frontends/blif/Makefile.inc diff --git a/backends/aiger/aiger.cc b/backends/aiger/aiger.cc index bb804f230..f77a64978 100644 --- a/backends/aiger/aiger.cc +++ b/backends/aiger/aiger.cc @@ -54,6 +54,8 @@ struct AigerWriter vector> aig_gates; vector aig_latchin, aig_latchinit, aig_outputs; + vector bit2aig_stack; + size_t next_loop_check = 1024; int aig_m = 0, aig_i = 0, aig_l = 0, aig_o = 0, aig_a = 0; int aig_b = 0, aig_c = 0, aig_j = 0, aig_f = 0; @@ -81,6 +83,23 @@ struct AigerWriter return it->second; } + if (bit2aig_stack.size() == next_loop_check) { + for (size_t i = 0; i < next_loop_check; ++i) + { + SigBit report_bit = bit2aig_stack[i]; + if (report_bit != bit) + continue; + for (size_t j = i; j < next_loop_check; ++j) { + report_bit = bit2aig_stack[j]; + if (report_bit.is_wire() && report_bit.wire->name.isPublic()) + break; + } + log_error("Found combinational logic loop while processing signal %s.\n", log_signal(report_bit)); + } + next_loop_check *= 2; + } + bit2aig_stack.push_back(bit); + // NB: Cannot use iterator returned from aig_map.insert() // since this function is called recursively @@ -101,6 +120,8 @@ struct AigerWriter a = initstate_ff; } + bit2aig_stack.pop_back(); + if (bit == State::Sx || bit == State::Sz) log_error("Design contains 'x' or 'z' bits. Use 'setundef' to replace those constants.\n"); diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index 2c35a1943..d2bca3065 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -1072,9 +1072,45 @@ struct CxxrtlWorker { dump_sigspec_rhs(cell->getPort(ID::EN)); f << " == value<1>{1u}) {\n"; inc_indent(); - f << indent << print_output; - fmt.emit_cxxrtl(f, [this](const RTLIL::SigSpec &sig) { dump_sigspec_rhs(sig); }); - f << ";\n"; + dict fmt_args; + f << indent << "struct : public lazy_fmt {\n"; + inc_indent(); + f << indent << "std::string operator() () const override {\n"; + inc_indent(); + fmt.emit_cxxrtl(f, indent, [&](const RTLIL::SigSpec &sig) { + if (sig.size() == 0) + f << "value<0>()"; + else { + std::string arg_name = "arg" + std::to_string(fmt_args.size()); + fmt_args[arg_name] = sig; + f << arg_name; + } + }, "performer"); + dec_indent(); + f << indent << "}\n"; + f << indent << "struct performer *performer;\n"; + for (auto arg : fmt_args) + f << indent << "value<" << arg.second.size() << "> " << arg.first << ";\n"; + dec_indent(); + f << indent << "} formatter;\n"; + f << indent << "formatter.performer = performer;\n"; + for (auto arg : fmt_args) { + f << indent << "formatter." << arg.first << " = "; + dump_sigspec_rhs(arg.second); + f << ";\n"; + } + f << indent << "if (performer) {\n"; + inc_indent(); + f << indent << "static const metadata_map attributes = "; + dump_metadata_map(cell->attributes); + f << ";\n"; + f << indent << "performer->on_print(formatter, attributes);\n"; + dec_indent(); + f << indent << "} else {\n"; + inc_indent(); + f << indent << print_output << " << formatter();\n"; + dec_indent(); + f << indent << "}\n"; dec_indent(); f << indent << "}\n"; } @@ -1291,20 +1327,29 @@ struct CxxrtlWorker { log_assert(!for_debug); // Sync $print cells are grouped into PRINT_SYNC nodes in the FlowGraph. - log_assert(!cell->getParam(ID::TRG_ENABLE).as_bool()); + log_assert(!cell->getParam(ID::TRG_ENABLE).as_bool() || (cell->getParam(ID::TRG_ENABLE).as_bool() && cell->getParam(ID::TRG_WIDTH).as_int() == 0)); - f << indent << "auto " << mangle(cell) << "_curr = "; - dump_sigspec_rhs(cell->getPort(ID::EN)); - f << ".concat("; - dump_sigspec_rhs(cell->getPort(ID::ARGS)); - f << ").val();\n"; + if (!cell->getParam(ID::TRG_ENABLE).as_bool()) { // async $print cell + f << indent << "auto " << mangle(cell) << "_curr = "; + dump_sigspec_rhs(cell->getPort(ID::EN)); + f << ".concat("; + dump_sigspec_rhs(cell->getPort(ID::ARGS)); + f << ").val();\n"; - f << indent << "if (" << mangle(cell) << " != " << mangle(cell) << "_curr) {\n"; - inc_indent(); - dump_print(cell); - f << indent << mangle(cell) << " = " << mangle(cell) << "_curr;\n"; - dec_indent(); - f << indent << "}\n"; + f << indent << "if (" << mangle(cell) << " != " << mangle(cell) << "_curr) {\n"; + inc_indent(); + dump_print(cell); + f << indent << mangle(cell) << " = " << mangle(cell) << "_curr;\n"; + dec_indent(); + f << indent << "}\n"; + } else { // initial $print cell + f << indent << "if (!" << mangle(cell) << ") {\n"; + inc_indent(); + dump_print(cell); + f << indent << mangle(cell) << " = value<1>{1u};\n"; + dec_indent(); + f << indent << "}\n"; + } // Flip-flops } else if (is_ff_cell(cell->type)) { log_assert(!for_debug); @@ -1401,7 +1446,7 @@ struct CxxrtlWorker { f << indent; dump_sigspec_lhs(cell->getPort(ID::Q)); f << " = "; - dump_sigspec_lhs(cell->getPort(ID::Q)); + dump_sigspec_rhs(cell->getPort(ID::Q)); f << ".update("; dump_const(RTLIL::Const(RTLIL::S1, cell->getParam(ID::WIDTH).as_int())); f << ", "; @@ -1413,7 +1458,7 @@ struct CxxrtlWorker { f << indent; dump_sigspec_lhs(cell->getPort(ID::Q)); f << " = "; - dump_sigspec_lhs(cell->getPort(ID::Q)); + dump_sigspec_rhs(cell->getPort(ID::Q)); f << ".update("; dump_const(RTLIL::Const(RTLIL::S0, cell->getParam(ID::WIDTH).as_int())); f << ", "; @@ -1485,11 +1530,11 @@ struct CxxrtlWorker { }; if (buffered_inputs) { // If we have any buffered inputs, there's no chance of converging immediately. - f << indent << mangle(cell) << access << "eval();\n"; + f << indent << mangle(cell) << access << "eval(performer);\n"; f << indent << "converged = false;\n"; assign_from_outputs(/*cell_converged=*/false); } else { - f << indent << "if (" << mangle(cell) << access << "eval()) {\n"; + f << indent << "if (" << mangle(cell) << access << "eval(performer)) {\n"; inc_indent(); assign_from_outputs(/*cell_converged=*/true); dec_indent(); @@ -2002,6 +2047,11 @@ struct CxxrtlWorker { } } for (auto cell : module->cells()) { + // Certain $print cells have additional state, which must be reset as well. + if (cell->type == ID($print) && !cell->getParam(ID::TRG_ENABLE).as_bool()) + f << indent << mangle(cell) << " = value<" << (1 + cell->getParam(ID::ARGS_WIDTH).as_int()) << ">();\n"; + if (cell->type == ID($print) && cell->getParam(ID::TRG_ENABLE).as_bool() && cell->getParam(ID::TRG_WIDTH).as_int() == 0) + f << indent << mangle(cell) << " = value<1>();\n"; if (is_internal_cell(cell->type)) continue; f << indent << mangle(cell); @@ -2367,7 +2417,8 @@ struct CxxrtlWorker { dump_reset_method(module); f << indent << "}\n"; f << "\n"; - f << indent << "bool eval() override {\n"; + // No default argument, to prevent unintentional `return bb_foo::eval();` calls that drop performer. + f << indent << "bool eval(performer *performer) override {\n"; dump_eval_method(module); f << indent << "}\n"; f << "\n"; @@ -2377,7 +2428,7 @@ struct CxxrtlWorker { f << indent << "}\n"; f << "\n"; f << indent << "bool commit() override {\n"; - f << indent << indent << "null_observer observer;\n"; + f << indent << indent << "observer observer;\n"; f << indent << indent << "return commit<>(observer);\n"; f << indent << "}\n"; if (debug_info) { @@ -2430,11 +2481,11 @@ struct CxxrtlWorker { f << "\n"; bool has_cells = false; for (auto cell : module->cells()) { - if (cell->type == ID($print) && !cell->getParam(ID::TRG_ENABLE).as_bool()) { - // comb $print cell -- store the last EN/ARGS values to know when they change. - dump_attrs(cell); + // Certain $print cells have additional state, which requires storage. + if (cell->type == ID($print) && !cell->getParam(ID::TRG_ENABLE).as_bool()) f << indent << "value<" << (1 + cell->getParam(ID::ARGS_WIDTH).as_int()) << "> " << mangle(cell) << ";\n"; - } + if (cell->type == ID($print) && cell->getParam(ID::TRG_ENABLE).as_bool() && cell->getParam(ID::TRG_WIDTH).as_int() == 0) + f << indent << "value<1> " << mangle(cell) << ";\n"; if (is_internal_cell(cell->type)) continue; dump_attrs(cell); @@ -2464,7 +2515,7 @@ struct CxxrtlWorker { f << "\n"; f << indent << "void reset() override;\n"; f << "\n"; - f << indent << "bool eval() override;\n"; + f << indent << "bool eval(performer *performer = nullptr) override;\n"; f << "\n"; f << indent << "template\n"; f << indent << "bool commit(ObserverT &observer) {\n"; @@ -2472,7 +2523,7 @@ struct CxxrtlWorker { f << indent << "}\n"; f << "\n"; f << indent << "bool commit() override {\n"; - f << indent << indent << "null_observer observer;\n"; + f << indent << indent << "observer observer;\n"; f << indent << indent << "return commit<>(observer);\n"; f << indent << "}\n"; if (debug_info) { @@ -2503,7 +2554,7 @@ struct CxxrtlWorker { dump_reset_method(module); f << indent << "}\n"; f << "\n"; - f << indent << "bool " << mangle(module) << "::eval() {\n"; + f << indent << "bool " << mangle(module) << "::eval(performer *performer) {\n"; dump_eval_method(module); f << indent << "}\n"; if (debug_info) { @@ -2527,7 +2578,6 @@ struct CxxrtlWorker { RTLIL::Module *top_module = nullptr; std::vector modules; TopoSort topo_design; - bool has_prints = false; for (auto module : design->modules()) { if (!design->selected_module(module)) continue; @@ -2540,8 +2590,6 @@ struct CxxrtlWorker { topo_design.node(module); for (auto cell : module->cells()) { - if (cell->type == ID($print)) - has_prints = true; if (is_internal_cell(cell->type) || is_cxxrtl_blackbox_cell(cell)) continue; RTLIL::Module *cell_module = design->module(cell->type); @@ -2600,8 +2648,6 @@ struct CxxrtlWorker { f << "#include \"" << basename(intf_filename) << "\"\n"; else f << "#include \n"; - if (has_prints) - f << "#include \n"; f << "\n"; f << "#if defined(CXXRTL_INCLUDE_CAPI_IMPL) || \\\n"; f << " defined(CXXRTL_INCLUDE_VCD_CAPI_IMPL)\n"; @@ -2964,8 +3010,9 @@ struct CxxrtlWorker { for (auto node : node_order) if (live_nodes[node]) { if (node->type == FlowGraph::Node::Type::CELL_EVAL && - node->cell->type == ID($print) && - node->cell->getParam(ID::TRG_ENABLE).as_bool()) + node->cell->type == ID($print) && + node->cell->getParam(ID::TRG_ENABLE).as_bool() && + node->cell->getParam(ID::TRG_WIDTH).as_int() != 0) sync_print_cells[make_pair(node->cell->getPort(ID::TRG), node->cell->getParam(ID::TRG_POLARITY))].push_back(node->cell); else schedule[module].push_back(*node); @@ -3278,7 +3325,7 @@ struct CxxrtlBackend : public Backend { log(" value<8> p_i_data;\n"); log(" wire<8> p_o_data;\n"); log("\n"); - log(" bool eval() override;\n"); + log(" bool eval(performer *performer) override;\n"); log(" template\n"); log(" bool commit(ObserverT &observer);\n"); log(" bool commit() override;\n"); @@ -3293,11 +3340,11 @@ struct CxxrtlBackend : public Backend { log(" namespace cxxrtl_design {\n"); log("\n"); log(" struct stderr_debug : public bb_p_debug {\n"); - log(" bool eval() override {\n"); + log(" bool eval(performer *performer) 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(" return bb_p_debug::eval(performer);\n"); log(" }\n"); log(" };\n"); log("\n"); @@ -3398,7 +3445,7 @@ struct CxxrtlBackend : public Backend { log(" -print-output \n"); log(" $print cells in the generated code direct their output to .\n"); log(" must be one of \"std::cout\", \"std::cerr\". if not specified,\n"); - log(" \"std::cout\" is used.\n"); + log(" \"std::cout\" is used. explicitly provided performer overrides this.\n"); log("\n"); log(" -nohierarchy\n"); log(" use design hierarchy as-is. in most designs, a top module should be\n"); diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h index 3f8247226..550571497 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -39,6 +39,7 @@ #include #include #include +#include // `cxxrtl::debug_item` has to inherit from `cxxrtl_object` to satisfy strict aliasing requirements. #include @@ -565,7 +566,7 @@ struct value : public expr_base> { } value neg() const { - return value { 0u }.sub(*this); + return value().sub(*this); } bool ucmp(const value &other) const { @@ -763,123 +764,6 @@ std::ostream &operator<<(std::ostream &os, const value &val) { return os; } -template -struct value_formatted { - const value &val; - bool character; - bool justify_left; - char padding; - int width; - int base; - bool signed_; - bool plus; - - value_formatted(const value &val, bool character, bool justify_left, char padding, int width, int base, bool signed_, bool plus) : - val(val), character(character), justify_left(justify_left), padding(padding), width(width), base(base), signed_(signed_), plus(plus) {} - value_formatted(const value_formatted &) = delete; - value_formatted &operator=(const value_formatted &rhs) = delete; -}; - -template -std::ostream &operator<<(std::ostream &os, const value_formatted &vf) -{ - value val = vf.val; - - std::string buf; - - // We might want to replace some of these bit() calls with direct - // chunk access if it turns out to be slow enough to matter. - - if (!vf.character) { - size_t width = Bits; - if (vf.base != 10) { - width = 0; - for (size_t index = 0; index < Bits; index++) - if (val.bit(index)) - width = index + 1; - } - - if (vf.base == 2) { - for (size_t i = width; i > 0; i--) - buf += (val.bit(i - 1) ? '1' : '0'); - } else if (vf.base == 8 || vf.base == 16) { - size_t step = (vf.base == 16) ? 4 : 3; - for (size_t index = 0; index < width; index += step) { - uint8_t value = val.bit(index) | (val.bit(index + 1) << 1) | (val.bit(index + 2) << 2); - if (step == 4) - value |= val.bit(index + 3) << 3; - buf += "0123456789abcdef"[value]; - } - std::reverse(buf.begin(), buf.end()); - } else if (vf.base == 10) { - bool negative = vf.signed_ && val.is_neg(); - if (negative) - val = val.neg(); - if (val.is_zero()) - buf += '0'; - while (!val.is_zero()) { - value quotient, remainder; - if (Bits >= 4) - std::tie(quotient, remainder) = val.udivmod(value{10u}); - else - std::tie(quotient, remainder) = std::make_pair(value{0u}, val); - buf += '0' + remainder.template trunc<(Bits > 4 ? 4 : Bits)>().val().template get(); - val = quotient; - } - if (negative || vf.plus) - buf += negative ? '-' : '+'; - std::reverse(buf.begin(), buf.end()); - } else assert(false); - } else { - buf.reserve(Bits/8); - for (int i = 0; i < Bits; i += 8) { - char ch = 0; - for (int j = 0; j < 8 && i + j < int(Bits); j++) - if (val.bit(i + j)) - ch |= 1 << j; - if (ch != 0) - buf.append({ch}); - } - std::reverse(buf.begin(), buf.end()); - } - - assert(vf.width == 0 || vf.padding != '\0'); - if (!vf.justify_left && buf.size() < vf.width) { - size_t pad_width = vf.width - buf.size(); - if (vf.padding == '0' && (buf.front() == '+' || buf.front() == '-')) { - os << buf.front(); - buf.erase(0, 1); - } - os << std::string(pad_width, vf.padding); - } - os << buf; - if (vf.justify_left && buf.size() < vf.width) - os << std::string(vf.width - buf.size(), vf.padding); - - return os; -} - -// An object that can be passed to a `commit()` method in order to produce a replay log of every state change in -// the simulation. -struct observer { - // Called when the `commit()` method for a wire is about to update the `chunks` chunks at `base` with `chunks` chunks - // at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to the wire chunk count and - // `base` points to the first chunk. - virtual void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value) = 0; - - // Called when the `commit()` method for a memory is about to update the `chunks` chunks at `&base[chunks * index]` - // with `chunks` chunks at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to - // the memory element chunk count and `base` points to the first chunk of the first element of the memory. - virtual void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) = 0; -}; - -// The `null_observer` class has the same interface as `observer`, but has no invocation overhead, since its methods -// are final and have no implementation. This allows the observer feature to be zero-cost when not in use. -struct null_observer final: observer { - void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value) override {} - void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) override {} -}; - template struct wire { static constexpr size_t bits = Bits; @@ -916,12 +800,11 @@ struct wire { // This method intentionally takes a mandatory argument (to make it more difficult to misuse in // black box implementations, leading to missed observer events). It is generic over its argument - // to make sure the `on_commit` call is devirtualized. This is somewhat awkward but lets us keep - // a single implementation for both this method and the one in `memory`. + // to allow the `on_update` method to be non-virtual. template bool commit(ObserverT &observer) { if (curr != next) { - observer.on_commit(curr.chunks, curr.data, next.data); + observer.on_update(curr.chunks, curr.data, next.data); curr = next; return true; } @@ -1003,7 +886,7 @@ struct memory { value elem = data[entry.index]; elem = elem.update(entry.val, entry.mask); if (data[entry.index] != elem) { - observer.on_commit(value::chunks, data[0].data, elem.data, entry.index); + observer.on_update(value::chunks, data[0].data, elem.data, entry.index); changed |= true; } data[entry.index] = elem; @@ -1062,6 +945,174 @@ struct metadata { typedef std::map metadata_map; +struct performer; + +// An object that allows formatting a string lazily. +struct lazy_fmt { + virtual std::string operator() () const = 0; +}; + +// An object that can be passed to a `eval()` method in order to act on side effects. +struct performer { + // Called by generated formatting code to evaluate a Verilog `$time` expression. + virtual int64_t vlog_time() const { return 0; } + + // Called by generated formatting code to evaluate a Verilog `$realtime` expression. + virtual double vlog_realtime() const { return vlog_time(); } + + // Called when a `$print` cell is triggered. + virtual void on_print(const lazy_fmt &formatter, const metadata_map &attributes) { + std::cout << formatter(); + } +}; + +// An object that can be passed to a `commit()` method in order to produce a replay log of every state change in +// the simulation. Unlike `performer`, `observer` does not use virtual calls as their overhead is unacceptable, and +// a comparatively heavyweight template-based solution is justified. +struct observer { + // Called when the `commit()` method for a wire is about to update the `chunks` chunks at `base` with `chunks` chunks + // at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to the wire chunk count and + // `base` points to the first chunk. + void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) {} + + // Called when the `commit()` method for a memory is about to update the `chunks` chunks at `&base[chunks * index]` + // with `chunks` chunks at `value` that have a different bit pattern. It is guaranteed that `chunks` is equal to + // the memory element chunk count and `base` points to the first chunk of the first element of the memory. + void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) {} +}; + +// Must be kept in sync with `struct FmtPart` in kernel/fmt.h! +// Default member initializers would make this a non-aggregate-type in C++11, so they are commented out. +struct fmt_part { + enum { + STRING = 0, + INTEGER = 1, + CHARACTER = 2, + VLOG_TIME = 3, + } type; + + // STRING type + std::string str; + + // INTEGER/CHARACTER types + // + value val; + + // INTEGER/CHARACTER/VLOG_TIME types + enum { + RIGHT = 0, + LEFT = 1, + } justify; // = RIGHT; + char padding; // = '\0'; + size_t width; // = 0; + + // INTEGER type + unsigned base; // = 10; + bool signed_; // = false; + bool plus; // = false; + + // VLOG_TIME type + bool realtime; // = false; + // + int64_t itime; + // + double ftime; + + // Format the part as a string. + // + // The values of `vlog_time` and `vlog_realtime` are used for Verilog `$time` and `$realtime`, correspondingly. + template + std::string render(value val, performer *performer = nullptr) + { + // We might want to replace some of these bit() calls with direct + // chunk access if it turns out to be slow enough to matter. + std::string buf; + switch (type) { + case STRING: + return str; + + case CHARACTER: { + buf.reserve(Bits/8); + for (int i = 0; i < Bits; i += 8) { + char ch = 0; + for (int j = 0; j < 8 && i + j < int(Bits); j++) + if (val.bit(i + j)) + ch |= 1 << j; + if (ch != 0) + buf.append({ch}); + } + std::reverse(buf.begin(), buf.end()); + break; + } + + case INTEGER: { + size_t width = Bits; + if (base != 10) { + width = 0; + for (size_t index = 0; index < Bits; index++) + if (val.bit(index)) + width = index + 1; + } + + if (base == 2) { + for (size_t i = width; i > 0; i--) + buf += (val.bit(i - 1) ? '1' : '0'); + } else if (base == 8 || base == 16) { + size_t step = (base == 16) ? 4 : 3; + for (size_t index = 0; index < width; index += step) { + uint8_t value = val.bit(index) | (val.bit(index + 1) << 1) | (val.bit(index + 2) << 2); + if (step == 4) + value |= val.bit(index + 3) << 3; + buf += "0123456789abcdef"[value]; + } + std::reverse(buf.begin(), buf.end()); + } else if (base == 10) { + bool negative = signed_ && val.is_neg(); + if (negative) + val = val.neg(); + if (val.is_zero()) + buf += '0'; + value<(Bits > 4 ? Bits : 4)> xval = val.template zext<(Bits > 4 ? Bits : 4)>(); + while (!xval.is_zero()) { + value<(Bits > 4 ? Bits : 4)> quotient, remainder; + if (Bits >= 4) + std::tie(quotient, remainder) = xval.udivmod(value<(Bits > 4 ? Bits : 4)>{10u}); + else + std::tie(quotient, remainder) = std::make_pair(value<(Bits > 4 ? Bits : 4)>{0u}, xval); + buf += '0' + remainder.template trunc<4>().template get(); + xval = quotient; + } + if (negative || plus) + buf += negative ? '-' : '+'; + std::reverse(buf.begin(), buf.end()); + } else assert(false && "Unsupported base for fmt_part"); + break; + } + + case VLOG_TIME: { + if (performer) { + buf = realtime ? std::to_string(performer->vlog_realtime()) : std::to_string(performer->vlog_time()); + } else { + buf = realtime ? std::to_string(0.0) : std::to_string(0); + } + break; + } + } + + std::string str; + assert(width == 0 || padding != '\0'); + if (justify == RIGHT && buf.size() < width) { + size_t pad_width = width - buf.size(); + if (padding == '0' && (buf.front() == '+' || buf.front() == '-')) { + str += buf.front(); + buf.erase(0, 1); + } + str += std::string(pad_width, padding); + } + str += buf; + if (justify == LEFT && buf.size() < width) + str += std::string(width - buf.size(), padding); + return str; + } +}; + // Tag class to disambiguate values/wires and their aliases. struct debug_alias {}; @@ -1304,17 +1355,14 @@ struct module { virtual void reset() = 0; - virtual bool eval() = 0; - virtual bool commit() = 0; + virtual bool eval(performer *performer = nullptr) = 0; + virtual bool commit() = 0; // commit observer isn't available since it avoids virtual calls - unsigned int steps = 0; - - size_t step() { - ++steps; + size_t step(performer *performer = nullptr) { size_t deltas = 0; bool converged = false; do { - converged = eval(); + converged = eval(performer); deltas++; } while (commit() && !converged); return deltas; diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h index 94f59bb0d..d824fdb83 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h @@ -556,22 +556,20 @@ public: bool record_incremental(ModuleT &module) { assert(streaming); - struct : public observer { + struct { std::unordered_map *ident_lookup; spool::writer *writer; CXXRTL_ALWAYS_INLINE - void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value) override { + void on_update(size_t chunks, const chunk_t *base, const chunk_t *value) { writer->write_change(ident_lookup->at(base), chunks, value); } CXXRTL_ALWAYS_INLINE - void on_commit(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) override { + void on_update(size_t chunks, const chunk_t *base, const chunk_t *value, size_t index) { writer->write_change(ident_lookup->at(base), chunks, value, index); } - } record_observer; - record_observer.ident_lookup = &ident_lookup; - record_observer.writer = &writer; + } record_observer = { &ident_lookup, &writer }; writer.write_sample(/*incremental=*/true, pointer++, timestamp); for (auto input_index : inputs) { diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index 9ff2c5c86..41e51f328 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -1053,6 +1053,15 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf(";\n"); return true; } + + if (cell->type == ID($_BUF_)) { + f << stringf("%s" "assign ", indent.c_str()); + dump_sigspec(f, cell->getPort(ID::Y)); + f << stringf(" = "); + dump_cell_expr_port(f, cell, "A", false); + f << stringf(";\n"); + return true; + } if (cell->type.in(ID($_AND_), ID($_NAND_), ID($_OR_), ID($_NOR_), ID($_XOR_), ID($_XNOR_), ID($_ANDNOT_), ID($_ORNOT_))) { f << stringf("%s" "assign ", indent.c_str()); @@ -1830,7 +1839,8 @@ void dump_cell(std::ostream &f, std::string indent, RTLIL::Cell *cell) if (it != cell->parameters.begin()) f << stringf(","); f << stringf("\n%s .%s(", indent.c_str(), id(it->first).c_str()); - dump_const(f, it->second); + if (it->second.size() > 0) + dump_const(f, it->second); f << stringf(")"); } f << stringf("\n%s" ")", indent.c_str()); @@ -1895,17 +1905,21 @@ void dump_cell(std::ostream &f, std::string indent, RTLIL::Cell *cell) void dump_sync_print(std::ostream &f, std::string indent, const RTLIL::SigSpec &trg, const RTLIL::Const &polarity, std::vector &cells) { - f << stringf("%s" "always @(", indent.c_str()); - for (int i = 0; i < trg.size(); i++) { - if (i != 0) - f << " or "; - if (polarity[i]) - f << "posedge "; - else - f << "negedge "; - dump_sigspec(f, trg[i]); + if (trg.size() == 0) { + f << stringf("%s" "initial begin\n", indent.c_str()); + } else { + f << stringf("%s" "always @(", indent.c_str()); + for (int i = 0; i < trg.size(); i++) { + if (i != 0) + f << " or "; + if (polarity[i]) + f << "posedge "; + else + f << "negedge "; + dump_sigspec(f, trg[i]); + } + f << ") begin\n"; } - f << ") begin\n"; std::sort(cells.begin(), cells.end(), [](const RTLIL::Cell *a, const RTLIL::Cell *b) { return a->getParam(ID::PRIORITY).as_int() > b->getParam(ID::PRIORITY).as_int(); @@ -1944,13 +1958,8 @@ void dump_conn(std::ostream &f, std::string indent, const RTLIL::SigSpec &left, void dump_proc_switch(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw); -void dump_case_body(std::ostream &f, std::string indent, RTLIL::CaseRule *cs, bool omit_trailing_begin = false) +void dump_case_actions(std::ostream &f, std::string indent, RTLIL::CaseRule *cs) { - int number_of_stmts = cs->switches.size() + cs->actions.size(); - - if (!omit_trailing_begin && number_of_stmts >= 2) - f << stringf("%s" "begin\n", indent.c_str()); - for (auto it = cs->actions.begin(); it != cs->actions.end(); ++it) { if (it->first.size() == 0) continue; @@ -1960,15 +1969,6 @@ void dump_case_body(std::ostream &f, std::string indent, RTLIL::CaseRule *cs, bo dump_sigspec(f, it->second); f << stringf(";\n"); } - - for (auto it = cs->switches.begin(); it != cs->switches.end(); ++it) - dump_proc_switch(f, indent + " ", *it); - - if (!omit_trailing_begin && number_of_stmts == 0) - f << stringf("%s /* empty */;\n", indent.c_str()); - - if (omit_trailing_begin || number_of_stmts >= 2) - f << stringf("%s" "end\n", indent.c_str()); } bool dump_proc_switch_ifelse(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw) @@ -1991,36 +1991,52 @@ bool dump_proc_switch_ifelse(std::ostream &f, std::string indent, RTLIL::SwitchR } } + dump_attributes(f, indent, sw->attributes); f << indent; auto sig_it = sw->signal.begin(); for (auto it = sw->cases.begin(); it != sw->cases.end(); ++it, ++sig_it) { - bool had_newline = true; if (it != sw->cases.begin()) { - if ((*it)->compare.empty()) { - f << indent << "else\n"; - had_newline = true; - } else { - f << indent << "else "; - had_newline = false; - } + if ((*it)->compare.empty()) + f << " else begin\n"; + else + f << " else "; } if (!(*it)->compare.empty()) { - if (!(*it)->attributes.empty()) { - if (!had_newline) - f << "\n" << indent; - dump_attributes(f, "", (*it)->attributes, "\n" + indent); - } f << stringf("if ("); dump_sigspec(f, *sig_it); - f << stringf(")\n"); + f << stringf(") begin\n"); } - dump_case_body(f, indent, *it); + + dump_case_actions(f, indent, (*it)); + for (auto it2 = (*it)->switches.begin(); it2 != (*it)->switches.end(); ++it2) + dump_proc_switch(f, indent + " ", *it2); + + f << indent << "end"; if ((*it)->compare.empty()) break; } + f << "\n"; return true; } +void dump_case_body(std::ostream &f, std::string indent, RTLIL::CaseRule *cs, bool omit_trailing_begin = false) +{ + int number_of_stmts = cs->switches.size() + cs->actions.size(); + + if (!omit_trailing_begin && number_of_stmts >= 2) + f << stringf("%s" "begin\n", indent.c_str()); + + dump_case_actions(f, indent, cs); + for (auto it = cs->switches.begin(); it != cs->switches.end(); ++it) + dump_proc_switch(f, indent + " ", *it); + + if (!omit_trailing_begin && number_of_stmts == 0) + f << stringf("%s /* empty */;\n", indent.c_str()); + + if (omit_trailing_begin || number_of_stmts >= 2) + f << stringf("%s" "end\n", indent.c_str()); +} + void dump_proc_switch(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw) { if (sw->signal.size() == 0) { diff --git a/docs/source/CHAPTER_CellLib.rst b/docs/source/CHAPTER_CellLib.rst index 494c0651c..0f0d79123 100644 --- a/docs/source/CHAPTER_CellLib.rst +++ b/docs/source/CHAPTER_CellLib.rst @@ -120,7 +120,7 @@ All binary RTL cells have two input ports ``\A`` and ``\B`` and one output port :verilog:`Y = A >>> B` $sshr :verilog:`Y = A - B` $sub :verilog:`Y = A && B` $logic_and :verilog:`Y = A * B` $mul :verilog:`Y = A || B` $logic_or :verilog:`Y = A / B` $div - :verilog:`Y = A === B` $eqx :verilog:`Y = A % B` $mod + :verilog:`Y = A === B` $eqx :verilog:`Y = A % B` $mod :verilog:`Y = A !== B` $nex ``N/A`` $divfloor :verilog:`Y = A ** B` $pow ``N/A`` $modfoor ======================= ============= ======================= ========= @@ -661,6 +661,8 @@ Ports: ``\TRG`` The signals that control when this ``$print`` cell is triggered. + If the width of this port is zero and ``\TRG_ENABLE`` is true, the cell is + triggered during initial evaluation (time zero) only. ``\EN`` Enable signal for the whole cell. diff --git a/frontends/ast/ast.cc b/frontends/ast/ast.cc index 4b2b7a822..fe075b270 100644 --- a/frontends/ast/ast.cc +++ b/frontends/ast/ast.cc @@ -45,7 +45,7 @@ namespace AST { // instantiate global variables (private API) namespace AST_INTERNAL { - bool flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_vlog1, flag_dump_vlog2, flag_dump_rtlil, flag_nolatches, flag_nomeminit; + bool flag_nodisplay, flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_vlog1, flag_dump_vlog2, flag_dump_rtlil, flag_nolatches, flag_nomeminit; bool flag_nomem2reg, flag_mem2reg, flag_noblackbox, flag_lib, flag_nowb, flag_noopt, flag_icells, flag_pwires, flag_autowire; AstNode *current_ast, *current_ast_mod; std::map current_scope; @@ -1320,11 +1320,12 @@ static void rename_in_package_stmts(AstNode *pkg) } // create AstModule instances for all modules in the AST tree and add them to 'design' -void AST::process(RTLIL::Design *design, AstNode *ast, bool dump_ast1, bool dump_ast2, bool no_dump_ptr, bool dump_vlog1, bool dump_vlog2, bool dump_rtlil, +void AST::process(RTLIL::Design *design, AstNode *ast, bool nodisplay, bool dump_ast1, bool dump_ast2, bool no_dump_ptr, bool dump_vlog1, bool dump_vlog2, bool dump_rtlil, bool nolatches, bool nomeminit, bool nomem2reg, bool mem2reg, bool noblackbox, bool lib, bool nowb, bool noopt, bool icells, bool pwires, bool nooverwrite, bool overwrite, bool defer, bool autowire) { current_ast = ast; current_ast_mod = nullptr; + flag_nodisplay = nodisplay; flag_dump_ast1 = dump_ast1; flag_dump_ast2 = dump_ast2; flag_no_dump_ptr = no_dump_ptr; diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h index 97903d0a0..c44746131 100644 --- a/frontends/ast/ast.h +++ b/frontends/ast/ast.h @@ -287,7 +287,7 @@ namespace AST bool is_simple_const_expr(); // helper for parsing format strings - Fmt processFormat(int stage, bool sformat_like, int default_base = 10, size_t first_arg_at = 0); + Fmt processFormat(int stage, bool sformat_like, int default_base = 10, size_t first_arg_at = 0, bool may_fail = false); bool is_recursive_function() const; std::pair get_tern_choice(); @@ -376,7 +376,7 @@ namespace AST }; // process an AST tree (ast must point to an AST_DESIGN node) and generate RTLIL code - void process(RTLIL::Design *design, AstNode *ast, bool dump_ast1, bool dump_ast2, bool no_dump_ptr, bool dump_vlog1, bool dump_vlog2, bool dump_rtlil, bool nolatches, bool nomeminit, + void process(RTLIL::Design *design, AstNode *ast, bool nodisplay, bool dump_ast1, bool dump_ast2, bool no_dump_ptr, bool dump_vlog1, bool dump_vlog2, bool dump_rtlil, bool nolatches, bool nomeminit, bool nomem2reg, bool mem2reg, bool noblackbox, bool lib, bool nowb, bool noopt, bool icells, bool pwires, bool nooverwrite, bool overwrite, bool defer, bool autowire); // parametric modules are supported directly by the AST library @@ -432,7 +432,7 @@ namespace AST namespace AST_INTERNAL { // internal state variables - extern bool flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_rtlil, flag_nolatches, flag_nomeminit; + extern bool flag_nodisplay, flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_rtlil, flag_nolatches, flag_nomeminit; extern bool flag_nomem2reg, flag_mem2reg, flag_lib, flag_noopt, flag_icells, flag_pwires, flag_autowire; extern AST::AstNode *current_ast, *current_ast_mod; extern std::map current_scope; diff --git a/frontends/ast/genrtlil.cc b/frontends/ast/genrtlil.cc index 0bae0f673..0a502162e 100644 --- a/frontends/ast/genrtlil.cc +++ b/frontends/ast/genrtlil.cc @@ -718,7 +718,7 @@ struct AST_INTERNAL::ProcessGenerator } } cell->parameters[ID::TRG_WIDTH] = triggers.size(); - cell->parameters[ID::TRG_ENABLE] = !triggers.empty(); + cell->parameters[ID::TRG_ENABLE] = (always->type == AST_INITIAL) || !triggers.empty(); cell->parameters[ID::TRG_POLARITY] = polarity; cell->parameters[ID::PRIORITY] = --last_print_priority; cell->setPort(ID::TRG, triggers); diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index 945f286a1..fa079f3e3 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -145,7 +145,7 @@ void AstNode::fixup_hierarchy_flags(bool force_descend) // Process a format string and arguments for $display, $write, $sprintf, etc -Fmt AstNode::processFormat(int stage, bool sformat_like, int default_base, size_t first_arg_at) { +Fmt AstNode::processFormat(int stage, bool sformat_like, int default_base, size_t first_arg_at, bool may_fail) { std::vector args; for (size_t index = first_arg_at; index < children.size(); index++) { AstNode *node_arg = children[index]; @@ -169,6 +169,9 @@ Fmt AstNode::processFormat(int stage, bool sformat_like, int default_base, size_ arg.type = VerilogFmtArg::INTEGER; arg.sig = node_arg->bitsAsConst(); arg.signed_ = node_arg->is_signed; + } else if (may_fail) { + log_file_info(filename, location.first_line, "Skipping system task `%s' with non-constant argument at position %zu.\n", str.c_str(), index + 1); + return Fmt(); } else { log_file_error(filename, location.first_line, "Failed to evaluate system task `%s' with non-constant argument at position %zu.\n", str.c_str(), index + 1); } @@ -217,8 +220,8 @@ void AstNode::annotateTypedEnums(AstNode *template_node) log_assert(enum_item->children[1]->type == AST_RANGE); is_signed = enum_item->children[1]->is_signed; } else { - log_error("enum_item children size==%lu, expected 1 or 2 for %s (%s)\n", - enum_item->children.size(), + log_error("enum_item children size==%zu, expected 1 or 2 for %s (%s)\n", + (size_t) enum_item->children.size(), enum_item->str.c_str(), enum_node->str.c_str() ); } @@ -1055,30 +1058,31 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin { if (!current_always) { log_file_warning(filename, location.first_line, "System task `%s' outside initial or always block is unsupported.\n", str.c_str()); - } else if (current_always->type == AST_INITIAL) { - int default_base = 10; - if (str.back() == 'b') - default_base = 2; - else if (str.back() == 'o') - default_base = 8; - else if (str.back() == 'h') - default_base = 16; - - // when $display()/$write() functions are used in an initial block, print them during synthesis - Fmt fmt = processFormat(stage, /*sformat_like=*/false, default_base); - if (str.substr(0, 8) == "$display") - fmt.append_string("\n"); - log("%s", fmt.render().c_str()); + delete_children(); + str = std::string(); } else { - // when $display()/$write() functions are used in an always block, simplify the expressions and - // convert them to a special cell later in genrtlil + // simplify the expressions and convert them to a special cell later in genrtlil for (auto node : children) while (node->simplify(true, stage, -1, false)) {} + + if (current_always->type == AST_INITIAL && !flag_nodisplay && stage == 2) { + int default_base = 10; + if (str.back() == 'b') + default_base = 2; + else if (str.back() == 'o') + default_base = 8; + else if (str.back() == 'h') + default_base = 16; + + // when $display()/$write() functions are used in an initial block, print them during synthesis + Fmt fmt = processFormat(stage, /*sformat_like=*/false, default_base, /*first_arg_at=*/0, /*may_fail=*/true); + if (str.substr(0, 8) == "$display") + fmt.append_string("\n"); + log("%s", fmt.render().c_str()); + } + return false; } - - delete_children(); - str = std::string(); } // activate const folding if this is anything that must be evaluated statically (ranges, parameters, attributes, etc.) @@ -2966,96 +2970,67 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin } } else { // mask and shift operations - - AstNode *wire_mask = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(wire_width-1, true), mkconst_int(0, true))); - wire_mask->str = stringf("$bitselwrite$mask$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++); - wire_mask->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); - wire_mask->is_logic = true; - while (wire_mask->simplify(true, 1, -1, false)) { } - current_ast_mod->children.push_back(wire_mask); - - AstNode *wire_data = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(wire_width-1, true), mkconst_int(0, true))); - wire_data->str = stringf("$bitselwrite$data$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++); - wire_data->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); - wire_data->is_logic = true; - while (wire_data->simplify(true, 1, -1, false)) { } - current_ast_mod->children.push_back(wire_data); - - int shamt_width_hint = -1; - bool shamt_sign_hint = true; - shift_expr->detectSignWidth(shamt_width_hint, shamt_sign_hint); - - AstNode *wire_sel = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(shamt_width_hint-1, true), mkconst_int(0, true))); - wire_sel->str = stringf("$bitselwrite$sel$%s:%d$%d", RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++); - wire_sel->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); - wire_sel->is_logic = true; - wire_sel->is_signed = shamt_sign_hint; - while (wire_sel->simplify(true, 1, -1, false)) { } - current_ast_mod->children.push_back(wire_sel); - - did_something = true; - newNode = new AstNode(AST_BLOCK); + // dst = (dst & ~(width'1 << lsb)) | unsigned'(width'(src)) << lsb) AstNode *lvalue = children[0]->clone(); lvalue->delete_children(); if (member_node) lvalue->set_attribute(ID::wiretype, member_node->clone()); - AstNode *ref_mask = new AstNode(AST_IDENTIFIER); - ref_mask->str = wire_mask->str; - ref_mask->id2ast = wire_mask; - ref_mask->was_checked = true; - - AstNode *ref_data = new AstNode(AST_IDENTIFIER); - ref_data->str = wire_data->str; - ref_data->id2ast = wire_data; - ref_data->was_checked = true; - - AstNode *ref_sel = new AstNode(AST_IDENTIFIER); - ref_sel->str = wire_sel->str; - ref_sel->id2ast = wire_sel; - ref_sel->was_checked = true; - AstNode *old_data = lvalue->clone(); if (type == AST_ASSIGN_LE) old_data->lookahead = true; - AstNode *s = new AstNode(AST_ASSIGN_EQ, ref_sel->clone(), shift_expr); - newNode->children.push_back(s); + int shift_width_hint; + bool shift_sign_hint; + shift_expr->detectSignWidth(shift_width_hint, shift_sign_hint); - AstNode *shamt = ref_sel; + // All operations are carried out in a new block. + newNode = new AstNode(AST_BLOCK); - // convert to signed while preserving the sign and value - shamt = new AstNode(AST_CAST_SIZE, mkconst_int(shamt_width_hint + 1, true), shamt); - shamt = new AstNode(AST_TO_SIGNED, shamt); + // Temporary register holding the result of the bit- or part-select position expression. + AstNode *pos = mktemp_logic("$bitselwrite$pos$", current_ast_mod, true, shift_width_hint - 1, 0, shift_sign_hint); + newNode->children.push_back(new AstNode(AST_ASSIGN_EQ, pos, shift_expr)); + + // Calculate lsb from position. + AstNode *shift_val = pos->clone(); + + // If the expression is signed, we must add an extra bit for possible negation of the most negative number. + // If the expression is unsigned, we must add an extra bit for sign. + shift_val = new AstNode(AST_CAST_SIZE, mkconst_int(shift_width_hint + 1, true), shift_val); + if (!shift_sign_hint) + shift_val = new AstNode(AST_TO_SIGNED, shift_val); // offset the shift amount by the lower bound of the dimension - int start_bit = wire_offset; - shamt = new AstNode(AST_SUB, shamt, mkconst_int(start_bit, true)); + if (wire_offset != 0) + shift_val = new AstNode(AST_SUB, shift_val, mkconst_int(wire_offset, true)); // reflect the shift amount if the dimension is swapped if (children[0]->id2ast->range_swapped) - shamt = new AstNode(AST_SUB, mkconst_int(wire_width - result_width, true), shamt); + shift_val = new AstNode(AST_SUB, mkconst_int(wire_width - result_width, true), shift_val); // AST_SHIFT uses negative amounts for shifting left - shamt = new AstNode(AST_NEG, shamt); + shift_val = new AstNode(AST_NEG, shift_val); - AstNode *t; - - t = mkconst_bits(std::vector(result_width, State::S1), false); - t = new AstNode(AST_SHIFT, t, shamt->clone()); - t = new AstNode(AST_ASSIGN_EQ, ref_mask->clone(), t); - newNode->children.push_back(t); - - t = new AstNode(AST_BIT_AND, mkconst_bits(std::vector(result_width, State::S1), false), children[1]->clone()); - t = new AstNode(AST_SHIFT, t, shamt); - t = new AstNode(AST_ASSIGN_EQ, ref_data->clone(), t); - newNode->children.push_back(t); - - t = new AstNode(AST_BIT_AND, old_data, new AstNode(AST_BIT_NOT, ref_mask)); - t = new AstNode(AST_BIT_OR, t, ref_data); - t = new AstNode(type, lvalue, t); - newNode->children.push_back(t); + // dst = (dst & ~(width'1 << lsb)) | unsigned'(width'(src)) << lsb) + did_something = true; + AstNode *bitmask = mkconst_bits(std::vector(result_width, State::S1), false); + newNode->children.push_back( + new AstNode(type, + lvalue, + new AstNode(AST_BIT_OR, + new AstNode(AST_BIT_AND, + old_data, + new AstNode(AST_BIT_NOT, + new AstNode(AST_SHIFT, + bitmask, + shift_val->clone()))), + new AstNode(AST_SHIFT, + new AstNode(AST_TO_UNSIGNED, + new AstNode(AST_CAST_SIZE, + mkconst_int(result_width, true), + children[1]->clone())), + shift_val)))); newNode->fixup_hierarchy_flags(true); } diff --git a/frontends/verific/verific.cc b/frontends/verific/verific.cc index 9737fde89..adabd2700 100644 --- a/frontends/verific/verific.cc +++ b/frontends/verific/verific.cc @@ -2110,7 +2110,7 @@ VerificClocking::VerificClocking(VerificImporter *importer, Net *net, bool sva_a if (sva_at_only) do { Instance *inst_mux = net->Driver(); - if (inst_mux->Type() != PRIM_MUX) + if (inst_mux == nullptr || inst_mux->Type() != PRIM_MUX) break; bool pwr1 = inst_mux->GetInput1()->IsPwr(); diff --git a/frontends/verilog/verilog_frontend.cc b/frontends/verilog/verilog_frontend.cc index 9b277c6b9..5c59fe3af 100644 --- a/frontends/verilog/verilog_frontend.cc +++ b/frontends/verilog/verilog_frontend.cc @@ -100,6 +100,10 @@ struct VerilogFrontend : public Frontend { log(" -assert-assumes\n"); log(" treat all assume() statements like assert() statements\n"); log("\n"); + log(" -nodisplay\n"); + log(" suppress output from display system tasks ($display et. al).\n"); + log(" This does not affect the output from a later 'sim' command.\n"); + log("\n"); log(" -debug\n"); log(" alias for -dump_ast1 -dump_ast2 -dump_vlog1 -dump_vlog2 -yydebug\n"); log("\n"); @@ -235,6 +239,7 @@ struct VerilogFrontend : public Frontend { } void execute(std::istream *&f, std::string filename, std::vector args, RTLIL::Design *design) override { + bool flag_nodisplay = false; bool flag_dump_ast1 = false; bool flag_dump_ast2 = false; bool flag_no_dump_ptr = false; @@ -308,6 +313,10 @@ struct VerilogFrontend : public Frontend { assert_assumes_mode = true; continue; } + if (arg == "-nodisplay") { + flag_nodisplay = true; + continue; + } if (arg == "-debug") { flag_dump_ast1 = true; flag_dump_ast2 = true; @@ -510,7 +519,7 @@ struct VerilogFrontend : public Frontend { if (flag_nodpi) error_on_dpi_function(current_ast); - AST::process(design, current_ast, flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_vlog1, flag_dump_vlog2, flag_dump_rtlil, flag_nolatches, + AST::process(design, current_ast, flag_nodisplay, flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_vlog1, flag_dump_vlog2, flag_dump_rtlil, flag_nolatches, flag_nomeminit, flag_nomem2reg, flag_mem2reg, flag_noblackbox, lib_mode, flag_nowb, flag_noopt, flag_icells, flag_pwires, flag_nooverwrite, flag_overwrite, flag_defer, default_nettype_wire); diff --git a/kernel/fmt.cc b/kernel/fmt.cc index 383fa7de1..18eb7cb71 100644 --- a/kernel/fmt.cc +++ b/kernel/fmt.cc @@ -110,9 +110,9 @@ void Fmt::parse_rtlil(const RTLIL::Cell *cell) { } else if (fmt[i] == 'c') { part.type = FmtPart::CHARACTER; } else if (fmt[i] == 't') { - part.type = FmtPart::TIME; + part.type = FmtPart::VLOG_TIME; } else if (fmt[i] == 'r') { - part.type = FmtPart::TIME; + part.type = FmtPart::VLOG_TIME; part.realtime = true; } else { log_assert(false && "Unexpected character in format substitution"); @@ -172,7 +172,7 @@ void Fmt::emit_rtlil(RTLIL::Cell *cell) const { } break; - case FmtPart::TIME: + case FmtPart::VLOG_TIME: log_assert(part.sig.size() == 0); YS_FALLTHROUGH case FmtPart::CHARACTER: @@ -205,7 +205,7 @@ void Fmt::emit_rtlil(RTLIL::Cell *cell) const { fmt += part.signed_ ? 's' : 'u'; } else if (part.type == FmtPart::CHARACTER) { fmt += 'c'; - } else if (part.type == FmtPart::TIME) { + } else if (part.type == FmtPart::VLOG_TIME) { if (part.realtime) fmt += 'r'; else @@ -328,7 +328,7 @@ void Fmt::parse_verilog(const std::vector &args, bool sformat_lik case VerilogFmtArg::TIME: { FmtPart part = {}; - part.type = FmtPart::TIME; + part.type = FmtPart::VLOG_TIME; part.realtime = arg->realtime; part.padding = ' '; part.width = 20; @@ -419,7 +419,7 @@ void Fmt::parse_verilog(const std::vector &args, bool sformat_lik part.padding = ' '; } else if (fmt[i] == 't' || fmt[i] == 'T') { if (arg->type == VerilogFmtArg::TIME) { - part.type = FmtPart::TIME; + part.type = FmtPart::VLOG_TIME; part.realtime = arg->realtime; if (!has_width && !has_leading_zero) part.width = 20; @@ -541,7 +541,7 @@ std::vector Fmt::emit_verilog() const break; } - case FmtPart::TIME: { + case FmtPart::VLOG_TIME: { VerilogFmtArg arg; arg.type = VerilogFmtArg::TIME; if (part.realtime) @@ -569,82 +569,60 @@ std::vector Fmt::emit_verilog() const return args; } -void Fmt::emit_cxxrtl(std::ostream &f, std::function emit_sig) const +std::string escape_cxx_string(const std::string &input) { - for (auto &part : parts) { - switch (part.type) { - case FmtPart::STRING: - f << " << \""; - for (char c : part.str) { - switch (c) { - case '\\': - YS_FALLTHROUGH - case '"': - f << '\\' << c; - break; - case '\a': - f << "\\a"; - break; - case '\b': - f << "\\b"; - break; - case '\f': - f << "\\f"; - break; - case '\n': - f << "\\n"; - break; - case '\r': - f << "\\r"; - break; - case '\t': - f << "\\t"; - break; - case '\v': - f << "\\v"; - break; - default: - f << c; - break; - } - } - f << '"'; - break; - - case FmtPart::INTEGER: - case FmtPart::CHARACTER: { - f << " << value_formatted<" << part.sig.size() << ">("; - emit_sig(part.sig); - f << ", " << (part.type == FmtPart::CHARACTER); - f << ", " << (part.justify == FmtPart::LEFT); - f << ", (char)" << (int)part.padding; - f << ", " << part.width; - f << ", " << part.base; - f << ", " << part.signed_; - f << ", " << part.plus; - f << ')'; - break; - } - - case FmtPart::TIME: { - // CXXRTL only records steps taken, so there's no difference between - // the values taken by $time and $realtime. - f << " << value_formatted<64>("; - f << "value<64>{steps}"; - f << ", " << (part.type == FmtPart::CHARACTER); - f << ", " << (part.justify == FmtPart::LEFT); - f << ", (char)" << (int)part.padding; - f << ", " << part.width; - f << ", " << part.base; - f << ", " << part.signed_; - f << ", " << part.plus; - f << ')'; - break; - } - - default: log_abort(); + std::string output = "\""; + for (auto c : input) { + if (::isprint(c)) { + if (c == '\\') + output.push_back('\\'); + output.push_back(c); + } else { + char l = c & 0xf, h = (c >> 4) & 0xf; + output.append("\\x"); + output.push_back((h < 10 ? '0' + h : 'a' + h - 10)); + output.push_back((l < 10 ? '0' + l : 'a' + l - 10)); } } + output.push_back('"'); + if (output.find('\0') != std::string::npos) { + output.insert(0, "std::string {"); + output.append(stringf(", %zu}", input.size())); + } + return output; +} + +void Fmt::emit_cxxrtl(std::ostream &os, std::string indent, std::function emit_sig, const std::string &context) const +{ + os << indent << "std::string buf;\n"; + for (auto &part : parts) { + os << indent << "buf += fmt_part { "; + os << "fmt_part::"; + switch (part.type) { + case FmtPart::STRING: os << "STRING"; break; + case FmtPart::INTEGER: os << "INTEGER"; break; + case FmtPart::CHARACTER: os << "CHARACTER"; break; + case FmtPart::VLOG_TIME: os << "VLOG_TIME"; break; + } + os << ", "; + os << escape_cxx_string(part.str) << ", "; + os << "fmt_part::"; + switch (part.justify) { + case FmtPart::LEFT: os << "LEFT"; break; + case FmtPart::RIGHT: os << "RIGHT"; break; + } + os << ", "; + os << "(char)" << (int)part.padding << ", "; + os << part.width << ", "; + os << part.base << ", "; + os << part.signed_ << ", "; + os << part.plus << ", "; + os << part.realtime; + os << " }.render("; + emit_sig(part.sig); + os << ", " << context << ");\n"; + } + os << indent << "return buf;\n"; } std::string Fmt::render() const @@ -658,8 +636,8 @@ std::string Fmt::render() const break; case FmtPart::INTEGER: - case FmtPart::TIME: - case FmtPart::CHARACTER: { + case FmtPart::CHARACTER: + case FmtPart::VLOG_TIME: { std::string buf; if (part.type == FmtPart::INTEGER) { RTLIL::Const value = part.sig.as_const(); @@ -742,7 +720,7 @@ std::string Fmt::render() const } else log_abort(); } else if (part.type == FmtPart::CHARACTER) { buf = part.sig.as_const().decode_string(); - } else if (part.type == FmtPart::TIME) { + } else if (part.type == FmtPart::VLOG_TIME) { // We only render() during initial, so time is always zero. buf = "0"; } diff --git a/kernel/fmt.h b/kernel/fmt.h index 39a278c56..3bedb786e 100644 --- a/kernel/fmt.h +++ b/kernel/fmt.h @@ -50,12 +50,13 @@ struct VerilogFmtArg { // RTLIL format part, such as the substitutions in: // "foo {4:> 4du} bar {2:<01hs}" +// Must be kept in sync with `struct fmt_part` in backends/cxxrtl/runtime/cxxrtl/cxxrtl.h! struct FmtPart { enum { STRING = 0, INTEGER = 1, CHARACTER = 2, - TIME = 3, + VLOG_TIME = 3, } type; // STRING type @@ -64,20 +65,20 @@ struct FmtPart { // INTEGER/CHARACTER types RTLIL::SigSpec sig; - // INTEGER/CHARACTER/TIME types + // INTEGER/CHARACTER/VLOG_TIME types enum { RIGHT = 0, LEFT = 1, } justify = RIGHT; char padding = '\0'; size_t width = 0; - + // INTEGER type unsigned base = 10; bool signed_ = false; bool plus = false; - // TIME type + // VLOG_TIME type bool realtime = false; }; @@ -93,7 +94,7 @@ public: void parse_verilog(const std::vector &args, bool sformat_like, int default_base, RTLIL::IdString task_name, RTLIL::IdString module_name); std::vector emit_verilog() const; - void emit_cxxrtl(std::ostream &f, std::function emit_sig) const; + void emit_cxxrtl(std::ostream &os, std::string indent, std::function emit_sig, const std::string &context) const; std::string render() const; diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc index ffbd4b3ff..f19da69e2 100644 --- a/kernel/rtlil.cc +++ b/kernel/rtlil.cc @@ -2157,17 +2157,10 @@ void RTLIL::Module::remove(const pool &wires) } void operator()(RTLIL::SigSpec &lhs, RTLIL::SigSpec &rhs) { - log_assert(GetSize(lhs) == GetSize(rhs)); - lhs.unpack(); - rhs.unpack(); - for (int i = 0; i < GetSize(lhs); i++) { - RTLIL::SigBit &lhs_bit = lhs.bits_[i]; - RTLIL::SigBit &rhs_bit = rhs.bits_[i]; - if ((lhs_bit.wire != nullptr && wires_p->count(lhs_bit.wire)) || (rhs_bit.wire != nullptr && wires_p->count(rhs_bit.wire))) { - lhs_bit = State::Sx; - rhs_bit = State::Sx; - } - } + // If a deleted wire occurs on the lhs or rhs we just remove that part + // of the assignment + lhs.remove2(*wires_p, &rhs); + rhs.remove2(*wires_p, &lhs); } }; @@ -3693,6 +3686,9 @@ RTLIL::SigChunk::SigChunk(const RTLIL::SigBit &bit) RTLIL::SigChunk RTLIL::SigChunk::extract(int offset, int length) const { + log_assert(offset >= 0); + log_assert(length >= 0); + log_assert(offset + length <= width); RTLIL::SigChunk ret; if (wire) { ret.wire = wire; @@ -4238,6 +4234,34 @@ void RTLIL::SigSpec::remove2(const std::set &pattern, RTLIL::SigS check(); } +void RTLIL::SigSpec::remove2(const pool &pattern, RTLIL::SigSpec *other) +{ + if (other) + cover("kernel.rtlil.sigspec.remove_other"); + else + cover("kernel.rtlil.sigspec.remove"); + + unpack(); + + if (other != NULL) { + log_assert(width_ == other->width_); + other->unpack(); + } + + for (int i = GetSize(bits_) - 1; i >= 0; i--) { + if (bits_[i].wire != NULL && pattern.count(bits_[i].wire)) { + bits_.erase(bits_.begin() + i); + width_--; + if (other != NULL) { + other->bits_.erase(other->bits_.begin() + i); + other->width_--; + } + } + } + + check(); +} + RTLIL::SigSpec RTLIL::SigSpec::extract(const RTLIL::SigSpec &pattern, const RTLIL::SigSpec *other) const { if (other) @@ -4377,6 +4401,9 @@ void RTLIL::SigSpec::remove(int offset, int length) RTLIL::SigSpec RTLIL::SigSpec::extract(int offset, int length) const { + log_assert(offset >= 0); + log_assert(length >= 0); + log_assert(offset + length <= width_); unpack(); cover("kernel.rtlil.sigspec.extract_pos"); return std::vector(bits_.begin() + offset, bits_.begin() + offset + length); diff --git a/kernel/rtlil.h b/kernel/rtlil.h index d419872c6..928bc0440 100644 --- a/kernel/rtlil.h +++ b/kernel/rtlil.h @@ -924,6 +924,7 @@ public: void remove(const pool &pattern, RTLIL::SigSpec *other) const; void remove2(const pool &pattern, RTLIL::SigSpec *other); void remove2(const std::set &pattern, RTLIL::SigSpec *other); + void remove2(const pool &pattern, RTLIL::SigSpec *other); void remove(int offset, int length = 1); void remove_const(); diff --git a/passes/cmds/show.cc b/passes/cmds/show.cc index d57cbc6bb..8c2695dbe 100644 --- a/passes/cmds/show.cc +++ b/passes/cmds/show.cc @@ -539,7 +539,7 @@ struct ShowWorker std::string proc_src = RTLIL::unescape_id(proc->name); if (proc->attributes.count(ID::src) > 0) proc_src = proc->attributes.at(ID::src).decode_string(); - fprintf(f, "p%d [shape=box, style=rounded, label=\"PROC %s\\n%s\"];\n", pidx, findLabel(proc->name.str()), proc_src.c_str()); + fprintf(f, "p%d [shape=box, style=rounded, label=\"PROC %s\\n%s\", %s];\n", pidx, findLabel(proc->name.str()), proc_src.c_str(), findColor(proc->name).c_str()); } for (auto &conn : module->connections()) diff --git a/passes/cmds/stat.cc b/passes/cmds/stat.cc index f0021cf87..d34373c1c 100644 --- a/passes/cmds/stat.cc +++ b/passes/cmds/stat.cc @@ -366,7 +366,7 @@ struct StatPass : public Pass { log(" use cell area information from the provided liberty file\n"); log("\n"); log(" -tech \n"); - log(" print area estemate for the specified technology. Currently supported\n"); + log(" print area estimate for the specified technology. Currently supported\n"); log(" values for : xilinx, cmos\n"); log("\n"); log(" -width\n"); diff --git a/passes/hierarchy/hierarchy.cc b/passes/hierarchy/hierarchy.cc index bf0137503..69cc6fb91 100644 --- a/passes/hierarchy/hierarchy.cc +++ b/passes/hierarchy/hierarchy.cc @@ -655,6 +655,17 @@ void hierarchy_clean(RTLIL::Design *design, RTLIL::Module *top, bool purge_lib) log("Removed %d unused modules.\n", del_counter); } +bool set_keep_print(std::map &cache, RTLIL::Module *mod) +{ + if (cache.count(mod) == 0) + for (auto c : mod->cells()) { + RTLIL::Module *m = mod->design->module(c->type); + if ((m != nullptr && set_keep_print(cache, m)) || c->type == ID($print)) + return cache[mod] = true; + } + return cache[mod]; +} + bool set_keep_assert(std::map &cache, RTLIL::Module *mod) { if (cache.count(mod) == 0) @@ -762,6 +773,11 @@ struct HierarchyPass : public Pass { log(" -nodefaults\n"); log(" do not resolve input port default values\n"); log("\n"); + log(" -nokeep_prints\n"); + log(" per default this pass sets the \"keep\" attribute on all modules\n"); + log(" that directly or indirectly display text on the terminal.\n"); + log(" This option disables this behavior.\n"); + log("\n"); log(" -nokeep_asserts\n"); log(" per default this pass sets the \"keep\" attribute on all modules\n"); log(" that directly or indirectly contain one or more formal properties.\n"); @@ -818,6 +834,7 @@ struct HierarchyPass : public Pass { bool keep_positionals = false; bool keep_portwidths = false; bool nodefaults = false; + bool nokeep_prints = false; bool nokeep_asserts = false; std::vector generate_cells; std::vector generate_ports; @@ -893,6 +910,10 @@ struct HierarchyPass : public Pass { nodefaults = true; continue; } + if (args[argidx] == "-nokeep_prints") { + nokeep_prints = true; + continue; + } if (args[argidx] == "-nokeep_asserts") { nokeep_asserts = true; continue; @@ -1091,6 +1112,15 @@ struct HierarchyPass : public Pass { } } + if (!nokeep_prints) { + std::map cache; + for (auto mod : design->modules()) + if (set_keep_print(cache, mod)) { + log("Module %s directly or indirectly displays text -> setting \"keep\" attribute.\n", log_id(mod)); + mod->set_bool_attribute(ID::keep); + } + } + if (!nokeep_asserts) { std::map cache; for (auto mod : design->modules()) diff --git a/passes/memory/memory_libmap.cc b/passes/memory/memory_libmap.cc index ec181a142..2e683b8eb 100644 --- a/passes/memory/memory_libmap.cc +++ b/passes/memory/memory_libmap.cc @@ -690,7 +690,7 @@ bool apply_clock(MemConfig &cfg, const PortVariant &def, SigBit clk, bool clk_po // Perform write port assignment, validating clock options as we go. void MemMapping::assign_wr_ports() { - log_reject(stringf("Assigning write ports... (candidate configs: %lu)", cfgs.size())); + log_reject(stringf("Assigning write ports... (candidate configs: %zu)", (size_t) cfgs.size())); for (auto &port: mem.wr_ports) { if (!port.clk_enable) { // Async write ports not supported. @@ -739,7 +739,7 @@ void MemMapping::assign_wr_ports() { // Perform read port assignment, validating clock and rden options as we go. void MemMapping::assign_rd_ports() { - log_reject(stringf("Assigning read ports... (candidate configs: %lu)", cfgs.size())); + log_reject(stringf("Assigning read ports... (candidate configs: %zu)", (size_t) cfgs.size())); for (int pidx = 0; pidx < GetSize(mem.rd_ports); pidx++) { auto &port = mem.rd_ports[pidx]; MemConfigs new_cfgs; @@ -900,7 +900,7 @@ void MemMapping::assign_rd_ports() { // Validate transparency restrictions, determine where to add soft transparency logic. void MemMapping::handle_trans() { - log_reject(stringf("Handling transparency... (candidate configs: %lu)", cfgs.size())); + log_reject(stringf("Handling transparency... (candidate configs: %zu)", (size_t) cfgs.size())); if (mem.emulate_read_first_ok()) { MemConfigs new_cfgs; for (auto &cfg: cfgs) { diff --git a/passes/opt/opt_lut.cc b/passes/opt/opt_lut.cc index 3b079d964..3907285f3 100644 --- a/passes/opt/opt_lut.cc +++ b/passes/opt/opt_lut.cc @@ -24,6 +24,10 @@ USING_YOSYS_NAMESPACE PRIVATE_NAMESPACE_BEGIN +// Type represents the following constraint: Preserve connections to dedicated +// logic cell that has ports connected to LUT inputs. This includes +// the case where both LUT and dedicated logic input are connected to the same +// constant. struct dlogic_t { IdString cell_type; // LUT input idx -> hard cell's port name @@ -515,16 +519,6 @@ struct OptLutWorker } }; -static void split(std::vector &tokens, const std::string &text, char sep) -{ - size_t start = 0, end = 0; - while ((end = text.find(sep, start)) != std::string::npos) { - tokens.push_back(text.substr(start, end - start)); - start = end + 1; - } - tokens.push_back(text.substr(start)); -} - struct OptLutPass : public Pass { OptLutPass() : Pass("opt_lut", "optimize LUT cells") { } void help() override @@ -541,6 +535,10 @@ struct OptLutPass : public Pass { log(" the case where both LUT and dedicated logic input are connected to\n"); log(" the same constant.\n"); log("\n"); + log(" -tech ice40\n"); + log(" treat the design as a LUT-mapped circuit for the iCE40 architecture\n"); + log(" and preserve connections to SB_CARRY as appropriate\n"); + log("\n"); log(" -limit N\n"); log(" only perform the first N combines, then stop. useful for debugging.\n"); log("\n"); @@ -555,28 +553,28 @@ struct OptLutPass : public Pass { size_t argidx; for (argidx = 1; argidx < args.size(); argidx++) { - if (args[argidx] == "-dlogic" && argidx+1 < args.size()) + if (args[argidx] == "-tech" && argidx+1 < args.size()) { - std::vector tokens; - split(tokens, args[++argidx], ':'); - if (tokens.size() < 2) - log_cmd_error("The -dlogic option requires at least one connection.\n"); - dlogic_t entry; - entry.cell_type = "\\" + tokens[0]; - for (auto it = tokens.begin() + 1; it != tokens.end(); ++it) { - std::vector conn_tokens; - split(conn_tokens, *it, '='); - if (conn_tokens.size() != 2) - log_cmd_error("Invalid format of -dlogic signal mapping.\n"); - IdString logic_port = "\\" + conn_tokens[0]; - int lut_input = atoi(conn_tokens[1].c_str()); - entry.lut_input_port[lut_input] = logic_port; - } - dlogic.push_back(entry); + std::string tech = args[++argidx]; + if (tech != "ice40") + log_cmd_error("Unsupported -tech argument: %s\n", tech.c_str()); + + dlogic = {{ + ID(SB_CARRY), + dict{ + std::make_pair(1, ID(I0)), + std::make_pair(2, ID(I1)), + std::make_pair(3, ID(CI)) + } + }, { + ID(SB_CARRY), + dict{ + std::make_pair(3, ID(CO)) + } + }}; continue; } - if (args[argidx] == "-limit" && argidx + 1 < args.size()) - { + if (args[argidx] == "-limit" && argidx + 1 < args.size()) { limit = atoi(args[++argidx].c_str()); continue; } diff --git a/passes/sat/recover_names.cc b/passes/sat/recover_names.cc index 4c30a3632..4870e2cac 100644 --- a/passes/sat/recover_names.cc +++ b/passes/sat/recover_names.cc @@ -26,6 +26,7 @@ #include #include +#include USING_YOSYS_NAMESPACE @@ -623,7 +624,7 @@ struct RecoverNamesWorker { if (pop == 1 || pop == (8*sizeof(equiv_cls_t) - 1)) continue; - log_debug("equivalence class: %016lx\n", cls.first); + log_debug("equivalence class: %016" PRIx64 "\n", cls.first); const pool &gold_bits = cls2bits.at(cls.first).first; const pool &gate_bits = cls2bits.at(cls.first).second; if (gold_bits.empty() || gate_bits.empty()) diff --git a/passes/sat/sim.cc b/passes/sat/sim.cc index 8c4fadb69..aeed16b97 100644 --- a/passes/sat/sim.cc +++ b/passes/sat/sim.cc @@ -88,6 +88,17 @@ struct TriggeredAssertion { { } }; +struct DisplayOutput { + int step; + SimInstance *instance; + Cell *cell; + std::string output; + + DisplayOutput(int step, SimInstance *instance, Cell *cell, std::string output) : + step(step), instance(instance), cell(cell), output(output) + { } +}; + struct SimShared { bool debug = false; @@ -110,6 +121,7 @@ struct SimShared int next_output_id = 0; int step = 0; std::vector triggered_assertions; + std::vector display_output; bool serious_asserts = false; bool initstate = true; }; @@ -173,6 +185,7 @@ struct SimInstance struct print_state_t { + bool initial_done; Const past_trg; Const past_en; Const past_args; @@ -338,6 +351,7 @@ struct SimInstance print.past_trg = Const(State::Sx, cell->getPort(ID::TRG).size()); print.past_args = Const(State::Sx, cell->getPort(ID::ARGS).size()); print.past_en = State::Sx; + print.initial_done = false; } } @@ -799,7 +813,7 @@ struct SimInstance } } - void update_ph3(bool check_assertions) + void update_ph3(bool gclk_trigger) { for (auto &it : ff_database) { @@ -840,45 +854,57 @@ struct SimInstance bool triggered = false; Const trg = get_state(cell->getPort(ID::TRG)); + bool trg_en = cell->getParam(ID::TRG_ENABLE).as_bool(); Const en = get_state(cell->getPort(ID::EN)); Const args = get_state(cell->getPort(ID::ARGS)); - if (!en.as_bool()) - goto update_print; + bool sampled = trg_en && trg.size() > 0; - if (cell->getParam(ID::TRG_ENABLE).as_bool()) { - Const trg_pol = cell->getParam(ID::TRG_POLARITY); - for (int i = 0; i < trg.size(); i++) { - bool pol = trg_pol[i] == State::S1; - State curr = trg[i], past = print.past_trg[i]; - if (pol && curr == State::S1 && past == State::S0) + if (sampled ? print.past_en.as_bool() : en.as_bool()) { + if (sampled) { + sampled = true; + Const trg_pol = cell->getParam(ID::TRG_POLARITY); + for (int i = 0; i < trg.size(); i++) { + bool pol = trg_pol[i] == State::S1; + State curr = trg[i], past = print.past_trg[i]; + if (pol && curr == State::S1 && past == State::S0) + triggered = true; + if (!pol && curr == State::S0 && past == State::S1) + triggered = true; + } + } else if (trg_en) { + // initial $print (TRG width = 0, TRG_ENABLE = true) + if (!print.initial_done && en != print.past_en) triggered = true; - if (!pol && curr == State::S0 && past == State::S1) + } else if (cell->get_bool_attribute(ID(trg_on_gclk))) { + // unified $print for cycle based FV semantics + triggered = gclk_trigger; + } else { + // always @(*) $print + if (args != print.past_args || en != print.past_en) triggered = true; } - } else { - if (args != print.past_args || en != print.past_en) - triggered = true; - } - if (triggered) { - int pos = 0; - for (auto &part : print.fmt.parts) { - part.sig = args.extract(pos, part.sig.size()); - pos += part.sig.size(); + if (triggered) { + int pos = 0; + for (auto &part : print.fmt.parts) { + part.sig = (sampled ? print.past_args : args).extract(pos, part.sig.size()); + pos += part.sig.size(); + } + + std::string rendered = print.fmt.render(); + log("%s", rendered.c_str()); + shared->display_output.emplace_back(shared->step, this, cell, rendered); } - - std::string rendered = print.fmt.render(); - log("%s", rendered.c_str()); } - update_print: print.past_trg = trg; print.past_en = en; print.past_args = args; + print.initial_done = true; } - if (check_assertions) + if (gclk_trigger) { for (auto cell : formal_database) { @@ -910,7 +936,7 @@ struct SimInstance } for (auto it : children) - it.second->update_ph3(check_assertions); + it.second->update_ph3(gclk_trigger); } void set_initstate_outputs(State state) @@ -2055,6 +2081,20 @@ struct SimWorker : SimShared json.end_object(); } json.end_array(); + json.name("display_output"); + json.begin_array(); + for (auto &output : display_output) { + json.begin_object(); + json.entry("step", output.step); + json.entry("path", output.instance->witness_full_path(output.cell)); + auto src = output.cell->get_string_attribute(ID::src); + if (!src.empty()) { + json.entry("src", src); + } + json.entry("output", output.output); + json.end_object(); + } + json.end_array(); json.end_object(); } diff --git a/techlibs/gowin/brams.txt b/techlibs/gowin/brams.txt index 0c0d8fa3e..435d3b5cf 100644 --- a/techlibs/gowin/brams.txt +++ b/techlibs/gowin/brams.txt @@ -1,13 +1,11 @@ ram block $__GOWIN_SP_ { abits 14; widths 1 2 4 9 18 36 per_port; - byte 9; cost 128; init no_undef; port srsw "A" { clock posedge; clken; - wrbe_separate; option "RESET_MODE" "SYNC" { rdsrst zero ungated; } @@ -30,13 +28,11 @@ ram block $__GOWIN_SP_ { ram block $__GOWIN_DP_ { abits 14; widths 1 2 4 9 18 per_port; - byte 9; cost 128; init no_undef; port srsw "A" "B" { clock posedge; clken; - wrbe_separate; option "RESET_MODE" "SYNC" { rdsrst zero ungated; } @@ -59,7 +55,6 @@ ram block $__GOWIN_DP_ { ram block $__GOWIN_SDP_ { abits 14; widths 1 2 4 9 18 36 per_port; - byte 9; cost 128; init no_undef; port sr "R" { @@ -76,6 +71,5 @@ ram block $__GOWIN_SDP_ { port sw "W" { clock posedge; clken; - wrbe_separate; } } diff --git a/techlibs/gowin/brams_map.v b/techlibs/gowin/brams_map.v index 7ffc91bac..8e6cc6140 100644 --- a/techlibs/gowin/brams_map.v +++ b/techlibs/gowin/brams_map.v @@ -14,8 +14,7 @@ `define x8_width(width) (width / 9 * 8 + width % 9) `define x8_rd_data(data) {1'bx, data[31:24], 1'bx, data[23:16], 1'bx, data[15:8], 1'bx, data[7:0]} `define x8_wr_data(data) {data[34:27], data[25:18], data[16:9], data[7:0]} -`define wre(width, wr_en, wr_be) (width < 18 ? wr_en | wr_be[0] : wr_en) -`define addrbe(width, addr, wr_be) (width < 18 ? addr : {addr[13:4], wr_be}) +`define addrbe_always(width, addr) (width < 18 ? addr : width == 18 ? {addr[13:4], 4'b0011} : {addr[13:5], 5'b01111}) `define INIT(func) \ @@ -90,7 +89,6 @@ parameter INIT = 0; parameter OPTION_RESET_MODE = "SYNC"; parameter PORT_A_WIDTH = 36; -parameter PORT_A_WR_BE_WIDTH = 4; parameter PORT_A_OPTION_WRITE_MODE = 0; input PORT_A_CLK; @@ -99,15 +97,13 @@ input PORT_A_WR_EN; input PORT_A_RD_SRST; input PORT_A_RD_ARST; input [13:0] PORT_A_ADDR; -input [PORT_A_WR_BE_WIDTH-1:0] PORT_A_WR_BE; input [PORT_A_WIDTH-1:0] PORT_A_WR_DATA; output [PORT_A_WIDTH-1:0] PORT_A_RD_DATA; `DEF_FUNCS wire RST = OPTION_RESET_MODE == "SYNC" ? PORT_A_RD_SRST : PORT_A_RD_ARST; -wire WRE = `wre(PORT_A_WIDTH, PORT_A_WR_EN, PORT_A_WR_BE); -wire [13:0] AD = `addrbe(PORT_A_WIDTH, PORT_A_ADDR, PORT_A_WR_BE); +wire [13:0] AD = `addrbe_always(PORT_A_WIDTH, PORT_A_ADDR); generate @@ -129,9 +125,9 @@ if (PORT_A_WIDTH < 9) begin .BLKSEL(3'b000), .CLK(PORT_A_CLK), .CE(PORT_A_CLK_EN), - .WRE(WRE), + .WRE(PORT_A_WR_EN), .RESET(RST), - .OCE(1'b0), + .OCE(1'b1), .AD(AD), .DI(DI), .DO(DO), @@ -155,9 +151,9 @@ end else begin .BLKSEL(3'b000), .CLK(PORT_A_CLK), .CE(PORT_A_CLK_EN), - .WRE(WRE), + .WRE(PORT_A_WR_EN), .RESET(RST), - .OCE(1'b0), + .OCE(1'b1), .AD(AD), .DI(DI), .DO(DO), @@ -176,11 +172,9 @@ parameter INIT = 0; parameter OPTION_RESET_MODE = "SYNC"; parameter PORT_A_WIDTH = 18; -parameter PORT_A_WR_BE_WIDTH = 2; parameter PORT_A_OPTION_WRITE_MODE = 0; parameter PORT_B_WIDTH = 18; -parameter PORT_B_WR_BE_WIDTH = 2; parameter PORT_B_OPTION_WRITE_MODE = 0; input PORT_A_CLK; @@ -189,7 +183,6 @@ input PORT_A_WR_EN; input PORT_A_RD_SRST; input PORT_A_RD_ARST; input [13:0] PORT_A_ADDR; -input [PORT_A_WR_BE_WIDTH-1:0] PORT_A_WR_BE; input [PORT_A_WIDTH-1:0] PORT_A_WR_DATA; output [PORT_A_WIDTH-1:0] PORT_A_RD_DATA; @@ -199,7 +192,6 @@ input PORT_B_WR_EN; input PORT_B_RD_SRST; input PORT_B_RD_ARST; input [13:0] PORT_B_ADDR; -input [PORT_A_WR_BE_WIDTH-1:0] PORT_B_WR_BE; input [PORT_A_WIDTH-1:0] PORT_B_WR_DATA; output [PORT_A_WIDTH-1:0] PORT_B_RD_DATA; @@ -207,10 +199,8 @@ output [PORT_A_WIDTH-1:0] PORT_B_RD_DATA; wire RSTA = OPTION_RESET_MODE == "SYNC" ? PORT_A_RD_SRST : PORT_A_RD_ARST; wire RSTB = OPTION_RESET_MODE == "SYNC" ? PORT_B_RD_SRST : PORT_B_RD_ARST; -wire WREA = `wre(PORT_A_WIDTH, PORT_A_WR_EN, PORT_A_WR_BE); -wire WREB = `wre(PORT_B_WIDTH, PORT_B_WR_EN, PORT_B_WR_BE); -wire [13:0] ADA = `addrbe(PORT_A_WIDTH, PORT_A_ADDR, PORT_A_WR_BE); -wire [13:0] ADB = `addrbe(PORT_B_WIDTH, PORT_B_ADDR, PORT_B_WR_BE); +wire [13:0] ADA = `addrbe_always(PORT_A_WIDTH, PORT_A_ADDR); +wire [13:0] ADB = `addrbe_always(PORT_B_WIDTH, PORT_B_ADDR); generate @@ -224,7 +214,7 @@ if (PORT_A_WIDTH < 9 || PORT_B_WIDTH < 9) begin assign PORT_A_RD_DATA = `x8_rd_data(DOA); assign PORT_B_RD_DATA = `x8_rd_data(DOB); - DP #( + DPB #( `INIT(init_slice_x8) .READ_MODE0(1'b0), .READ_MODE1(1'b0), @@ -232,25 +222,27 @@ if (PORT_A_WIDTH < 9 || PORT_B_WIDTH < 9) begin .WRITE_MODE1(PORT_B_OPTION_WRITE_MODE), .BIT_WIDTH_0(`x8_width(PORT_A_WIDTH)), .BIT_WIDTH_1(`x8_width(PORT_B_WIDTH)), - .BLK_SEL(3'b000), + .BLK_SEL_0(3'b000), + .BLK_SEL_1(3'b000), .RESET_MODE(OPTION_RESET_MODE), ) _TECHMAP_REPLACE_ ( - .BLKSEL(3'b000), + .BLKSELA(3'b000), + .BLKSELB(3'b000), .CLKA(PORT_A_CLK), .CEA(PORT_A_CLK_EN), - .WREA(WREA), + .WREA(PORT_A_WR_EN), .RESETA(RSTA), - .OCEA(1'b0), + .OCEA(1'b1), .ADA(ADA), .DIA(DIA), .DOA(DOA), .CLKB(PORT_B_CLK), .CEB(PORT_B_CLK_EN), - .WREB(WREB), + .WREB(PORT_B_WR_EN), .RESETB(RSTB), - .OCEB(1'b0), + .OCEB(1'b1), .ADB(ADB), .DIB(DIB), .DOB(DOB), @@ -266,7 +258,7 @@ end else begin assign PORT_A_RD_DATA = DOA; assign PORT_B_RD_DATA = DOB; - DPX9 #( + DPX9B #( `INIT(init_slice_x9) .READ_MODE0(1'b0), .READ_MODE1(1'b0), @@ -274,25 +266,27 @@ end else begin .WRITE_MODE1(PORT_B_OPTION_WRITE_MODE), .BIT_WIDTH_0(PORT_A_WIDTH), .BIT_WIDTH_1(PORT_B_WIDTH), - .BLK_SEL(3'b000), + .BLK_SEL_0(3'b000), + .BLK_SEL_1(3'b000), .RESET_MODE(OPTION_RESET_MODE), ) _TECHMAP_REPLACE_ ( - .BLKSEL(3'b000), + .BLKSELA(3'b000), + .BLKSELB(3'b000), .CLKA(PORT_A_CLK), .CEA(PORT_A_CLK_EN), - .WREA(WREA), + .WREA(PORT_A_WR_EN), .RESETA(RSTA), - .OCEA(1'b0), + .OCEA(1'b1), .ADA(ADA), .DIA(DIA), .DOA(DOA), .CLKB(PORT_B_CLK), .CEB(PORT_B_CLK_EN), - .WREB(WREB), + .WREB(PORT_B_WR_EN), .RESETB(RSTB), - .OCEB(1'b0), + .OCEB(1'b1), .ADB(ADB), .DIB(DIB), .DOB(DOB), @@ -311,9 +305,7 @@ parameter INIT = 0; parameter OPTION_RESET_MODE = "SYNC"; parameter PORT_R_WIDTH = 18; - parameter PORT_W_WIDTH = 18; -parameter PORT_W_WR_BE_WIDTH = 2; input PORT_R_CLK; input PORT_R_CLK_EN; @@ -326,14 +318,13 @@ input PORT_W_CLK; input PORT_W_CLK_EN; input PORT_W_WR_EN; input [13:0] PORT_W_ADDR; -input [PORT_W_WR_BE_WIDTH-1:0] PORT_W_WR_BE; input [PORT_W_WIDTH-1:0] PORT_W_WR_DATA; `DEF_FUNCS wire RST = OPTION_RESET_MODE == "SYNC" ? PORT_R_RD_SRST : PORT_R_RD_ARST; -wire WRE = `wre(PORT_W_WIDTH, PORT_W_WR_EN, PORT_W_WR_BE); -wire [13:0] ADW = `addrbe(PORT_W_WIDTH, PORT_W_ADDR, PORT_W_WR_BE); +wire [13:0] ADW = `addrbe_always(PORT_W_WIDTH, PORT_W_ADDR); +wire WRE = PORT_W_CLK_EN & PORT_W_WR_EN; generate @@ -344,28 +335,28 @@ if (PORT_W_WIDTH < 9 || PORT_R_WIDTH < 9) begin assign PORT_R_RD_DATA = `x8_rd_data(DO); - SDP #( + SDPB #( `INIT(init_slice_x8) .READ_MODE(1'b0), .BIT_WIDTH_0(`x8_width(PORT_W_WIDTH)), .BIT_WIDTH_1(`x8_width(PORT_R_WIDTH)), - .BLK_SEL(3'b000), + .BLK_SEL_0(3'b000), + .BLK_SEL_1(3'b000), .RESET_MODE(OPTION_RESET_MODE), ) _TECHMAP_REPLACE_ ( - .BLKSEL(3'b000), + .BLKSELA(3'b000), + .BLKSELB(3'b000), .CLKA(PORT_W_CLK), - .CEA(PORT_W_CLK_EN), - .WREA(WRE), + .CEA(WRE), .RESETA(1'b0), .ADA(ADW), .DI(DI), .CLKB(PORT_R_CLK), .CEB(PORT_R_CLK_EN), - .WREB(1'b0), .RESETB(RST), - .OCE(1'b0), + .OCE(1'b1), .ADB(PORT_R_ADDR), .DO(DO), ); @@ -377,28 +368,28 @@ end else begin assign PORT_R_RD_DATA = DO; - SDPX9 #( + SDPX9B #( `INIT(init_slice_x9) .READ_MODE(1'b0), .BIT_WIDTH_0(PORT_W_WIDTH), .BIT_WIDTH_1(PORT_R_WIDTH), - .BLK_SEL(3'b000), + .BLK_SEL_0(3'b000), + .BLK_SEL_1(3'b000), .RESET_MODE(OPTION_RESET_MODE), ) _TECHMAP_REPLACE_ ( - .BLKSEL(3'b000), + .BLKSELA(3'b000), + .BLKSELB(3'b000), .CLKA(PORT_W_CLK), - .CEA(PORT_W_CLK_EN), - .WREA(WRE), + .CEA(WRE), .RESETA(1'b0), .ADA(ADW), .DI(DI), .CLKB(PORT_R_CLK), .CEB(PORT_R_CLK_EN), - .WREB(1'b0), .RESETB(RST), - .OCE(1'b0), + .OCE(1'b1), .ADB(PORT_R_ADDR), .DO(DO), ); diff --git a/techlibs/ice40/synth_ice40.cc b/techlibs/ice40/synth_ice40.cc index a9982649b..4c691c7a5 100644 --- a/techlibs/ice40/synth_ice40.cc +++ b/techlibs/ice40/synth_ice40.cc @@ -432,7 +432,7 @@ struct SynthIce40Pass : public ScriptPass run("ice40_wrapcarry -unwrap"); run("techmap -map +/ice40/ff_map.v"); run("clean"); - run("opt_lut -dlogic SB_CARRY:I0=1:I1=2:CI=3 -dlogic SB_CARRY:CO=3"); + run("opt_lut -tech ice40"); } if (check_label("map_cells")) diff --git a/tests/fmt/always_full_tb.cc b/tests/fmt/always_full_tb.cc index 229f78aeb..94991ca25 100644 --- a/tests/fmt/always_full_tb.cc +++ b/tests/fmt/always_full_tb.cc @@ -2,8 +2,13 @@ int main() { + struct : public performer { + int64_t vlog_time() const override { return 1; } + void on_print(const lazy_fmt &output, const cxxrtl::metadata_map &) override { std::cerr << output(); } + } performer; + cxxrtl_design::p_always__full uut; uut.p_clk.set(!uut.p_clk); - uut.step(); + uut.step(&performer); return 0; } diff --git a/tests/simple/sign_part_assign.v b/tests/simple/sign_part_assign.v new file mode 100644 index 000000000..5f65ac284 --- /dev/null +++ b/tests/simple/sign_part_assign.v @@ -0,0 +1,20 @@ +module test ( + offset_i, + data_o, + data_ref_o +); +input wire [ 2:0] offset_i; +output reg [15:0] data_o; +output reg [15:0] data_ref_o; + +always @(*) begin + // defaults + data_o = '0; + data_ref_o = '0; + + // partial assigns + data_ref_o[offset_i+:4] = 4'b1111; // unsigned + data_o[offset_i+:4] = 1'sb1; // sign extension to 4'b1111 +end + +endmodule diff --git a/tests/various/bug4082.ys b/tests/various/bug4082.ys new file mode 100644 index 000000000..272be2fad --- /dev/null +++ b/tests/various/bug4082.ys @@ -0,0 +1,8 @@ +read_verilog <