From b548722beec87a7bfec0b9a307c1267f2ddb996b Mon Sep 17 00:00:00 2001
From: Udi Finkelstein <github@udifink.com>
Date: Tue, 15 Sep 2020 20:49:52 +0300
Subject: [PATCH] Added $high(), $low(), $left(), $right()

---
 frontends/ast/ast.cc      |   6 ++
 frontends/ast/ast.h       |   1 +
 frontends/ast/simplify.cc | 121 ++++++++++++++++++++++++++++----------
 tests/sat/sizebits.sv     |  49 +++++++++++++++
 4 files changed, 147 insertions(+), 30 deletions(-)

diff --git a/frontends/ast/ast.cc b/frontends/ast/ast.cc
index 9520ae32c..c8183580b 100644
--- a/frontends/ast/ast.cc
+++ b/frontends/ast/ast.cc
@@ -336,6 +336,12 @@ void AstNode::dumpAst(FILE *f, std::string indent) const
 			fprintf(f, " %d", v);
 		fprintf(f, " ]");
 	}
+	if (!multirange_swapped.empty()) {
+		fprintf(f, " multirange_swapped=[");
+		for (auto v : multirange_swapped)
+			fprintf(f, " %d", v);
+		fprintf(f, " ]");
+	}
 	if (is_enum) {
 		fprintf(f, " type=enum");
 	}
diff --git a/frontends/ast/ast.h b/frontends/ast/ast.h
index 203b50021..1b8ed22ca 100644
--- a/frontends/ast/ast.h
+++ b/frontends/ast/ast.h
@@ -202,6 +202,7 @@ namespace AST
 
 		// if this is a multirange memory then this vector contains offset and length of each dimension
 		std::vector<int> multirange_dimensions;
+		std::vector<bool> multirange_swapped; // true if range is swapped, not used for structs
 
 		// this is set by simplify and used during RTLIL generation
 		AstNode *id2ast;
diff --git a/frontends/ast/simplify.cc b/frontends/ast/simplify.cc
index 0ba2ab6ac..52dfc575f 100644
--- a/frontends/ast/simplify.cc
+++ b/frontends/ast/simplify.cc
@@ -1504,11 +1504,14 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
 	{
 		int total_size = 1;
 		multirange_dimensions.clear();
+		multirange_swapped.clear();
 		for (auto range : children[1]->children) {
 			if (!range->range_valid)
 				log_file_error(filename, location.first_line, "Non-constant range on memory decl.\n");
 			multirange_dimensions.push_back(min(range->range_left, range->range_right));
 			multirange_dimensions.push_back(max(range->range_left, range->range_right) - min(range->range_left, range->range_right) + 1);
+			log_file_warning(filename, location.first_line, "left:%d right:%d\n", range->range_left, range->range_right);
+			multirange_swapped.push_back(range->range_swapped);
 			total_size *= multirange_dimensions.back();
 		}
 		delete children[1];
@@ -2831,26 +2834,28 @@ skip_dynamic_range_lvalue_expansion:;
 				goto apply_newNode;
 			}
 
-			if (str == "\\$size" || str == "\\$bits")
+			if (str == "\\$size" || str == "\\$bits" || str == "\\$high" || str == "\\$low" || str == "\\$left" || str == "\\$right")
 			{
-				if (str == "\\$bits" && children.size() != 1)
-					log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1.\n",
-							RTLIL::unescape_id(str).c_str(), int(children.size()));
-
-				if (str == "\\$size" && children.size() != 1 && children.size() != 2)
-					log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1 or 2.\n",
-							RTLIL::unescape_id(str).c_str(), int(children.size()));
-
 				int dim = 1;
