verilog: fix dynamic dynamic range asgn elab
authorZachary Snow <zach@zachjs.com>
Tue, 18 Jan 2022 06:18:12 +0000 (23:18 -0700)
committerZachary Snow <zachary.j.snow@gmail.com>
Fri, 11 Feb 2022 21:54:55 +0000 (22:54 +0100)
CHANGELOG
frontends/ast/simplify.cc
tests/verilog/dynamic_range_lhs.sh [new file with mode: 0755]
tests/verilog/dynamic_range_lhs.v [new file with mode: 0644]

index 4312ec7ee58ee86b2653789540d740cac80fe4e3..46ce01699b216e5f4cb16ee4fd268a3e0bb8c693 100644 (file)
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -8,6 +8,8 @@ Yosys 0.14 .. Yosys 0.14-dev
  * Verilog
     - Fixed evaluation of constant functions with variables or arguments with
       reversed dimensions
+    - Fixed elaboration of dynamic range assignments where the vector is
+      reversed or is not zero-indexed
 
 Yosys 0.13 .. Yosys 0.14
 --------------------------
index 524ae6318673f86712f10672c2d28784dcd93766..000877612926c60698ec135d0f859f9c0fec4f6e 100644 (file)
@@ -2704,6 +2704,18 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                        while (wire_data->simplify(true, false, false, 1, -1, false, false)) { }
                        current_ast_mod->children.push_back(wire_data);
 
+                       int shamt_width_hint = -1;
+                       bool shamt_sign_hint = true;
+                       shift_expr->detectSignWidth(shamt_width_hint, shamt_sign_hint);
+
+                       AstNode *wire_sel = new AstNode(AST_WIRE, new AstNode(AST_RANGE, mkconst_int(shamt_width_hint-1, true), mkconst_int(0, true)));
+                       wire_sel->str = stringf("$bitselwrite$sel$%s:%d$%d", filename.c_str(), location.first_line, autoidx++);
+                       wire_sel->attributes[ID::nosync] = AstNode::mkconst_int(1, false);
+                       wire_sel->is_logic = true;
+                       wire_sel->is_signed = shamt_sign_hint;
+                       while (wire_sel->simplify(true, false, false, 1, -1, false, false)) { }
+                       current_ast_mod->children.push_back(wire_sel);
+
                        did_something = true;
                        newNode = new AstNode(AST_BLOCK);
 
@@ -2720,39 +2732,44 @@ bool AstNode::simplify(bool const_fold, bool at_zero, bool in_lvalue, int stage,
                        ref_data->id2ast = wire_data;
                        ref_data->was_checked = true;
 
+                       AstNode *ref_sel = new AstNode(AST_IDENTIFIER);
+                       ref_sel->str = wire_sel->str;
+                       ref_sel->id2ast = wire_sel;
+                       ref_sel->was_checked = true;
+
                        AstNode *old_data = lvalue->clone();
                        if (type == AST_ASSIGN_LE)
                                old_data->lookahead = true;
 
-                       AstNode *shamt = shift_expr;
+                       AstNode *s = new AstNode(AST_ASSIGN_EQ, ref_sel->clone(), shift_expr);
+                       newNode->children.push_back(s);
 
-                       int shamt_width_hint = 0;
-                       bool shamt_sign_hint = true;
-                       shamt->detectSignWidth(shamt_width_hint, shamt_sign_hint);
+                       AstNode *shamt = ref_sel;
 
+                       // convert to signed while preserving the sign and value
+                       shamt = new AstNode(AST_CAST_SIZE, mkconst_int(shamt_width_hint + 1, true), shamt);
+                       shamt = new AstNode(AST_TO_SIGNED, shamt);
+
+                       // offset the shift amount by the lower bound of the dimension
                        int start_bit = children[0]->id2ast->range_right;
-                       bool use_shift = shamt_sign_hint;
+                       shamt = new AstNode(AST_SUB, shamt, mkconst_int(start_bit, true));
 
-                       if (start_bit != 0) {
-                               shamt = new AstNode(AST_SUB, shamt, mkconst_int(start_bit, true));
-                               use_shift = true;
-                       }
+                       // reflect the shift amount if the dimension is swapped
+                       if (children[0]->id2ast->range_swapped)
+                               shamt = new AstNode(AST_SUB, mkconst_int(source_width - result_width, true), shamt);
+
+                       // AST_SHIFT uses negative amounts for shifting left
+                       shamt = new AstNode(AST_NEG, shamt);
 
                        AstNode *t;
 
                        t = mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false);
