add experimental feedback pipe test
authorLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Wed, 7 Aug 2019 02:20:56 +0000 (03:20 +0100)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Wed, 7 Aug 2019 02:20:56 +0000 (03:20 +0100)
src/nmutil/multipipe.py
src/nmutil/test/test_inout_feedback_pipe.py [new file with mode: 0644]

index c0d9127c37b8feab0ed79a1e4033e7a5d06b1f38..c0cc90992cc58ff2152f653e1f1540009931fe45 100644 (file)
@@ -26,7 +26,7 @@ from .iocontrol import NextControl, PrevControl
 class MultiInControlBase(Elaboratable):
     """ Common functions for Pipeline API
     """
-    def __init__(self, in_multi=None, p_len=1, maskwid=0):
+    def __init__(self, in_multi=None, p_len=1, maskwid=0, routemask=False):
         """ Multi-input Control class.  Conforms to same API as ControlBase...
             mostly.  has additional indices to the *multiple* input stages
 
@@ -37,12 +37,17 @@ class MultiInControlBase(Elaboratable):
             * add data_i members to PrevControl and
             * add data_o member  to NextControl
         """
+        self.routemask = routemask
         # set up input and output IO ACK (prev/next ready/valid)
         print ("multi_in", maskwid, p_len)
         p = []
         for i in range(p_len):
             p.append(PrevControl(in_multi, maskwid=maskwid))
         self.p = Array(p)
+        if routemask:
+            nmaskwid = maskwid # straight route mask mode
+        else:
+            nmaskwid = maskwid * p_len # fan-in mode
         self.n = NextControl(maskwid=maskwid*p_len) # masks fan in (Cat)
 
     def connect_to_next(self, nxt, p_idx=0):
@@ -90,7 +95,7 @@ class MultiInControlBase(Elaboratable):
 class MultiOutControlBase(Elaboratable):
     """ Common functions for Pipeline API
     """
-    def __init__(self, n_len=1, in_multi=None, maskwid=0):
+    def __init__(self, n_len=1, in_multi=None, maskwid=0, routemask=False):
         """ Multi-output Control class.  Conforms to same API as ControlBase...
             mostly.  has additional indices to the multiple *output* stages
             [MultiInControlBase has multiple *input* stages]
@@ -103,8 +108,13 @@ class MultiOutControlBase(Elaboratable):
             * add data_o members to NextControl
         """
 
+        if routemask:
+            nmaskwid = maskwid # straight route mask mode
+        else:
+            nmaskwid = maskwid * n_len # fan-out mode
+
         # set up input and output IO ACK (prev/next ready/valid)
-        self.p = PrevControl(in_multi, maskwid=maskwid*n_len) # masks fan out
+        self.p = PrevControl(in_multi, maskwid=nmaskwid) 
         n = []
         for i in range(n_len):
             n.append(NextControl(maskwid=maskwid))
@@ -159,10 +169,12 @@ class CombMultiOutPipeline(MultiOutControlBase):
         n.data_o : stage output data array.       shaped according to ospec
     """
 
-    def __init__(self, stage, n_len, n_mux, maskwid=0):
-        MultiOutControlBase.__init__(self, n_len=n_len, maskwid=maskwid)
+    def __init__(self, stage, n_len, n_mux, maskwid=0, routemask=False):
+        MultiOutControlBase.__init__(self, n_len=n_len, maskwid=maskwid,
+                                            routemask=routemask)
         self.stage = stage
         self.maskwid = maskwid
+        self.routemask = routemask
         self.n_mux = n_mux
 
         # set up the input and output data
@@ -212,16 +224,22 @@ class CombMultiOutPipeline(MultiOutControlBase):
             m.d.comb += eq(r_data, self.p.data_i)
         m.d.comb += eq(self.n[muxid].data_o, self.process(r_data))
 
-        # conditionally fan-out mask bits, always fan-out stop bits
         if self.maskwid:
-            ml = [] # accumulate output masks
-            ms = [] # accumulate output masks
-            for i in range(len(self.n)):
-                ml.append(self.n[i].mask_o)
-                ms.append(self.n[i].stop_o)
-            m.d.comb += Cat(*ms).eq(self.p.stop_i)
-            with m.If(pv):
-                m.d.comb += Cat(*ml).eq(self.p.mask_i)
+            if self.routemask: # straight "routing" mode - treat like data
+                m.d.comb += self.n[muxid].stop_o.eq(self.p.stop_i)
+                with m.If(pv):
+                    m.d.comb += self.n[muxid].mask_o.eq(self.p.mask_i)
+            else:
+                ml = [] # accumulate output masks
+                ms = [] # accumulate output stops
+                # fan-out mode.
+                # conditionally fan-out mask bits, always fan-out stop bits
+                for i in range(len(self.n)):
+                    ml.append(self.n[i].mask_o)
+                    ms.append(self.n[i].stop_o)
+                m.d.comb += Cat(*ms).eq(self.p.stop_i)
+                with m.If(pv):
+                    m.d.comb += Cat(*ml).eq(self.p.mask_i)
         return m
 
 
@@ -240,8 +258,9 @@ class CombMultiInPipeline(MultiInControlBase):
             SYNCHRONOUSLY.
     """
 
