add new priority multi-input mux example and test... sim failing
authorLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Mon, 25 Mar 2019 12:39:03 +0000 (12:39 +0000)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Mon, 25 Mar 2019 12:39:03 +0000 (12:39 +0000)
src/add/example_buf_pipe.py
src/add/test_prioritymux_pipe.py [new file with mode: 0644]

index c521197586932888660872977982d4bd5b8c4f72..3ff5bbf2d18f0c4f198cd77537cae67a660ef5e6 100644 (file)
@@ -181,11 +181,11 @@ def eq(o, i):
         member names as the Record may be assigned: it does not have to
         *be* a Record.
     """
-    if not isinstance(o, Sequence):
+    if not isinstance(o, list) and not isinstance(o, tuple):
         o, i = [o], [i]
     res = []
     for (ao, ai) in zip(o, i):
-        #print ("eq", ao, ai)
+        #print ("eq ao", repr(ao), "ai:", repr(ai))
         if isinstance(ao, Record):
             for idx, (field_name, field_shape, _) in enumerate(ao.layout):
                 if isinstance(field_shape, Layout):
@@ -351,7 +351,7 @@ class BufferedPipeline(PipelineBase):
             * p_mux: optional multiplex selector for incoming data
             * n_mux: optional multiplex router for outgoing data
         """
-        PipelineBase.__init__(self, stage)
+        PipelineBase.__init__(self, stage, n_len, p_len)
         self.p_mux = p_mux
         self.n_mux = n_mux
 
