diff --git a/.github/workflows/test-macos.yml b/.github/workflows/test-macos.yml index 048457234..2b48b7252 100644 --- a/.github/workflows/test-macos.yml +++ b/.github/workflows/test-macos.yml @@ -8,7 +8,7 @@ jobs: strategy: matrix: os: - - { id: macos-11, name: 'Big Sur' } + - { id: macos-13, name: 'Ventura' } cpp_std: - 'c++11' - 'c++17' 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 e87b38ff0..fb9165a57 100644 --- a/Makefile +++ b/Makefile @@ -141,7 +141,7 @@ LDLIBS += -lrt endif endif -YOSYS_VER := 0.36+13 +YOSYS_VER := 0.37+21 # 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 # @@ -888,6 +888,7 @@ endif +cd tests/verilog && bash run-test.sh +cd tests/xprop && bash run-test.sh $(SEEDOPT) +cd tests/fmt && bash run-test.sh + +cd tests/cxxrtl && bash run-test.sh @echo "" @echo " Passed \"make test\"." @echo "" diff --git a/backends/btor/test_cells.sh b/backends/btor/test_cells.sh index 0a011932d..f8bd79782 100755 --- a/backends/btor/test_cells.sh +++ b/backends/btor/test_cells.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index a322ed308..c9c644a52 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -628,6 +628,20 @@ std::string escape_cxx_string(const std::string &input) return output; } +std::string basename(const std::string &filepath) +{ +#ifdef _WIN32 + const std::string dir_seps = "\\/"; +#else + const std::string dir_seps = "/"; +#endif + size_t sep_pos = filepath.find_last_of(dir_seps); + if (sep_pos != std::string::npos) + return filepath.substr(sep_pos + 1); + else + return filepath; +} + template std::string get_hdl_name(T *object) { @@ -1058,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"; } @@ -1277,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); @@ -1471,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(); @@ -1988,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); @@ -2101,19 +2165,19 @@ struct CxxrtlWorker { if (wire_type.type == WireType::MEMBER && edge_wires[wire]) f << indent << "prev_" << mangle(wire) << " = " << mangle(wire) << ";\n"; if (wire_type.is_buffered()) - f << indent << "if (" << mangle(wire) << ".commit()) changed = true;\n"; + f << indent << "if (" << mangle(wire) << ".commit(observer)) changed = true;\n"; } if (!module->get_bool_attribute(ID(cxxrtl_blackbox))) { for (auto &mem : mod_memories[module]) { if (!writable_memories.count({module, mem.memid})) continue; - f << indent << "if (" << mangle(&mem) << ".commit()) changed = true;\n"; + f << indent << "if (" << mangle(&mem) << ".commit(observer)) changed = true;\n"; } for (auto cell : module->cells()) { if (is_internal_cell(cell->type)) continue; const char *access = is_cxxrtl_blackbox_cell(cell) ? "->" : "."; - f << indent << "if (" << mangle(cell) << access << "commit()) changed = true;\n"; + f << indent << "if (" << mangle(cell) << access << "commit(observer)) changed = true;\n"; } } f << indent << "return changed;\n"; @@ -2132,7 +2196,7 @@ struct CxxrtlWorker { if (!metadata_item.first.isPublic()) continue; if (metadata_item.second.size() > 64 && (metadata_item.second.flags & RTLIL::CONST_FLAG_STRING) == 0) { - f << indent << "/* attribute " << metadata_item.first.str().substr(1) << " is over 64 bits wide */"; + f << indent << "/* attribute " << metadata_item.first.str().substr(1) << " is over 64 bits wide */\n"; continue; } f << indent << "{ " << escape_cxx_string(metadata_item.first.str().substr(1)) << ", "; @@ -2353,20 +2417,27 @@ 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"; - f << indent << "bool commit() override {\n"; + f << indent << "template\n"; + f << indent << "bool commit(ObserverT &observer) {\n"; dump_commit_method(module); f << indent << "}\n"; f << "\n"; + f << indent << "bool commit() override {\n"; + f << indent << indent << "observer observer;\n"; + f << indent << indent << "return commit<>(observer);\n"; + f << indent << "}\n"; if (debug_info) { + f << "\n"; f << indent << "void debug_info(debug_items &items, std::string path = \"\") override {\n"; dump_debug_info_method(module); f << indent << "}\n"; - f << "\n"; } + f << "\n"; f << indent << "static std::unique_ptr<" << mangle(module); f << template_params(module, /*is_decl=*/false) << "> "; f << "create(std::string name, metadata_map parameters, metadata_map attributes);\n"; @@ -2410,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); @@ -2443,8 +2514,18 @@ struct CxxrtlWorker { f << indent << "};\n"; f << "\n"; f << indent << "void reset() override;\n"; - f << indent << "bool eval() override;\n"; - f << indent << "bool commit() override;\n"; + f << "\n"; + f << indent << "bool eval(performer *performer = nullptr) override;\n"; + f << "\n"; + f << indent << "template\n"; + f << indent << "bool commit(ObserverT &observer) {\n"; + dump_commit_method(module); + f << indent << "}\n"; + f << "\n"; + f << indent << "bool commit() override {\n"; + f << indent << indent << "observer observer;\n"; + f << indent << indent << "return commit<>(observer);\n"; + f << indent << "}\n"; if (debug_info) { if (debug_eval) { f << "\n"; @@ -2473,27 +2554,23 @@ 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"; - f << "\n"; - f << indent << "bool " << mangle(module) << "::commit() {\n"; - dump_commit_method(module); - f << indent << "}\n"; - f << "\n"; if (debug_info) { if (debug_eval) { + f << "\n"; f << indent << "void " << mangle(module) << "::debug_eval() {\n"; dump_debug_eval_method(module); f << indent << "}\n"; - f << "\n"; } + f << "\n"; f << indent << "CXXRTL_EXTREMELY_COLD\n"; f << indent << "void " << mangle(module) << "::debug_info(debug_items &items, std::string path) {\n"; dump_debug_info_method(module); f << indent << "}\n"; - f << "\n"; } + f << "\n"; } void dump_design(RTLIL::Design *design) @@ -2501,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; @@ -2514,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); @@ -2571,11 +2645,9 @@ struct CxxrtlWorker { } if (split_intf) - f << "#include \"" << intf_filename << "\"\n"; + 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"; @@ -2938,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); @@ -3252,7 +3325,9 @@ 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"); log("\n"); log(" static std::unique_ptr\n"); @@ -3265,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"); @@ -3370,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 2d5451287..550571497 100644 --- a/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl.h @@ -28,6 +28,7 @@ #include #include +#include #include #include #include @@ -38,6 +39,7 @@ #include #include #include +#include // `cxxrtl::debug_item` has to inherit from `cxxrtl_object` to satisfy strict aliasing requirements. #include @@ -145,7 +147,7 @@ struct value : public expr_base> { // These functions ensure that a conversion is never out of range, and should be always used, if at all // possible, instead of direct manipulation of the `data` member. For very large types, .slice() and // .concat() can be used to split them into more manageable parts. - template + template::value, int>::type = 0> CXXRTL_ALWAYS_INLINE IntegerT get() const { static_assert(std::numeric_limits::is_integer && !std::numeric_limits::is_signed, @@ -158,15 +160,32 @@ struct value : public expr_base> { return result; } - template + template::value, int>::type = 0> CXXRTL_ALWAYS_INLINE - void set(IntegerT other) { + IntegerT get() const { + auto unsigned_result = get::type>(); + IntegerT result; + memcpy(&result, &unsigned_result, sizeof(IntegerT)); + return result; + } + + template::value, int>::type = 0> + CXXRTL_ALWAYS_INLINE + void set(IntegerT value) { static_assert(std::numeric_limits::is_integer && !std::numeric_limits::is_signed, "set() requires T to be an unsigned integral type"); static_assert(std::numeric_limits::digits >= Bits, "set() requires the value to be at least as wide as T is"); for (size_t n = 0; n < chunks; n++) - data[n] = (other >> (n * chunk::bits)) & chunk::mask; + data[n] = (value >> (n * chunk::bits)) & chunk::mask; + } + + template::value, int>::type = 0> + CXXRTL_ALWAYS_INLINE + void set(IntegerT value) { + typename std::make_unsigned::type unsigned_value; + memcpy(&unsigned_value, &value, sizeof(IntegerT)); + set(unsigned_value); } // Operations with compile-time parameters. @@ -419,6 +438,7 @@ struct value : public expr_base> { carry = (shift_bits == 0) ? 0 : data[n] >> (chunk::bits - shift_bits); } + result.data[result.chunks - 1] &= result.msb_mask; return result; } @@ -429,12 +449,12 @@ struct value : public expr_base> { // Detect shifts definitely large than Bits early. for (size_t n = 1; n < amount.chunks; n++) if (amount.data[n] != 0) - return {}; + return (Signed && is_neg()) ? value().bit_not() : value(); // Past this point we can use the least significant chunk as the shift size. size_t shift_chunks = amount.data[0] / chunk::bits; size_t shift_bits = amount.data[0] % chunk::bits; if (shift_chunks >= chunks) - return {}; + return (Signed && is_neg()) ? value().bit_not() : value(); value result; chunk::type carry = 0; for (size_t n = 0; n < chunks - shift_chunks; n++) { @@ -443,12 +463,13 @@ struct value : public expr_base> { : data[chunks - 1 - n] << (chunk::bits - shift_bits); } if (Signed && is_neg()) { - size_t top_chunk_idx = (Bits - shift_bits) / chunk::bits; - size_t top_chunk_bits = (Bits - shift_bits) % chunk::bits; + size_t top_chunk_idx = amount.data[0] > Bits ? 0 : (Bits - amount.data[0]) / chunk::bits; + size_t top_chunk_bits = amount.data[0] > Bits ? 0 : (Bits - amount.data[0]) % chunk::bits; for (size_t n = top_chunk_idx + 1; n < chunks; n++) result.data[n] = chunk::mask; - if (shift_bits != 0) + if (amount.data[0] != 0) result.data[top_chunk_idx] |= chunk::mask << top_chunk_bits; + result.data[result.chunks - 1] &= result.msb_mask; } return result; } @@ -473,6 +494,7 @@ struct value : public expr_base> { carry = (shift_bits == 0) ? 0 : data[result.chunks + shift_chunks - 1 - n] << (chunk::bits - shift_bits); } + result.data[result.chunks - 1] &= result.msb_mask; return result; } @@ -509,7 +531,8 @@ struct value : public expr_base> { for (size_t n = 0; n < chunks; n++) { chunk::type x = data[chunks - 1 - n]; // First add to `count` as if the chunk is zero - count += (n == 0 ? Bits % chunk::bits : chunk::bits); + constexpr size_t msb_chunk_bits = Bits % chunk::bits != 0 ? Bits % chunk::bits : chunk::bits; + count += (n == 0 ? msb_chunk_bits : chunk::bits); // If the chunk isn't zero, correct the `count` value and return if (x != 0) { for (; x != 0; count--) @@ -543,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 { @@ -741,102 +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; -} - template struct wire { static constexpr size_t bits = Bits; @@ -871,8 +798,13 @@ struct wire { next.template set(other); } - bool commit() { + // 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 allow the `on_update` method to be non-virtual. + template + bool commit(ObserverT &observer) { if (curr != next) { + observer.on_update(curr.chunks, curr.data, next.data); curr = next; return true; } @@ -946,12 +878,17 @@ struct memory { write { index, val, mask, priority }); } - bool commit() { + // See the note for `wire::commit()`. + template + bool commit(ObserverT &observer) { bool changed = false; for (const write &entry : write_queue) { value elem = data[entry.index]; elem = elem.update(entry.val, entry.mask); - changed |= (data[entry.index] != elem); + if (data[entry.index] != elem) { + observer.on_update(value::chunks, data[0].data, elem.data, entry.index); + changed |= true; + } data[entry.index] = elem; } write_queue.clear(); @@ -1008,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 {}; @@ -1250,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 new file mode 100644 index 000000000..d824fdb83 --- /dev/null +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_replay.h @@ -0,0 +1,783 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2023 Catherine + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef CXXRTL_REPLAY_H +#define CXXRTL_REPLAY_H + +#if !defined(WIN32) +#include +#define O_BINARY 0 +#else +#include +#endif + +#include +#include +#include +#include +#include + +#include +#include + +// Theory of operation +// =================== +// +// Log format +// ---------- +// +// The replay log is a simple data format based on a sequence of 32-bit words. The following BNF-like grammar describes +// enough detail to understand the overall structure of the log data and be able to read hex dumps. For a greater +// degree of detail see the source code. The format is considered fully internal to CXXRTL and is subject to change +// without notice. +// +// ::= + +// ::= 0x52585843 0x00004c54 +// ::= * +// ::= * +// ::= 0xc0000000 ... +// ::= 0xc0000001 ... +// ::= 0x0??????? + | 0x1??????? + | 0x2??????? | 0x3??????? +// , ::= 0x???????? +// ::= 0xFFFFFFFF +// +// The replay log contains sample data, however, it does not cover the entire design. Rather, it only contains sample +// data for the subset of debug items containing _design state_: inputs and registers/latches. This keeps its size to +// a minimum, and recording speed to a maximum. The player samples any missing data by setting the design state items +// to the same values they had during recording, and re-evaluating the design. +// +// Limits +// ------ +// +// The log may contain: +// +// * Up to 2**28-1 debug items containing design state. +// * Up to 2**32 chunks per debug item. +// * Up to 2**32 rows per memory. +// * Up to 2**32 samples. +// +// Of these limits, the last two are most likely to be eventually exceeded by practical recordings. However, other +// performance considerations will likely limit the size of such practical recordings first, so the log data format +// will undergo a breaking change at that point. +// +// Operations +// ---------- +// +// As suggested by the name "replay log", this format is designed for recording (writing) once and playing (reading) +// many times afterwards, such that reading the format can be done linearly and quickly. The log format is designed to +// support three primary read operations: +// +// 1. Initialization +// 2. Rewinding (to time T) +// 3. Replaying (for N samples) +// +// During initialization, the player establishes the mapping between debug item names and their 28-bit identifiers in +// the log. It is done once. +// +// During rewinding, the player begins reading at the latest non-incremental sample that still lies before the requested +// sample time. It continues reading incremental samples after that point until it reaches the requested sample time. +// This process is very cheap as the design is not evaluated; it is essentially a (convoluted) memory copy operation. +// +// During replaying, the player evaluates the design at the current time, which causes all debug items to assume +// the values they had before recording. This process is expensive. Once done, the player advances to the next state +// by reading the next (complete or incremental) sample, as above. Since a range of samples is replayed, this process +// is repeated several times in a row. +// +// In principle, when replaying, the player could only read the state of the inputs and the time delta and use a normal +// eval/commit loop to progress the simulation, which is fully deterministic so its calculated design state should be +// exactly the same as the recorded design state. In practice, it is both faster and more reliable (in presence of e.g. +// user-defined black boxes) to read the recorded values instead of calculating them. +// +// Note: The operations described above are conceptual and do not correspond exactly to methods on `cxxrtl::player`. +// The `cxxrtl::player::replay()` method does not evaluate the design. This is so that delta cycles could be ignored +// if they are not of interest while replaying. + +namespace cxxrtl { + +// A spool stores CXXRTL design state changes in a file. +class spool { +public: + // Unique pointer to a specific sample within a replay log. (Timestamps are not unique.) + typedef uint32_t pointer_t; + + // Numeric identifier assigned to a debug item within a replay log. Range limited to [1, MAXIMUM_IDENT]. + typedef uint32_t ident_t; + + static constexpr uint16_t VERSION = 0x0400; + + static constexpr uint64_t HEADER_MAGIC = 0x00004c5452585843; + static constexpr uint64_t VERSION_MASK = 0xffff000000000000; + + static constexpr uint32_t PACKET_DEFINE = 0xc0000000; + + static constexpr uint32_t PACKET_SAMPLE = 0xc0000001; + enum sample_flag : uint32_t { + EMPTY = 0, + INCREMENTAL = 1, + }; + + static constexpr uint32_t MAXIMUM_IDENT = 0x0fffffff; + static constexpr uint32_t CHANGE_MASK = 0x30000000; + + static constexpr uint32_t PACKET_CHANGE = 0x00000000/* | ident */; + static constexpr uint32_t PACKET_CHANGEI = 0x10000000/* | ident */; + static constexpr uint32_t PACKET_CHANGEL = 0x20000000/* | ident */; + static constexpr uint32_t PACKET_CHANGEH = 0x30000000/* | ident */; + + static constexpr uint32_t PACKET_END = 0xffffffff; + + // Writing spools. + + class writer { + int fd; + size_t position; + std::vector buffer; + + // These functions aren't overloaded because of implicit numeric conversions. + + void emit_word(uint32_t word) { + if (position + 1 == buffer.size()) + flush(); + buffer[position++] = word; + } + + void emit_dword(uint64_t dword) { + emit_word(dword >> 0); + emit_word(dword >> 32); + } + + void emit_ident(ident_t ident) { + assert(ident <= MAXIMUM_IDENT); + emit_word(ident); + } + + void emit_size(size_t size) { + assert(size <= std::numeric_limits::max()); + emit_word(size); + } + + // Same implementation as `emit_size()`, different declared intent. + void emit_index(size_t index) { + assert(index <= std::numeric_limits::max()); + emit_word(index); + } + + void emit_string(std::string str) { + // Align to a word boundary, and add at least one terminating \0. + str.resize(str.size() + (sizeof(uint32_t) - (str.size() + sizeof(uint32_t)) % sizeof(uint32_t))); + for (size_t index = 0; index < str.size(); index += sizeof(uint32_t)) { + uint32_t word; + memcpy(&word, &str[index], sizeof(uint32_t)); + emit_word(word); + } + } + + void emit_time(const time ×tamp) { + const value &raw_timestamp(timestamp); + emit_word(raw_timestamp.data[0]); + emit_word(raw_timestamp.data[1]); + emit_word(raw_timestamp.data[2]); + } + + public: + // Creates a writer, and transfers ownership of `fd`, which must be open for appending. + // + // The buffer size is currently fixed to a "reasonably large" size, determined empirically by measuring writer + // performance on a representative design; large but not so large it would e.g. cause address space exhaustion + // on 32-bit platforms. + writer(spool &spool) : fd(spool.take_write()), position(0), buffer(32 * 1024 * 1024) { + assert(fd != -1); +#if !defined(WIN32) + int result = ftruncate(fd, 0); +#else + int result = _chsize_s(fd, 0); +#endif + assert(result == 0); + } + + writer(writer &&moved) : fd(moved.fd), position(moved.position), buffer(moved.buffer) { + moved.fd = -1; + moved.position = 0; + } + + writer(const writer &) = delete; + writer &operator=(const writer &) = delete; + + // Both write() calls and fwrite() calls are too expensive to perform implicitly. The API consumer must determine + // the optimal time to flush the writer and do that explicitly for best performance. + void flush() { + assert(fd != -1); + size_t data_size = position * sizeof(uint32_t); + size_t data_written = write(fd, buffer.data(), data_size); + assert(data_size == data_written); + position = 0; + } + + ~writer() { + if (fd != -1) { + flush(); + close(fd); + } + } + + void write_magic() { + // `CXXRTL` followed by version in binary. This header will read backwards on big-endian machines, which allows + // detection of this case, both visually and programmatically. + emit_dword(((uint64_t)VERSION << 48) | HEADER_MAGIC); + } + + void write_define(ident_t ident, const std::string &name, size_t part_index, size_t chunks, size_t depth) { + emit_word(PACKET_DEFINE); + emit_ident(ident); + emit_string(name); + emit_index(part_index); + emit_size(chunks); + emit_size(depth); + } + + void write_sample(bool incremental, pointer_t pointer, const time ×tamp) { + uint32_t flags = (incremental ? sample_flag::INCREMENTAL : 0); + emit_word(PACKET_SAMPLE); + emit_word(flags); + emit_word(pointer); + emit_time(timestamp); + } + + void write_change(ident_t ident, size_t chunks, const chunk_t *data) { + assert(ident <= MAXIMUM_IDENT); + + if (chunks == 1 && *data == 0) { + emit_word(PACKET_CHANGEL | ident); + } else if (chunks == 1 && *data == 1) { + emit_word(PACKET_CHANGEH | ident); + } else { + emit_word(PACKET_CHANGE | ident); + for (size_t offset = 0; offset < chunks; offset++) + emit_word(data[offset]); + } + } + + void write_change(ident_t ident, size_t chunks, const chunk_t *data, size_t index) { + assert(ident <= MAXIMUM_IDENT); + + emit_word(PACKET_CHANGEI | ident); + emit_index(index); + for (size_t offset = 0; offset < chunks; offset++) + emit_word(data[offset]); + } + + void write_end() { + emit_word(PACKET_END); + } + }; + + // Reading spools. + + class reader { + FILE *f; + + uint32_t absorb_word() { + // If we're at end of file, `fread` will not write to `word`, and `PACKET_END` will be returned. + uint32_t word = PACKET_END; + fread(&word, sizeof(word), 1, f); + return word; + } + + uint64_t absorb_dword() { + uint32_t lo = absorb_word(); + uint32_t hi = absorb_word(); + return ((uint64_t)hi << 32) | lo; + } + + ident_t absorb_ident() { + ident_t ident = absorb_word(); + assert(ident <= MAXIMUM_IDENT); + return ident; + } + + size_t absorb_size() { + return absorb_word(); + } + + size_t absorb_index() { + return absorb_word(); + } + + std::string absorb_string() { + std::string str; + do { + size_t end = str.size(); + str.resize(end + 4); + uint32_t word = absorb_word(); + memcpy(&str[end], &word, sizeof(uint32_t)); + } while (str.back() != '\0'); + // Strings have no embedded zeroes besides the terminating one(s). + return str.substr(0, str.find('\0')); + } + + time absorb_time() { + value raw_timestamp; + raw_timestamp.data[0] = absorb_word(); + raw_timestamp.data[1] = absorb_word(); + raw_timestamp.data[2] = absorb_word(); + return time(raw_timestamp); + } + + public: + typedef uint64_t pos_t; + + // Creates a reader, and transfers ownership of `fd`, which must be open for reading. + reader(spool &spool) : f(fdopen(spool.take_read(), "r")) { + assert(f != nullptr); + } + + reader(reader &&moved) : f(moved.f) { + moved.f = nullptr; + } + + reader(const reader &) = delete; + reader &operator=(const reader &) = delete; + + ~reader() { + if (f != nullptr) + fclose(f); + } + + pos_t position() { + return ftell(f); + } + + void rewind(pos_t position) { + fseek(f, position, SEEK_SET); + } + + void read_magic() { + uint64_t magic = absorb_dword(); + assert((magic & ~VERSION_MASK) == HEADER_MAGIC); + assert((magic >> 48) == VERSION); + } + + bool read_define(ident_t &ident, std::string &name, size_t &part_index, size_t &chunks, size_t &depth) { + uint32_t header = absorb_word(); + if (header == PACKET_END) + return false; + assert(header == PACKET_DEFINE); + ident = absorb_ident(); + name = absorb_string(); + part_index = absorb_index(); + chunks = absorb_size(); + depth = absorb_size(); + return true; + } + + bool read_sample(bool &incremental, pointer_t &pointer, time ×tamp) { + uint32_t header = absorb_word(); + if (header == PACKET_END) + return false; + assert(header == PACKET_SAMPLE); + uint32_t flags = absorb_word(); + incremental = (flags & sample_flag::INCREMENTAL); + pointer = absorb_word(); + timestamp = absorb_time(); + return true; + } + + bool read_change_header(uint32_t &header, ident_t &ident) { + header = absorb_word(); + if (header == PACKET_END) + return false; + assert((header & ~(CHANGE_MASK | MAXIMUM_IDENT)) == 0); + ident = header & MAXIMUM_IDENT; + return true; + } + + void read_change_data(uint32_t header, size_t chunks, size_t depth, chunk_t *data) { + uint32_t index = 0; + switch (header & CHANGE_MASK) { + case PACKET_CHANGEL: + *data = 0; + return; + case PACKET_CHANGEH: + *data = 1; + return; + case PACKET_CHANGE: + break; + case PACKET_CHANGEI: + index = absorb_word(); + assert(index < depth); + break; + default: + assert(false && "Unrecognized change packet"); + } + for (size_t offset = 0; offset < chunks; offset++) + data[chunks * index + offset] = absorb_word(); + } + }; + + // Opening spools. For certain uses of the record/replay mechanism, two distinct open files (two open files, i.e. + // two distinct file pointers, and not just file descriptors, which share the file pointer if duplicated) are used, + // for a reader and writer thread. This class manages the lifetime of the descriptors for these files. When only + // one of them is used, the other is closed harmlessly when the spool is destroyed. +private: + std::atomic writefd; + std::atomic readfd; + +public: + spool(const std::string &filename) + : writefd(open(filename.c_str(), O_CREAT|O_BINARY|O_WRONLY|O_APPEND, 0644)), + readfd(open(filename.c_str(), O_BINARY|O_RDONLY)) { + assert(writefd.load() != -1 && readfd.load() != -1); + } + + spool(spool &&moved) : writefd(moved.writefd.exchange(-1)), readfd(moved.readfd.exchange(-1)) {} + + spool(const spool &) = delete; + spool &operator=(const spool &) = delete; + + ~spool() { + if (int fd = writefd.exchange(-1)) + close(fd); + if (int fd = readfd.exchange(-1)) + close(fd); + } + + // Atomically acquire a write file descriptor for the spool. Can be called once, and will return -1 the next time + // it is called. Thread-safe. + int take_write() { + return writefd.exchange(-1); + } + + // Atomically acquire a read file descriptor for the spool. Can be called once, and will return -1 the next time + // it is called. Thread-safe. + int take_read() { + return readfd.exchange(-1); + } +}; + +// A CXXRTL recorder samples design state, producing complete or incremental updates, and writes them to a spool. +class recorder { + struct variable { + spool::ident_t ident; /* <= spool::MAXIMUM_IDENT */ + size_t chunks; + size_t depth; /* == 1 for wires */ + chunk_t *curr; + bool memory; + }; + + spool::writer writer; + std::vector variables; + std::vector inputs; // values of inputs must be recorded explicitly, as their changes are not observed + std::unordered_map ident_lookup; + bool streaming = false; // whether variable definitions have been written + spool::pointer_t pointer = 0; + time timestamp; + +public: + template + recorder(Args &&...args) : writer(std::forward(args)...) {} + + void start(module &module) { + debug_items items; + module.debug_info(items); + start(items); + } + + void start(const debug_items &items) { + assert(!streaming); + + writer.write_magic(); + for (auto item : items.table) + for (size_t part_index = 0; part_index < item.second.size(); part_index++) { + auto &part = item.second[part_index]; + if ((part.flags & debug_item::INPUT) || (part.flags & debug_item::DRIVEN_SYNC) || + (part.type == debug_item::MEMORY)) { + variable var; + var.ident = variables.size() + 1; + var.chunks = (part.width + sizeof(chunk_t) * 8 - 1) / (sizeof(chunk_t) * 8); + var.depth = part.depth; + var.curr = part.curr; + var.memory = (part.type == debug_item::MEMORY); + ident_lookup[var.curr] = var.ident; + + assert(variables.size() < spool::MAXIMUM_IDENT); + if (part.flags & debug_item::INPUT) + inputs.push_back(variables.size()); + variables.push_back(var); + + writer.write_define(var.ident, item.first, part_index, var.chunks, var.depth); + } + } + writer.write_end(); + streaming = true; + } + + const time &latest_time() { + return timestamp; + } + + const time &advance_time(const time &delta) { + assert(!delta.is_negative()); + timestamp += delta; + return timestamp; + } + + void record_complete() { + assert(streaming); + + writer.write_sample(/*incremental=*/false, pointer++, timestamp); + for (auto var : variables) { + assert(var.ident != 0); + if (!var.memory) + writer.write_change(var.ident, var.chunks, var.curr); + else + for (size_t index = 0; index < var.depth; index++) + writer.write_change(var.ident, var.chunks, &var.curr[var.chunks * index], index); + } + writer.write_end(); + } + + // This function is generic over ModuleT to encourage observer callbacks to be inlined into the commit function. + template + bool record_incremental(ModuleT &module) { + assert(streaming); + + struct { + std::unordered_map *ident_lookup; + spool::writer *writer; + + CXXRTL_ALWAYS_INLINE + 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_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 = { &ident_lookup, &writer }; + + writer.write_sample(/*incremental=*/true, pointer++, timestamp); + for (auto input_index : inputs) { + variable &var = variables.at(input_index); + assert(!var.memory); + writer.write_change(var.ident, var.chunks, var.curr); + } + bool changed = module.commit(record_observer); + writer.write_end(); + return changed; + } + + void flush() { + writer.flush(); + } +}; + +// A CXXRTL player reads samples from a spool, and changes the design state accordingly. To start reading samples, +// a spool must have been initialized: the recorder must have been started and an initial complete sample must have +// been written. +class player { + struct variable { + size_t chunks; + size_t depth; /* == 1 for wires */ + chunk_t *curr; + }; + + spool::reader reader; + std::unordered_map variables; + bool streaming = false; // whether variable definitions have been read + bool initialized = false; // whether a sample has ever been read + spool::pointer_t pointer = 0; + time timestamp; + + std::map> index_by_pointer; + std::map> index_by_timestamp; + + bool peek_sample(spool::pointer_t &pointer, time ×tamp) { + bool incremental; + auto position = reader.position(); + bool success = reader.read_sample(incremental, pointer, timestamp); + reader.rewind(position); + return success; + } + +public: + template + player(Args &&...args) : reader(std::forward(args)...) {} + + void start(module &module) { + debug_items items; + module.debug_info(items); + start(items); + } + + void start(const debug_items &items) { + assert(!streaming); + + reader.read_magic(); + while (true) { + spool::ident_t ident; + std::string name; + size_t part_index; + size_t chunks; + size_t depth; + if (!reader.read_define(ident, name, part_index, chunks, depth)) + break; + assert(variables.count(ident) == 0); + assert(items.count(name) != 0); + assert(part_index < items.count(name)); + + const debug_item &part = items.parts_at(name).at(part_index); + assert(chunks == (part.width + sizeof(chunk_t) * 8 - 1) / (sizeof(chunk_t) * 8)); + assert(depth == part.depth); + + variable &var = variables[ident]; + var.chunks = chunks; + var.depth = depth; + var.curr = part.curr; + } + assert(variables.size() > 0); + streaming = true; + + // Establish the initial state of the design. + initialized = replay(); + assert(initialized); + } + + // Returns the pointer of the current sample. + spool::pointer_t current_pointer() { + assert(initialized); + return pointer; + } + + // Returns the time of the current sample. + const time ¤t_time() { + assert(initialized); + return timestamp; + } + + // Returns `true` if there is a next sample to read, and sets `pointer` to its pointer if there is. + bool get_next_pointer(spool::pointer_t &pointer) { + assert(streaming); + time timestamp; + return peek_sample(pointer, timestamp); + } + + // Returns `true` if there is a next sample to read, and sets `timestamp` to its time if there is. + bool get_next_time(time ×tamp) { + assert(streaming); + uint32_t pointer; + return peek_sample(pointer, timestamp); + } + + // If this function returns `true`, then `current_pointer() == at_pointer`, and the module contains values that + // correspond to this pointer in the replay log. To obtain a valid pointer, call `current_pointer()`; while pointers + // are monotonically increasing for each consecutive sample, using arithmetic operations to create a new pointer is + // not allowed. + bool rewind_to(spool::pointer_t at_pointer) { + assert(initialized); + + // The pointers in the replay log start from one that is greater than `at_pointer`. In this case the pointer will + // never be reached. + assert(index_by_pointer.size() > 0); + if (at_pointer < index_by_pointer.rbegin()->first) + return false; + + // Find the last complete sample whose pointer is less than or equal to `at_pointer`. Note that the comparison + // function used here is `std::greater`, inverting the direction of `lower_bound`. + auto position_it = index_by_pointer.lower_bound(at_pointer); + assert(position_it != index_by_pointer.end()); + reader.rewind(position_it->second); + + // Replay samples until eventually arriving to `at_pointer` or encountering end of file. + while(replay()) { + if (pointer == at_pointer) + return true; + } + return false; + } + + // If this function returns `true`, then `current_time() <= at_or_before_timestamp`, and the module contains values + // that correspond to `current_time()` in the replay log. If `current_time() == at_or_before_timestamp` and there + // are several consecutive samples with the same time, the module contains values that correspond to the first of + // these samples. + bool rewind_to_or_before(const time &at_or_before_timestamp) { + assert(initialized); + + // The timestamps in the replay log start from one that is greater than `at_or_before_timestamp`. In this case + // the timestamp will never be reached. Otherwise, this function will always succeed. + assert(index_by_timestamp.size() > 0); + if (at_or_before_timestamp < index_by_timestamp.rbegin()->first) + return false; + + // Find the last complete sample whose timestamp is less than or equal to `at_or_before_timestamp`. Note that + // the comparison function used here is `std::greater`, inverting the direction of `lower_bound`. + auto position_it = index_by_timestamp.lower_bound(at_or_before_timestamp); + assert(position_it != index_by_timestamp.end()); + reader.rewind(position_it->second); + + // Replay samples until eventually arriving to or past `at_or_before_timestamp` or encountering end of file. + while (replay()) { + if (timestamp == at_or_before_timestamp) + break; + + time next_timestamp; + if (!get_next_time(next_timestamp)) + break; + if (next_timestamp > at_or_before_timestamp) + break; + } + return true; + } + + // If this function returns `true`, then `current_pointer()` and `current_time()` are updated for the next sample + // and the module now contains values that correspond to that sample. If it returns `false`, there was no next sample + // to read. + bool replay() { + assert(streaming); + + bool incremental; + auto position = reader.position(); + if (!reader.read_sample(incremental, pointer, timestamp)) + return false; + + // The very first sample that is read must be a complete sample. This is required for the rewind functions to work. + assert(initialized || !incremental); + + // It is possible (though not very useful) to have several complete samples with the same timestamp in a row. + // Ensure that we associate the timestamp with the position of the first such complete sample. (This condition + // works because the player never jumps over a sample.) + if (!incremental && !index_by_pointer.count(pointer)) { + assert(!index_by_timestamp.count(timestamp)); + index_by_pointer[pointer] = position; + index_by_timestamp[timestamp] = position; + } + + uint32_t header; + spool::ident_t ident; + variable var; + while (reader.read_change_header(header, ident)) { + variable &var = variables.at(ident); + reader.read_change_data(header, var.chunks, var.depth, var.curr); + } + return true; + } +}; + +} + +#endif diff --git a/backends/cxxrtl/runtime/cxxrtl/cxxrtl_time.h b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_time.h new file mode 100644 index 000000000..f37c2b656 --- /dev/null +++ b/backends/cxxrtl/runtime/cxxrtl/cxxrtl_time.h @@ -0,0 +1,231 @@ +/* + * yosys -- Yosys Open SYnthesis Suite + * + * Copyright (C) 2023 Catherine + * + * Permission to use, copy, modify, and/or distribute this software for any + * purpose with or without fee is hereby granted. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + */ + +#ifndef CXXRTL_TIME_H +#define CXXRTL_TIME_H + +#include +#include + +#include + +namespace cxxrtl { + +// A timestamp or a difference in time, stored as a 96-bit number of femtoseconds (10e-15 s). The range and resolution +// of this format can represent any VCD timestamp within approx. ±1255321.2 years, without the need for a timescale. +class time { +public: + static constexpr size_t bits = 96; // 3 chunks + +private: + static constexpr size_t resolution_digits = 15; + + static_assert(sizeof(chunk_t) == 4, "a chunk is expected to be 32-bit"); + static constexpr value resolution = value { + chunk_t(1000000000000000ull & 0xffffffffull), chunk_t(1000000000000000ull >> 32), 0u + }; + + // Signed number of femtoseconds from the beginning of time. + value raw; + +public: + constexpr time() {} + + explicit constexpr time(const value &raw) : raw(raw) {} + explicit operator const value &() const { return raw; } + + static constexpr time maximum() { + return time(value { 0xffffffffu, 0xffffffffu, 0x7fffffffu }); + } + + time(int64_t secs, int64_t femtos) { + value<64> secs_val; + secs_val.set(secs); + value<64> femtos_val; + femtos_val.set(femtos); + raw = secs_val.sext().mul(resolution).add(femtos_val.sext()); + } + + bool is_zero() const { + return raw.is_zero(); + } + + // Extracts the sign of the value. + bool is_negative() const { + return raw.is_neg(); + } + + // Extracts the number of whole seconds. Negative if the value is negative. + int64_t secs() const { + return raw.sdivmod(resolution).first.trunc<64>().get(); + } + + // Extracts the number of femtoseconds in the fractional second. Negative if the value is negative. + int64_t femtos() const { + return raw.sdivmod(resolution).second.trunc<64>().get(); + } + + bool operator==(const time &other) const { + return raw == other.raw; + } + + bool operator!=(const time &other) const { + return raw != other.raw; + } + + bool operator>(const time &other) const { + return other.raw.scmp(raw); + } + + bool operator>=(const time &other) const { + return !raw.scmp(other.raw); + } + + bool operator<(const time &other) const { + return raw.scmp(other.raw); + } + + bool operator<=(const time &other) const { + return !other.raw.scmp(raw); + } + + time operator+(const time &other) const { + return time(raw.add(other.raw)); + } + + time &operator+=(const time &other) { + *this = *this + other; + return *this; + } + + time operator-() const { + return time(raw.neg()); + } + + time operator-(const time &other) const { + return *this + (-other); + } + + time &operator-=(const time &other) { + *this = *this - other; + return *this; + } + + operator std::string() const { + char buf[48]; // x=2**95; len(f"-{x/1_000_000_000_000_000}.{x^1_000_000_000_000_000}") == 48 + int64_t secs = this->secs(); + int64_t femtos = this->femtos(); + snprintf(buf, sizeof(buf), "%s%" PRIi64 ".%015" PRIi64, + is_negative() ? "-" : "", secs >= 0 ? secs : -secs, femtos >= 0 ? femtos : -femtos); + return buf; + } + +#if __cplusplus >= 201603L + [[nodiscard("ignoring parse errors")]] +#endif + bool parse(const std::string &str) { + enum { + parse_sign_opt, + parse_integral, + parse_fractional, + } state = parse_sign_opt; + bool negative = false; + int64_t integral = 0; + int64_t fractional = 0; + size_t frac_digits = 0; + for (auto chr : str) { + switch (state) { + case parse_sign_opt: + state = parse_integral; + if (chr == '+' || chr == '-') { + negative = (chr == '-'); + break; + } + /* fallthrough */ + case parse_integral: + if (chr >= '0' && chr <= '9') { + integral *= 10; + integral += chr - '0'; + } else if (chr == '.') { + state = parse_fractional; + } else { + return false; + } + break; + case parse_fractional: + if (chr >= '0' && chr <= '9' && frac_digits < resolution_digits) { + fractional *= 10; + fractional += chr - '0'; + frac_digits++; + } else { + return false; + } + break; + } + } + if (frac_digits == 0) + return false; + while (frac_digits++ < resolution_digits) + fractional *= 10; + *this = negative ? -time { integral, fractional} : time { integral, fractional }; + return true; + } +}; + +// Out-of-line definition required until C++17. +constexpr value time::resolution; + +std::ostream &operator<<(std::ostream &os, const time &val) { + os << (std::string)val; + return os; +} + +// These literals are (confusingly) compatible with the ones from `std::chrono`: the `std::chrono` literals do not +// have an underscore (e.g. 1ms) and the `cxxrtl::time` literals do (e.g. 1_ms). This syntactic difference is +// a requirement of the C++ standard. Despite being compatible the literals should not be mixed in the same namespace. +namespace time_literals { + +time operator""_s(unsigned long long seconds) { + return time { (int64_t)seconds, 0 }; +} + +time operator""_ms(unsigned long long milliseconds) { + return time { 0, (int64_t)milliseconds * 1000000000000 }; +} + +time operator""_us(unsigned long long microseconds) { + return time { 0, (int64_t)microseconds * 1000000000 }; +} + +time operator""_ns(unsigned long long nanoseconds) { + return time { 0, (int64_t)nanoseconds * 1000000 }; +} + +time operator""_ps(unsigned long long picoseconds) { + return time { 0, (int64_t)picoseconds * 1000 }; +} + +time operator""_fs(unsigned long long femtoseconds) { + return time { 0, (int64_t)femtoseconds }; +} + +}; + +}; + +#endif diff --git a/backends/firrtl/test.sh b/backends/firrtl/test.sh index fe7e3a329..dd675e917 100644 --- a/backends/firrtl/test.sh +++ b/backends/firrtl/test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex cd ../../ diff --git a/backends/simplec/test00.sh b/backends/simplec/test00.sh index ede757273..9895a97e4 100644 --- a/backends/simplec/test00.sh +++ b/backends/simplec/test00.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex ../../yosys -p 'synth -top test; write_simplec -verbose -i8 test00_uut.c' test00_uut.v clang -o test00_tb test00_tb.c diff --git a/backends/smt2/smtbmc.py b/backends/smt2/smtbmc.py index dd3f9ac48..34657be26 100644 --- a/backends/smt2/smtbmc.py +++ b/backends/smt2/smtbmc.py @@ -1327,6 +1327,9 @@ def write_yw_trace(steps, index, allregs=False, filename=None): sig = yw.add_sig(word_path, overlap_start, overlap_end - overlap_start, True) mem_init_values.append((sig, overlap_bits.replace("x", "?"))) + exprs = [] + all_sigs = [] + for i, k in enumerate(steps): step_values = WitnessValues() @@ -1337,8 +1340,15 @@ def write_yw_trace(steps, index, allregs=False, filename=None): else: sigs = seqs + exprs.extend(smt.witness_net_expr(topmod, f"s{k}", sig) for sig in sigs) + + all_sigs.append(sigs) + + bvs = iter(smt.get_list(exprs)) + + for sigs in all_sigs: for sig in sigs: - value = smt.bv2bin(smt.get(smt.witness_net_expr(topmod, f"s{k}", sig))) + value = smt.bv2bin(next(bvs)) step_values[sig["sig"]] = value yw.step(step_values) diff --git a/backends/smt2/smtbmc_incremental.py b/backends/smt2/smtbmc_incremental.py index 2be4fb679..1a2a45703 100644 --- a/backends/smt2/smtbmc_incremental.py +++ b/backends/smt2/smtbmc_incremental.py @@ -194,9 +194,31 @@ class Incremental: return "Bool" + def expr_smtlib(self, expr, smt_out): + self.expr_arg_len(expr, 2) + + smtlib_expr = expr[1] + sort = expr[2] + + if not isinstance(smtlib_expr, str): + raise InteractiveError( + "raw SMT-LIB expression has to be a string, " + f"got {json.dumps(smtlib_expr)}" + ) + + if not isinstance(sort, str): + raise InteractiveError( + f"raw SMT-LIB sort has to be a string, got {json.dumps(sort)}" + ) + + smt_out.append(smtlib_expr) + return sort + def expr_label(self, expr, smt_out): if len(expr) != 3: - raise InteractiveError(f'expected ["!", label, sub_expr], got {expr!r}') + raise InteractiveError( + f'expected ["!", label, sub_expr], got {json.dumps(expr)}' + ) label = expr[1] subexpr = expr[2] @@ -226,6 +248,7 @@ class Incremental: "or": expr_andor, "=": expr_eq, "yw": expr_yw, + "smtlib": expr_smtlib, "!": expr_label, } @@ -251,7 +274,8 @@ class Incremental: ) ): raise InteractiveError( - f"required sort {json.dumps(required_sort)} found sort {json.dumps(sort)}" + f"required sort {json.dumps(required_sort)} " + f"found sort {json.dumps(sort)}" ) return sort raise InteractiveError(f"unknown expression {json.dumps(expr[0])}") @@ -287,6 +311,14 @@ class Incremental: def cmd_check(self, cmd): return smtbmc.smt_check_sat() + def cmd_smtlib(self, cmd): + command = cmd.get("command") + if not isinstance(command, str): + raise InteractiveError( + f"raw SMT-LIB command must be a string, found {json.dumps(command)}" + ) + smtbmc.smt.write(command) + def cmd_design_hierwitness(self, cmd=None): allregs = (cmd is None) or bool(cmd.get("allreges", False)) if self._cached_hierwitness[allregs] is not None: @@ -326,13 +358,17 @@ class Incremental: map_steps = {i: int(j) for i, j in enumerate(steps)} - smtbmc.ywfile_constraints(path, constraints, map_steps=map_steps, skip_x=skip_x) + last_step = smtbmc.ywfile_constraints( + path, constraints, map_steps=map_steps, skip_x=skip_x + ) self._yw_constraints[name] = { map_steps.get(i, i): [smtexpr for cexfile, smtexpr in constraint_list] for i, constraint_list in constraints.items() } + return dict(last_step=last_step) + def cmd_ping(self, cmd): return cmd @@ -344,6 +380,7 @@ class Incremental: "push": cmd_push, "pop": cmd_pop, "check": cmd_check, + "smtlib": cmd_smtlib, "design_hierwitness": cmd_design_hierwitness, "write_yw_trace": cmd_write_yw_trace, "read_yw_trace": cmd_read_yw_trace, diff --git a/backends/smt2/test_cells.sh b/backends/smt2/test_cells.sh index 34adb7af3..3f84d65a2 100644 --- a/backends/smt2/test_cells.sh +++ b/backends/smt2/test_cells.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex diff --git a/backends/smt2/witness.py b/backends/smt2/witness.py index a39500c2d..b7e25851c 100644 --- a/backends/smt2/witness.py +++ b/backends/smt2/witness.py @@ -183,7 +183,8 @@ This requires a Yosys witness AIGER map file as generated by 'write_aiger -ywmap @click.argument("mapfile", type=click.File("r")) @click.argument("output", type=click.File("w")) @click.option("--skip-x", help="Leave input x bits unassigned.", is_flag=True) -def aiw2yw(input, mapfile, output, skip_x): +@click.option("--present-only", help="Only include bits present in at least one time step.", is_flag=True) +def aiw2yw(input, mapfile, output, skip_x, present_only): input_name = input.name click.echo(f"Converting AIGER witness trace {input_name!r} to Yosys witness trace {output.name!r}...") click.echo(f"Using Yosys witness AIGER map file {mapfile.name!r}") @@ -211,16 +212,23 @@ def aiw2yw(input, mapfile, output, skip_x): if not re.match(r'[0]*$', ffline): raise click.ClickException(f"{input_name}: non-default initial state not supported") - outyw = WriteWitness(output, 'yosys-witness aiw2yw') + if not present_only: + outyw = WriteWitness(output, "yosys-witness aiw2yw") - for clock in aiger_map.clocks: - outyw.add_clock(clock["path"], clock["offset"], clock["edge"]) + for clock in aiger_map.clocks: + outyw.add_clock(clock["path"], clock["offset"], clock["edge"]) - for (path, offset), id in aiger_map.sigmap.bit_to_id.items(): - outyw.add_sig(path, offset, init_only=id in aiger_map.init_inputs) + for (path, offset), id in aiger_map.sigmap.bit_to_id.items(): + outyw.add_sig(path, offset, init_only=id in aiger_map.init_inputs) missing = set() + seen = set() + buffered_steps = [] + + skip = "x?" if skip_x else "?" + + t = -1 while True: inline = next(input, None) if inline is None: @@ -232,17 +240,20 @@ def aiw2yw(input, mapfile, output, skip_x): if inline.startswith("#"): continue - if not re.match(r'[01x]*$', ffline): + t += 1 + + if not re.match(r"[01x]*$", inline): raise click.ClickException(f"{input_name}: unexpected data in AIGER witness file") if len(inline) != aiger_map.input_count: raise click.ClickException( - f"{input_name}: {mapfile.name}: number of inputs does not match, " - f"{len(inline)} in witness, {aiger_map.input_count} in map file") + f"{input_name}: {mapfile.name}: number of inputs does not match, " + f"{len(inline)} in witness, {aiger_map.input_count} in map file" + ) values = WitnessValues() for i, v in enumerate(inline): - if outyw.t > 0 and i in aiger_map.init_inputs: + if v in skip or (t > 0 and i in aiger_map.init_inputs): continue try: @@ -250,11 +261,29 @@ def aiw2yw(input, mapfile, output, skip_x): except IndexError: bit = None if bit is None: - missing.insert(i) + missing.add(i) + elif present_only: + seen.add(i) values[bit] = v - outyw.step(values, skip_x=skip_x) + if present_only: + buffered_steps.append(values) + else: + outyw.step(values) + + if present_only: + outyw = WriteWitness(output, "yosys-witness aiw2yw") + + for clock in aiger_map.clocks: + outyw.add_clock(clock["path"], clock["offset"], clock["edge"]) + + for (path, offset), id in aiger_map.sigmap.bit_to_id.items(): + if id in seen: + outyw.add_sig(path, offset, init_only=id in aiger_map.init_inputs) + + for values in buffered_steps: + outyw.step(values) outyw.end_trace() diff --git a/backends/smv/test_cells.sh b/backends/smv/test_cells.sh index 145b9c33b..1347b7044 100644 --- a/backends/smv/test_cells.sh +++ b/backends/smv/test_cells.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index 735672a43..1fa31e31e 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -376,7 +376,7 @@ void dump_sigspec(std::ostream &f, const RTLIL::SigSpec &sig) } } -void dump_attributes(std::ostream &f, std::string indent, dict &attributes, char term = '\n', bool modattr = false, bool regattr = false, bool as_comment = false) +void dump_attributes(std::ostream &f, std::string indent, dict &attributes, std::string term = "\n", bool modattr = false, bool regattr = false, bool as_comment = false) { if (noattr) return; @@ -392,13 +392,13 @@ void dump_attributes(std::ostream &f, std::string indent, dictsecond, -1, 0, false, as_comment); - f << stringf(" %s%c", as_comment ? "*/" : "*)", term); + f << stringf(" %s%s", as_comment ? "*/" : "*)", term.c_str()); } } void dump_wire(std::ostream &f, std::string indent, RTLIL::Wire *wire) { - dump_attributes(f, indent, wire->attributes, '\n', /*modattr=*/false, /*regattr=*/reg_wires.count(wire->name)); + dump_attributes(f, indent, wire->attributes, "\n", /*modattr=*/false, /*regattr=*/reg_wires.count(wire->name)); #if 0 if (wire->port_input && !wire->port_output) f << stringf("%s" "input %s", indent.c_str(), reg_wires.count(wire->name) ? "reg " : ""); @@ -989,7 +989,7 @@ void dump_cell_expr_uniop(std::ostream &f, std::string indent, RTLIL::Cell *cell f << stringf("%s" "assign ", indent.c_str()); dump_sigspec(f, cell->getPort(ID::Y)); f << stringf(" = %s ", op.c_str()); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); dump_cell_expr_port(f, cell, "A", true); f << stringf(";\n"); } @@ -1001,7 +1001,7 @@ void dump_cell_expr_binop(std::ostream &f, std::string indent, RTLIL::Cell *cell f << stringf(" = "); dump_cell_expr_port(f, cell, "A", true); f << stringf(" %s ", op.c_str()); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); dump_cell_expr_port(f, cell, "B", true); f << stringf(";\n"); } @@ -1048,7 +1048,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) dump_sigspec(f, cell->getPort(ID::Y)); f << stringf(" = "); f << stringf("~"); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); dump_cell_expr_port(f, cell, "A", false); f << stringf(";\n"); return true; @@ -1068,7 +1068,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf("|"); if (cell->type.in(ID($_XOR_), ID($_XNOR_))) f << stringf("^"); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); f << stringf(" "); if (cell->type.in(ID($_ANDNOT_), ID($_ORNOT_))) f << stringf("~("); @@ -1085,7 +1085,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf(" = "); dump_cell_expr_port(f, cell, "S", false); f << stringf(" ? "); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); dump_cell_expr_port(f, cell, "B", false); f << stringf(" : "); dump_cell_expr_port(f, cell, "A", false); @@ -1099,7 +1099,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf(" = !("); dump_cell_expr_port(f, cell, "S", false); f << stringf(" ? "); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); dump_cell_expr_port(f, cell, "B", false); f << stringf(" : "); dump_cell_expr_port(f, cell, "A", false); @@ -1115,7 +1115,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf(cell->type == ID($_AOI3_) ? " & " : " | "); dump_cell_expr_port(f, cell, "B", false); f << stringf(cell->type == ID($_AOI3_) ? ") |" : ") &"); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); f << stringf(" "); dump_cell_expr_port(f, cell, "C", false); f << stringf(");\n"); @@ -1130,7 +1130,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf(cell->type == ID($_AOI4_) ? " & " : " | "); dump_cell_expr_port(f, cell, "B", false); f << stringf(cell->type == ID($_AOI4_) ? ") |" : ") &"); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); f << stringf(" ("); dump_cell_expr_port(f, cell, "C", false); f << stringf(cell->type == ID($_AOI4_) ? " & " : " | "); @@ -1232,7 +1232,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf("%s" "assign ", indent.c_str()); dump_sigspec(f, cell->getPort(ID::Y)); f << stringf(" = $signed(%s) / ", buf_num.c_str()); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); f << stringf("$signed(%s);\n", buf_b.c_str()); return true; } else { @@ -1255,7 +1255,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf("%s" "wire [%d:0] %s = ", indent.c_str(), GetSize(cell->getPort(ID::A))-1, temp_id.c_str()); dump_cell_expr_port(f, cell, "A", true); f << stringf(" %% "); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); dump_cell_expr_port(f, cell, "B", true); f << stringf(";\n"); @@ -1330,7 +1330,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf(" = "); dump_sigspec(f, cell->getPort(ID::S)); f << stringf(" ? "); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); dump_sigspec(f, cell->getPort(ID::B)); f << stringf(" : "); dump_sigspec(f, cell->getPort(ID::A)); @@ -1439,7 +1439,7 @@ bool dump_cell_expr(std::ostream &f, std::string indent, RTLIL::Cell *cell) f << stringf(" = "); dump_const(f, cell->parameters.at(ID::LUT)); f << stringf(" >> "); - dump_attributes(f, "", cell->attributes, ' '); + dump_attributes(f, "", cell->attributes, " "); dump_sigspec(f, cell->getPort(ID::A)); f << stringf(";\n"); return true; @@ -1830,7 +1830,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 +1896,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(); @@ -1971,6 +1976,56 @@ void dump_case_body(std::ostream &f, std::string indent, RTLIL::CaseRule *cs, bo f << stringf("%s" "end\n", indent.c_str()); } +bool dump_proc_switch_ifelse(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw) +{ + for (auto it = sw->cases.begin(); it != sw->cases.end(); ++it) { + if ((*it)->compare.size() == 0) { + break; + } else if ((*it)->compare.size() == 1) { + int case_index = it - sw->cases.begin(); + SigSpec compare = (*it)->compare.at(0); + if (case_index >= compare.size()) + return false; + if (compare[case_index] != State::S1) + return false; + for (int bit_index = 0; bit_index < compare.size(); bit_index++) + if (bit_index != case_index && compare[bit_index] != State::Sa) + return false; + } else { + return false; + } + } + + 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()) { + 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"); + } + dump_case_body(f, indent, *it); + if ((*it)->compare.empty()) + break; + } + return true; +} + void dump_proc_switch(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw) { if (sw->signal.size() == 0) { @@ -1983,17 +2038,18 @@ void dump_proc_switch(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw return; } + if (dump_proc_switch_ifelse(f, indent, sw)) + return; + dump_attributes(f, indent, sw->attributes); f << stringf("%s" "casez (", indent.c_str()); dump_sigspec(f, sw->signal); f << stringf(")\n"); - bool got_default = false; for (auto it = sw->cases.begin(); it != sw->cases.end(); ++it) { - dump_attributes(f, indent + " ", (*it)->attributes, '\n', /*modattr=*/false, /*regattr=*/false, /*as_comment=*/true); + bool got_default = false; + dump_attributes(f, indent + " ", (*it)->attributes, "\n", /*modattr=*/false, /*regattr=*/false, /*as_comment=*/true); if ((*it)->compare.size() == 0) { - if (got_default) - continue; f << stringf("%s default", indent.c_str()); got_default = true; } else { @@ -2006,6 +2062,14 @@ void dump_proc_switch(std::ostream &f, std::string indent, RTLIL::SwitchRule *sw } f << stringf(":\n"); dump_case_body(f, indent + " ", *it); + + if (got_default) { + // If we followed up the default with more cases the Verilog + // semantics would be to match those *before* the default, but + // the RTLIL semantics are to match those *after* the default + // (so they can never be selected). Exit now. + break; + } } if (sw->cases.empty()) { @@ -2167,7 +2231,7 @@ void dump_module(std::ostream &f, std::string indent, RTLIL::Module *module) } } - dump_attributes(f, indent, module->attributes, '\n', /*modattr=*/true); + dump_attributes(f, indent, module->attributes, "\n", /*modattr=*/true); f << stringf("%s" "module %s(", indent.c_str(), id(module->name, false).c_str()); bool keep_running = true; int cnt = 0; diff --git a/docs/source/getting_started/example_synth.rst b/docs/source/getting_started/example_synth.rst index db7511c76..edeb2c118 100644 --- a/docs/source/getting_started/example_synth.rst +++ b/docs/source/getting_started/example_synth.rst @@ -127,8 +127,6 @@ Our ``addr_gen`` circuit now looks like this: ``addr_gen`` module after :cmd:ref:`hierarchy` -.. TODO:: pending https://github.com/YosysHQ/yosys/pull/4133 - Simple operations like ``addr + 1`` and ``addr == MAX_DATA-1`` can be extracted from our ``always @`` block in :ref:`addr_gen-v`. This gives us the highlighted ``$add`` and ``$eq`` cells we see. But control logic (like the ``if .. else``) diff --git a/docs/source/yosys_internals/formats/cell_library.rst b/docs/source/yosys_internals/formats/cell_library.rst index 11f55dabc..bbbec836c 100644 --- a/docs/source/yosys_internals/formats/cell_library.rst +++ b/docs/source/yosys_internals/formats/cell_library.rst @@ -122,7 +122,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 ======================= ============= ======================= ========= @@ -664,6 +664,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 34e624993..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; @@ -850,6 +850,25 @@ AstNode *AstNode::mkconst_str(const std::string &str) return node; } +// create a temporary register +AstNode *AstNode::mktemp_logic(const std::string &name, AstNode *mod, bool nosync, int range_left, int range_right, bool is_signed) +{ + AstNode *wire = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(range_left, true), mkconst_int(range_right, true))); + wire->str = stringf("%s%s:%d$%d", name.c_str(), RTLIL::encode_filename(filename).c_str(), location.first_line, autoidx++); + if (nosync) + wire->set_attribute(ID::nosync, AstNode::mkconst_int(1, false)); + wire->is_signed = is_signed; + wire->is_logic = true; + mod->children.push_back(wire); + while (wire->simplify(true, 1, -1, false)) { } + + AstNode *ident = new AstNode(AST_IDENTIFIER); + ident->str = wire->str; + ident->id2ast = wire; + + return ident; +} + bool AstNode::bits_only_01() const { for (auto bit : bits) @@ -1301,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 f789e930b..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(); @@ -321,6 +321,9 @@ namespace AST static AstNode *mkconst_str(const std::vector &v); static AstNode *mkconst_str(const std::string &str); + // helper function to create an AST node for a temporary register + AstNode *mktemp_logic(const std::string &name, AstNode *mod, bool nosync, int range_left, int range_right, bool is_signed); + // helper function for creating sign-extended const objects RTLIL::Const bitsAsConst(int width, bool is_signed); RTLIL::Const bitsAsConst(int width = -1); @@ -373,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 @@ -429,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 2a500b56b..fa079f3e3 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -35,12 +35,25 @@ #include #include #include +// For std::gcd in C++17 +// #include YOSYS_NAMESPACE_BEGIN using namespace AST; using namespace AST_INTERNAL; +// gcd computed by Euclidian division. +// To be replaced by C++17 std::gcd +template I gcd(I a, I b) { + while (b != 0) { + I tmp = b; + b = a%b; + a = tmp; + } + return std::abs(a); +} + void AstNode::set_in_lvalue_flag(bool flag, bool no_descend) { if (flag != in_lvalue_from_above) { @@ -132,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]; @@ -156,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); } @@ -204,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() ); } @@ -226,17 +242,6 @@ void AstNode::annotateTypedEnums(AstNode *template_node) } } -static bool name_has_dot(const std::string &name, std::string &struct_name) -{ - // check if plausible struct member name \sss.mmm - std::string::size_type pos; - if (name.substr(0, 1) == "\\" && (pos = name.find('.', 0)) != std::string::npos) { - struct_name = name.substr(0, pos); - return true; - } - return false; -} - static AstNode *make_range(int left, int right, bool is_signed = false) { // generate a pre-validated range node for a fixed signal range. @@ -1053,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.) @@ -2185,11 +2191,24 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin if (type == AST_IDENTIFIER && !basic_prep) { // check if a plausible struct member sss.mmmm - std::string sname; - if (name_has_dot(str, sname)) { - if (current_scope.count(str) > 0) { - auto item_node = current_scope[str]; - if (item_node->type == AST_STRUCT_ITEM || item_node->type == AST_STRUCT || item_node->type == AST_UNION) { + if (!str.empty() && str[0] == '\\' && current_scope.count(str)) { + auto item_node = current_scope[str]; + if (item_node->type == AST_STRUCT_ITEM || item_node->type == AST_STRUCT || item_node->type == AST_UNION) { + // Traverse any hierarchical path until the full name for the referenced struct/union is found. + std::string sname; + bool found_sname = false; + for (std::string::size_type pos = 0; (pos = str.find('.', pos)) != std::string::npos; pos++) { + sname = str.substr(0, pos); + if (current_scope.count(sname)) { + auto stype = current_scope[sname]->type; + if (stype == AST_WIRE || stype == AST_PARAMETER || stype == AST_LOCALPARAM) { + found_sname = true; + break; + } + } + } + + if (found_sname) { // structure member, rewrite this node to reference the packed struct wire auto range = make_struct_member_range(this, item_node); newNode = new AstNode(AST_IDENTIFIER, range); @@ -2816,27 +2835,12 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin if (!children[0]->id2ast->range_valid) goto skip_dynamic_range_lvalue_expansion; - int source_width = children[0]->id2ast->range_left - children[0]->id2ast->range_right + 1; - int source_offset = children[0]->id2ast->range_right; - int result_width = 1; - int stride = 1; AST::AstNode *member_node = get_struct_member(children[0]); - if (member_node) { - // Clamp chunk to range of member within struct/union. - log_assert(!source_offset && !children[0]->id2ast->range_swapped); - source_width = member_node->range_left - member_node->range_right + 1; - - // When the (* nowrshmsk *) attribute is set, a CASE block is generated below - // to select the indexed bit slice. When a multirange array is indexed, the - // start of each possible slice is separated by the bit stride of the last - // index dimension, and we can optimize the CASE block accordingly. - // The dimension of the original array expression is saved in the 'integer' field. - int dims = children[0]->integer; - stride = source_width; - for (int dim = 0; dim < dims; dim++) { - stride /= get_struct_range_width(member_node, dim); - } - } + int wire_width = member_node ? + member_node->range_left - member_node->range_right + 1 : + children[0]->id2ast->range_left - children[0]->id2ast->range_right + 1; + int wire_offset = children[0]->id2ast->range_right; + int result_width = 1; AstNode *shift_expr = NULL; AstNode *range = children[0]->children[0]; @@ -2849,133 +2853,184 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin else shift_expr = range->children[0]->clone(); - bool use_case_method = false; - - if (children[0]->id2ast->attributes.count(ID::nowrshmsk)) { - AstNode *node = children[0]->id2ast->attributes.at(ID::nowrshmsk); - while (node->simplify(true, stage, -1, false)) { } - if (node->type != AST_CONSTANT) - input_error("Non-constant value for `nowrshmsk' attribute on `%s'!\n", children[0]->id2ast->str.c_str()); - if (node->asAttrConst().as_bool()) - use_case_method = true; - } + bool use_case_method = children[0]->id2ast->get_bool_attribute(ID::nowrshmsk); if (!use_case_method && current_always->detect_latch(children[0]->str)) use_case_method = true; - if (use_case_method) - { + if (use_case_method) { // big case block + int stride = 1; + long long bitno_div = stride; + + int case_width_hint; + bool case_sign_hint; + shift_expr->detectSignWidth(case_width_hint, case_sign_hint); + int max_width = case_width_hint; + + if (member_node) { // Member in packed struct/union + // Clamp chunk to range of member within struct/union. + log_assert(!wire_offset && !children[0]->id2ast->range_swapped); + + // When the (* nowrshmsk *) attribute is set, a CASE block is generated below + // to select the indexed bit slice. When a multirange array is indexed, the + // start of each possible slice is separated by the bit stride of the last + // index dimension, and we can optimize the CASE block accordingly. + // The dimension of the original array expression is saved in the 'integer' field. + int dims = children[0]->integer; + stride = wire_width; + for (int dim = 0; dim < dims; dim++) { + stride /= get_struct_range_width(member_node, dim); + } + bitno_div = stride; + } else { + // Extract (index)*(width) from non_opt_range pattern ((@selfsz@((index)*(width)))+(0)). + AstNode *lsb_expr = + shift_expr->type == AST_ADD && shift_expr->children[0]->type == AST_SELFSZ && + shift_expr->children[1]->type == AST_CONSTANT && shift_expr->children[1]->integer == 0 ? + shift_expr->children[0]->children[0] : + shift_expr; + + // Extract stride from indexing of two-dimensional packed arrays and + // variable slices on the form dst[i*stride +: width] = src. + if (lsb_expr->type == AST_MUL && + (lsb_expr->children[0]->type == AST_CONSTANT || + lsb_expr->children[1]->type == AST_CONSTANT)) + { + int stride_ix = lsb_expr->children[1]->type == AST_CONSTANT; + stride = (int)lsb_expr->children[stride_ix]->integer; + bitno_div = stride != 0 ? stride : 1; + + // Check whether i*stride can overflow. + int i_width; + bool i_sign; + lsb_expr->children[1 - stride_ix]->detectSignWidth(i_width, i_sign); + int stride_width; + bool stride_sign; + lsb_expr->children[stride_ix]->detectSignWidth(stride_width, stride_sign); + max_width = std::max(i_width, stride_width); + // Stride width calculated from actual stride value. + stride_width = std::ceil(std::log2(std::abs(stride))); + + if (i_width + stride_width > max_width) { + // For (truncated) i*stride to be within the range of dst, the following must hold: + // i*stride ≡ bitno (mod shift_mod), i.e. + // i*stride = k*shift_mod + bitno + // + // The Diophantine equation on the form ax + by = c: + // stride*i - shift_mod*k = bitno + // has solutions iff c is a multiple of d = gcd(a, b), i.e. + // bitno mod gcd(stride, shift_mod) = 0 + // + // long long is at least 64 bits in C++11 + long long shift_mod = 1ll << (max_width - case_sign_hint); + // std::gcd requires C++17 + // bitno_div = std::gcd(stride, shift_mod); + bitno_div = gcd((long long)stride, shift_mod); + } + } + } + + // long long is at least 64 bits in C++11 + long long max_offset = (1ll << (max_width - case_sign_hint)) - 1; + long long min_offset = case_sign_hint ? -(1ll << (max_width - 1)) : 0; + + // A temporary register holds the result of the (possibly complex) rvalue expression, + // avoiding repetition in each AST_COND below. + int rvalue_width; + bool rvalue_sign; + children[1]->detectSignWidth(rvalue_width, rvalue_sign); + AstNode *rvalue = mktemp_logic("$bitselwrite$rvalue$", current_ast_mod, true, rvalue_width - 1, 0, rvalue_sign); + AstNode *caseNode = new AstNode(AST_CASE, shift_expr); + newNode = new AstNode(AST_BLOCK, + new AstNode(AST_ASSIGN_EQ, rvalue, children[1]->clone()), + caseNode); + did_something = true; - newNode = new AstNode(AST_CASE, shift_expr); - for (int i = 0; i < source_width; i += stride) { - int start_bit = source_offset + i; - int end_bit = std::min(start_bit+result_width,source_width) - 1; - AstNode *cond = new AstNode(AST_COND, mkconst_int(start_bit, true)); + for (int i = 1 - result_width; i < wire_width; i++) { + // Out of range indexes are handled in genrtlil.cc + int start_bit = wire_offset + i; + int end_bit = start_bit + result_width - 1; + // Check whether the current index can be generated by shift_expr. + if (start_bit < min_offset || start_bit > max_offset) + continue; + if (start_bit%bitno_div != 0 || (stride == 0 && start_bit != 0)) + continue; + + AstNode *cond = new AstNode(AST_COND, mkconst_int(start_bit, case_sign_hint, max_width)); AstNode *lvalue = children[0]->clone(); lvalue->delete_children(); if (member_node) lvalue->set_attribute(ID::wiretype, member_node->clone()); lvalue->children.push_back(new AstNode(AST_RANGE, mkconst_int(end_bit, true), mkconst_int(start_bit, true))); - cond->children.push_back(new AstNode(AST_BLOCK, new AstNode(type, lvalue, children[1]->clone()))); - newNode->children.push_back(cond); + cond->children.push_back(new AstNode(AST_BLOCK, new AstNode(type, lvalue, rvalue->clone()))); + caseNode->children.push_back(cond); } - } - else - { - // mask and shift operations, disabled for now - - AstNode *wire_mask = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(source_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(source_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); + } else { + // mask and shift operations + // 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 = source_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(source_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); } @@ -4681,6 +4736,8 @@ void AstNode::expand_genblock(const std::string &prefix) switch (child->type) { case AST_WIRE: case AST_MEMORY: + case AST_STRUCT: + case AST_UNION: case AST_PARAMETER: case AST_LOCALPARAM: case AST_FUNCTION: 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/hashlib.h b/kernel/hashlib.h index 47aba71a8..25aa94b80 100644 --- a/kernel/hashlib.h +++ b/kernel/hashlib.h @@ -420,7 +420,7 @@ public: operator const_iterator() const { return const_iterator(ptr, index); } }; - dict() + constexpr dict() { } @@ -855,7 +855,7 @@ public: operator const_iterator() const { return const_iterator(ptr, index); } }; - pool() + constexpr pool() { } @@ -1062,6 +1062,10 @@ public: const K *operator->() const { return &container[index]; } }; + constexpr idict() + { + } + int operator()(const K &key) { int hash = database.do_hash(key); @@ -1132,6 +1136,10 @@ class mfp public: typedef typename idict::const_iterator const_iterator; + constexpr mfp() + { + } + int operator()(const K &key) const { int i = database(key); diff --git a/libs/bigint/run-testsuite b/libs/bigint/run-testsuite index ff7372916..8436ebda0 100755 --- a/libs/bigint/run-testsuite +++ b/libs/bigint/run-testsuite @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash bad= 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/equiv/equiv_simple.cc b/passes/equiv/equiv_simple.cc index 7621341a7..2c9d82914 100644 --- a/passes/equiv/equiv_simple.cc +++ b/passes/equiv/equiv_simple.cc @@ -60,7 +60,7 @@ struct EquivSimpleWorker for (auto &conn : cell->connections()) if (yosys_celltypes.cell_input(cell->type, conn.first)) for (auto bit : sigmap(conn.second)) { - if (cell->type.in(ID($dff), ID($_DFF_P_), ID($_DFF_N_), ID($ff), ID($_FF_))) { + if (RTLIL::builtin_ff_cell_types().count(cell->type)) { if (!conn.first.in(ID::CLK, ID::C)) next_seed.insert(bit); } else @@ -133,11 +133,9 @@ struct EquivSimpleWorker for (auto bit_a : seed_a) find_input_cone(next_seed_a, full_cells_cone_a, full_bits_cone_a, no_stop_cells, no_stop_bits, nullptr, bit_a); - next_seed_a.clear(); for (auto bit_b : seed_b) find_input_cone(next_seed_b, full_cells_cone_b, full_bits_cone_b, no_stop_cells, no_stop_bits, nullptr, bit_b); - next_seed_b.clear(); pool short_cells_cone_a, short_cells_cone_b; pool short_bits_cone_a, short_bits_cone_b; @@ -145,10 +143,12 @@ struct EquivSimpleWorker if (short_cones) { + next_seed_a.clear(); for (auto bit_a : seed_a) find_input_cone(next_seed_a, short_cells_cone_a, short_bits_cone_a, full_cells_cone_b, full_bits_cone_b, &input_bits, bit_a); next_seed_a.swap(seed_a); + next_seed_b.clear(); for (auto bit_b : seed_b) find_input_cone(next_seed_b, short_cells_cone_b, short_bits_cone_b, full_cells_cone_a, full_bits_cone_a, &input_bits, bit_b); next_seed_b.swap(seed_b); @@ -364,7 +364,7 @@ struct EquivSimplePass : public Pass { unproven_cells_counter, GetSize(unproven_equiv_cells), log_id(module)); for (auto cell : module->cells()) { - if (!ct.cell_known(cell->type) && !cell->type.in(ID($dff), ID($_DFF_P_), ID($_DFF_N_), ID($ff), ID($_FF_))) + if (!ct.cell_known(cell->type)) continue; for (auto &conn : cell->connections()) if (yosys_celltypes.cell_output(cell->type, conn.first)) 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..542eb5c18 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; } } @@ -840,13 +854,14 @@ 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; - if (cell->getParam(ID::TRG_ENABLE).as_bool()) { + if (trg.size() > 0 && trg_en) { Const trg_pol = cell->getParam(ID::TRG_POLARITY); for (int i = 0; i < trg.size(); i++) { bool pol = trg_pol[i] == State::S1; @@ -856,7 +871,12 @@ struct SimInstance 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; } else { + // always @(*) $print if (args != print.past_args || en != print.past_en) triggered = true; } @@ -870,12 +890,14 @@ struct SimInstance std::string rendered = print.fmt.render(); log("%s", rendered.c_str()); + shared->display_output.emplace_back(shared->step, this, cell, rendered); } update_print: print.past_trg = trg; print.past_en = en; print.past_args = args; + print.initial_done = true; } if (check_assertions) @@ -2055,6 +2077,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_map.v b/techlibs/gowin/brams_map.v index 7ffc91bac..5e1299c28 100644 --- a/techlibs/gowin/brams_map.v +++ b/techlibs/gowin/brams_map.v @@ -131,7 +131,7 @@ if (PORT_A_WIDTH < 9) begin .CE(PORT_A_CLK_EN), .WRE(WRE), .RESET(RST), - .OCE(1'b0), + .OCE(1'b1), .AD(AD), .DI(DI), .DO(DO), @@ -157,7 +157,7 @@ end else begin .CE(PORT_A_CLK_EN), .WRE(WRE), .RESET(RST), - .OCE(1'b0), + .OCE(1'b1), .AD(AD), .DI(DI), .DO(DO), @@ -224,7 +224,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,16 +232,18 @@ 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), .RESETA(RSTA), - .OCEA(1'b0), + .OCEA(1'b1), .ADA(ADA), .DIA(DIA), .DOA(DOA), @@ -250,7 +252,7 @@ if (PORT_A_WIDTH < 9 || PORT_B_WIDTH < 9) begin .CEB(PORT_B_CLK_EN), .WREB(WREB), .RESETB(RSTB), - .OCEB(1'b0), + .OCEB(1'b1), .ADB(ADB), .DIB(DIB), .DOB(DOB), @@ -266,7 +268,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,16 +276,18 @@ 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), .RESETA(RSTA), - .OCEA(1'b0), + .OCEA(1'b1), .ADA(ADA), .DIA(DIA), .DOA(DOA), @@ -292,7 +296,7 @@ end else begin .CEB(PORT_B_CLK_EN), .WREB(WREB), .RESETB(RSTB), - .OCEB(1'b0), + .OCEB(1'b1), .ADB(ADB), .DIB(DIB), .DOB(DOB), @@ -344,28 +348,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), .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 +381,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), .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/gowin/synth_gowin.cc b/techlibs/gowin/synth_gowin.cc index 302ba76e5..85022c1cf 100644 --- a/techlibs/gowin/synth_gowin.cc +++ b/techlibs/gowin/synth_gowin.cc @@ -130,7 +130,6 @@ struct SynthGowinPass : public ScriptPass } if (args[argidx] == "-json" && argidx+1 < args.size()) { json_file = args[++argidx]; - nobram = true; continue; } if (args[argidx] == "-run" && argidx+1 < args.size()) { 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/techlibs/ice40/tests/test_bram.sh b/techlibs/ice40/tests/test_bram.sh index d4d641a9c..5c30db644 100644 --- a/techlibs/ice40/tests/test_bram.sh +++ b/techlibs/ice40/tests/test_bram.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex diff --git a/techlibs/ice40/tests/test_dsp_map.sh b/techlibs/ice40/tests/test_dsp_map.sh index 3f7f134e4..8523a4676 100644 --- a/techlibs/ice40/tests/test_dsp_map.sh +++ b/techlibs/ice40/tests/test_dsp_map.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex for iter in {1..100} diff --git a/techlibs/ice40/tests/test_dsp_model.sh b/techlibs/ice40/tests/test_dsp_model.sh index 1e564d1b2..c79456f70 100644 --- a/techlibs/ice40/tests/test_dsp_model.sh +++ b/techlibs/ice40/tests/test_dsp_model.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex sed 's/SB_MAC16/SB_MAC16_UUT/; /SB_MAC16_UUT/,/endmodule/ p; d;' < ../cells_sim.v > test_dsp_model_uut.v if [ ! -f "test_dsp_model_ref.v" ]; then diff --git a/techlibs/ice40/tests/test_ffs.sh b/techlibs/ice40/tests/test_ffs.sh index ff79ec534..438629c3e 100644 --- a/techlibs/ice40/tests/test_ffs.sh +++ b/techlibs/ice40/tests/test_ffs.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex for CLKPOL in 0 1; do for ENABLE_EN in 0 1; do diff --git a/techlibs/lattice/brams_8kc.txt b/techlibs/lattice/brams_8kc.txt index 3afbeda07..f254c46aa 100644 --- a/techlibs/lattice/brams_8kc.txt +++ b/techlibs/lattice/brams_8kc.txt @@ -32,6 +32,10 @@ ram block $__PDPW8KC_ { cost 64; init no_undef; port sr "R" { + # width 2 cannot be supported because of quirks + # of the primitive, and memlib requires us to + # remove width 1 as well + width 4 9 18; clock posedge; clken; option "RESETMODE" "SYNC" { diff --git a/techlibs/lattice/brams_map_8kc.v b/techlibs/lattice/brams_map_8kc.v index 6783e5b29..b57250fc7 100644 --- a/techlibs/lattice/brams_map_8kc.v +++ b/techlibs/lattice/brams_map_8kc.v @@ -38,8 +38,20 @@ endfunction wire [8:0] DOA; wire [8:0] DOB; -wire [8:0] DIA = PORT_A_WR_DATA; -wire [8:0] DIB = PORT_B_WR_DATA; +wire [8:0] DIA; +wire [8:0] DIB; + +case(PORT_A_WIDTH) + 1: assign DIA = {7'bx, PORT_A_WR_DATA[0], 1'bx}; + 2: assign DIA = {3'bx, PORT_A_WR_DATA[1], 2'bx, PORT_A_WR_DATA[0], 2'bx}; + default: assign DIA = PORT_A_WR_DATA; +endcase + +case(PORT_B_WIDTH) + 1: assign DIB = {7'bx, PORT_B_WR_DATA[0], 1'bx}; + 2: assign DIB = {3'bx, PORT_B_WR_DATA[1], 2'bx, PORT_B_WR_DATA[0], 2'bx}; + default: assign DIB = PORT_B_WR_DATA; +endcase assign PORT_A_RD_DATA = DOA; assign PORT_B_RD_DATA = DOB; diff --git a/techlibs/quicklogic/ql_bram_merge.cc b/techlibs/quicklogic/ql_bram_merge.cc index 1098bc8f6..bed1ba572 100644 --- a/techlibs/quicklogic/ql_bram_merge.cc +++ b/techlibs/quicklogic/ql_bram_merge.cc @@ -31,9 +31,6 @@ PRIVATE_NAMESPACE_BEGIN struct QlBramMergeWorker { - const RTLIL::IdString split_cell_type = ID($__QLF_TDP36K); - const RTLIL::IdString merged_cell_type = ID($__QLF_TDP36K_MERGED); - // can be used to record parameter values that have to match on both sides typedef dict MergeableGroupKeyType; @@ -42,6 +39,8 @@ struct QlBramMergeWorker { QlBramMergeWorker(RTLIL::Module* module) : module(module) { + const RTLIL::IdString split_cell_type = ID($__QLF_TDP36K); + for (RTLIL::Cell* cell : module->selected_cells()) { if(cell->type != split_cell_type) continue; @@ -125,6 +124,7 @@ struct QlBramMergeWorker { void merge_brams(RTLIL::Cell* bram1, RTLIL::Cell* bram2) { + const RTLIL::IdString merged_cell_type = ID($__QLF_TDP36K_MERGED); // Create the new cell RTLIL::Cell* merged = module->addCell(NEW_ID, merged_cell_type); diff --git a/techlibs/quicklogic/ql_dsp_io_regs.cc b/techlibs/quicklogic/ql_dsp_io_regs.cc index 523c86e73..ecf163dbf 100644 --- a/techlibs/quicklogic/ql_dsp_io_regs.cc +++ b/techlibs/quicklogic/ql_dsp_io_regs.cc @@ -30,10 +30,6 @@ PRIVATE_NAMESPACE_BEGIN // ============================================================================ struct QlDspIORegs : public Pass { - const std::vector ports2del_mult = {ID(load_acc), ID(subtract), ID(acc_fir), ID(dly_b), - ID(saturate_enable), ID(shift_right), ID(round)}; - const std::vector ports2del_mult_acc = {ID(acc_fir), ID(dly_b)}; - SigMap sigmap; // .......................................... @@ -67,6 +63,11 @@ struct QlDspIORegs : public Pass { void ql_dsp_io_regs_pass(RTLIL::Module *module) { + static const std::vector ports2del_mult = {ID(load_acc), ID(subtract), ID(acc_fir), ID(dly_b), + ID(saturate_enable), ID(shift_right), ID(round)}; + static const std::vector ports2del_mult_acc = {ID(acc_fir), ID(dly_b)}; + + sigmap.set(module); for (auto cell : module->cells()) { diff --git a/techlibs/quicklogic/ql_dsp_simd.cc b/techlibs/quicklogic/ql_dsp_simd.cc index 153f3995f..9df979c33 100644 --- a/techlibs/quicklogic/ql_dsp_simd.cc +++ b/techlibs/quicklogic/ql_dsp_simd.cc @@ -60,43 +60,11 @@ struct QlDspSimdPass : public Pass { // .......................................... - // DSP control and config ports to consider and how to map them to ports - // of the target DSP cell - const std::vector> m_DspCfgPorts = { - std::make_pair(ID(clock_i), ID(clk)), - std::make_pair(ID(reset_i), ID(reset)), - std::make_pair(ID(feedback_i), ID(feedback)), - std::make_pair(ID(load_acc_i), ID(load_acc)), - std::make_pair(ID(unsigned_a_i), ID(unsigned_a)), - std::make_pair(ID(unsigned_b_i), ID(unsigned_b)), - std::make_pair(ID(subtract_i), ID(subtract)), - std::make_pair(ID(output_select_i), ID(output_select)), - std::make_pair(ID(saturate_enable_i), ID(saturate_enable)), - std::make_pair(ID(shift_right_i), ID(shift_right)), - std::make_pair(ID(round_i), ID(round)), - std::make_pair(ID(register_inputs_i), ID(register_inputs)) - }; - const int m_ModeBitsSize = 80; - // DSP data ports and how to map them to ports of the target DSP cell - const std::vector> m_DspDataPorts = { - std::make_pair(ID(a_i), ID(a)), - std::make_pair(ID(b_i), ID(b)), - std::make_pair(ID(acc_fir_i), ID(acc_fir)), - std::make_pair(ID(z_o), ID(z)), - std::make_pair(ID(dly_b_o), ID(dly_b)) - }; - // DSP parameters const std::vector m_DspParams = {"COEFF_3", "COEFF_2", "COEFF_1", "COEFF_0"}; - // Source DSP cell type (SISD) - const IdString m_SisdDspType = ID(dsp_t1_10x9x32); - - // Target DSP cell types for the SIMD mode - const IdString m_SimdDspType = ID(QL_DSP2); - /// Temporary SigBit to SigBit helper map. SigMap sigmap; @@ -106,6 +74,38 @@ struct QlDspSimdPass : public Pass { { log_header(a_Design, "Executing QL_DSP_SIMD pass.\n"); + // DSP control and config ports to consider and how to map them to ports + // of the target DSP cell + static const std::vector> m_DspCfgPorts = { + std::make_pair(ID(clock_i), ID(clk)), + std::make_pair(ID(reset_i), ID(reset)), + std::make_pair(ID(feedback_i), ID(feedback)), + std::make_pair(ID(load_acc_i), ID(load_acc)), + std::make_pair(ID(unsigned_a_i), ID(unsigned_a)), + std::make_pair(ID(unsigned_b_i), ID(unsigned_b)), + std::make_pair(ID(subtract_i), ID(subtract)), + std::make_pair(ID(output_select_i), ID(output_select)), + std::make_pair(ID(saturate_enable_i), ID(saturate_enable)), + std::make_pair(ID(shift_right_i), ID(shift_right)), + std::make_pair(ID(round_i), ID(round)), + std::make_pair(ID(register_inputs_i), ID(register_inputs)) + }; + + // DSP data ports and how to map them to ports of the target DSP cell + static const std::vector> m_DspDataPorts = { + std::make_pair(ID(a_i), ID(a)), + std::make_pair(ID(b_i), ID(b)), + std::make_pair(ID(acc_fir_i), ID(acc_fir)), + std::make_pair(ID(z_o), ID(z)), + std::make_pair(ID(dly_b_o), ID(dly_b)) + }; + + // Source DSP cell type (SISD) + static const IdString m_SisdDspType = ID(dsp_t1_10x9x32); + + // Target DSP cell types for the SIMD mode + static const IdString m_SimdDspType = ID(QL_DSP2); + // Parse args extra_args(a_Args, 1, a_Design); @@ -126,7 +126,7 @@ struct QlDspSimdPass : public Pass { continue; // Add to a group - const auto key = getDspConfig(cell); + const auto key = getDspConfig(cell, m_DspCfgPorts); groups[key].push_back(cell); } @@ -255,11 +255,11 @@ struct QlDspSimdPass : public Pass { } /// Given a DSP cell populates and returns a DspConfig struct for it. - DspConfig getDspConfig(RTLIL::Cell *a_Cell) + DspConfig getDspConfig(RTLIL::Cell *a_Cell, const std::vector> &dspCfgPorts) { DspConfig config; - for (const auto &it : m_DspCfgPorts) { + for (const auto &it : dspCfgPorts) { auto port = it.first; // Port unconnected diff --git a/techlibs/xilinx/tests/bram1.sh b/techlibs/xilinx/tests/bram1.sh index 7451a1b3e..7e9936804 100644 --- a/techlibs/xilinx/tests/bram1.sh +++ b/techlibs/xilinx/tests/bram1.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e diff --git a/techlibs/xilinx/tests/bram2.sh b/techlibs/xilinx/tests/bram2.sh index 5d9c84dac..5c18d30e2 100644 --- a/techlibs/xilinx/tests/bram2.sh +++ b/techlibs/xilinx/tests/bram2.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex unisims=/opt/Xilinx/Vivado/2014.4/data/verilog/src/unisims diff --git a/techlibs/xilinx/tests/test_dsp48_model.sh b/techlibs/xilinx/tests/test_dsp48_model.sh index 9a73f9b0c..bd72572d5 100644 --- a/techlibs/xilinx/tests/test_dsp48_model.sh +++ b/techlibs/xilinx/tests/test_dsp48_model.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex if [ -z $ISE_DIR ]; then ISE_DIR=/opt/Xilinx/ISE/14.7 diff --git a/techlibs/xilinx/tests/test_dsp48a1_model.sh b/techlibs/xilinx/tests/test_dsp48a1_model.sh index a14a78e72..02ce2fef9 100644 --- a/techlibs/xilinx/tests/test_dsp48a1_model.sh +++ b/techlibs/xilinx/tests/test_dsp48a1_model.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex if [ -z $ISE_DIR ]; then ISE_DIR=/opt/Xilinx/ISE/14.7 diff --git a/techlibs/xilinx/tests/test_dsp_model.sh b/techlibs/xilinx/tests/test_dsp_model.sh index d005cd40c..b2a2bb445 100644 --- a/techlibs/xilinx/tests/test_dsp_model.sh +++ b/techlibs/xilinx/tests/test_dsp_model.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex if [ -z $VIVADO_DIR ]; then VIVADO_DIR=/opt/Xilinx/Vivado/2019.1 diff --git a/tests/aiger/run-test.sh b/tests/aiger/run-test.sh index bcf2b964a..ca7339ff0 100755 --- a/tests/aiger/run-test.sh +++ b/tests/aiger/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e diff --git a/tests/arch/run-test.sh b/tests/arch/run-test.sh index 5d923db56..a14b79509 100755 --- a/tests/arch/run-test.sh +++ b/tests/arch/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e diff --git a/tests/asicworld/run-test.sh b/tests/asicworld/run-test.sh index c22ab6928..5131ed646 100755 --- a/tests/asicworld/run-test.sh +++ b/tests/asicworld/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash OPTIND=1 seed="" # default to no seed specified diff --git a/tests/blif/run-test.sh b/tests/blif/run-test.sh index 44ce7e674..e9698386e 100755 --- a/tests/blif/run-test.sh +++ b/tests/blif/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e for x in *.ys; do echo "Running $x.." diff --git a/tests/bram/run-single.sh b/tests/bram/run-single.sh index 429a79e3c..358423f32 100644 --- a/tests/bram/run-single.sh +++ b/tests/bram/run-single.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e ../../yosys -qq -f verilog -p "proc; opt; memory -nomap -bram temp/brams_${2}.txt; opt -fast -full" \ -l temp/synth_${1}_${2}.log -o temp/synth_${1}_${2}.v temp/brams_${1}.v diff --git a/tests/bram/run-test.sh b/tests/bram/run-test.sh index d6ba0de43..37fc91d0e 100755 --- a/tests/bram/run-test.sh +++ b/tests/bram/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # run this test many times: # MAKE="make -j8" time bash -c 'for ((i=0; i<100; i++)); do echo "-- $i --"; bash run-test.sh || exit 1; done' diff --git a/tests/cxxrtl/.gitignore b/tests/cxxrtl/.gitignore new file mode 100644 index 000000000..91caee986 --- /dev/null +++ b/tests/cxxrtl/.gitignore @@ -0,0 +1 @@ +cxxrtl-test-* diff --git a/tests/cxxrtl/run-test.sh b/tests/cxxrtl/run-test.sh new file mode 100755 index 000000000..89de71c6b --- /dev/null +++ b/tests/cxxrtl/run-test.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +set -ex + +run_subtest () { + local subtest=$1; shift + + ${CC:-gcc} -std=c++11 -O2 -o cxxrtl-test-${subtest} -I../../backends/cxxrtl/runtime test_${subtest}.cc -lstdc++ + ./cxxrtl-test-${subtest} +} + +run_subtest value +run_subtest value_fuzz diff --git a/tests/cxxrtl/test_value.cc b/tests/cxxrtl/test_value.cc new file mode 100644 index 000000000..0750d412e --- /dev/null +++ b/tests/cxxrtl/test_value.cc @@ -0,0 +1,52 @@ +#include +#include + +#include "cxxrtl/cxxrtl.h" + +int main() +{ + { + // shl exceeding Bits should be masked + cxxrtl::value<6> a(1u); + cxxrtl::value<6> b(8u); + cxxrtl::value<6> c = a.shl(b); + assert(c.get() == 0); + } + + { + // sshr of unreasonably large size should sign extend correctly + cxxrtl::value<64> a(0u, 0x80000000u); + cxxrtl::value<64> b(0u, 1u); + cxxrtl::value<64> c = a.sshr(b); + assert(c.get() == 0xffffffffffffffffu); + } + + { + // sshr of exteeding Bits should sign extend correctly + cxxrtl::value<8> a(0x80u); + cxxrtl::value<8> b(10u); + cxxrtl::value<8> c = a.sshr(b); + assert(c.get() == 0xffu); + } + + { + // Sign extension should occur correctly + cxxrtl::value<64> a(0x23456789u, 0x8abcdef1u); + cxxrtl::value<8> b(32u); + cxxrtl::value<64> c = a.sshr(b); + assert(c.get() == 0xffffffff8abcdef1u); + } + + { + // ctlz should work with Bits that are a multiple of chunk size + cxxrtl::value<32> a(0x00040000u); + assert(a.ctlz() == 13); + } + + { + // bmux clears top bits of result + cxxrtl::value<8> val(0x1fu); + cxxrtl::value<1> sel(0u); + assert(val.template bmux<4>(sel).get() == 0xfu); + } +} diff --git a/tests/cxxrtl/test_value_fuzz.cc b/tests/cxxrtl/test_value_fuzz.cc new file mode 100644 index 000000000..4428e9f6e --- /dev/null +++ b/tests/cxxrtl/test_value_fuzz.cc @@ -0,0 +1,251 @@ +#include +#include +#include +#include +#include +#include +#include + +#include "cxxrtl/cxxrtl.h" + +template +T rand_int(T min = std::numeric_limits::min(), T max = std::numeric_limits::max()) +{ + static_assert(std::is_integral::value, "T must be an integral type."); + static_assert(!std::is_same::value && !std::is_same::value, + "Using char with uniform_int_distribution is undefined behavior."); + + static std::mt19937 generator = [] { + std::random_device rd; + std::mt19937 mt{rd()}; + return mt; + }(); + + std::uniform_int_distribution dist(min, max); + return dist(generator); +} + +struct BinaryOperationBase +{ + void tweak_input(uint64_t &a, uint64_t &b) {} +}; + +template +void test_binary_operation_for_bitsize(Operation &op) +{ + constexpr int iteration_count = 10000000; + + constexpr uint64_t mask = std::numeric_limits::max() >> (64 - Bits); + + using chunk_type = typename cxxrtl::value::chunk::type; + constexpr size_t chunk_bits = cxxrtl::value::chunk::bits; + + for (int iteration = 0; iteration < iteration_count; iteration++) { + uint64_t ia = rand_int() >> (64 - Bits); + uint64_t ib = rand_int() >> (64 - Bits); + op.tweak_input(ia, ib); + + cxxrtl::value va, vb; + for (size_t i = 0; i * chunk_bits < Bits; i++) { + va.data[i] = (chunk_type)(ia >> (i * chunk_bits)); + vb.data[i] = (chunk_type)(ib >> (i * chunk_bits)); + } + + uint64_t iresult = op.reference_impl(Bits, ia, ib) & mask; + cxxrtl::value vresult = op.template testing_impl(va, vb); + + for (size_t i = 0; i * chunk_bits < Bits; i++) { + if ((chunk_type)(iresult >> (i * chunk_bits)) != vresult.data[i]) { + std::printf("Test failure:\n"); + std::printf("Bits: %i\n", Bits); + std::printf("a: %016lx\n", ia); + std::printf("b: %016lx\n", ib); + std::printf("iresult: %016lx\n", iresult); + std::printf("vresult: %016lx\n", vresult.template get()); + + std::terminate(); + } + } + } + std::printf("Test passed @ Bits = %i.\n", Bits); +} + +template +void test_binary_operation(Operation &op) +{ + // Test at a variety of bitwidths + test_binary_operation_for_bitsize<8>(op); + test_binary_operation_for_bitsize<32>(op); + test_binary_operation_for_bitsize<42>(op); + test_binary_operation_for_bitsize<63>(op); + test_binary_operation_for_bitsize<64>(op); +} + +template +struct UnaryOperationWrapper : BinaryOperationBase +{ + Operation &op; + + UnaryOperationWrapper(Operation &op) : op(op) {} + + uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b) + { + return op.reference_impl(bits, a); + } + + template + cxxrtl::value testing_impl(cxxrtl::value a, cxxrtl::value b) + { + return op.template testing_impl(a); + } +}; + +template +void test_unary_operation(Operation &op) +{ + UnaryOperationWrapper wrapped(op); + test_binary_operation(wrapped); +} + +struct ShlTest : BinaryOperationBase +{ + ShlTest() + { + std::printf("Randomized tests for value::shl:\n"); + test_binary_operation(*this); + } + + uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b) + { + return b >= 64 ? 0 : a << b; + } + + template + cxxrtl::value testing_impl(cxxrtl::value a, cxxrtl::value b) + { + return a.shl(b); + } + + void tweak_input(uint64_t &, uint64_t &b) + { + b &= 0x7f; + } +} shl; + +struct ShrTest : BinaryOperationBase +{ + ShrTest() + { + std::printf("Randomized tests for value::shr:\n"); + test_binary_operation(*this); + } + + uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b) + { + return b >= 64 ? 0 : a >> b; + } + + template + cxxrtl::value testing_impl(cxxrtl::value a, cxxrtl::value b) + { + return a.shr(b); + } + + void tweak_input(uint64_t &, uint64_t &b) + { + b &= 0x7f; + } +} shr; + +struct SshrTest : BinaryOperationBase +{ + SshrTest() + { + std::printf("Randomized tests for value::sshr:\n"); + test_binary_operation(*this); + } + + uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b) + { + int64_t sa = (int64_t)(a << (64 - bits)); + return sa >> (b >= bits ? 63 : (b + 64 - bits)); + } + + template + cxxrtl::value testing_impl(cxxrtl::value a, cxxrtl::value b) + { + return a.sshr(b); + } + + void tweak_input(uint64_t &, uint64_t &b) + { + b &= 0x7f; + } +} sshr; + +struct AddTest : BinaryOperationBase +{ + AddTest() + { + std::printf("Randomized tests for value::add:\n"); + test_binary_operation(*this); + } + + uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b) + { + return a + b; + } + + template + cxxrtl::value testing_impl(cxxrtl::value a, cxxrtl::value b) + { + return a.add(b); + } +} add; + +struct SubTest : BinaryOperationBase +{ + SubTest() + { + std::printf("Randomized tests for value::sub:\n"); + test_binary_operation(*this); + } + + uint64_t reference_impl(size_t bits, uint64_t a, uint64_t b) + { + return a - b; + } + + template + cxxrtl::value testing_impl(cxxrtl::value a, cxxrtl::value b) + { + return a.sub(b); + } +} sub; + +struct CtlzTest +{ + CtlzTest() + { + std::printf("Randomized tests for value::ctlz:\n"); + test_unary_operation(*this); + } + + uint64_t reference_impl(size_t bits, uint64_t a) + { + if (a == 0) + return bits; + return __builtin_clzl(a) - (64 - bits); + } + + template + cxxrtl::value testing_impl(cxxrtl::value a) + { + size_t result = a.ctlz(); + return cxxrtl::value((cxxrtl::chunk_t)result); + } +} ctlz; + +int main() +{ +} 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/fmt/run-test.sh b/tests/fmt/run-test.sh index 5b28c18c1..998047f83 100644 --- a/tests/fmt/run-test.sh +++ b/tests/fmt/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex diff --git a/tests/fsm/run-test.sh b/tests/fsm/run-test.sh index fbdcbf048..dc60c69c4 100755 --- a/tests/fsm/run-test.sh +++ b/tests/fsm/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # run this test many times: # time bash -c 'for ((i=0; i<100; i++)); do echo "-- $i --"; bash run-test.sh || exit 1; done' diff --git a/tests/hana/run-test.sh b/tests/hana/run-test.sh index 878c80b3f..99be37f5e 100755 --- a/tests/hana/run-test.sh +++ b/tests/hana/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash OPTIND=1 seed="" # default to no seed specified diff --git a/tests/liberty/run-test.sh b/tests/liberty/run-test.sh index 61f19b09b..19ce0667d 100755 --- a/tests/liberty/run-test.sh +++ b/tests/liberty/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e for x in *.lib; do diff --git a/tests/lut/run-test.sh b/tests/lut/run-test.sh index f8964f146..a10559cac 100755 --- a/tests/lut/run-test.sh +++ b/tests/lut/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e for x in *.v; do echo "Running $x.." diff --git a/tests/memfile/run-test.sh b/tests/memfile/run-test.sh index e43ddd093..db0ec54ee 100755 --- a/tests/memfile/run-test.sh +++ b/tests/memfile/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e diff --git a/tests/memlib/run-test.sh b/tests/memlib/run-test.sh index abe88a6cb..5f230a03e 100755 --- a/tests/memlib/run-test.sh +++ b/tests/memlib/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -eu OPTIND=1 diff --git a/tests/memories/run-test.sh b/tests/memories/run-test.sh index c65066a9c..4f1da7ce7 100755 --- a/tests/memories/run-test.sh +++ b/tests/memories/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e diff --git a/tests/opt/run-test.sh b/tests/opt/run-test.sh index 2007cd6e4..74589dfeb 100755 --- a/tests/opt/run-test.sh +++ b/tests/opt/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -eu source ../gen-tests-makefile.sh run_tests --yosys-scripts diff --git a/tests/opt_share/run-test.sh b/tests/opt_share/run-test.sh index e0008a259..e80cd4214 100755 --- a/tests/opt_share/run-test.sh +++ b/tests/opt_share/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # run this test many times: # time bash -c 'for ((i=0; i<100; i++)); do echo "-- $i --"; bash run-test.sh || exit 1; done' diff --git a/tests/proc/run-test.sh b/tests/proc/run-test.sh index 44ce7e674..e9698386e 100755 --- a/tests/proc/run-test.sh +++ b/tests/proc/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e for x in *.ys; do echo "Running $x.." diff --git a/tests/realmath/run-test.sh b/tests/realmath/run-test.sh index e1a36c694..833e8c3f4 100755 --- a/tests/realmath/run-test.sh +++ b/tests/realmath/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e OPTIND=1 diff --git a/tests/rpc/run-test.sh b/tests/rpc/run-test.sh index eeb309347..624043750 100755 --- a/tests/rpc/run-test.sh +++ b/tests/rpc/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e for x in *.ys; do echo "Running $x.." diff --git a/tests/select/run-test.sh b/tests/select/run-test.sh index 44ce7e674..e9698386e 100755 --- a/tests/select/run-test.sh +++ b/tests/select/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -e for x in *.ys; do echo "Running $x.." diff --git a/tests/share/run-test.sh b/tests/share/run-test.sh index 1bcd8e423..a7b5fc4a0 100755 --- a/tests/share/run-test.sh +++ b/tests/share/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash # run this test many times: # time bash -c 'for ((i=0; i<100; i++)); do echo "-- $i --"; bash run-test.sh || exit 1; done' diff --git a/tests/simple/run-test.sh b/tests/simple/run-test.sh index 47bcfd6da..b9e79f34a 100755 --- a/tests/simple/run-test.sh +++ b/tests/simple/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash OPTIND=1 seed="" # default to no seed specified 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/simple_abc9/run-test.sh b/tests/simple_abc9/run-test.sh index 4a5bf01a3..b75e54dd3 100755 --- a/tests/simple_abc9/run-test.sh +++ b/tests/simple_abc9/run-test.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash OPTIND=1 seed="" # default to no seed specified diff --git a/tests/smv/run-single.sh b/tests/smv/run-single.sh index a261f4ea6..a0a6eba0a 100644 --- a/tests/smv/run-single.sh +++ b/tests/smv/run-single.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash cat > $1.tpl <&2; exit 1' ERR diff --git a/tests/various/dynamic_part_select.ys b/tests/various/dynamic_part_select.ys index 2dc061e89..9e303b9db 100644 --- a/tests/various/dynamic_part_select.ys +++ b/tests/various/dynamic_part_select.ys @@ -69,6 +69,24 @@ design -copy-from gate -as gate gate miter -equiv -make_assert -make_outcmp -flatten gold gate equiv sat -prove-asserts -seq 10 -show-public -verify -set-init-zero equiv +### For-loop select, one dynamic input, (* nowrshmsk *) +design -reset +read_verilog ./dynamic_part_select/forloop_select_nowrshmsk.v +proc +rename -top gold +design -stash gold + +read_verilog ./dynamic_part_select/forloop_select_gate.v +proc +rename -top gate +design -stash gate + +design -copy-from gold -as gold gold +design -copy-from gate -as gate gate + +miter -equiv -make_assert -make_outcmp -flatten gold gate equiv +sat -prove-asserts -seq 10 -show-public -verify -set-init-zero equiv + #### Double loop (part-select, reset) ### design -reset read_verilog ./dynamic_part_select/reset_test.v diff --git a/tests/various/dynamic_part_select/forloop_select_nowrshmsk.v b/tests/various/dynamic_part_select/forloop_select_nowrshmsk.v new file mode 100644 index 000000000..75415c313 --- /dev/null +++ b/tests/various/dynamic_part_select/forloop_select_nowrshmsk.v @@ -0,0 +1,20 @@ +`default_nettype none +module forloop_select #(parameter WIDTH=16, SELW=4, CTRLW=$clog2(WIDTH), DINW=2**SELW) + (input wire clk, + input wire [CTRLW-1:0] ctrl, + input wire [DINW-1:0] din, + input wire en, + (* nowrshmsk *) + output reg [WIDTH-1:0] dout); + + reg [SELW:0] sel; + localparam SLICE = WIDTH/(SELW**2); + + always @(posedge clk) + begin + if (en) begin + for (sel = 0; sel <= 4'hf; sel=sel+1'b1) + dout[(ctrl*sel)+:SLICE] <= din; + end + end +endmodule diff --git a/tests/various/logger_fail.sh b/tests/various/logger_fail.sh index 19b650007..c318b648d 100755 --- a/tests/various/logger_fail.sh +++ b/tests/various/logger_fail.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash fail() { echo "$1" >&2 diff --git a/tests/various/smtlib2_module.sh b/tests/various/smtlib2_module.sh index 491f65148..a2206eea7 100755 --- a/tests/various/smtlib2_module.sh +++ b/tests/various/smtlib2_module.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash set -ex ../../yosys -q -p 'read_verilog -formal smtlib2_module.v; prep; write_smt2 smtlib2_module.smt2' sed 's/; SMT-LIBv2 description generated by Yosys .*/; SMT-LIBv2 description generated by Yosys $VERSION/;s/ *$//' smtlib2_module.smt2 > smtlib2_module-filtered.smt2 diff --git a/tests/various/sv_implicit_ports.sh b/tests/various/sv_implicit_ports.sh index 9a01447f7..5266fffe5 100755 --- a/tests/various/sv_implicit_ports.sh +++ b/tests/various/sv_implicit_ports.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash trap 'echo "ERROR in sv_implicit_ports.sh" >&2; exit 1' ERR diff --git a/tests/various/svalways.sh b/tests/various/svalways.sh index 2cc09f801..7765907c7 100755 --- a/tests/various/svalways.sh +++ b/tests/various/svalways.sh @@ -1,4 +1,4 @@ -#!/bin/bash +#!/usr/bin/env bash trap 'echo "ERROR in svalways.sh" >&2; exit 1' ERR diff --git a/tests/verific/clocking.ys b/tests/verific/clocking.ys new file mode 100644 index 000000000..bfdbeb748 --- /dev/null +++ b/tests/verific/clocking.ys @@ -0,0 +1,10 @@ +read -sv <