From d5c9953c09f5430ccc299d214c77f48e98cdc908 Mon Sep 17 00:00:00 2001
From: whitequark <whitequark@whitequark.org>
Date: Sun, 29 Nov 2020 14:34:17 +0000
Subject: [PATCH] ast: translate $display/$write tasks in always blocks to new
 $print cell.

---
 frontends/ast/ast.h       |  2 ++
 frontends/ast/genrtlil.cc | 74 ++++++++++++++++++++++++++++++++++++++-
 frontends/ast/simplify.cc | 40 +++++++++++----------
 kernel/constids.inc       |  7 ++++
 kernel/rtlil.cc           | 11 ++++++
 5 files changed, 115 insertions(+), 19 deletions(-)

diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h
index 55e05709d..e357579ad 100644
--- a/frontends/ast/ast.h
+++ b/frontends/ast/ast.h
@@ -278,6 +278,8 @@ namespace AST
 		bool replace_variables(std::map<std::string, varinfo_t> &variables, AstNode *fcall, bool must_succeed);
 		AstNode *eval_const_function(AstNode *fcall, bool must_succeed);
 		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);
 
 		bool is_recursive_function() const;
diff --git a/frontends/ast/genrtlil.cc b/frontends/ast/genrtlil.cc
index 3aa19b706..40e71e8bd 100644
--- a/frontends/ast/genrtlil.cc
+++ b/frontends/ast/genrtlil.cc
@@ -693,8 +693,80 @@ struct AST_INTERNAL::ProcessGenerator
 			ast->input_error("Found parameter declaration in block without label!\n");
 			break;
 
-		case AST_NONE:
 		case AST_TCALL:
+			if (ast->str == "$display" || ast->str == "$displayb" || ast->str == "$displayh" || ast->str == "$displayo" ||
+		  ast->str == "$write"   || ast->str == "$writeb"   || ast->str == "$writeh"   || ast->str == "$writeo") {
+				std::stringstream sstr;
+				sstr << ast->str << "$" << ast->filename << ":" << ast->location.first_line << "$" << (autoidx++);
+
+				RTLIL::Cell *cell = current_module->addCell(sstr.str(), ID($print));
+				cell->attributes[ID::src] = stringf("%s:%d.%d-%d.%d", ast->filename.c_str(), ast->location.first_line, ast->location.first_column, ast->location.last_line, ast->location.last_column);
+
+				RTLIL::SigSpec triggers;
+				RTLIL::Const polarity;
+				for (auto sync : proc->syncs) {
+					if (sync->type == RTLIL::STp) {
+						triggers.append(sync->signal);
+						polarity.bits.push_back(RTLIL::S1);
+					} else if (sync->type == RTLIL::STn) {
+						triggers.append(sync->signal);
+						polarity.bits.push_back(RTLIL::S0);
+					}
+				}
+				cell->parameters[ID::TRG_WIDTH] = triggers.size();
+				cell->parameters[ID::TRG_ENABLE] = !triggers.empty();
+				cell->parameters[ID::TRG_POLARITY] = polarity;
+				cell->setPort(ID::TRG, triggers);
+
+				Wire *wire = current_module->addWire(sstr.str() + "_EN", 1);
+				wire->attributes[ID::src] = stringf("%s:%d.%d-%d.%d", ast->filename.c_str(), ast->location.first_line, ast->location.first_column, ast->location.last_line, ast->location.last_column);
+				cell->setPort(ID::EN, wire);
+
+				proc->root_case.actions.push_back(SigSig(wire, false));
+				current_case->actions.push_back(SigSig(wire, true));
+
+				int default_base = 10;
+				if (ast->str.back() == 'b')
+					default_base = 2;
+				else if (ast->str.back() == 'o')
+					default_base = 8;
+				else if (ast->str.back() == 'h')
+					default_base = 16;
+
+				std::vector<VerilogFmtArg> args;
+				for (auto node : ast->children) {
+					int width;
+					bool is_signed;
+					node->detectSignWidth(width, is_signed, nullptr);
+
+					VerilogFmtArg arg = {};
+					arg.filename = node->filename;
+					arg.first_line = node->location.first_line;
+					if (node->type == AST_CONSTANT && node->is_string) {
+						arg.type = VerilogFmtArg::STRING;
+						arg.str = node->bitsAsConst().decode_string();
+						// and in case this will be used as an argument...
+						arg.sig = node->bitsAsConst();
+						arg.signed_ = false;
+					} else {
+						arg.type = VerilogFmtArg::INTEGER;
+						arg.sig = node->genRTLIL();
+						arg.signed_ = is_signed;
+					}
+					args.push_back(arg);
+				}
+
+				Fmt fmt = {};
+				fmt.parse_verilog(args, /*sformat_like=*/false, default_base, /*task_name=*/ast->str, current_module->name);
+				if (ast->str.substr(0, 8) == "$display")
+					fmt.append_string("\n");
+				fmt.emit_rtlil(cell);
+			} else if (!ast->str.empty()) {
+				log_file_error(ast->filename, ast->location.first_line, "Found unsupported invocation of system task `%s'!\n", ast->str.c_str());
+			}
+			break;
+
+		case AST_NONE:
 		case AST_FOR:
 			break;
 
diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc
index 489ce255a..19e62668b 100644
--- a/frontends/ast/simplify.cc
+++ b/frontends/ast/simplify.cc
@@ -951,29 +951,33 @@ bool AstNode::simplify(bool const_fold, bool in_lvalue, int stage, int width_hin
 		str = std::string();
 	}
 
-	if ((type == AST_TCALL) && (str.substr(0, 8) == "$display" || str.substr(0, 6) == "$write") && (!current_always || current_always->type != AST_INITIAL)) {
-		log_file_warning(filename, location.first_line, "System task `%s' outside initial block is unsupported.\n", str.c_str());
-		delete_children();
-		str = std::string();
-	}
-
-	// print messages if this a call to $display() or $write() family of functions
 	if ((type == AST_TCALL) &&
 	    (str == "$display" || str == "$displayb" || str == "$displayh" || str == "$displayo" ||
 	     str == "$write"   || str == "$writeb"   || str == "$writeh"   || str == "$writeo"))
 	{
-		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;
+		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;
 
-		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());
+			// 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());
+		} 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, false, stage, -1, false, false)) {}
+			return false;
+		}
 
 		delete_children();
 		str = std::string();
diff --git a/kernel/constids.inc b/kernel/constids.inc
index 39211d0c7..08b0ecdc2 100644
--- a/kernel/constids.inc
+++ b/kernel/constids.inc
@@ -22,6 +22,8 @@ X(always_ff)
 X(always_latch)
 X(anyconst)
 X(anyseq)
+X(ARGS)
+X(ARGS_WIDTH)
 X(ARST)
 X(ARST_POLARITY)
 X(ARST_VALUE)
@@ -86,6 +88,7 @@ X(equiv_merged)
 X(equiv_region)
 X(extract_order)
 X(F)
+X(FORMAT)
 X(force_downto)
 X(force_upto)
 X(fsm_encoding)
@@ -233,6 +236,10 @@ X(TRANS_NUM)
 X(TRANSPARENCY_MASK)
 X(TRANSPARENT)
 X(TRANS_TABLE)
+X(TRG)
+X(TRG_ENABLE)
+X(TRG_POLARITY)
+X(TRG_WIDTH)
 X(T_RISE_MAX)
 X(T_RISE_MIN)
 X(T_RISE_TYP)
diff --git a/kernel/rtlil.cc b/kernel/rtlil.cc
index 7011429ff..09fe08000 100644
--- a/kernel/rtlil.cc
+++ b/kernel/rtlil.cc
@@ -1720,6 +1720,17 @@ namespace {
 				return;
 			}
 
+			if (cell->type == ID($print)) {
+				param(ID(FORMAT));
+				param_bool(ID::TRG_ENABLE);
+				param(ID::TRG_POLARITY);
+				port(ID::EN, 1);
+				port(ID::TRG, param(ID::TRG_WIDTH));
+				port(ID::ARGS, param(ID::ARGS_WIDTH));
+				check_expected();
+				return;
+			}
+
 			if (cell->type == ID($_BUF_))    { port(ID::A,1); port(ID::Y,1); check_expected(); return; }
 			if (cell->type == ID($_NOT_))    { port(ID::A,1); port(ID::Y,1); check_expected(); return; }
 			if (cell->type == ID($_AND_))    { port(ID::A,1); port(ID::B,1); port(ID::Y,1); check_expected(); return; }