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):
* 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
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):
# 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:
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
--- /dev/null
+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")
+