-				if (str == "\\$size" && children.size() == 2) {
-					AstNode *buf = children[1]->clone();
-					// Evaluate constant expression
-					while (buf->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
-					dim = buf->asInt(false);
-					delete buf;
+				if (str == "\\$bits") {
+					if (children.size() != 1)
+						log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1.\n",
+								RTLIL::unescape_id(str).c_str(), int(children.size()));
+				} else {
+					if (children.size() != 1 && children.size() != 2)
+						log_file_error(filename, location.first_line, "System function %s got %d arguments, expected 1 or 2.\n",
+							RTLIL::unescape_id(str).c_str(), int(children.size()));
+					if (children.size() == 2) {
+						AstNode *buf = children[1]->clone();
+						// Evaluate constant expression
+						while (buf->simplify(true, false, false, stage, width_hint, sign_hint, false)) { }
+						dim = buf->asInt(false);
+						delete buf;
+					}
 				}
 				AstNode *buf = children[0]->clone();
 				int mem_depth = 1;
+				int result, high = 0, low = 0, left = 0, right = 0, width = 1; // defaults for a simple wire
 				AstNode *id_ast = NULL;
 
 				// Is this needed?
@@ -2863,6 +2868,32 @@ skip_dynamic_range_lvalue_expansion:;
 						id_ast = current_scope.at(buf->str);
 					if (!id_ast)
 						log_file_error(filename, location.first_line, "Failed to resolve identifier %s for width detection!\n", buf->str.c_str());
+					// a slice of our identifier means we advance to the next dimension, e.g. $size(a[3])
+					if (buf->children.size() > 0) {
+						// we give up here because when we try to support thing such as $size(a[1][1]) the AST at this point doesn't contain
+						// the information how many indexes were given. The array is already flattened and a composite index is given in the AST instead.
+						log_file_error(filename, location.first_line, "%s() only supported for pure identifiers (no further indexing)!\n", str.c_str());
+						// something is hanging below this identifier
+						if (buf->children[0]->type == AST_RANGE)
+							dim++;
+						// more than one range, e.g. $size(a[3][2])
+						else if (buf->children[0]->type == AST_MULTIRANGE)
+							dim += buf->children[0]->children.size(); // increment by multirange size
+					}
+					// We have 4 cases:
+					// AST_WIRE, no AST_RANGE children
+					// AST_WIRE, AST_RANGE children
+					// AST_MEMORY, two AST_RANGE children (1st for packed, 2nd for unpacked)
+					// AST_MEMORY, one AST_RANGE child (0) for packed, then AST_MULTIRANGE child (1) for unpacked
+					// case 0 handled by default
+					if ((id_ast->type == AST_WIRE || id_ast->type == AST_MEMORY) && id_ast->children.size() > 0) {
+						// handle packed array left/right for case 1, and cases 2/3 when requesting the last dimension (packed side)
+						AstNode *wire_range = id_ast->children[0];
+						left = wire_range->children[0]->integer;
+						right = wire_range->children[1]->integer;
+						high = max(left, right);
+						low  = min(left, right);
+					}
 					if (id_ast->type == AST_MEMORY) {
 						// We got here only if the argument is a memory
 						// Otherwise $size() and $bits() return the expression width
@@ -2875,29 +2906,59 @@ skip_dynamic_range_lvalue_expansion:;
 							} else
 								log_file_error(filename, location.first_line, "Unknown memory depth AST type in `%s'!\n", buf->str.c_str());
 						} else {
-							// $size()
+							// $size(), $left(), $right(), $high(), $low()
+							int dims = 1;
 							if (mem_range->type == AST_RANGE) {
-								if (!mem_range->range_valid)
-									log_file_error(filename, location.first_line, "Failed to detect width of memory access `%s'!\n", buf->str.c_str());
-								int dims;
-								if (id_ast->multirange_dimensions.empty())
-									dims = 1;
-								else
+								if (id_ast->multirange_dimensions.empty()) {
+									if (!mem_range->range_valid)
+										log_file_error(filename, location.first_line, "Failed to detect width of memory access `%s'!\n", buf->str.c_str());
+									if (dim == 1) {
+										left  = mem_range->range_right;
+										right = mem_range->range_left;
+										high = max(left, right);
+										low  = min(left, right);
+									}
+								} else {
 									dims = GetSize(id_ast->multirange_dimensions)/2;
-								if (dim == 1)
-									width_hint = (dims > 1) ? id_ast->multirange_dimensions[1] : (mem_range->range_left - mem_range->range_right + 1);
-								else if (dim <= dims) {
-									width_hint = id_ast->multirange_dimensions[2*dim-1];
-								} else if ((dim > dims+1) || (dim < 0))
-									log_file_error(filename, location.first_line, "Dimension %d out of range in `%s', as it only has dimensions 1..%d!\n", dim, buf->str.c_str(), dims+1);
-							} else
+									if (dim <= dims) {
+										width_hint = id_ast->multirange_dimensions[2*dim-1];
+										high = id_ast->multirange_dimensions[2*dim-2] + id_ast->multirange_dimensions[2*dim-1] - 1;
+										low  = id_ast->multirange_dimensions[2*dim-2];
+										if (id_ast->multirange_swapped[dim-1]) {
+											left = low;
+											right = high;
+										} else {
+											right = low;
+											left = high;
+										}
+									} else if ((dim > dims+1) || (dim < 0))
+										log_file_error(filename, location.first_line, "Dimension %d out of range in `%s', as it only has dimensions 1..%d!\n", dim, buf->str.c_str(), dims+1);
+								}
+							} else {
 								log_file_error(filename, location.first_line, "Unknown memory depth AST type in `%s'!\n", buf->str.c_str());
+							}
 						}
 					}