@@ -540,11 +540,10 @@ class UnbufferedPipeline(PipelineBase):
             SYNCHRONOUSLY.
     """
 
-    def __init__(self, stage, n_len=1, p_len=1, p_mux=None, n_mux=None):
-        PipelineBase.__init__(self, stage, p_len, n_len)
+    def __init__(self, stage, p_len=1, n_len=1, p_mux=None, n_mux=None):
+        PipelineBase.__init__(self, stage, n_len, p_len)
         self.p_mux = p_mux
         self.n_mux = n_mux
-        self._data_valid = Signal()
 
         # set up the input and output data
         for i in range(p_len):
@@ -560,10 +559,12 @@ class UnbufferedPipeline(PipelineBase):
 
         # need an array of buffer registers conforming to *input* spec
         r_data = []
+        data_valid = []
         p_len = len(self.p)
         for i in range(p_len):
             r = self.stage.ispec() # input type
             r_data.append(r)
+            data_valid.append(Signal(name="data_valid"))
             if hasattr(self.stage, "setup"):
                 self.stage.setup(m, r)
         if len(r_data) > 1:
@@ -571,24 +572,26 @@ class UnbufferedPipeline(PipelineBase):
 
         ni = 0 # TODO: use n_nux to decide which to select
 
-        if self.p_mux:
-            pi = self.p_mux.mid
-            p_i_valid = self.p_mux.valid
-        else:
-            pi = 0
-            p_i_valid = Signal(reset_less=True)
-            m.d.comb += p_i_valid.eq(self.p[pi].i_valid_logic())
+        n_i_readyn = Signal(reset_less=True)
+        m.d.comb += n_i_readyn.eq(~self.n[ni].i_ready & data_valid[i])
 
-        m.d.comb += p_i_valid.eq(self.p[pi].i_valid_logic())
-        m.d.comb += self.n[ni].o_valid.eq(self._data_valid)
         for i in range(p_len):
-            m.d.comb += self.p[i].o_ready.eq(~self._data_valid | \
+            p_i_valid = Signal(reset_less=True)
+            m.d.comb += p_i_valid.eq(self.p[i].i_valid_logic())
+            m.d.comb += self.n[ni].o_valid.eq(data_valid[i])
+            m.d.comb += self.p[i].o_ready.eq(~data_valid[i] | \
                                               self.n[ni].i_ready)
-        m.d.sync += self._data_valid.eq(p_i_valid | \
-                                    (~self.n[ni].i_ready & self._data_valid))
-        with m.If(self.p[pi].i_valid & self.p[pi].o_ready):
-            m.d.sync += eq(r_data[pi], self.p[pi].i_data)
-        m.d.comb += eq(self.n[ni].o_data, self.stage.process(r_data[pi]))
+            m.d.sync += data_valid[i].eq(p_i_valid | \
+                                        (n_i_readyn & data_valid[i]))
+            with m.If(self.p[i].i_valid & self.p[i].o_ready):
+                m.d.sync += eq(r_data[i], self.p[i].i_data)
+        if self.p_mux:
+            mid = self.p_mux.m_id
+            with m.If(self.p_mux.active):
+                m.d.comb += eq(self.n[ni].o_data,
+                               self.stage.process(r_data[mid]))
+        else:
+            m.d.comb += eq(self.n[ni].o_data, self.stage.process(r_data[i]))
         return m
 
 
diff --git a/src/add/test_prioritymux_pipe.py b/src/add/test_prioritymux_pipe.py
new file mode 100644 (file)
index 0000000..93fe18e
--- /dev/null
@@ -0,0 +1,271 @@
+from random import randint
+from math import log
+from nmigen import Module, Signal, Cat
+from nmigen.compat.sim import run_simulation
+from nmigen.cli import verilog, rtlil
+from nmigen.lib.coding import PriorityEncoder
+
+from example_buf_pipe import UnbufferedPipeline
+
+
+class InputPriorityArbiter:
+    def __init__(self, pipe, num_rows):
+        self.pipe = pipe
+        self.num_rows = num_rows
+        self.mmax = int(log(self.num_rows) / log(2))
+        self.m_id = Signal(self.mmax, reset_less=True) # multiplex id
+        self.active = Signal(reset_less=True)
+
+    def elaborate(self, platform):
+        m = Module()
+
+        assert len(self.pipe.p) == self.num_rows, \
+                "must declare input to be same size"
+        pe = PriorityEncoder(self.num_rows)
+        m.submodules.selector = pe
+
+        # connect priority encoder
+        in_ready = []
+        for i in range(self.num_rows):
+            p_i_valid = Signal(reset_less=True)
+            m.d.comb += p_i_valid.eq(self.pipe.p[i].i_valid_logic())
+            in_ready.append(p_i_valid)
+        m.d.comb += pe.i.eq(Cat(*in_ready)) # array of input "valids"
+        m.d.comb += self.active.eq(~pe.n)   # encoder active (one input valid)
+        m.d.comb += self.m_id.eq(pe.o)       # output one active input
+
+        return m
+
+    def ports(self):
+        return [self.m_id, self.active]
+
+
+class PriorityUnbufferedPipeline(UnbufferedPipeline):
+    def __init__(self, stage, p_len=4):
+        p_mux = InputPriorityArbiter(self, p_len)
+        UnbufferedPipeline.__init__(self, stage, p_len=p_len, p_mux=p_mux)
+
+    def ports(self):
+        return self.p_mux.ports()
+        #return UnbufferedPipeline.ports(self) + self.p_mux.ports()
+
+class PassData:
+    def __init__(self):
+        self.mid = Signal(2)
+        self.idx = Signal(5)
+        self.data = Signal(16)
+
+    def eq(self, i):
+        return [self.mid.eq(i.mid), self.idx.eq(i.idx), self.data.eq(i.data)]
+
+    def ports(self):
+        return [self.mid, self.idx, self.data]
+
+class PassThroughStage:
+    def ispec(self):
+        return PassData()
+    def ospec(self):
+        return self.ispec() # same as ospec
+                
+    def process(self, i):
+        return i # pass-through
+
+
+
+def testbench(dut):
+    stb = yield dut.out_op.stb
+    assert stb == 0
+    ack = yield dut.out_op.ack
+    assert ack == 0
+
+    # set row 1 input 0
+    yield dut.rs[1].in_op[0].eq(5)
+    yield dut.rs[1].stb.eq(0b01) # strobe indicate 1st op ready
+    #yield dut.rs[1].ack.eq(1)
+    yield
+
+    # check row 1 output (should be inactive)
+    decode = yield dut.rs[1].out_decode
+    assert decode == 0
+    if False:
+        op0 = yield dut.rs[1].out_op[0]
+        op1 = yield dut.rs[1].out_op[1]
+        assert op0 == 0 and op1 == 0
+
+    # output should be inactive
+    out_stb = yield dut.out_op.stb
+    assert out_stb == 1
+
+    # set row 0 input 1
+    yield dut.rs[1].in_op[1].eq(6)
+    yield dut.rs[1].stb.eq(0b11) # strobe indicate both ops ready
+
+    # set acknowledgement of output... takes 1 cycle to respond
+    yield dut.out_op.ack.eq(1)
+    yield
+    yield dut.out_op.ack.eq(0) # clear ack on output
+    yield dut.rs[1].stb.eq(0) # clear row 1 strobe
+
+    # output strobe should be active, MID should be 0 until "ack" is set...
+    out_stb = yield dut.out_op.stb
+    assert out_stb == 1
+    out_mid = yield dut.mid
+    assert out_mid == 0
+
+    # ... and output should not yet be passed through either
+    op0 = yield dut.out_op.v[0]
+    op1 = yield dut.out_op.v[1]
+    assert op0 == 0 and op1 == 0
+
+    # wait for out_op.ack to activate...
+    yield dut.rs[1].stb.eq(0b00) # set row 1 strobes to zero
+    yield
+
+    # *now* output should be passed through
+    op0 = yield dut.out_op.v[0]
+    op1 = yield dut.out_op.v[1]
+    assert op0 == 5 and op1 == 6
+
+    # set row 2 input
+    yield dut.rs[2].in_op[0].eq(3)
+    yield dut.rs[2].in_op[1].eq(4)
+    yield dut.rs[2].stb.eq(0b11) # strobe indicate 1st op ready
+    yield dut.out_op.ack.eq(1) # set output ack
+    yield
+    yield dut.rs[2].stb.eq(0) # clear row 2 strobe
+    yield dut.out_op.ack.eq(0) # set output ack
+    yield
+    op0 = yield dut.out_op.v[0]
+    op1 = yield dut.out_op.v[1]
+    assert op0 == 3 and op1 == 4, "op0 %d op1 %d" % (op0, op1)
+    out_mid = yield dut.mid
+    assert out_mid == 2
+
+    # set row 0 and 3 input
+    yield dut.rs[0].in_op[0].eq(9)
+    yield dut.rs[0].in_op[1].eq(8)
+    yield dut.rs[0].stb.eq(0b11) # strobe indicate 1st op ready
+    yield dut.rs[3].in_op[0].eq(1)
+    yield dut.rs[3].in_op[1].eq(2)
+    yield dut.rs[3].stb.eq(0b11) # strobe indicate 1st op ready
+
+    # set acknowledgement of output... takes 1 cycle to respond
+    yield dut.out_op.ack.eq(1)
+    yield
+    yield dut.rs[0].stb.eq(0) # clear row 1 strobe
+    yield
+    out_mid = yield dut.mid
+    assert out_mid == 0, "out mid %d" % out_mid
+
+    yield
+    yield dut.rs[3].stb.eq(0) # clear row 1 strobe
+    yield dut.out_op.ack.eq(0) # clear ack on output
+    yield
+    out_mid = yield dut.mid
+    assert out_mid == 3, "out mid %d" % out_mid
+
+
+class InputTest:
+    def __init__(self, dut):
+        self.dut = dut
+        self.di = {}
+        self.do = {}
+        self.tlen = 10
+        for mid in range(dut.num_rows):
+            self.di[mid] = {}
+            self.do[mid] = {}
+            for i in range(self.tlen):
+                self.di[mid][i] = randint(0, 100)
+                self.do[mid][i] = self.di[mid][i]
+
+    def send(self, mid):
+        for i in range(self.tlen):
+            op2 = self.di[mid][i]
+            rs = dut.p[mid]
+            yield rs.i_valid.eq(0)
+            o_p_ready = yield rs.o_ready
+            while not o_p_ready:
+                yield
+                o_p_ready = yield rs.o_ready
+
+            yield rs.i_valid.eq(1)
+            yield rs.i_data.data.eq(op2)
+            yield rs.i_data.idx.eq(i)
+            yield rs.i_data.mid.eq(mid)
+            #for v in self.dut.set_input((op2, i, mid)):
+            #    yield v
+            yield
+
+        yield p.i_valid.eq(0)
+        ## 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):
+        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[0]
+            yield n.i_ready.eq(1)
+            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
+
+            mid = yield n.o_data[2]
+            out_i = yield n.o_data[0]
+            out_v = yield n.o_data[1]
+
+            # see if this output has occurred already, delete it if it has
+            assert out_i in self.do[mid]
+            assert self.do[mid][out_i] == out_v # pass-through data
+            del self.do[mid][out_i]
+
+            # check if there's any more outputs
+            zerolen = True
+            for (k, v) in self.do.items():
+                if v:
+                    zerolen = False
+            if zerolen:
+                break
+
+
+class TestPriorityMuxPipe(PriorityUnbufferedPipeline):
+    def __init__(self):
+        self.num_rows = 2
+        stage = PassThroughStage()
+        PriorityUnbufferedPipeline.__init__(self, stage, p_len=self.num_rows)
+
+    def ports(self):
+        res = []
+        for i in range(len(self.p)):
+            res += [self.p[i].i_valid, self.p[i].o_ready] + \
+                    self.p[i].i_data.ports()
+        for i in range(len(self.n)):
+            res += [self.n[i].i_ready, self.n[i].o_valid] + \
+                    self.n[i].o_data.ports()
+        return res
+
+
+if __name__ == '__main__':
+    dut = TestPriorityMuxPipe()
+    vl = rtlil.convert(dut, ports=dut.ports())
+    with open("test_inputgroup.il", "w") as f:
+        f.write(vl)
+    #run_simulation(dut, testbench(dut), vcd_name="test_inputgroup.vcd")
+
+    test = InputTest(dut)
+    run_simulation(dut, [test.send(1), test.send(0),
+                         #test.send(3), test.send(2),
+                         test.rcv()],
+                   vcd_name="test_inputgroup_parallel.vcd")
+