-    def __init__(self, stage, p_len, p_mux, maskwid=0):
-        MultiInControlBase.__init__(self, p_len=p_len, maskwid=maskwid)
+    def __init__(self, stage, p_len, p_mux, maskwid=0, routemask=False):
+        MultiInControlBase.__init__(self, p_len=p_len, maskwid=maskwid,
+                                          routemask=routemask)
         self.stage = stage
         self.maskwid = maskwid
         self.p_mux = p_mux
@@ -303,22 +322,31 @@ class CombMultiInPipeline(MultiInControlBase):
         m.d.comb += data_valid[mid].eq(p_valid_i[mid] | \
                                     (n_ready_in[mid] & data_valid[mid]))
 
-        ml = [] # accumulate output masks
-        ms = [] # accumulate output stops
-        for i in range(p_len):
-            vr = Signal(reset_less=True)
-            m.d.comb += vr.eq(self.p[i].valid_i & self.p[i].ready_o)
-            with m.If(vr):
-                m.d.comb += eq(r_data[i], self.p[i].data_i)
+        if self.routemask:
+            m.d.comb += eq(self.n.stop_o, self.p[mid].stop_i)
+            for i in range(p_len):
+                m.d.comb += eq(self.n.stop_o, self.p[i].stop_i)
+                vr = Signal(reset_less=True)
+                m.d.comb += vr.eq(self.p[i].valid_i & self.p[i].ready_o)
+                with m.If(vr):
+                    m.d.comb += eq(self.n.mask_o, self.p[mid].mask_i)
+        else:
+            ml = [] # accumulate output masks
+            ms = [] # accumulate output stops
+            for i in range(p_len):
+                vr = Signal(reset_less=True)
+                m.d.comb += vr.eq(self.p[i].valid_i & self.p[i].ready_o)
+                with m.If(vr):
+                    m.d.comb += eq(r_data[i], self.p[i].data_i)
+                if self.maskwid:
+                    mlen = len(self.p[i].mask_i)
+                    s = mlen*i
+                    e = mlen*(i+1)
+                    ml.append(Mux(vr, self.p[i].mask_i, Const(0, mlen)))
+                    ms.append(self.p[i].stop_i)
             if self.maskwid:
-                mlen = len(self.p[i].mask_i)
-                s = mlen*i
-                e = mlen*(i+1)
-                ml.append(Mux(vr, self.p[i].mask_i, Const(0, mlen)))
-                ms.append(self.p[i].stop_i)
-        if self.maskwid:
-            m.d.comb += self.n.mask_o.eq(Cat(*ml))
-            m.d.comb += self.n.stop_o.eq(Cat(*ms))
+                m.d.comb += self.n.mask_o.eq(Cat(*ml))
+                m.d.comb += self.n.stop_o.eq(Cat(*ms))
 
         m.d.comb += eq(self.n.data_o, self.process(r_data[mid]))
 
@@ -326,14 +354,18 @@ class CombMultiInPipeline(MultiInControlBase):
 
 
 class CombMuxOutPipe(CombMultiOutPipeline):
