From aaa99c35bdcde8bec9d44ca23814f323a4e09c75 Mon Sep 17 00:00:00 2001
From: Clifford Wolf <clifford@clifford.at>
Date: Mon, 19 Sep 2016 01:30:07 +0200
Subject: [PATCH] Added $past, $stable, $rose, $fell SVA functions

---
 frontends/ast/genrtlil.cc |  10 +++
 frontends/ast/simplify.cc | 133 +++++++++++++++++++++++++++++++++++++-
 2 files changed, 141 insertions(+), 2 deletions(-)

diff --git a/frontends/ast/genrtlil.cc b/frontends/ast/genrtlil.cc
index d00738ecd..3c57162aa 100644
--- a/frontends/ast/genrtlil.cc
+++ b/frontends/ast/genrtlil.cc
@@ -765,6 +765,16 @@ void AstNode::detectSignWidthWorker(int &width_hint, bool &sign_hint, bool *foun
 			}
 			break;
 		}
+		if (str == "\\$past") {
+			if (GetSize(children) > 0) {
+				sub_width_hint = 0;
+				sub_sign_hint = true;
+				children.at(0)->detectSignWidthWorker(sub_width_hint, sub_sign_hint);
+				width_hint = max(width_hint, sub_width_hint);
+				sign_hint = false;
+			}
+			break;
+		}
 		/* fall through */
 
 	// everything should have been handled above -> print error if not.
diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc
index 8387c11c6..57aa648ce 100644
--- a/frontends/ast/simplify.cc
+++ b/frontends/ast/simplify.cc
@@ -63,7 +63,7 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
 
 #if 0
 	log("-------------\n");
-	log("AST simplify[%d] depth %d at %s:%d,\n", stage, recursion_counter, filename.c_str(), linenum);
+	log("AST simplify[%d] depth %d at %s:%d on %s %p:\n", stage, recursion_counter, filename.c_str(), linenum, type2str(type).c_str(), this);
 	log("const_fold=%d, at_zero=%d, in_lvalue=%d, stage=%d, width_hint=%d, sign_hint=%d, in_param=%d\n",
 			int(const_fold), int(at_zero), int(in_lvalue), int(stage), int(width_hint), int(sign_hint), int(in_param));
 	// dumpAst(NULL, "> ");
@@ -522,6 +522,11 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
 		children_are_self_determined = true;
 		break;
 
+	case AST_FCALL:
+	case AST_TCALL:
+		children_are_self_determined = true;
+		break;
+
 	default:
 		width_hint = -1;
 		sign_hint = false;
@@ -537,6 +542,9 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
 		detectSignWidth(width_hint, sign_hint);
 	}
 
+	if (type == AST_FCALL && str == "\\$past")
+		detectSignWidth(width_hint, sign_hint);
+
 	if (type == AST_TERNARY) {
 		int width_hint_left, width_hint_right;
 		bool sign_hint_left, sign_hint_right;
@@ -1682,9 +1690,128 @@ skip_dynamic_range_lvalue_expansion:;
 				goto apply_newNode;
 			}
 
