Merge pull request #4129 from daglem/simulation-display

Pass `$display` et al. in `initial` blocks on to `sim`
This commit is contained in:
Catherine 2024-01-11 16:03:29 +00:00 committed by GitHub
commit 782572895b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 120 additions and 57 deletions

View File

@ -1291,8 +1291,9 @@ struct CxxrtlWorker {
log_assert(!for_debug); log_assert(!for_debug);
// Sync $print cells are grouped into PRINT_SYNC nodes in the FlowGraph. // 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));
if (!cell->getParam(ID::TRG_ENABLE).as_bool()) { // async $print cell
f << indent << "auto " << mangle(cell) << "_curr = "; f << indent << "auto " << mangle(cell) << "_curr = ";
dump_sigspec_rhs(cell->getPort(ID::EN)); dump_sigspec_rhs(cell->getPort(ID::EN));
f << ".concat("; f << ".concat(";
@ -1305,6 +1306,14 @@ struct CxxrtlWorker {
f << indent << mangle(cell) << " = " << mangle(cell) << "_curr;\n"; f << indent << mangle(cell) << " = " << mangle(cell) << "_curr;\n";
dec_indent(); dec_indent();
f << indent << "}\n"; 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 // Flip-flops
} else if (is_ff_cell(cell->type)) { } else if (is_ff_cell(cell->type)) {
log_assert(!for_debug); log_assert(!for_debug);
@ -2002,6 +2011,11 @@ struct CxxrtlWorker {
} }
} }
for (auto cell : module->cells()) { 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)) if (is_internal_cell(cell->type))
continue; continue;
f << indent << mangle(cell); f << indent << mangle(cell);
@ -2430,11 +2444,11 @@ struct CxxrtlWorker {
f << "\n"; f << "\n";
bool has_cells = false; bool has_cells = false;
for (auto cell : module->cells()) { for (auto cell : module->cells()) {
if (cell->type == ID($print) && !cell->getParam(ID::TRG_ENABLE).as_bool()) { // Certain $print cells have additional state, which requires storage.
// comb $print cell -- store the last EN/ARGS values to know when they change. if (cell->type == ID($print) && !cell->getParam(ID::TRG_ENABLE).as_bool())
dump_attrs(cell);
f << indent << "value<" << (1 + cell->getParam(ID::ARGS_WIDTH).as_int()) << "> " << mangle(cell) << ";\n"; 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)) if (is_internal_cell(cell->type))
continue; continue;
dump_attrs(cell); dump_attrs(cell);
@ -2965,7 +2979,8 @@ struct CxxrtlWorker {
if (live_nodes[node]) { if (live_nodes[node]) {
if (node->type == FlowGraph::Node::Type::CELL_EVAL && if (node->type == FlowGraph::Node::Type::CELL_EVAL &&
node->cell->type == ID($print) && node->cell->type == ID($print) &&
node->cell->getParam(ID::TRG_ENABLE).as_bool()) 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); sync_print_cells[make_pair(node->cell->getPort(ID::TRG), node->cell->getParam(ID::TRG_POLARITY))].push_back(node->cell);
else else
schedule[module].push_back(*node); schedule[module].push_back(*node);

View File

@ -1830,6 +1830,7 @@ void dump_cell(std::ostream &f, std::string indent, RTLIL::Cell *cell)
if (it != cell->parameters.begin()) if (it != cell->parameters.begin())
f << stringf(","); f << stringf(",");
f << stringf("\n%s .%s(", indent.c_str(), id(it->first).c_str()); f << stringf("\n%s .%s(", indent.c_str(), id(it->first).c_str());
if (it->second.size() > 0)
dump_const(f, it->second); dump_const(f, it->second);
f << stringf(")"); f << stringf(")");
} }
@ -1895,6 +1896,9 @@ 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<const RTLIL::Cell*> &cells) void dump_sync_print(std::ostream &f, std::string indent, const RTLIL::SigSpec &trg, const RTLIL::Const &polarity, std::vector<const RTLIL::Cell*> &cells)
{ {
if (trg.size() == 0) {
f << stringf("%s" "initial begin\n", indent.c_str());
} else {
f << stringf("%s" "always @(", indent.c_str()); f << stringf("%s" "always @(", indent.c_str());
for (int i = 0; i < trg.size(); i++) { for (int i = 0; i < trg.size(); i++) {
if (i != 0) if (i != 0)
@ -1906,6 +1910,7 @@ void dump_sync_print(std::ostream &f, std::string indent, const RTLIL::SigSpec &
dump_sigspec(f, trg[i]); 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) { 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(); return a->getParam(ID::PRIORITY).as_int() > b->getParam(ID::PRIORITY).as_int();

View File

@ -661,6 +661,8 @@ Ports:
``\TRG`` ``\TRG``
The signals that control when this ``$print`` cell is triggered. 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`` ``\EN``
Enable signal for the whole cell. Enable signal for the whole cell.

View File

@ -45,7 +45,7 @@ namespace AST {
// instantiate global variables (private API) // instantiate global variables (private API)
namespace AST_INTERNAL { 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; 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; AstNode *current_ast, *current_ast_mod;
std::map<std::string, AstNode*> current_scope; std::map<std::string, AstNode*> 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' // 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) 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 = ast;
current_ast_mod = nullptr; current_ast_mod = nullptr;
flag_nodisplay = nodisplay;
flag_dump_ast1 = dump_ast1; flag_dump_ast1 = dump_ast1;
flag_dump_ast2 = dump_ast2; flag_dump_ast2 = dump_ast2;
flag_no_dump_ptr = no_dump_ptr; flag_no_dump_ptr = no_dump_ptr;

View File

@ -287,7 +287,7 @@ namespace AST
bool is_simple_const_expr(); bool is_simple_const_expr();
// helper for parsing format strings // 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; bool is_recursive_function() const;
std::pair<AstNode*, AstNode*> get_tern_choice(); std::pair<AstNode*, AstNode*> 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 // 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); 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 // parametric modules are supported directly by the AST library
@ -432,7 +432,7 @@ namespace AST
namespace AST_INTERNAL namespace AST_INTERNAL
{ {
// internal state variables // 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 bool flag_nomem2reg, flag_mem2reg, flag_lib, flag_noopt, flag_icells, flag_pwires, flag_autowire;
extern AST::AstNode *current_ast, *current_ast_mod; extern AST::AstNode *current_ast, *current_ast_mod;
extern std::map<std::string, AST::AstNode*> current_scope; extern std::map<std::string, AST::AstNode*> current_scope;

View File

@ -718,7 +718,7 @@ struct AST_INTERNAL::ProcessGenerator
} }
} }
cell->parameters[ID::TRG_WIDTH] = triggers.size(); 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::TRG_POLARITY] = polarity;
cell->parameters[ID::PRIORITY] = --last_print_priority; cell->parameters[ID::PRIORITY] = --last_print_priority;
cell->setPort(ID::TRG, triggers); cell->setPort(ID::TRG, triggers);

View File

@ -145,7 +145,7 @@ void AstNode::fixup_hierarchy_flags(bool force_descend)
// Process a format string and arguments for $display, $write, $sprintf, etc // 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<VerilogFmtArg> args; std::vector<VerilogFmtArg> args;
for (size_t index = first_arg_at; index < children.size(); index++) { for (size_t index = first_arg_at; index < children.size(); index++) {
AstNode *node_arg = children[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.type = VerilogFmtArg::INTEGER;
arg.sig = node_arg->bitsAsConst(); arg.sig = node_arg->bitsAsConst();
arg.signed_ = node_arg->is_signed; 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 { } 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); 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,7 +1058,14 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin
{ {
if (!current_always) { if (!current_always) {
log_file_warning(filename, location.first_line, "System task `%s' outside initial or always block is unsupported.\n", str.c_str()); 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) { delete_children();
str = std::string();
} else {
// 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; int default_base = 10;
if (str.back() == 'b') if (str.back() == 'b')
default_base = 2; default_base = 2;
@ -1065,20 +1075,14 @@ bool AstNode::simplify(bool const_fold, int stage, int width_hint, bool sign_hin
default_base = 16; default_base = 16;
// when $display()/$write() functions are used in an initial block, print them during synthesis // when $display()/$write() functions are used in an initial block, print them during synthesis
Fmt fmt = processFormat(stage, /*sformat_like=*/false, default_base); Fmt fmt = processFormat(stage, /*sformat_like=*/false, default_base, /*first_arg_at=*/0, /*may_fail=*/true);
if (str.substr(0, 8) == "$display") if (str.substr(0, 8) == "$display")
fmt.append_string("\n"); fmt.append_string("\n");
log("%s", fmt.render().c_str()); log("%s", fmt.render().c_str());
} else {
// when $display()/$write() functions are used in an always block, simplify the expressions and
// convert them to a special cell later in genrtlil
for (auto node : children)
while (node->simplify(true, stage, -1, false)) {}
return false;
} }
delete_children(); return false;
str = std::string(); }
} }
// activate const folding if this is anything that must be evaluated statically (ranges, parameters, attributes, etc.) // activate const folding if this is anything that must be evaluated statically (ranges, parameters, attributes, etc.)

View File

@ -100,6 +100,10 @@ struct VerilogFrontend : public Frontend {
log(" -assert-assumes\n"); log(" -assert-assumes\n");
log(" treat all assume() statements like assert() statements\n"); log(" treat all assume() statements like assert() statements\n");
log("\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(" -debug\n");
log(" alias for -dump_ast1 -dump_ast2 -dump_vlog1 -dump_vlog2 -yydebug\n"); log(" alias for -dump_ast1 -dump_ast2 -dump_vlog1 -dump_vlog2 -yydebug\n");
log("\n"); log("\n");
@ -235,6 +239,7 @@ struct VerilogFrontend : public Frontend {
} }
void execute(std::istream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override void execute(std::istream *&f, std::string filename, std::vector<std::string> args, RTLIL::Design *design) override
{ {
bool flag_nodisplay = false;
bool flag_dump_ast1 = false; bool flag_dump_ast1 = false;
bool flag_dump_ast2 = false; bool flag_dump_ast2 = false;
bool flag_no_dump_ptr = false; bool flag_no_dump_ptr = false;
@ -308,6 +313,10 @@ struct VerilogFrontend : public Frontend {
assert_assumes_mode = true; assert_assumes_mode = true;
continue; continue;
} }
if (arg == "-nodisplay") {
flag_nodisplay = true;
continue;
}
if (arg == "-debug") { if (arg == "-debug") {
flag_dump_ast1 = true; flag_dump_ast1 = true;
flag_dump_ast2 = true; flag_dump_ast2 = true;
@ -510,7 +519,7 @@ struct VerilogFrontend : public Frontend {
if (flag_nodpi) if (flag_nodpi)
error_on_dpi_function(current_ast); 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); 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);

View File

@ -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 struct SimShared
{ {
bool debug = false; bool debug = false;
@ -110,6 +121,7 @@ struct SimShared
int next_output_id = 0; int next_output_id = 0;
int step = 0; int step = 0;
std::vector<TriggeredAssertion> triggered_assertions; std::vector<TriggeredAssertion> triggered_assertions;
std::vector<DisplayOutput> display_output;
bool serious_asserts = false; bool serious_asserts = false;
bool initstate = true; bool initstate = true;
}; };
@ -870,6 +882,7 @@ struct SimInstance
std::string rendered = print.fmt.render(); std::string rendered = print.fmt.render();
log("%s", rendered.c_str()); log("%s", rendered.c_str());
shared->display_output.emplace_back(shared->step, this, cell, rendered);
} }
update_print: update_print:
@ -2055,6 +2068,20 @@ struct SimWorker : SimShared
json.end_object(); json.end_object();
} }
json.end_array(); 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(); json.end_object();
} }