-    def __init__(self, stage, n_len, maskwid=0):
+    def __init__(self, stage, n_len, maskwid=0, muxidname=None,
+                                     routemask=False):
+        muxidname = muxidname or "muxid"
         # HACK: stage is also the n-way multiplexer
         CombMultiOutPipeline.__init__(self, stage, n_len=n_len,
-                                            n_mux=stage, maskwid=maskwid)
+                                            n_mux=stage, maskwid=maskwid,
+                                            routemask=routemask)
 
         # HACK: n-mux is also the stage... so set the muxid equal to input muxid
-        print ("combmuxout", self.p.data_i.muxid)
-        stage.m_id = self.p.data_i.muxid
+        muxid = getattr(self.p.data_i, muxidname)
+        print ("combmuxout", muxidname, muxid)
+        stage.m_id = muxid
 
 
 
@@ -376,10 +408,10 @@ class PriorityCombMuxInPipe(CombMultiInPipeline):
     """ an example of how to use the combinatorial pipeline.
     """
 
-    def __init__(self, stage, p_len=2, maskwid=0):
+    def __init__(self, stage, p_len=2, maskwid=0, routemask=False):
         p_mux = InputPriorityArbiter(self, p_len)
         CombMultiInPipeline.__init__(self, stage, p_len, p_mux,
-                                     maskwid=maskwid)
+                                     maskwid=maskwid, routemask=routemask)
 
 
 if __name__ == '__main__':
