add unit test for multi-in multi-out FPADDBasePipe
authorLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Thu, 28 Mar 2019 03:39:57 +0000 (03:39 +0000)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Thu, 28 Mar 2019 03:39:57 +0000 (03:39 +0000)
src/add/nmigen_add_experiment.py
src/add/singlepipe.py
src/add/test_buf_pipe.py
src/add/test_fpadd_pipe.py [new file with mode: 0644]

index 23f2386cbc7ba92eece293127fff7b323fcf90f7..92588762310a13acb34c906e0959a1b45d6c1df6 100644 (file)
@@ -1846,6 +1846,9 @@ class FPADDStageIn:
     def eq(self, i):
         return [self.a.eq(i.a), self.b.eq(i.b), self.mid.eq(i.mid)]
 
+    def ports(self):
+        return [self.a, self.b, self.mid]
+
 
 class FPADDStageOut:
     def __init__(self, width, id_wid):
@@ -1855,6 +1858,9 @@ class FPADDStageOut:
     def eq(self, i):
         return [self.z.eq(i.z), self.mid.eq(i.mid)]
 
+    def ports(self):
+        return [self.z, self.mid]
+
 
 # matches the format of FPADDStageOut, allows eq function to do assignments
 class PlaceHolder: pass
@@ -1878,14 +1884,23 @@ class FPAddBaseStage:
         return o
 
 
+class FPADDBasePipe1(UnbufferedPipeline):
+    def __init__(self, width, id_wid):
+        stage = FPAddBaseStage(width, id_wid)
+        UnbufferedPipeline.__init__(self, stage)
+
+
 class FPADDBasePipe(ControlBase):
     def __init__(self, width, id_wid):
         ControlBase.__init__(self)
+        self.pipe1 = FPADDBasePipe1(width, id_wid)
+        self.p.i_data = self.pipe1.stage.ispec()
+        self.n.o_data = self.pipe1.stage.ospec()
 
     def elaborate(self, platform):
         m = Module()
-        stage1 = FPAddBaseStage(width, id_wid)
-        m.d.comb += self.connect([stage1])
+        m.submodules.pipe1 = self.pipe1
+        self.connect(m, [self.pipe1])
         return m
 
 
@@ -1902,7 +1917,7 @@ class FPAddInPassThruStage:
     def __init__(self, width, id_wid):
         self.width, self.id_wid = width, id_wid
     def ispec(self): return FPADDStageIn(self.width, self.id_wid)
-    def ospec(self): return self.ospec()
+    def ospec(self): return self.ispec()
     def process(self, i): return i
 
 
@@ -1911,6 +1926,8 @@ class FPADDInMuxPipe(PriorityCombPipeline):
         self.num_rows = num_rows
         stage = FPAddInPassThruStage(width, id_width)
         PriorityCombPipeline.__init__(self, stage, p_len=self.num_rows)
+        #self.p.i_data = stage.ispec()
+        #self.n.o_data = stage.ospec()
 
     def ports(self):
         res = []
@@ -1938,7 +1955,7 @@ class FPAddOutPassThruStage:
     def __init__(self, width, id_wid):
         self.width, self.id_wid = width, id_wid
     def ispec(self): return FPADDStageOut(self.width, self.id_wid)
-    def ospec(self): return self.ospec()
+    def ospec(self): return self.ispec()
     def process(self, i): return i
 
 
@@ -1947,6 +1964,8 @@ class FPADDMuxOutPipe(MuxCombPipeline):
         self.num_rows = num_rows
         stage = FPAddOutPassThruStage(width, id_wid)
         MuxCombPipeline.__init__(self, stage, n_len=self.num_rows)
+        #self.p.i_data = stage.ispec()
+        #self.n.o_data = stage.ospec()
 
     def ports(self):
         res = [self.p.i_valid, self.p.o_ready] + \
@@ -1957,8 +1976,6 @@ class FPADDMuxOutPipe(MuxCombPipeline):
         return res
 
 
-
-
 class FPADDMuxInOut:
     """ Reservation-Station version of FPADD pipeline.
 
index 493fa553ce6aff2db08572109eb33ed31d227c23..8ae9b806a3ab474ac7687ca74f70d5d2b80b1765 100644 (file)
@@ -207,6 +207,7 @@ class NextControl:
             data/valid is passed *TO* nxt, and ready comes *IN* from nxt.
             use this when connecting stage-to-stage
         """
