Repository: phanrahan/magma (language: python)

Issue:
Extend set_index for N-D Arrays
- Add suppport for N-dimensional indices.
- Add more information to assertion messages.


Discussion / hints:

Fix patch:
diff --git a/magma/primitives/set_index.py b/magma/primitives/set_index.py
index b6cc4fdf7..d416da9b4 100644
--- a/magma/primitives/set_index.py
+++ b/magma/primitives/set_index.py
@@ -1,28 +1,45 @@
 from magma.array import Array
+from collections.abc import Sequence
+from typing import Union, Sequence as Sequnce_T
+
 from magma.bits import UInt
 from magma.bitutils import clog2
 from magma.primitives import mux
 
 
-def set_index(target: Array, value, idx: UInt):
+def set_index(target: Array, value, idx: Union[UInt, Sequnce_T[UInt]]):
     """
-    Returns a new value where index `idx` of value `target` is set to `value`
-    * `target` - a value of type `Array[N, T]`
+    Returns a new value where index `idx` of `target` is set to `value`
+    * `target` - a value of type `Array[N, T]` or `Array[(N, M, L, ...), T]`
     * `value` - a value of type `T`
-    * `idx` - a value of type `UInt[clog2(N)]`
+    * `idx` - a value of type `UInt[clog2(N)]` or `(UInt[clog2(L)],
+              UInt[clog2(M), UInt[clog2(N)], ...)`
+    NOTE(rkshthrmsh): Ordering of indices in `idx` is reverse of ordering in
+                      N-D Array definition.
+    For more details see: https://github.com/phanrahan/magma/issues/1310.
     """
     if not isinstance(target, Array):
         raise TypeError("Expected target to be an array")
-    target_T = type(target)
-    if not isinstance(value, target_T.T):
-        raise TypeError(
-            "Expected value to be the same type as `target`'s contents")
-    if not isinstance(idx, UInt):
-        raise TypeError("Expected `idx` to be a UInt")
-    if len(idx) != clog2(len(target_T)):
-        raise TypeError(
-            "Expected number of `idx` bits to map to the length of `target`")
 
-    return target_T([
-        mux([elem, value], idx == i) for i, elem in enumerate(target)
-    ])
+    if isinstance(idx, UInt):
+        target_T = type(target)
+        if not isinstance(value, target_T.T):
+            raise TypeError(
+                f"Expected `value` ({type(value)}) to be the same type as"
+                f"`target`'s contents ({target_T.T})"
+            )
+        if len(idx) != clog2(len(target_T)):
+            raise TypeError(
+                f"Expected number of `idx` ({len(idx)}) bits to map to the "
+                f"length of `target` ({clog2(len(target_T))})")
+        return target_T([
+            mux([elem, value], idx == i) for i, elem in enumerate(target)
+        ])
+    if isinstance(idx, Sequence):
+        if len(idx) == 1:
+            return set_index(target, value, idx[0])
+        return set_index(target, set_index(target[idx[0]], value, idx[1:]),
+                         idx[0])
+    raise TypeError(
+        f"Expected `idx` ({type(idx)}) to be UInt or List[UInt, ...] "
+    )


Test patch:
diff --git a/tests/test_module_linking.py b/tests/test_module_linking.py
index 7ebdf87c1..2a9dc3f3f 100644
--- a/tests/test_module_linking.py
+++ b/tests/test_module_linking.py
@@ -67,7 +67,8 @@ def _run_file_check(basename):
 
 
 @_wrap_with_clear_link_info
-@pytest.mark.parametrize("output", ("coreir", "mlir"))
+# @pytest.mark.parametrize("output", ("coreir", "mlir"))
+@pytest.mark.parametrize("output", ("mlir", ))
 def test_only_default(output):
     m.link_default_module(_BinOpInterface, _OrImpl)
     assert m.linking.has_default_linked_module(_BinOpInterface)
@@ -86,7 +87,7 @@ def test_only_default(output):
 
 
 @_wrap_with_clear_link_info
-@pytest.mark.parametrize("output", ("coreir", "mlir"))
+@pytest.mark.parametrize("output", ("mlir", ))
 def test_linked_modules_no_default(output):
     m.link_module(_BinOpInterface, "OR", _OrImpl)
     m.link_module(_BinOpInterface, "AND", _AndImpl)
@@ -111,7 +112,7 @@ def test_linked_modules_no_default(output):
 
 
 @_wrap_with_clear_link_info
-@pytest.mark.parametrize("output", ("coreir", "mlir"))
+@pytest.mark.parametrize("output", ("mlir", ))
 def test_linked_modules_with_default(output):
     m.link_module(_BinOpInterface, "OR", _OrImpl)
     m.link_module(_BinOpInterface, "AND", _AndImpl)
diff --git a/tests/test_primitives/test_set_index.py b/tests/test_primitives/test_set_index.py
index 92dd66a59..d6bf67275 100644
--- a/tests/test_primitives/test_set_index.py
+++ b/tests/test_primitives/test_set_index.py
@@ -52,3 +52,40 @@ class test_set_index_array(m.Circuit):
                            directory=os.path.join(os.path.dirname(__file__),
                                                   "build"),
                            flags=["-Wno-unused"])
+
+
+def test_set_ndindex_array():
+    class test_set_ndindex_array(m.Circuit):
+        io = m.IO(I=m.In(m.Array[(2, 2, 2), m.Bits[4]]),
+            val=m.In(m.Bits[4]),
+            idx_z=m.In(m.UInt[1]),
+            idx_y=m.In(m.UInt[1]),
+            idx_x=m.In(m.UInt[1]),
+            O=m.Out(m.Array[(2, 2, 2), m.Bits[4]]))
+        io.O @= m.set_index(io.I, io.val, [io.idx_z, io.idx_y, io.idx_x])
+
+    tester = fault.Tester(test_set_ndindex_array)
+    for i in range(5):
+        tester.circuit.I = I = [
+                                [
+                                    [BitVector.random(4), BitVector.random(4)], 
+                                    [BitVector.random(4), BitVector.random(4)]
+                                ],
+                                [
+                                    [BitVector.random(4), BitVector.random(4)],
+                                    [BitVector.random(4), BitVector.random(4)]
+                                ]
+                               ]
+        tester.circuit.val = val = BitVector.random(4)
+        tester.circuit.idx_z = idx_z = BitVector.random(1)
+        tester.circuit.idx_y = idx_y = BitVector.random(1)
+        tester.circuit.idx_x = idx_x = BitVector.random(1)
+        I[int(idx_z)][int(idx_y)][int(idx_x)] = val
+        tester.eval()
+        tester.circuit.O.expect(I)
+
+    m.compile("build/test_set_ndindex_array", test_set_ndindex_array)
+    tester.compile_and_run("verilator", skip_compile=True,
+                           directory=os.path.join(os.path.dirname(__file__),
+                                                  "build"),
+                           flags=["-Wno-unused"])