diff --git a/backends/cxxrtl/cxxrtl_backend.cc b/backends/cxxrtl/cxxrtl_backend.cc index 2c35a1943..ff11d7fe0 100644 --- a/backends/cxxrtl/cxxrtl_backend.cc +++ b/backends/cxxrtl/cxxrtl_backend.cc @@ -1291,20 +1291,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); @@ -2002,6 +2011,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); @@ -2430,11 +2444,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); @@ -2964,8 +2978,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); diff --git a/backends/verilog/verilog_backend.cc b/backends/verilog/verilog_backend.cc index 9ff2c5c86..1fa31e31e 100644 --- a/backends/verilog/verilog_backend.cc +++ b/backends/verilog/verilog_backend.cc @@ -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(); diff --git a/docs/source/CHAPTER_CellLib.rst b/docs/source/CHAPTER_CellLib.rst index 494c0651c..0f0d79123 100644 --- a/docs/source/CHAPTER_CellLib.rst +++ b/docs/source/CHAPTER_CellLib.rst @@ -120,7 +120,7 @@ All binary RTL cells have two input ports ``\A`` and ``\B`` and one output port :verilog:`Y = A >>> B` $sshr :verilog:`Y = A - B` $sub :verilog:`Y = A && B` $logic_and :verilog:`Y = A * B` $mul :verilog:`Y = A || B` $logic_or :verilog:`Y = A / B` $div - :verilog:`Y = A === B` $eqx :verilog:`Y = A % B` $mod + :verilog:`Y = A === B` $eqx :verilog:`Y = A % B` $mod :verilog:`Y = A !== B` $nex ``N/A`` $divfloor :verilog:`Y = A ** B` $pow ``N/A`` $modfoor ======================= ============= ======================= ========= @@ -661,6 +661,8 @@ Ports: ``\TRG`` The signals that control when this ``$print`` cell is triggered. + If the width of this port is zero and ``\TRG_ENABLE`` is true, the cell is + triggered during initial evaluation (time zero) only. ``\EN`` Enable signal for the whole cell. diff --git a/frontends/ast/ast.cc b/frontends/ast/ast.cc index 4b2b7a822..fe075b270 100644 --- a/frontends/ast/ast.cc +++ b/frontends/ast/ast.cc @@ -45,7 +45,7 @@ namespace AST { // instantiate global variables (private API) namespace AST_INTERNAL { - bool flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_vlog1, flag_dump_vlog2, flag_dump_rtlil, flag_nolatches, flag_nomeminit; + bool flag_nodisplay, flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_vlog1, flag_dump_vlog2, flag_dump_rtlil, flag_nolatches, flag_nomeminit; bool flag_nomem2reg, flag_mem2reg, flag_noblackbox, flag_lib, flag_nowb, flag_noopt, flag_icells, flag_pwires, flag_autowire; AstNode *current_ast, *current_ast_mod; std::map current_scope; @@ -1320,11 +1320,12 @@ static void rename_in_package_stmts(AstNode *pkg) } // create AstModule instances for all modules in the AST tree and add them to 'design' -void AST::process(RTLIL::Design *design, AstNode *ast, bool dump_ast1, bool dump_ast2, bool no_dump_ptr, bool dump_vlog1, bool dump_vlog2, bool dump_rtlil, +void AST::process(RTLIL::Design *design, AstNode *ast, bool nodisplay, bool dump_ast1, bool dump_ast2, bool no_dump_ptr, bool dump_vlog1, bool dump_vlog2, bool dump_rtlil, bool nolatches, bool nomeminit, bool nomem2reg, bool mem2reg, bool noblackbox, bool lib, bool nowb, bool noopt, bool icells, bool pwires, bool nooverwrite, bool overwrite, bool defer, bool autowire) { current_ast = ast; current_ast_mod = nullptr; + flag_nodisplay = nodisplay; flag_dump_ast1 = dump_ast1; flag_dump_ast2 = dump_ast2; flag_no_dump_ptr = no_dump_ptr; diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h index 97903d0a0..c44746131 100644 --- a/frontends/ast/ast.h +++ b/frontends/ast/ast.h @@ -287,7 +287,7 @@ namespace AST bool is_simple_const_expr(); // helper for parsing format strings - Fmt processFormat(int stage, bool sformat_like, int default_base = 10, size_t first_arg_at = 0); + Fmt processFormat(int stage, bool sformat_like, int default_base = 10, size_t first_arg_at = 0, bool may_fail = false); bool is_recursive_function() const; std::pair get_tern_choice(); @@ -376,7 +376,7 @@ namespace AST }; // process an AST tree (ast must point to an AST_DESIGN node) and generate RTLIL code - void process(RTLIL::Design *design, AstNode *ast, bool dump_ast1, bool dump_ast2, bool no_dump_ptr, bool dump_vlog1, bool dump_vlog2, bool dump_rtlil, bool nolatches, bool nomeminit, + void process(RTLIL::Design *design, AstNode *ast, bool nodisplay, bool dump_ast1, bool dump_ast2, bool no_dump_ptr, bool dump_vlog1, bool dump_vlog2, bool dump_rtlil, bool nolatches, bool nomeminit, bool nomem2reg, bool mem2reg, bool noblackbox, bool lib, bool nowb, bool noopt, bool icells, bool pwires, bool nooverwrite, bool overwrite, bool defer, bool autowire); // parametric modules are supported directly by the AST library @@ -432,7 +432,7 @@ namespace AST namespace AST_INTERNAL { // internal state variables - extern bool flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_rtlil, flag_nolatches, flag_nomeminit; + extern bool flag_nodisplay, flag_dump_ast1, flag_dump_ast2, flag_no_dump_ptr, flag_dump_rtlil, flag_nolatches, flag_nomeminit; extern bool flag_nomem2reg, flag_mem2reg, flag_lib, flag_noopt, flag_icells, flag_pwires, flag_autowire; extern AST::AstNode *current_ast, *current_ast_mod; extern std::map current_scope; diff --git a/frontends/ast/genrtlil.cc b/frontends/ast/genrtlil.cc index 0bae0f673..0a502162e 100644 --- a/frontends/ast/genrtlil.cc +++ b/frontends/ast/genrtlil.cc @@ -718,7 +718,7 @@ struct AST_INTERNAL::ProcessGenerator } } cell->parameters[ID::TRG_WIDTH] = triggers.size(); - cell->parameters[ID::TRG_ENABLE] = !triggers.empty(); + cell->parameters[ID::TRG_ENABLE] = (always->type == AST_INITIAL) || !triggers.empty(); cell->parameters[ID::TRG_POLARITY] = polarity; cell->parameters[ID::PRIORITY] = --last_print_priority; cell->setPort(ID::TRG, triggers); diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc index 51ed95621..5370c4187 100644 --- a/frontends/ast/simplify.cc +++ b/frontends/ast/simplify.cc @@ -145,7 +145,7 @@ void AstNode::fixup_hierarchy_flags(bool force_descend) // Process a format string and arguments for $display, $write, $sprintf, etc -Fmt AstNode::processFormat(int stage, bool sformat_like, int default_base, size_t first_arg_at) { +Fmt AstNode::processFormat(int stage, bool sformat_like, int default_base, size_t first_arg_at, bool may_fail) { std::vector args; for (size_t index = first_arg_at; index < children.size(); index++) { AstNode *node_arg = children[index]; @@ -169,6 +169,9 @@ Fmt AstNode::processFormat(int stage, bool sformat_like, int default_base, size_ arg.type = VerilogFmtArg::INTEGER; arg.sig = node_arg->bitsAsConst(); arg.signed_ = node_arg->is_signed; + } else if (may_fail) { + log_file_info(filename, location.first_line, "Skipping system task `%s' with non-constant argument at position %zu.\n", str.c_str(), index + 1); + return Fmt(); } else { log_file_error(filename, location.first_line, "Failed to evaluate system task `%s' with non-constant argument at position %zu.\n", str.c_str(), index + 1); } @@ -1055,30 +1058,31 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin { if (!current_always) { log_file_warning(filename, location.first_line, "System task `%s' outside initial or always block is unsupported.\n", str.c_str()); - } else if (current_always->type == AST_INITIAL) { - int default_base = 10; - if (str.back() == 'b') - default_base = 2; - else if (str.back() == 'o') - default_base = 8; - else if (str.back() == 'h') - default_base = 16; - - // when $display()/$write() functions are used in an initial block, print them during synthesis - Fmt fmt = processFormat(stage, /*sformat_like=*/false, default_base); - if (str.substr(0, 8) == "$display") - fmt.append_string("\n"); - log("%s", fmt.render().c_str()); + delete_children(); + str = std::string(); } else { - // when $display()/$write() functions are used in an always block, simplify the expressions and - // convert them to a special cell later in genrtlil + // simplify the expressions and convert them to a special cell later in genrtlil for (auto node : children) while (node->simplify(true, stage, -1, false)) {} + + if (current_always->type == AST_INITIAL && !flag_nodisplay && stage == 2) { + int default_base = 10; + if (str.back() == 'b') + default_base = 2; + else if (str.back() == 'o') + default_base = 8; + else if (str.back() == 'h') + default_base = 16; + + // when $display()/$write() functions are used in an initial block, print them during synthesis + Fmt fmt = processFormat(stage, /*sformat_like=*/false, default_base, /*first_arg_at=*/0, /*may_fail=*/true); + if (str.substr(0, 8) == "$display") + fmt.append_string("\n"); + log("%s", fmt.render().c_str()); + } + return false; } - - delete_children(); - str = std::string(); } // activate const folding if this is anything that must be evaluated statically (ranges, parameters, attributes, etc.) 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/passes/sat/sim.cc b/passes/sat/sim.cc index 8c4fadb69..b07534184 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; }; @@ -870,6 +882,7 @@ 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: @@ -2055,6 +2068,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(); }