adapt/debug SimdSignal when using ElwidPartType
authorLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Mon, 25 Oct 2021 12:29:51 +0000 (13:29 +0100)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Mon, 25 Oct 2021 12:29:51 +0000 (13:29 +0100)
example mini-test with test_partsig_scope.py working through issues

src/ieee754/part/partsig.py
src/ieee754/part/test/test_partsig_scope.py [new file with mode: 0644]

index f8c497c883f75e6c92b5572a72417bcf83d05b48..343aca58b2f58aea10f6e9eb0c0f9b60f374ecbb 100644 (file)
@@ -89,7 +89,7 @@ class PartType:  # TODO decide name
 # function and this class then "understands" the relationship
 # between elwidth and the PartitionPoints that were created
 # by layout()
-class ElWidthPartType:  # TODO decide name
+class ElwidPartType:  # TODO decide name
     def __init__(self, psig):
         self.psig = psig
 
@@ -120,7 +120,7 @@ class SimdShape(Shape):
                               fixed_width=None): # fixed overall width
         widths_at_elwid = width
         # this check is done inside layout but do it again here anyway
-        assert fixed_width == None and widths_at_elwidth == None, \
+        assert fixed_width == None and widths_at_elwid == None, \
             "both width (widths_at_elwid) and fixed_width cannot be None"
         (pp, bitp, lpoints, bmask, fixed_width, lane_shapes, part_wid) = \
             layout(scope.elwid,
@@ -143,17 +143,20 @@ class SimdSignal(UserValue):
     # XXX ################################################### XXX
     # XXX Keep these functions in the same order as ast.Value XXX
     # XXX ################################################### XXX
-    def __init__(self, mask, *args, src_loc_at=0, **kwargs):
+    def __init__(self, mask, shape=None, *args, src_loc_at=0, **kwargs):
         super().__init__(src_loc_at=src_loc_at)
-        self.sig = Signal(*args, **kwargs)
-        width = len(self.sig)  # get signal width
         # create partition points
-        if False: # isinstance(mask, SimdScope): # mask parameter is a SimdScope
-            self.ptype = ElwidPartType(self, scope=mask)
-            # parse the args, get elwid from SimdMode,
-            # get module as well, call self.set_module(mask.module)
+        if isinstance(mask, SimdScope): # mask parameter is a SimdScope
+            self.ptype = ElwidPartType(self)
+            # adapt shape to a SimdShape
+            if not isinstance(shape, SimdShape):
+                shape = SimdShape(mask, shape)
+            self.sig = Signal(shape, *args, **kwargs)
+            # get partpoints from SimdShape
             self.partpoints = ptype.partpoints
         else:
+            self.sig = Signal(shape, *args, **kwargs)
+            width = len(self.sig)  # get signal width
             if isinstance(mask, PartitionPoints):
                 self.partpoints = mask
             else:
diff --git a/src/ieee754/part/test/test_partsig_scope.py b/src/ieee754/part/test/test_partsig_scope.py
new file mode 100644 (file)
index 0000000..19ae956
--- /dev/null
@@ -0,0 +1,146 @@
+#!/usr/bin/env python3
+# SPDX-License-Identifier: LGPL-2.1-or-later
+# See Notices.txt for copyright information
+
+from nmigen import Signal, Module, Elaboratable, Mux, Cat, Shape, Repl
+from nmigen.back.pysim import Simulator, Delay, Settle
+from nmigen.cli import rtlil
+
+from ieee754.part.partsig import SimdSignal
+from ieee754.part.simd_scope import SimdScope
+
+from random import randint
+import unittest
+import itertools
+import math
+
+
+def create_ilang(dut, traces, test_name):
+    vl = rtlil.convert(dut, ports=traces)
+    with open("%s.il" % test_name, "w") as f:
+        f.write(vl)
+
+
+def create_simulator(module, traces, test_name):
+    create_ilang(module, traces, test_name)
+    return Simulator(module)
+
+
+
+class TestCatMod(Elaboratable):
+    def __init__(self, width, elwid, vec_el_counts):
+        self.m = Module()
+        with SimdScope(self.m, elwid, vec_el_counts) as s:
+            self.a = s.Signal(width)
+            self.b = s.Signal(width*2)
+            self.o = s.Signal(width*3)
+        self.cat_out = self.o.sig
+
+    def elaborate(self, platform):
+        m = Module()
+        comb = m.d.comb
+
+        comb += self.o.eq(Cat(self.a, self.b))
+
+        return m
+
+
+class TestCat(unittest.TestCase):
+    def test(self):
+        width = 16
+        elwid = Signal(2)  # elwid parameter
+        vec_el_counts = {0b00: 1, 0b01: 2, 0b10: 4}
+        module = TestCatMod(width, elwid, vec_el_counts)
+
+        test_name = "part_sig_cat_scope"
+        traces = [elwid,
+                  module.a.sig,
+                  module.b.sig,
+                  module.cat_out]
+        sim = create_simulator(module, traces, test_name)
+
+        # annoying recursive import issue
+        from ieee754.part_cat.cat import get_runlengths
+
+        def async_process():
+
+            def test_catop(msg_prefix):
+                # define lengths of a/b test input
+                alen, blen = 16, 32
+                # pairs of test values a, b
+                for a, b in [(0x0000, 0x00000000),
+                             (0xDCBA, 0x12345678),
+                             (0xABCD, 0x01234567),
+                             (0xFFFF, 0x0000),
+                             (0x0000, 0x0000),
+                             (0x1F1F, 0xF1F1F1F1),
+                             (0x0000, 0xFFFFFFFF)]:
+
+                    # convert a and b to partitions
+                    apart, bpart = [], []
+                    ajump, bjump = alen // 4, blen // 4
+                    for i in range(4):
+                        apart.append((a >> (ajump*i) & ((1<<ajump)-1)))
+                        bpart.append((b >> (bjump*i) & ((1<<bjump)-1)))
+
+                    print ("apart bpart", hex(a), hex(b),
+                            list(map(hex, apart)), list(map(hex, bpart)))
+
+                    yield module.a.lower().eq(a)
+                    yield module.b.lower().eq(b)
+                    yield Delay(0.1e-6)
+
+                    y = 0
+                    # work out the runlengths for this mask.
+                    # 0b011 returns [1,1,2] (for a mask of length 3)
+                    mval = yield elwid
+                    runlengths = get_runlengths(mval, 3)
+                    j = 0
+                    ai = 0
+                    bi = 0
+                    for i in runlengths:
+                        # a first
+                        for _ in range(i):
+                            print ("runlength", i,
+                                   "ai", ai,
+                                   "apart", hex(apart[ai]),
+                                   "j", j)
+                            y |= apart[ai] << j
+                            print ("    y", hex(y))
+                            j += ajump
+                            ai += 1
+                        # now b
+                        for _ in range(i):
+                            print ("runlength", i,
+                                   "bi", bi,
+                                   "bpart", hex(bpart[bi]),
+                                   "j", j)
+                            y |= bpart[bi] << j
+                            print ("    y", hex(y))
+                            j += bjump
+                            bi += 1
+
+                    # check the result
+                    outval = (yield module.cat_out)
+                    msg = f"{msg_prefix}: cat " + \
+                        f"0x{mval:X} 0x{a:X} : 0x{b:X}" + \
+                        f" => 0x{y:X} != 0x{outval:X}"
+                    self.assertEqual(y, outval, msg)
+
+            yield elwid.eq(0b00)
+            yield from test_catop("16-bit")
+            yield elwid.eq(0b01)
+            yield from test_catop("8-bit")
+            yield elwid.eq(0b10)
+            yield from test_catop("4-bit")
+
+        sim.add_process(async_process)
+        with sim.write_vcd(
+                vcd_file=open(test_name + ".vcd", "w"),
+                gtkw_file=open(test_name + ".gtkw", "w"),
+                traces=traces):
+            sim.run()
+
+
+if __name__ == '__main__':
+    unittest.main()