+			if (str == "\\$past")
+			{
+				if (width_hint <= 0)
+					goto replace_fcall_later;
+
+				int num_steps = 1;
+
+				if (GetSize(children) != 1 && GetSize(children) != 2)
+					log_error("System function %s got %d arguments, expected 1 or 2 at %s:%d.\n",
+							RTLIL::unescape_id(str).c_str(), int(children.size()), filename.c_str(), linenum);
+
+				if (!current_always_clocked)
+					log_error("System function %s is only allowed in clocked blocks at %s:%d.\n",
+							RTLIL::unescape_id(str).c_str(), filename.c_str(), linenum);
+
+				if (GetSize(children) == 2)
+				{
+					AstNode *buf = children[1]->clone();
+					while (buf->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
+					if (buf->type != AST_CONSTANT)
+						log_error("Failed to evaluate system function `%s' with non-constant value at %s:%d.\n", str.c_str(), filename.c_str(), linenum);
+
+					num_steps = buf->asInt(true);
+					delete buf;
+				}
+
+				AstNode *block = nullptr;
+
+				for (auto child : current_always->children)
+					if (child->type == AST_BLOCK)
+						block = child;
+
+				log_assert(block != nullptr);
+
+				int myidx = autoidx++;
+				AstNode *outreg = nullptr;
+
+				for (int i = 0; i < num_steps; i++)
+				{
+					AstNode *reg = new AstNode(AST_WIRE, new AstNode(AST_RANGE,
+							mkconst_int(width_hint-1, true), mkconst_int(0, true)));
+
+					reg->str = stringf("$past$%s:%d$%d$%d", filename.c_str(), linenum, myidx, i);
+					reg->is_reg = true;
+
+					current_ast_mod->children.push_back(reg);
+
+					while (reg->simplify(true, false, false, 1, -1, false, false)) { }
+
+					AstNode *regid = new AstNode(AST_IDENTIFIER);
+					regid->str = reg->str;
+					regid->id2ast = reg;
+
+					AstNode *rhs = nullptr;
+
+					if (outreg == nullptr) {
+						rhs = children.at(0)->clone();
+					} else {
+						rhs = new AstNode(AST_IDENTIFIER);
+						rhs->str = outreg->str;
+						rhs->id2ast = outreg;
+					}
+
+					block->children.push_back(new AstNode(AST_ASSIGN_LE, regid, rhs));
+					outreg = reg;
+				}
+
+				newNode = new AstNode(AST_IDENTIFIER);
+				newNode->str = outreg->str;
+				newNode->id2ast = outreg;
+				goto apply_newNode;
+			}
+
+			if (str == "\\$stable" || str == "\\$rose" || str == "\\$fell")
+			{
+				if (GetSize(children) != 1)
+					log_error("System function %s got %d arguments, expected 1 at %s:%d.\n",
+							RTLIL::unescape_id(str).c_str(), int(children.size()), filename.c_str(), linenum);
+
+				if (!current_always_clocked)
+					log_error("System function %s is only allowed in clocked blocks at %s:%d.\n",
+							RTLIL::unescape_id(str).c_str(), filename.c_str(), linenum);
+
+				AstNode *present = children.at(0)->clone();
+				AstNode *past = clone();
+				past->str = "\\$past";
+
+				if (str == "\\$stable")
+					newNode = new AstNode(AST_EQ, past, present);
+
+				else if (str == "\\$rose")
+					newNode = new AstNode(AST_LOGIC_AND, new AstNode(AST_LOGIC_NOT, past), present);
+
+				else if (str == "\\$fell")
+					newNode = new AstNode(AST_LOGIC_AND, past, new AstNode(AST_LOGIC_NOT, present));
+
+				else
+					log_abort();
+
+				goto apply_newNode;
+			}
+
+			if (str == "\\$rose" || str == "\\$fell")
+			{
+				if (GetSize(children) != 1)
+					log_error("System function %s got %d arguments, expected 1 at %s:%d.\n",
+							RTLIL::unescape_id(str).c_str(), int(children.size()), filename.c_str(), linenum);
+
+				if (!current_always_clocked)
+					log_error("System function %s is only allowed in clocked blocks at %s:%d.\n",
+							RTLIL::unescape_id(str).c_str(), filename.c_str(), linenum);
+
+				newNode = new AstNode(AST_EQ, children.at(0)->clone(), clone());
+				newNode->children.at(1)->str = "\\$past";
+				goto apply_newNode;
+			}
+
 			// $anyconst is mapped in AstNode::genRTLIL()
-			if (str == "\\$anyconst")
+			if (str == "\\$anyconst") {
+				recursion_counter--;
 				return false;
+			}
 
 			if (str == "\\$clog2")
 			{
@@ -2092,6 +2219,8 @@ skip_dynamic_range_lvalue_expansion:;
 		did_something = true;
 	}
 
+replace_fcall_later:;
+
 	// perform const folding when activated
 	if (const_fold)
 	{