+        print ("connect next", self, nxt)
         return [nxt.i_valid.eq(self.o_valid),
                 self.i_ready.eq(nxt.o_ready),
                 eq(nxt.i_data, self.o_data),
@@ -251,7 +252,7 @@ def eq(o, i):
         o, i = [o], [i]
     res = []
     for (ao, ai) in zip(o, i):
-        #print ("eq", ao, ai)
+        print ("eq", ao, ai)
         if isinstance(ao, Record):
             for idx, (field_name, field_shape, _) in enumerate(ao.layout):
                 if isinstance(field_shape, Layout):
@@ -397,6 +398,7 @@ class ControlBase:
     def connect_to_next(self, nxt):
         """ helper function to connect to the next stage data/valid/ready.
         """
+        print ("ControlBase connect next", self, nxt)
         return self.n.connect_to_next(nxt.p)
 
     def _connect_in(self, prev):
@@ -451,12 +453,12 @@ class ControlBase:
 
         # connect front of chain to ourselves
         front = pipechain[0]
-        self.p.i_data = front.stage.ispec()
+        #self.p.i_data = front.stage.ispec()
         eqs += front._connect_in(self)
 
         # connect end of chain to ourselves
         end = pipechain[-1]
-        self.n.o_data = end.stage.ospec()
+        #self.n.o_data = end.stage.ospec()
         eqs += end._connect_out(self)
 
         # activate the assignments
index e483e62f37c41a8f4a08dee6026b980945c2c02e..f4764974b9f93ecddcde589a38697c7a2bd24ff2 100644 (file)
@@ -291,6 +291,9 @@ class ExampleBufPipe2(ControlBase):
         pipe1 = ExampleBufPipe()
         pipe2 = ExampleBufPipe()
 
+        self.p.i_data = pipe1.stage.ispec()
+        self.n.o_data = pipe2.stage.ospec()
+
         m.submodules.pipe1 = pipe1
         m.submodules.pipe2 = pipe2
 
diff --git a/src/add/test_fpadd_pipe.py b/src/add/test_fpadd_pipe.py
new file mode 100644 (file)
index 0000000..577117d
--- /dev/null
@@ -0,0 +1,116 @@
+""" key strategic example showing how to do multi-input fan-in into a 
+    multi-stage pipeline, then multi-output fanout.
+
+    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
+from nmigen.compat.sim import run_simulation
+from nmigen.cli import verilog, rtlil
+
+from nmigen_add_experiment import (FPADDMuxInOut,)
+
+
+class InputTest:
+    def __init__(self, dut):
+        self.dut = dut
+        self.di = {}
+        self.do = {}
+        self.tlen = 4
+        for mid in range(dut.num_rows):
+            self.di[mid] = {}
+            self.do[mid] = []
+            for i in range(self.tlen):
+                op1 = randint(0, 255) 
+                op2 = randint(0, 255) 
+                self.di[mid][i] = (op1, op2)
+                self.do[mid].append(op1 + op2)
+
+    def send(self, mid):
+        for i in range(self.tlen):
+            op1, op2 = self.di[mid][i]
+            rs = dut.p[mid]
+            yield rs.i_valid.eq(1)
+            yield rs.i_data.a.eq(op1)
+            yield rs.i_data.b.eq(op2)
+            yield rs.i_data.mid.eq(mid)
+            yield
+            o_p_ready = yield rs.o_ready
+            while not o_p_ready:
+                yield
+                o_p_ready = yield rs.o_ready
+
+            print ("send", mid, i, op1, op2, op1+op2)
+
+            yield rs.i_valid.eq(0)
+            # wait random period of time before queueing another value
+            for i in range(randint(0, 3)):
+                yield
+
+        yield rs.i_valid.eq(0)
+        yield
+
+        print ("send ended", mid)
+
+        ## 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, mid):
+        while True:
+            #stall_range = randint(0, 3)
+            #for j in range(randint(1,10)):
+            #    stall = randint(0, stall_range) != 0
+            #    yield self.dut.n[0].i_ready.eq(stall)
+            #    yield
+            n = self.dut.n[mid]
+            yield n.i_ready.eq(1)
+            yield
+            o_n_valid = yield n.o_valid
+            i_n_ready = yield n.i_ready
+            if not o_n_valid or not i_n_ready:
+                continue
+
+            out_mid = yield n.o_data.mid
+            out_z = yield n.o_data.z
+
+            print ("recv", out_mid, out_z)
+
+            out_i = 0
+
+            # see if this output has occurred already, delete it if it has
+            assert mid == out_mid, "out_mid %d not correct %d" % (out_mid, mid)
+            assert self.do[mid][out_i] == out_z # pass-through data
+            del self.do[mid][out_i]
+
+            # check if there's any more outputs
+            if len(self.do[mid]) == 0:
+                break
+        print ("recv ended", mid)
+
+
+
+if __name__ == '__main__':
+    dut = FPADDMuxInOut(16, 2, 4)
+    vl = rtlil.convert(dut, ports=dut.ports())
+    with open("test_fpadd_pipe.il", "w") as f:
+        f.write(vl)
+    #run_simulation(dut, testbench(dut), vcd_name="test_inputgroup.vcd")
+
+    test = InputTest(dut)
+    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_pipe.vcd")
+