add instruction queue test
authorLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Thu, 30 May 2019 21:19:40 +0000 (22:19 +0100)
committerLuke Kenneth Casson Leighton <lkcl@lkcl.net>
Thu, 30 May 2019 21:19:40 +0000 (22:19 +0100)
src/scoreboard/instruction_q.py
src/scoreboard/test_iq.py [new file with mode: 0644]

index ae51375896dc75c405be31efb3e001b2dcf50487..f3be7669e70dd5d6934924d2152d9c29e975300e 100644 (file)
@@ -1,3 +1,5 @@
+from math import log
+
 from nmigen.compat.sim import run_simulation
 from nmigen.cli import verilog, rtlil
 from nmigen import Module, Signal, Cat, Array, Const, Repl, Elaboratable
@@ -24,9 +26,11 @@ class Instruction(RecordObject):
 class InstructionQ(Elaboratable):
     """ contains a queue of (part-decoded) instructions.
 
-        it is expected that the user of this queue will simply
-        inspect the queue contents directly, indicating at the start
-        of each clock cycle how many need to be removed.
+        output is copied combinatorially from the front of the queue,
+        for easy access on the clock cycle.  only "n_in" instructions
+        are made available this way
+
+        input and shifting occurs on sync.
     """
     def __init__(self, wid, opwid, iqlen, n_in, n_out):
         """ constructor
@@ -43,21 +47,22 @@ class InstructionQ(Elaboratable):
         self.opwid = opwid
         self.n_in = n_in
         self.n_out = n_out
+        mqbits = (int(log(iqlen) / log(2))+2, False)
 
-        self.p_add_i = Signal(max=n_in) # instructions to add (from data_i)
+        self.p_add_i = Signal(mqbits) # instructions to add (from data_i)
         self.p_ready_o = Signal() # instructions were added
         self.data_i = Instruction.nq(n_in, "data_i", wid, opwid)
         
         self.data_o = Instruction.nq(n_out, "data_o", wid, opwid)
-        self.n_sub_i = Signal(max=n_out) # number of instructions to remove
-        self.n_sub_o = Signal(max=n_out) # number of instructions removed
+        self.n_sub_i = Signal(mqbits) # number of instructions to remove
+        self.n_sub_o = Signal(mqbits) # number of instructions removed
 
         self.qsz = shape(self.data_o[0])[0]
         q = []
         for i in range(iqlen):
             q.append(Signal(self.qsz, name="q%d" % i))
         self.q = Array(q)
-        self.qlen_o = Signal(max=iqlen)
+        self.qlen_o = Signal(mqbits)
 
     def elaborate(self, platform):
         m = Module()
@@ -65,41 +70,58 @@ class InstructionQ(Elaboratable):
         sync = m.d.sync
 
         iqlen = self.iqlen
-        mqlen = Const(iqlen, iqlen+1)
+        mqbits = int(log(iqlen) / log(2))
 
-        start_copy = Signal(max=iqlen*2)
-        end_copy = Signal(max=iqlen*2)
+        left = Signal((mqbits+2, False))
+        spare = Signal((mqbits+2, False))
+        qmaxed = Signal()
 
-        # work out how many can be subtracted from the queue
-        with m.If(self.n_sub_i >= self.qlen_o):
-            comb += self.n_sub_o.eq(self.qlen_o)
-        with m.Elif(self.n_sub_i):
-            comb += self.n_sub_o.eq(self.n_sub_i)
+        start_q = Signal(mqbits)
+        end_q = Signal(mqbits)
+        mqlen = Const(iqlen, (len(left), False))
+        print ("mqlen", mqlen)
 
-        # work out the start and end of where data can be written
-        comb += start_copy.eq(self.qlen_o - self.n_sub_o)
-        comb += end_copy.eq(start_copy + self.p_add_i)
-        comb += self.p_ready_o.eq((end_copy < self.qlen_o) & self.p_add_i)
+        # work out how many can be subtracted from the queue
+        with m.If(self.n_sub_i):
+            qinmax = Signal()
+            comb += qinmax.eq(self.n_sub_i > self.qlen_o)
+            with m.If(qinmax):
+                comb += self.n_sub_o.eq(self.qlen_o)
+            with m.Else():
+                comb += self.n_sub_o.eq(self.n_sub_i)
+
+        # work out how many new items are going to be in the queue
+        comb += left.eq(self.qlen_o - self.n_sub_o)
+        comb += spare.eq(mqlen - self.p_add_i)
+        comb += qmaxed.eq(left < spare)
+        comb += self.p_ready_o.eq(qmaxed & (self.p_add_i != 0))
 
         # put q (flattened) into output
         for i in range(self.n_out):
-            comb += cat(self.data_o[i]).eq(self.q[i])
+            opos = Signal(mqbits)
+            comb += opos.eq(end_q + i - self.n_out) # end hasn't moved yet
+            comb += cat(self.data_o[i]).eq(self.q[opos])
+
+        with m.If(self.n_sub_o):
+            # ok now the end's moved
+            sync += end_q.eq(end_q + self.n_sub_o)
+
+        with m.If(self.p_ready_o):
+            # copy in the input... insanely gate-costly... *sigh*...
+            for i in range(self.n_in):
+                with m.If(self.p_add_i > Const(i, len(self.p_add_i))):
+                    ipos = Signal(mqbits)
+                    comb += ipos.eq(start_q + i) # should roll round
+                    sync += self.q[ipos].eq(cat(self.data_i[i]))
+            sync += start_q.eq(start_q + self.p_add_i)
 
-        # this is going to be _so_ expensive in terms of gates... *sigh*...
         with m.If(self.p_ready_o):
-            for i in range(iqlen-1):
-                cfrom = Signal(max=iqlen*2)
-                cto = Signal(max=iqlen*2)
-                comb += cfrom.eq(Const(i, iqlen+1) + start_copy)
-                comb += cto.eq(Const(i, iqlen+1) + end_copy)
-                with m.If((cfrom < mqlen) & (cto < mqlen)):
-                    sync += self.q[cto].eq(self.q[cfrom])
-
-        for i in range(self.n_in):
-            with m.If(self.p_add_i < i):
-                idx = Signal(max=iqlen)
-                comb += idx.eq(start_copy + i)
-                sync += self.q[idx].eq(cat(self.data_i[i]))
+            # update the queue length
+            add2 = Signal(mqbits+1)
+            comb += add2.eq(self.qlen_o + self.p_add_i)
+            sync += self.qlen_o.eq(add2 - self.n_sub_o)
+        with m.Else():
+            sync += self.qlen_o.eq(self.qlen_o - self.n_sub_o)
 
         return m
 
diff --git a/src/scoreboard/test_iq.py b/src/scoreboard/test_iq.py
new file mode 100644 (file)
index 0000000..6bbb26d
--- /dev/null
@@ -0,0 +1,111 @@
+""" testing of InstructionQ
+"""
+
+from copy import deepcopy
+from random import randint
+from nmigen.compat.sim import run_simulation
+from nmigen.cli import verilog, rtlil
+
+from scoreboard.instruction_q import InstructionQ
+from nmutil.nmoperator import eq
+
+
+class IQSim:
+    def __init__(self, dut, iq, n_in, n_out):
+        self.dut = dut
+        self.iq = iq
+        self.oq = []
+        self.n_in = n_in
+        self.n_out = n_out
+
+    def send(self):
+        i = 0
+        while i < len(self.iq):
+            sendlen = randint(1, self.n_in)
+            print (sendlen)
+            sendlen = min(len(self.iq) - i, sendlen)
+            print (len(self.iq)-i, sendlen)
+            for idx in range(sendlen):
+                instr = self.iq[i+idx]
+                yield from eq(self.dut.data_i[idx], instr)
+            yield self.dut.p_add_i.eq(sendlen)
+            o_p_ready = yield self.dut.p_ready_o
+            while not o_p_ready:
+                yield
+                o_p_ready = yield self.dut.p_ready_o
+
+            yield
+
+            print ("send", len(self.iq), i, sendlen)
+
+            yield self.dut.p_add_i.eq(0)
+            # wait random period of time before queueing another value
+            for j in range(randint(0, 3)):
+                yield
+
+            i += sendlen
+
+        yield self.dut.p_add_i.eq(0)
+        yield
+
+        print ("send ended")
+
+        ## 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):
+        i = 0
+        while i < len(self.iq):
+            rcvlen = randint(1, self.n_out)
+            #print ("outreq", rcvlen)
+            yield self.dut.n_sub_i.eq(rcvlen)
+            n_sub_o = yield self.dut.n_sub_o
+            yield
+            if n_sub_o == 0:
+                continue
+
+            print ("recv", n_sub_o)
+            i += n_sub_o
+
+        print ("recv ended")
+
+
+def mk_insns(n_insns, wid, opwid):
+    res = []
+    for i in range(n_insns):
+        op1 = randint(0, (1<<wid)-1)
+        op2 = randint(0, (1<<wid)-1)
+        dst = randint(0, (1<<wid)-1)
+        oper = randint(0, (1<<opwid)-1)
+        res.append({'oper_i': oper, 'dest_i': dst, 'src1_i': op1, 'src2_i': op2})
+    return res
+
+
+def test_iq():
+    wid = 8
+    opwid = 4
+    qlen = 5
+    n_in = 2
+    n_out = 3
+    dut = InstructionQ(wid, opwid, qlen, n_in, n_out)
+    insns = mk_insns(10, wid, opwid)
+
+    vl = rtlil.convert(dut, ports=dut.ports())
+    with open("test_iq.il", "w") as f:
+        f.write(vl)
+
+    test = IQSim(dut, insns, n_in, n_out)
+    print (insns)
+    run_simulation(dut, [test.rcv(), test.send()
+                        ],
+                   vcd_name="test_iq.vcd")
+
+if __name__ == '__main__':
+    test_iq()