+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
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
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()
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
--- /dev/null
+""" 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()