diff --git a/src/nmutil/test/test_inout_feedback_pipe.py b/src/nmutil/test/test_inout_feedback_pipe.py
new file mode 100644 (file)
index 0000000..31d051e
--- /dev/null
@@ -0,0 +1,283 @@
+""" key strategic example showing how to do multi-input fan-in into a
+    multi-stage pipeline, then multi-output fanout, with an unary muxid
+    and cancellation
+
+    the multiplex ID from the fan-in is passed in to the pipeline, preserved,
+    and used as a routing ID on the fanout.
+"""
+
+from random import randint
+from math import log
+from nmigen import Module, Signal, Cat, Value, Elaboratable
+from nmigen.compat.sim import run_simulation
+from nmigen.cli import verilog, rtlil
+
+from nmutil.multipipe import CombMultiOutPipeline, CombMuxOutPipe
+from nmutil.multipipe import PriorityCombMuxInPipe
+from nmutil.singlepipe import MaskCancellable, RecordObject, Object
+
+
+class PassData(Object):
+    def __init__(self):
+        Object.__init__(self)
+        self.muxid = Signal(2, reset_less=True)
+        self.idx = Signal(8, reset_less=True)
+        self.data = Signal(16, reset_less=True)
+        self.operator = Signal(2, reset_less=True)
+        self.routeid = Signal(2, reset_less=True) # muxidname
+
+
+class PassThroughStage:
+    def __init__(self):
+        self.o = self.ospec()
+    def ispec(self):
+        return PassData()
+    def ospec(self):
+        return self.ispec() # same as ospec
+    def setup(self, m, i):
+        comb = m.d.comb
+        #comb += self.o.eq(i)
+    def process(self, i):
+        return i
+
+
+class SplitRouteStage:
+    def __init__(self):
+        self.o = self.ospec()
+
+    def ispec(self):
+        return PassData()
+    def ospec(self):
+        return self.ispec() # same as ospec
+
+    def setup(self, m, i):
+        comb = m.d.comb
+        comb += self.o.eq(i)
+        with m.If(i.operator == 0):
+            #comb += self.o.routeid.eq(1) # selects 2nd output in CombMuxOutPipe
+            comb += self.o.data.eq(i.data + 1) # add 1 to say "we did it"
+            comb += self.o.operator.eq(1) # don't get into infinite loop
+        with m.Else():
+            comb += self.o.routeid.eq(0) # selects 2nd output in CombMuxOutPipe
+
+    def process(self, i):
+        return self.o
+
+
+class RouteBackPipe(CombMuxOutPipe):
+    """ routes data back to start of pipeline
+    """
+    def __init__(self):
+        self.num_rows = 2
+        stage = SplitRouteStage()
+        CombMuxOutPipe.__init__(self, stage, n_len=2,
+                                maskwid=4, muxidname="routeid",
+                                routemask=True)
+
+
+class MergeRoutePipe(PriorityCombMuxInPipe):
+    """ merges data coming from end of pipe (with operator now == 1)
+    """
+    def __init__(self):
+        self.num_rows = 2
+        stage = PassThroughStage()
+        PriorityCombMuxInPipe.__init__(self, stage, p_len=2, maskwid=4,
+                                        routemask=True)
+
+
+
+class PassThroughPipe(MaskCancellable):
+    def __init__(self, maskwid):
+        MaskCancellable.__init__(self, PassThroughStage(), maskwid)
+
+
+class InputTest:
+    def __init__(self, dut, tlen):
+        self.dut = dut
+        self.di = {}
+        self.do = {}
+        self.sent = {}
+        self.tlen = tlen
+        for muxid in range(dut.num_rows):
+            self.di[muxid] = {}
+            self.do[muxid] = {}
+            self.sent[muxid] = []
+            for i in range(self.tlen):
+                self.di[muxid][i] = randint(0, 255) + (muxid<<8)
+                self.do[muxid][i] = self.di[muxid][i]
+
+    def send(self, muxid):
+        for i in range(self.tlen):
+            op2 = self.di[muxid][i]
+            rs = self.dut.p[muxid]
+            yield rs.valid_i.eq(1)
+            yield rs.data_i.data.eq(op2)
+            yield rs.data_i.idx.eq(i)
+            yield rs.data_i.muxid.eq(muxid)
+            yield rs.mask_i.eq(1)
+            yield
+            o_p_ready = yield rs.ready_o
+            while not o_p_ready:
+                yield
+                o_p_ready = yield rs.ready_o
+
+            print ("send", muxid, i, hex(op2), op2)
+            self.sent[muxid].append(i)
+
+            yield rs.valid_i.eq(0)
+            yield rs.mask_i.eq(0)
+            # wait until it's received
+            while i in self.do[muxid]:
+                yield
+
+            # wait random period of time before queueing another value
+            for i in range(randint(0, 3)):
+                yield
+
+        yield rs.valid_i.eq(0)
+        yield
+
+        print ("send ended", muxid)
+
+        ## wait random period of time before queueing another value
+        #for i in range(randint(0, 3)):
+        #    yield
+
+        #send_range = randint(0, 3)
+        #if send_range == 0:
+        #    send = True
+        #else:
+        #    send = randint(0, send_range) != 0
+
+    def rcv(self, muxid):
+        rs = self.dut.p[muxid]
+        while True:
+
+            # check cancellation
+            if False and self.sent[muxid] and randint(0, 2) == 0:
+                todel = self.sent[muxid].pop()
+                print ("to delete", muxid, self.sent[muxid], todel)
+                if todel in self.do[muxid]:
+                    del self.do[muxid][todel]
+                    yield rs.stop_i.eq(1)
+                print ("left", muxid, self.do[muxid])
+                if len(self.do[muxid]) == 0:
+                    break
+
+            stall_range = randint(0, 3)
+            for j in range(randint(1,10)):
+                stall = randint(0, stall_range) != 0
+                yield self.dut.n[0].ready_i.eq(stall)
+                yield
+
+            n = self.dut.n[muxid]
+            yield n.ready_i.eq(1)
+            yield
+            yield rs.stop_i.eq(0) # resets cancel mask
+            o_n_valid = yield n.valid_o
+            i_n_ready = yield n.ready_i
+            if not o_n_valid or not i_n_ready:
+                continue
+
+            out_muxid = yield n.data_o.muxid
+            out_i = yield n.data_o.idx
+            out_v = yield n.data_o.data
+
+            print ("recv", out_muxid, out_i, hex(out_v), out_v)
+
+            # see if this output has occurred already, delete it if it has
+            assert muxid == out_muxid, \
+                    "out_muxid %d not correct %d" % (out_muxid, muxid)
+            if out_i not in self.sent[muxid]:
+                print ("cancelled/recv", muxid, out_i)
+                continue
+            assert out_i in self.do[muxid], "out_i %d not in array %s" % \
+                                          (out_i, repr(self.do[muxid]))
+            assert self.do[muxid][out_i] == out_v + 1 # check data
+            del self.do[muxid][out_i]
+            todel = self.sent[muxid].index(out_i)
+            del self.sent[muxid][todel]
+
+            # check if there's any more outputs
+            if len(self.do[muxid]) == 0:
+                break
+
+        print ("recv ended", muxid)
+
+
+class TestPriorityMuxPipe(PriorityCombMuxInPipe):
+    def __init__(self, num_rows):
+        self.num_rows = num_rows
+        stage = PassThroughStage()
+        PriorityCombMuxInPipe.__init__(self, stage,
+                                       p_len=self.num_rows, maskwid=1)
+
+
+class TestMuxOutPipe(CombMuxOutPipe):
+    def __init__(self, num_rows):
+        self.num_rows = num_rows
+        stage = PassThroughStage()
+        CombMuxOutPipe.__init__(self, stage, n_len=self.num_rows,
+                                maskwid=1)
+
+
+class TestInOutPipe(Elaboratable):
+    def __init__(self, num_rows=4):
+        self.num_rows = nr = num_rows
+        self.inpipe = TestPriorityMuxPipe(nr) # fan-in (combinatorial)
+        self.mergein = MergeRoutePipe()       # merge in feedback
+        self.pipe1 = PassThroughPipe(nr)      # stage 1 (clock-sync)
+        self.pipe2 = PassThroughPipe(nr)      # stage 2 (clock-sync)
+        #self.pipe3 = PassThroughPipe(nr)      # stage 3 (clock-sync)
+        #self.pipe4 = PassThroughPipe(nr)      # stage 4 (clock-sync)
+        self.splitback = RouteBackPipe()      # split back to mergein
+        self.outpipe = TestMuxOutPipe(nr)     # fan-out (combinatorial)
+
+        self.p = self.inpipe.p  # kinda annoying,
+        self.n = self.outpipe.n # use pipe in/out as this class in/out
+        self._ports = self.inpipe.ports() + self.outpipe.ports()
+
+    def elaborate(self, platform):
+        m = Module()
+        m.submodules.inpipe = self.inpipe
+        m.submodules.mergein = self.mergein
+        m.submodules.pipe1 = self.pipe1
+        m.submodules.pipe2 = self.pipe2
+        #m.submodules.pipe3 = self.pipe3
+        #m.submodules.pipe4 = self.pipe4
+        m.submodules.splitback = self.splitback
+        m.submodules.outpipe = self.outpipe
+
+        m.d.comb += self.inpipe.n.connect_to_next(self.mergein.p[0])
+        m.d.comb += self.mergein.n.connect_to_next(self.pipe1.p)
+        m.d.comb += self.pipe1.connect_to_next(self.pipe2)
+        #m.d.comb += self.pipe2.connect_to_next(self.pipe3)
+        #m.d.comb += self.pipe3.connect_to_next(self.pipe4)
+        m.d.comb += self.pipe2.connect_to_next(self.splitback)
+        m.d.comb += self.splitback.n[1].connect_to_next(self.mergein.p[1])
+        m.d.comb += self.splitback.n[0].connect_to_next(self.outpipe.p)
+
+        return m
+
+    def ports(self):
+        return self._ports
+
+
+def test1():
+    dut = TestInOutPipe()
+    vl = rtlil.convert(dut, ports=dut.ports())
+    with open("test_inoutmux_feedback_pipe.il", "w") as f:
+        f.write(vl)
+
+    tlen = 20
+
+    test = InputTest(dut, tlen)
+    run_simulation(dut, [test.rcv(1), test.rcv(0),
+                         test.rcv(3), test.rcv(2),
+                         test.send(0), test.send(1),
+                         test.send(3), test.send(2),
+                        ],
+                   vcd_name="test_inoutmux_feedback_pipe.vcd")
+
+if __name__ == '__main__':
+    test1()