+					width = high - low + 1;
+				} else {
+					width = width_hint;
 				}
 				delete buf;
+				if (str == "\\$high")
+					result = high;
+				else if (str == "\\$low")
+					result = low;
+				else if (str == "\\$left")
+					result = left;
+				else if (str == "\\$right")
+					result = right;
+				else if (str == "\\$size")
+					result = width;
+				else {
+					result = width * mem_depth;
+				}
 
-				newNode = mkconst_int(width_hint * mem_depth, false);
+				newNode = mkconst_int(result, false);
 				goto apply_newNode;
 			}
 
diff --git a/tests/sat/sizebits.sv b/tests/sat/sizebits.sv
index d7ce2326e..408c6f5c8 100644
--- a/tests/sat/sizebits.sv
+++ b/tests/sat/sizebits.sv
@@ -1,5 +1,6 @@
 module functions01;
 
+wire t;
 wire [5:2]x;
 wire [3:0]y[2:7];
 wire [3:0]z[7:2][2:9];
@@ -9,24 +10,72 @@ wire [3:0]z[7:2][2:9];
 //wire [$size(y)-1:0]y_size;
 //wire [$size(z)-1:0]z_size;
 
+assert property ($size(t) == 1);
 assert property ($size(x) == 4);
 assert property ($size({3{x}}) == 3*4);
 assert property ($size(y) == 6);
 assert property ($size(y, 1) == 6);
 assert property ($size(y, (1+1)) == 4);
+// This is unsupported at the moment
+//assert property ($size(y[2], 1) == 4);
+//assert property ($size(y[2][1], 1) == 1);
 
 assert property ($size(z) == 6);
 assert property ($size(z, 1) == 6);
 assert property ($size(z, 2) == 8);
 assert property ($size(z, 3) == 4);
+// This is unsupported at the moment
+//assert property ($size(z[3], 1) == 8);
+//assert property ($size(z[3][3], 1) == 4);
+//assert property ($size(z[3][3][3], 1) == 1);
 // This should trigger an error if enabled (it does).
 //assert property ($size(z, 4) == 4);
 
 //wire [$bits(x)-1:0]x_bits;
 //wire [$bits({x, x})-1:0]xx_bits;
 
+assert property ($bits(t) == 1);
 assert property ($bits(x) == 4);
 assert property ($bits(y) == 4*6);
 assert property ($bits(z) == 4*6*8);
 
+assert property ($high(x) == 5);
+assert property ($high(y) == 7);
+assert property ($high(y, 1) == 7);
+assert property ($high(y, (1+1)) == 3);
+
+assert property ($high(z) == 7);
+assert property ($high(z, 1) == 7);
+assert property ($high(z, 2) == 9);
+assert property ($high(z, 3) == 3);
+
+assert property ($low(x) == 2);
+assert property ($low(y) == 2);
+assert property ($low(y, 1) == 2);
+assert property ($low(y, (1+1)) == 0);
+
+assert property ($low(z) == 2);
+assert property ($low(z, 1) == 2);
+assert property ($low(z, 2) == 2);
+assert property ($low(z, 3) == 0);
+
+assert property ($left(x) == 5);
+assert property ($left(y) == 2);
+assert property ($left(y, 1) == 2);
+assert property ($left(y, (1+1)) == 3);
+
+assert property ($left(z) == 7);
+assert property ($left(z, 1) == 7);
+assert property ($left(z, 2) == 2);
+assert property ($left(z, 3) == 3);
+
+assert property ($right(x) == 2);
+assert property ($right(y) == 7);
+assert property ($right(y, 1) == 7);
+assert property ($right(y, (1+1)) == 0);
+
+assert property ($right(z) == 2);
+assert property ($right(z, 1) == 2);
+assert property ($right(z, 2) == 9);
+assert property ($right(z, 3) == 0);
 endmodule