From ed8771fc9e55bebce5bb74fae7a3e548ce9b7eaf Mon Sep 17 00:00:00 2001 From: Luke Kenneth Casson Leighton Date: Mon, 25 Mar 2019 12:39:03 +0000 Subject: [PATCH] add new priority multi-input mux example and test... sim failing --- src/add/example_buf_pipe.py | 45 ++--- src/add/test_prioritymux_pipe.py | 271 +++++++++++++++++++++++++++++++ 2 files changed, 295 insertions(+), 21 deletions(-) create mode 100644 src/add/test_prioritymux_pipe.py diff --git a/src/add/example_buf_pipe.py b/src/add/example_buf_pipe.py index c5211975..3ff5bbf2 100644 --- a/src/add/example_buf_pipe.py +++ b/src/add/example_buf_pipe.py @@ -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 index 00000000..93fe18e4 --- /dev/null +++ b/src/add/test_prioritymux_pipe.py @@ -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") + -- 2.30.2