-                       if (use_shift)
-                               t = new AstNode(AST_SHIFT, t, new AstNode(AST_NEG, shamt->clone()));
-                       else
-                               t = new AstNode(AST_SHIFT_LEFT, t, shamt->clone());
+                       t = new AstNode(AST_SHIFT, t, shamt->clone());
                        t = new AstNode(AST_ASSIGN_EQ, ref_mask->clone(), t);
                        newNode->children.push_back(t);
 
                        t = new AstNode(AST_BIT_AND, mkconst_bits(std::vector<RTLIL::State>(result_width, State::S1), false), children[1]->clone());
-                       if (use_shift)
-                               t = new AstNode(AST_SHIFT, t, new AstNode(AST_NEG, shamt));
-                       else
-                               t = new AstNode(AST_SHIFT_LEFT, t, shamt);
+                       t = new AstNode(AST_SHIFT, t, shamt);
                        t = new AstNode(AST_ASSIGN_EQ, ref_data->clone(), t);
                        newNode->children.push_back(t);
 
diff --git a/tests/verilog/dynamic_range_lhs.sh b/tests/verilog/dynamic_range_lhs.sh
new file mode 100755 (executable)
index 0000000..618204a
--- /dev/null
@@ -0,0 +1,32 @@
+#!/bin/bash
+
+run() {
+    alt=$1
+    span=$2
+    left=$3
+    right=$4
+    echo "a=$alt s=$span l=$left r=$right"
+
+    ../../yosys -q \
+        -DALT=$alt \
+        -DSPAN=$span \
+        -DLEFT=$left \
+        -DRIGHT=$right \
+        -p "read_verilog dynamic_range_lhs.v" \
+        -p "proc" \
+        -p "equiv_make gold gate equiv" \
+        -p "equiv_simple" \
+        -p "equiv_status -assert"
+}
+
+trap 'echo "ERROR in dynamic_range_lhs.sh span=$span left=$left right=$right" >&2; exit 1' ERR
+
+for alt in `seq 0 1`; do
+for span in `seq 1 4`; do
+for left in `seq -4 4`; do
+for right in `seq $(expr $left + -3) $(expr $left + 3)`; do
+    run $alt $span $left $right
+done
+done
+done
+done
diff --git a/tests/verilog/dynamic_range_lhs.v b/tests/verilog/dynamic_range_lhs.v
new file mode 100644 (file)
index 0000000..ae29137
--- /dev/null
@@ -0,0 +1,76 @@
+module gate(
+    output reg [`LEFT:`RIGHT] out_u, out_s,
+    (* nowrshmsk = `ALT *)
+    input wire data,
+    input wire [1:0] sel1, sel2
+);
+always @* begin
+    out_u = 0;
+    out_s = 0;
+    case (`SPAN)
+    1: begin
+        out_u[sel1*sel2] = data;
+        out_s[$signed(sel1*sel2)] = data;
+    end
+    2: begin
+        out_u[sel1*sel2+:2] = {data, data};
+        out_s[$signed(sel1*sel2)+:2] = {data, data};
+    end
+    3: begin
+        out_u[sel1*sel2+:3] = {data, data, data};
+        out_s[$signed(sel1*sel2)+:3] = {data, data, data};
+    end
+    4: begin
+        out_u[sel1*sel2+:4] = {data, data, data, data};
+        out_s[$signed(sel1*sel2)+:4] = {data, data, data, data};
+    end
+    endcase
+end
+endmodule
+
+module gold(
+    output reg [`LEFT:`RIGHT] out_u, out_s,
+    input wire data,
+    input wire [1:0] sel1, sel2
+);
+task set;
+    input integer a, b;
+    localparam LOW = `LEFT > `RIGHT ? `RIGHT : `LEFT;
+    localparam HIGH = `LEFT > `RIGHT ? `LEFT : `RIGHT;
+    if (LOW <= a && a <= HIGH)
+        out_u[a] = data;
+    if (LOW <= b && b <= HIGH)
+        out_s[b] = data;
+endtask
+always @* begin
+    out_u = 0;
+    out_s = 0;
+    case (sel1*sel2)
+        2'b00: set(0, 0);
+        2'b01: set(1, 1);
+        2'b10: set(2, -2);
+        2'b11: set(3, -1);
+    endcase
+    if (`SPAN >= 2)
+        case (sel1*sel2)
+            2'b00: set(1, 1);
+            2'b01: set(2, 2);
+            2'b10: set(3, -1);
+            2'b11: set(4, 0);
+        endcase
+    if (`SPAN >= 3)
+        case (sel1*sel2)
+            2'b00: set(2, 2);
+            2'b01: set(3, 3);
+            2'b10: set(4, 0);
+            2'b11: set(5, 1);
+        endcase
+    if (`SPAN >= 4)
+        case (sel1*sel2)
+            2'b00: set(3, 3);
+            2'b01: set(4, 4);
+            2'b10: set(5, 1);
+            2'b11: set(6, 2);
+        endcase
+end
+endmodule