From: Luke Kenneth Casson Leighton Date: Tue, 26 Mar 2019 12:48:06 +0000 (+0000) Subject: create multipipe from former multi-input Pipeline X-Git-Tag: ls180-24jan2020~1489 X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=ec62c2f43709b45652417e0b2b67c64856c789a1;p=ieee754fpu.git create multipipe from former multi-input Pipeline --- diff --git a/src/add/example_buf_pipe.py b/src/add/example_buf_pipe.py index 8c4cae44..7f23f366 100644 --- a/src/add/example_buf_pipe.py +++ b/src/add/example_buf_pipe.py @@ -244,7 +244,7 @@ class StageChain: class PipelineBase: """ Common functions for Pipeline API """ - def __init__(self, stage, in_multi=None): + def __init__(self, stage=None, in_multi=None): """ pass in a "stage" which may be either a static class or a class instance, which has four functions (one optional): * ispec: returns input signals according to the input specification diff --git a/src/add/multipipe.py b/src/add/multipipe.py new file mode 100644 index 00000000..337e2dca --- /dev/null +++ b/src/add/multipipe.py @@ -0,0 +1,201 @@ +""" Combinatorial Multi-input multiplexer block conforming to Pipeline API +""" + +from math import log +from nmigen import Signal, Cat, Const, Mux, Module, Array +from nmigen.cli import verilog, rtlil +from nmigen.lib.coding import PriorityEncoder +from nmigen.hdl.rec import Record, Layout + +from collections.abc import Sequence + +from example_buf_pipe import eq, NextControl, PrevControl, ExampleStage + + +class PipelineBase: + """ Common functions for Pipeline API + """ + def __init__(self, stage, in_multi=None, p_len=1): + """ pass in a "stage" which may be either a static class or a class + instance, which has four functions (one optional): + * ispec: returns input signals according to the input specification + * ispec: returns output signals to the output specification + * process: takes an input instance and returns processed data + * setup: performs any module linkage if the stage uses one. + + User must also: + * add i_data member to PrevControl and + * add o_data member to NextControl + """ + self.stage = stage + + # set up input and output IO ACK (prev/next ready/valid) + p = [] + for i in range(p_len): + p.append(PrevControl(in_multi)) + self.p = Array(p) + self.n = NextControl() + + def connect_to_next(self, nxt, p_idx=0): + """ helper function to connect to the next stage data/valid/ready. + """ + return self.n.connect_to_next(nxt.p[p_idx]) + + def connect_in(self, prev, idx=0, prev_idx=None): + """ helper function to connect stage to an input source. do not + use to connect stage-to-stage! + """ + if prev_idx is None: + return self.p[idx].connect_in(prev.p) + return self.p[idx].connect_in(prev.p[prev_idx]) + + def connect_out(self, nxt): + """ helper function to connect stage to an output source. do not + use to connect stage-to-stage! + """ + if nxt_idx is None: + return self.n.connect_out(nxt.n) + return self.n.connect_out(nxt.n) + + def set_input(self, i, idx=0): + """ helper function to set the input data + """ + return eq(self.p[idx].i_data, i) + + 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]# XXX need flattening!] + res += [self.n.i_ready, self.n.o_valid, + self.n.o_data] # XXX need flattening!] + return res + + + +class CombMultiInPipeline(PipelineBase): + """ A multi-input Combinatorial block conforming to the Pipeline API + + Attributes: + ----------- + p.i_data : StageInput, shaped according to ispec + The pipeline input + p.o_data : StageOutput, shaped according to ospec + The pipeline output + r_data : input_shape according to ispec + A temporary (buffered) copy of a prior (valid) input. + This is HELD if the output is not ready. It is updated + SYNCHRONOUSLY. + """ + + def __init__(self, stage, p_len, p_mux): + PipelineBase.__init__(self, stage, p_len=p_len) + self.p_mux = p_mux + + # set up the input and output data + for i in range(p_len): + self.p[i].i_data = stage.ispec() # input type + self.n.o_data = stage.ospec() + + def elaborate(self, platform): + m = Module() + + m.submodules += self.p_mux + + # need an array of buffer registers conforming to *input* spec + r_data = [] + data_valid = [] + p_i_valid = [] + n_i_readyn = [] + 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", reset_less=True)) + p_i_valid.append(Signal(name="p_i_valid", reset_less=True)) + n_i_readyn.append(Signal(name="n_i_readyn", reset_less=True)) + if hasattr(self.stage, "setup"): + self.stage.setup(m, r) + if len(r_data) > 1: + r_data = Array(r_data) + p_i_valid = Array(p_i_valid) + n_i_readyn = Array(n_i_readyn) + data_valid = Array(data_valid) + + mid = self.p_mux.m_id + for i in range(p_len): + m.d.comb += data_valid[i].eq(0) + m.d.comb += n_i_readyn[i].eq(1) + m.d.comb += p_i_valid[i].eq(0) + m.d.comb += self.p[i].o_ready.eq(0) + m.d.comb += p_i_valid[mid].eq(self.p_mux.active) + m.d.comb += self.p[mid].o_ready.eq(~data_valid[mid] | self.n.i_ready) + m.d.comb += n_i_readyn[mid].eq(~self.n.i_ready & data_valid[mid]) + anyvalid = Signal(i, reset_less=True) + av = [] + for i in range(p_len): + av.append(data_valid[i]) + anyvalid = Cat(*av) + m.d.comb += self.n.o_valid.eq(anyvalid.bool()) + m.d.comb += data_valid[mid].eq(p_i_valid[mid] | \ + (n_i_readyn[mid] & data_valid[mid])) + + for i in range(p_len): + vr = Signal(reset_less=True) + m.d.comb += vr.eq(self.p[i].i_valid & self.p[i].o_ready) + with m.If(vr): + m.d.comb += eq(r_data[i], self.p[i].i_data) + + m.d.comb += eq(self.n.o_data, self.stage.process(r_data[mid])) + + return m + + +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 ExamplePipeline(CombMultiInPipeline): + """ an example of how to use the combinatorial pipeline. + """ + + def __init__(self, p_len=2): + p_mux = InputPriorityArbiter(self, p_len) + CombMultiInPipeline.__init__(self, ExampleStage, p_len, p_mux) + + +if __name__ == '__main__': + + dut = ExamplePipeline() + vl = rtlil.convert(dut, ports=dut.ports()) + with open("test_combpipe.il", "w") as f: + f.write(vl) diff --git a/src/add/test_prioritymux_pipe.py b/src/add/test_prioritymux_pipe.py index 7cc5b817..7e71d7fd 100644 --- a/src/add/test_prioritymux_pipe.py +++ b/src/add/test_prioritymux_pipe.py @@ -3,47 +3,15 @@ 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 +from multipipe import CombMultiInPipeline, InputPriorityArbiter -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): +class PriorityUnbufferedPipeline(CombMultiInPipeline): 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) + CombMultiInPipeline.__init__(self, stage, p_len=p_len, p_mux=p_mux) def ports(self): return self.p_mux.ports() @@ -217,7 +185,7 @@ class InputTest: # stall = randint(0, stall_range) != 0 # yield self.dut.n[0].i_ready.eq(stall) # yield - n = self.dut.n[0] + n = self.dut.n yield n.i_ready.eq(1) yield o_n_valid = yield n.o_valid @@ -248,7 +216,7 @@ class InputTest: class TestPriorityMuxPipe(PriorityUnbufferedPipeline): def __init__(self): - self.num_rows = 4 + self.num_rows = 2 stage = PassThroughStage() PriorityUnbufferedPipeline.__init__(self, stage, p_len=self.num_rows) @@ -257,22 +225,21 @@ class TestPriorityMuxPipe(PriorityUnbufferedPipeline): 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() + res += [self.n.i_ready, self.n.o_valid] + \ + self.n.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: + with open("test_inputgroup_multi.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.send(3), test.send(2), test.rcv()], - vcd_name="test_inputgroup_parallel.vcd") + vcd_name="test_inputgroup_multi.vcd")