X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;f=src%2Fadd%2Fsinglepipe.py;h=96fb86f3e91a46f0f8f52d57458d3f2e9cbb3b2c;hb=f997f92aae9a42a1482c6273a807c1e49fc0ab28;hp=3d4eefb7c2404ba3a254fb0e10c08538097b4a0c;hpb=a06052f25384f212909d90fa1f3cd2a98fcb8df5;p=ieee754fpu.git diff --git a/src/add/singlepipe.py b/src/add/singlepipe.py index 3d4eefb7..96fb86f3 100644 --- a/src/add/singlepipe.py +++ b/src/add/singlepipe.py @@ -166,12 +166,29 @@ from nmigen import Signal, Cat, Const, Mux, Module, Value from nmigen.cli import verilog, rtlil -from nmigen.lib.fifo import SyncFIFO +from nmigen.lib.fifo import SyncFIFO, SyncFIFOBuffered from nmigen.hdl.ast import ArrayProxy from nmigen.hdl.rec import Record, Layout from abc import ABCMeta, abstractmethod from collections.abc import Sequence +from queue import Queue + + +class RecordObject(Record): + def __init__(self, layout=None, name=None): + Record.__init__(self, layout=layout or [], name=None) + + def __setattr__(self, k, v): + if k in dir(Record) or "fields" not in self.__dict__: + return object.__setattr__(self, k, v) + self.fields[k] = v + if isinstance(v, Record): + newlayout = {k: (k, v.layout)} + else: + newlayout = {k: (k, v.shape())} + self.layout.fields.update(newlayout) + class PrevControl: @@ -199,17 +216,15 @@ class PrevControl: return self.s_o_ready # set dynamically by stage return self._o_ready # return this when not under dynamic control - def _connect_in(self, prev, direct=False): + def _connect_in(self, prev, direct=False, fn=None): """ internal helper function to connect stage to an input source. do not use to connect stage-to-stage! """ - if direct: - i_valid = prev.i_valid - else: - i_valid = prev.i_valid_test + i_valid = prev.i_valid if direct else prev.i_valid_test + i_data = fn(prev.i_data) if fn is not None else prev.i_data return [self.i_valid.eq(i_valid), prev.o_ready.eq(self.o_ready), - eq(self.i_data, prev.i_data), + eq(self.i_data, i_data), ] @property @@ -261,17 +276,15 @@ class NextControl: eq(nxt.i_data, self.o_data), ] - def _connect_out(self, nxt, direct=False): + def _connect_out(self, nxt, direct=False, fn=None): """ internal helper function to connect stage to an output source. do not use to connect stage-to-stage! """ - if direct: - i_ready = nxt.i_ready - else: - i_ready = nxt.i_ready_test + i_ready = nxt.i_ready if direct else nxt.i_ready_test + o_data = fn(nxt.o_data) if fn is not None else nxt.o_data return [nxt.o_valid.eq(self.o_valid), self.i_ready.eq(i_ready), - eq(nxt.o_data, self.o_data), + eq(o_data, self.o_data), ] @@ -297,52 +310,71 @@ class Visitor: python object, enumerate them, find out the list of Signals that way, and assign them. """ - def visit(self, o, i, fn): - res = [] + def visit(self, o, i, act): if isinstance(o, dict): - for (k, v) in o.items(): - print ("d-eq", v, i[k]) - res.append(fn(v, i[k])) - return res + return self.dict_visit(o, i, act) + res = act.prepare() if not isinstance(o, Sequence): o, i = [o], [i] for (ao, ai) in zip(o, i): #print ("visit", fn, ao, ai) if isinstance(ao, Record): - rres = [] - for idx, (field_name, field_shape, _) in enumerate(ao.layout): - if isinstance(field_shape, Layout): - val = ai.fields - else: - val = ai - if hasattr(val, field_name): # check for attribute - val = getattr(val, field_name) - else: - val = val[field_name] # dictionary-style specification - rres += self.visit(ao.fields[field_name], val, fn) + rres = self.record_visit(ao, ai, act) elif isinstance(ao, ArrayProxy) and not isinstance(ai, Value): - rres = [] - for p in ai.ports(): - op = getattr(ao, p.name) - #print (op, p, p.name) - rres.append(fn(op, p)) + rres = self.arrayproxy_visit(ao, ai, act) else: - rres = fn(ao, ai) - if not isinstance(rres, Sequence): - rres = [rres] + rres = act.fn(ao, ai) res += rres return res + def dict_visit(self, o, i, act): + res = act.prepare() + for (k, v) in o.items(): + print ("d-eq", v, i[k]) + res.append(act.fn(v, i[k])) + return res + + def record_visit(self, ao, ai, act): + res = act.prepare() + for idx, (field_name, field_shape, _) in enumerate(ao.layout): + if isinstance(field_shape, Layout): + val = ai.fields + else: + val = ai + if hasattr(val, field_name): # check for attribute + val = getattr(val, field_name) + else: + val = val[field_name] # dictionary-style specification + val = self.visit(ao.fields[field_name], val, act) + if isinstance(val, Sequence): + res += val + else: + res.append(val) + return res + + def arrayproxy_visit(self, ao, ai, act): + res = act.prepare() + for p in ai.ports(): + op = getattr(ao, p.name) + #print (op, p, p.name) + res.append(fn(op, p)) + return res + class Eq(Visitor): def __init__(self): self.res = [] + def prepare(self): + return [] + def fn(self, o, i): + rres = o.eq(i) + if not isinstance(rres, Sequence): + rres = [rres] + return rres def __call__(self, o, i): - def _eq_fn(o, i): - return o.eq(i) - res = self.visit(o, i, _eq_fn) - return res + return self.visit(o, i, self) + def eq(o, i): """ makes signals equal: a helper routine which identifies if it is being @@ -352,6 +384,50 @@ def eq(o, i): return Eq()(o, i) +def flatten(i): + """ flattens a compound structure recursively using Cat + """ + if not isinstance(i, Sequence): + i = [i] + res = [] + for ai in i: + print ("flatten", ai) + if isinstance(ai, Record): + print ("record", list(ai.layout)) + rres = [] + for idx, (field_name, field_shape, _) in enumerate(ai.layout): + if isinstance(field_shape, Layout): + val = ai.fields + else: + val = ai + if hasattr(val, field_name): # check for attribute + val = getattr(val, field_name) + else: + val = val[field_name] # dictionary-style specification + print ("recidx", idx, field_name, field_shape, val) + val = flatten(val) + print ("recidx flat", idx, val) + if isinstance(val, Sequence): + rres += val + else: + rres.append(val) + + elif isinstance(ai, ArrayProxy) and not isinstance(ai, Value): + rres = [] + for p in ai.ports(): + op = getattr(ai, p.name) + #print (op, p, p.name) + rres.append(flatten(p)) + else: + rres = ai + if not isinstance(rres, Sequence): + rres = [rres] + res += rres + print ("flatten res", res) + return Cat(*res) + + + class StageCls(metaclass=ABCMeta): """ Class-based "Stage" API. requires instantiation (after derivation) @@ -553,6 +629,11 @@ class ControlBase: return eqs + def _postprocess(self, i): + if hasattr(self.stage, "postprocess"): + return self.stage.postprocess(i) + return i + def set_input(self, i): """ helper function to set the input data """ @@ -659,14 +740,16 @@ class BufferedHandshake(ControlBase): # data pass-through conditions with self.m.If(npnn): + o_data = self._postprocess(result) self.m.d.sync += [self.n.o_valid.eq(p_i_valid), # valid if p_valid - eq(self.n.o_data, result), # update output + eq(self.n.o_data, o_data), # update output ] # buffer flush conditions (NOTE: can override data passthru conditions) with self.m.If(nir_por_n): # not stalled # Flush the [already processed] buffer to the output port. + o_data = self._postprocess(r_data) self.m.d.sync += [self.n.o_valid.eq(1), # reg empty - eq(self.n.o_data, r_data), # flush buffer + eq(self.n.o_data, o_data), # flush buffer ] # output ready conditions self.m.d.sync += self.p._o_ready.eq(nir_novn | por_pivn) @@ -687,32 +770,32 @@ class SimpleHandshake(ControlBase): +--process->--^ Truth Table - Inputs Temporary Output - ------- ---------- ----- - P P N N PiV& ~NiV& N P + Inputs Temporary Output Data + ------- ---------- ----- ---- + P P N N PiV& ~NiR& N P i o i o PoR NoV o o V R R V V R ------- - - - - - 0 0 0 0 0 0 >0 0 - 0 0 0 1 0 1 >1 0 - 0 0 1 0 0 0 0 1 - 0 0 1 1 0 0 0 1 + 0 0 0 0 0 0 >0 0 reg + 0 0 0 1 0 1 >1 0 reg + 0 0 1 0 0 0 0 1 process(i_data) + 0 0 1 1 0 0 0 1 process(i_data) ------- - - - - - 0 1 0 0 0 0 >0 0 - 0 1 0 1 0 1 >1 0 - 0 1 1 0 0 0 0 1 - 0 1 1 1 0 0 0 1 + 0 1 0 0 0 0 >0 0 reg + 0 1 0 1 0 1 >1 0 reg + 0 1 1 0 0 0 0 1 process(i_data) + 0 1 1 1 0 0 0 1 process(i_data) ------- - - - - - 1 0 0 0 0 0 >0 0 - 1 0 0 1 0 1 >1 0 - 1 0 1 0 0 0 0 1 - 1 0 1 1 0 0 0 1 + 1 0 0 0 0 0 >0 0 reg + 1 0 0 1 0 1 >1 0 reg + 1 0 1 0 0 0 0 1 process(i_data) + 1 0 1 1 0 0 0 1 process(i_data) ------- - - - - - 1 1 0 0 1 0 1 0 - 1 1 0 1 1 1 1 0 - 1 1 1 0 1 0 1 1 - 1 1 1 1 1 0 1 1 + 1 1 0 0 1 0 1 0 process(i_data) + 1 1 0 1 1 1 1 0 process(i_data) + 1 1 1 0 1 0 1 1 process(i_data) + 1 1 1 1 1 0 1 1 process(i_data) ------- - - - - """ @@ -736,12 +819,14 @@ class SimpleHandshake(ControlBase): # previous valid and ready with m.If(p_i_valid_p_o_ready): + o_data = self._postprocess(result) m.d.sync += [r_busy.eq(1), # output valid - eq(self.n.o_data, result), # update output + eq(self.n.o_data, o_data), # update output ] # previous invalid or not ready, however next is accepting with m.Elif(n_i_ready): - m.d.sync += [eq(self.n.o_data, result)] + o_data = self._postprocess(result) + m.d.sync += [eq(self.n.o_data, o_data)] # TODO: could still send data here (if there was any) #m.d.sync += self.n.o_valid.eq(0) # ...so set output invalid m.d.sync += r_busy.eq(0) # ...so set output invalid @@ -791,32 +876,32 @@ class UnbufferedPipeline(ControlBase): Truth Table - Inputs Temp Output - ------- - ----- + Inputs Temp Output Data + ------- - ----- ---- P P N N ~NiR& N P i o i o NoV o o V R R V V R ------- - - - - 0 0 0 0 0 0 1 - 0 0 0 1 1 1 0 - 0 0 1 0 0 0 1 - 0 0 1 1 0 0 1 + 0 0 0 0 0 0 1 reg + 0 0 0 1 1 1 0 reg + 0 0 1 0 0 0 1 reg + 0 0 1 1 0 0 1 reg ------- - - - - 0 1 0 0 0 0 1 - 0 1 0 1 1 1 0 - 0 1 1 0 0 0 1 - 0 1 1 1 0 0 1 + 0 1 0 0 0 0 1 reg + 0 1 0 1 1 1 0 reg + 0 1 1 0 0 0 1 reg + 0 1 1 1 0 0 1 reg ------- - - - - 1 0 0 0 0 1 1 - 1 0 0 1 1 1 0 - 1 0 1 0 0 1 1 - 1 0 1 1 0 1 1 + 1 0 0 0 0 1 1 reg + 1 0 0 1 1 1 0 reg + 1 0 1 0 0 1 1 reg + 1 0 1 1 0 1 1 reg ------- - - - - 1 1 0 0 0 1 1 - 1 1 0 1 1 1 0 - 1 1 1 0 0 1 1 - 1 1 1 1 0 1 1 + 1 1 0 0 0 1 1 process(i_data) + 1 1 0 1 1 1 0 process(i_data) + 1 1 1 0 0 1 1 process(i_data) + 1 1 1 1 0 1 1 process(i_data) ------- - - - Note: PoR is *NOT* involved in the above decision-making. @@ -831,16 +916,19 @@ class UnbufferedPipeline(ControlBase): # some temporaries p_i_valid = Signal(reset_less=True) pv = Signal(reset_less=True) + buf_full = Signal(reset_less=True) m.d.comb += p_i_valid.eq(self.p.i_valid_test) m.d.comb += pv.eq(self.p.i_valid & self.p.o_ready) + m.d.comb += buf_full.eq(~self.n.i_ready_test & data_valid) m.d.comb += self.n.o_valid.eq(data_valid) m.d.comb += self.p._o_ready.eq(~data_valid | self.n.i_ready_test) - m.d.sync += data_valid.eq(p_i_valid | \ - (~self.n.i_ready_test & data_valid)) + m.d.sync += data_valid.eq(p_i_valid | buf_full) + with m.If(pv): m.d.sync += eq(r_data, self.stage.process(self.p.i_data)) - m.d.comb += eq(self.n.o_data, r_data) + o_data = self._postprocess(r_data) + m.d.comb += eq(self.n.o_data, o_data) return self.m @@ -874,6 +962,36 @@ class UnbufferedPipeline2(ControlBase): A temporary (buffered) copy of a valid output This is HELD if the output is not ready. It is updated SYNCHRONOUSLY. + + Inputs Temp Output Data + ------- - ----- + P P N N ~NiR& N P (buf_full) + i o i o NoV o o + V R R V V R + + ------- - - - + 0 0 0 0 0 0 1 process(i_data) + 0 0 0 1 1 1 0 reg (odata, unchanged) + 0 0 1 0 0 0 1 process(i_data) + 0 0 1 1 0 0 1 process(i_data) + ------- - - - + 0 1 0 0 0 0 1 process(i_data) + 0 1 0 1 1 1 0 reg (odata, unchanged) + 0 1 1 0 0 0 1 process(i_data) + 0 1 1 1 0 0 1 process(i_data) + ------- - - - + 1 0 0 0 0 1 1 process(i_data) + 1 0 0 1 1 1 0 reg (odata, unchanged) + 1 0 1 0 0 1 1 process(i_data) + 1 0 1 1 0 1 1 process(i_data) + ------- - - - + 1 1 0 0 0 1 1 process(i_data) + 1 1 0 1 1 1 0 reg (odata, unchanged) + 1 1 1 0 0 1 1 process(i_data) + 1 1 1 1 0 1 1 process(i_data) + ------- - - - + + Note: PoR is *NOT* involved in the above decision-making. """ def elaborate(self, platform): @@ -890,8 +1008,10 @@ class UnbufferedPipeline2(ControlBase): m.d.comb += self.p._o_ready.eq(~buf_full) m.d.sync += buf_full.eq(~self.n.i_ready_test & self.n.o_valid) - odata = Mux(buf_full, buf, self.stage.process(self.p.i_data)) - m.d.comb += eq(self.n.o_data, odata) + o_data = Mux(buf_full, buf, self.stage.process(self.p.i_data)) + if hasattr(self.stage, "postprocess"): + o_data = self.stage.postprocess(o_data) + m.d.comb += eq(self.n.o_data, o_data) m.d.sync += eq(buf, self.n.o_data) return self.m @@ -910,11 +1030,42 @@ class PassThroughStage(StageCls): class PassThroughHandshake(ControlBase): """ A control block that delays by one clock cycle. + + Inputs Temporary Output Data + ------- ------------------ ----- ---- + P P N N PiV& PiV| NiR| pvr N P (pvr) + i o i o PoR ~PoR ~NoV o o + V R R V V R + + ------- - - - - - - + 0 0 0 0 0 1 1 0 1 1 odata (unchanged) + 0 0 0 1 0 1 0 0 1 0 odata (unchanged) + 0 0 1 0 0 1 1 0 1 1 odata (unchanged) + 0 0 1 1 0 1 1 0 1 1 odata (unchanged) + ------- - - - - - - + 0 1 0 0 0 0 1 0 0 1 odata (unchanged) + 0 1 0 1 0 0 0 0 0 0 odata (unchanged) + 0 1 1 0 0 0 1 0 0 1 odata (unchanged) + 0 1 1 1 0 0 1 0 0 1 odata (unchanged) + ------- - - - - - - + 1 0 0 0 0 1 1 1 1 1 process(in) + 1 0 0 1 0 1 0 0 1 0 odata (unchanged) + 1 0 1 0 0 1 1 1 1 1 process(in) + 1 0 1 1 0 1 1 1 1 1 process(in) + ------- - - - - - - + 1 1 0 0 1 1 1 1 1 1 process(in) + 1 1 0 1 1 1 0 0 1 0 odata (unchanged) + 1 1 1 0 1 1 1 1 1 1 process(in) + 1 1 1 1 1 1 1 1 1 1 process(in) + ------- - - - - - - + """ def elaborate(self, platform): self.m = m = ControlBase._elaborate(self, platform) + r_data = self.stage.ospec() # output type + # temporaries p_i_valid = Signal(reset_less=True) pvr = Signal(reset_less=True) @@ -924,8 +1075,11 @@ class PassThroughHandshake(ControlBase): m.d.comb += self.p.o_ready.eq(~self.n.o_valid | self.n.i_ready_test) m.d.sync += self.n.o_valid.eq(p_i_valid | ~self.p.o_ready) - odata = Mux(pvr, self.stage.process(self.p.i_data), self.n.o_data) - m.d.sync += eq(self.n.o_data, odata) + odata = Mux(pvr, self.stage.process(self.p.i_data), r_data) + m.d.sync += eq(r_data, odata) + if hasattr(self.stage, "postprocess"): + r_data = self.stage.postprocess(r_data) + m.d.comb += eq(self.n.o_data, r_data) return m @@ -939,42 +1093,87 @@ class RegisterPipeline(UnbufferedPipeline): UnbufferedPipeline.__init__(self, PassThroughStage(iospecfn)) -class FIFOtest(ControlBase): - """ A test of using a SyncFIFO to see if it will work. - Note: the only things it will accept is a Signal of width "width". +class FIFOControl(ControlBase): + """ FIFO Control. Uses SyncFIFO to store data, coincidentally + happens to have same valid/ready signalling as Stage API. + + i_data -> fifo.din -> FIFO -> fifo.dout -> o_data """ - def __init__(self, width, depth): + def __init__(self, depth, stage, in_multi=None, stage_ctl=False, + fwft=True, buffered=False): + """ FIFO Control + + * depth: number of entries in the FIFO + * stage: data processing block + * fwft : first word fall-thru mode (non-fwft introduces delay) + * buffered: use buffered FIFO (introduces extra cycle delay) - self.fwidth = width + NOTE 1: FPGAs may have trouble with the defaults for SyncFIFO + (fwft=True, buffered=False) + + NOTE 2: i_data *must* have a shape function. it can therefore + be a Signal, or a Record, or a RecordObject. + + data is processed (and located) as follows: + + self.p self.stage temp fn temp fn temp fp self.n + i_data->process()->result->flatten->din.FIFO.dout->flatten(o_data) + + yes, really: flatten produces a Cat() which can be assigned to. + this is how the FIFO gets de-flattened without needing a de-flatten + function + """ + + assert not (fwft and buffered), "buffered cannot do fwft" + if buffered: + depth += 1 + self.fwft = fwft + self.buffered = buffered self.fdepth = depth - def iospecfn(): - return Signal(width, name="data") - stage = PassThroughStage(iospecfn) - ControlBase.__init__(self, stage=stage) + ControlBase.__init__(self, stage, in_multi, stage_ctl) def elaborate(self, platform): self.m = m = ControlBase._elaborate(self, platform) - fifo = SyncFIFO(self.fwidth, self.fdepth) + # make a FIFO with a signal of equal width to the o_data. + (fwidth, _) = self.n.o_data.shape() + if self.buffered: + fifo = SyncFIFOBuffered(fwidth, self.fdepth) + else: + fifo = Queue(fwidth, self.fdepth, fwft=self.fwft) m.submodules.fifo = fifo - # prev: make the FIFO "look" like a PrevControl... - fp = PrevControl() - fp.i_valid = fifo.we - fp._o_ready = fifo.writable - fp.i_data = fifo.din - # ... so we can do this! - m.d.comb += fp._connect_in(self.p, True) - - # next: make the FIFO "look" like a NextControl... - fn = NextControl() - fn.o_valid = fifo.readable - fn.i_ready = fifo.re - fn.o_data = fifo.dout - # ... so we can do this! - m.d.comb += fn._connect_out(self.n) - - # err... that should be all! + # store result of processing in combinatorial temporary + result = self.stage.ospec() + m.d.comb += eq(result, self.stage.process(self.p.i_data)) + + # connect previous rdy/valid/data - do flatten on i_data + # NOTE: cannot do the PrevControl-looking trick because + # of need to process the data. shaaaame.... + m.d.comb += [fifo.we.eq(self.p.i_valid_test), + self.p.o_ready.eq(fifo.writable), + eq(fifo.din, flatten(result)), + ] + + # connect next rdy/valid/data - do flatten on o_data + connections = [self.n.o_valid.eq(fifo.readable), + fifo.re.eq(self.n.i_ready_test), + ] + if self.fwft or self.buffered: + m.d.comb += connections + else: + m.d.sync += connections # unbuffered fwft mode needs sync + o_data = flatten(self.n.o_data).eq(fifo.dout) + if hasattr(self.stage, "postprocess"): + o_data = self.stage.postprocess(o_data) + m.d.comb += o_data + return m +""" +class BufferedHandshake(FIFOControl): + def __init__(self, stage, in_multi=None, stage_ctl=False): + FIFOControl.__init__(self, 2, stage, in_multi, stage_ctl, + fwft=True, buffered=False) +"""