X-Git-Url: https://git.libre-soc.org/?a=blobdiff_plain;f=src%2Fadd%2Fexample_buf_pipe.py;h=00eecc3ecd37db7b603c1dd4f50ce84e1b98ca52;hb=b13c8a7a5368a53bedc71e5b8969c721103144c4;hp=fa52f3c70551abe1ebacff864fee96bed8bdbd5a;hpb=b58c1a8f96dfaa63e89c7f3d7fd65f0fec9c1932;p=ieee754fpu.git diff --git a/src/add/example_buf_pipe.py b/src/add/example_buf_pipe.py index fa52f3c7..00eecc3e 100644 --- a/src/add/example_buf_pipe.py +++ b/src/add/example_buf_pipe.py @@ -12,12 +12,12 @@ where data will flow on *every* clock when the conditions are right. input acceptance conditions are when: - * incoming previous-stage strobe (i_p_stb) is HIGH - * outgoing previous-stage busy (o_p_busy) is LOW + * incoming previous-stage strobe (i.p_valid) is HIGH + * outgoing previous-stage ready (o.p_ready) is LOW output transmission conditions are when: - * outgoing next-stage strobe (o_n_stb) is HIGH - * outgoing next-stage busy (i_n_busy) is LOW + * outgoing next-stage strobe (o.n_valid) is HIGH + * outgoing next-stage ready (i.n_ready) is LOW the tricky bit is when the input has valid data and the output is not ready to accept it. if it wasn't for the clock synchronisation, it @@ -25,124 +25,196 @@ not ready". unfortunately, it's not possible to "change the past": the previous stage *has no choice* but to pass on its data. - therefore, the incoming data *must* be accepted - and stored. + therefore, the incoming data *must* be accepted - and stored: that + is the responsibility / contract that this stage *must* accept. on the same clock, it's possible to tell the input that it must not send any more data. this is the "stall" condition. we now effectively have *two* possible pieces of data to "choose" from: the buffered data, and the incoming data. the decision as to which to process and output is based on whether we are in "stall" or not. - i.e. when the next stage is no longer busy, the output comes from + i.e. when the next stage is no longer ready, the output comes from the buffer if a stall had previously occurred, otherwise it comes direct from processing the input. + this allows us to respect a synchronous "travelling STB" with what + dan calls a "buffered handshake". + it's quite a complex state machine! """ from nmigen import Signal, Cat, Const, Mux, Module from nmigen.cli import verilog, rtlil -class BufPipe: + +class ExampleStage: + """ an example of how to use the buffered pipeline. actual names of + variables (i_data, r_data, o_data, result) below do not matter: + the functions however do. + + input data i_data is read (only), is processed and goes into an + intermediate result store [process()]. this is updated combinatorially. + + in a non-stall condition, the intermediate result will go into the + output (update_output). however if ever there is a stall, it goes + into r_data instead [update_buffer()]. + + when the non-stall condition is released, r_data is the first + to be transferred to the output [flush_buffer()], and the stall + condition cleared. + + on the next cycle (as long as stall is not raised again) the + input may begin to be processed and transferred directly to output. + """ + + def __init__(self): + """ i_data can be a DIFFERENT type from everything else + o_data, r_data and result are best of the same type. + however this is not strictly necessary. an intermediate + transformation process could hypothetically be applied, however + it is result and r_data that definitively need to be of the same + (intermediary) type, as it is both result and r_data that + are transferred into o_data: + + i_data -> process() -> result --> o_data + | ^ + | | + +-> r_data -+ + """ + self.i_data = Signal(16) + self.r_data = Signal(16) + self.o_data = Signal(16) + self.result = Signal(16) + + def process(self): + """ process the input data and store it in result. + (not needed to be known: result is combinatorial) + """ + return self.result.eq(self.i_data + 1) + + def update_buffer(self): + """ copies the result into the intermediate register r_data, + which will need to be outputted on a subsequent cycle + prior to allowing "normal" operation. + """ + return self.r_data.eq(self.result) + + def update_output(self): + """ copies the (combinatorial) result into the output + """ + return self.o_data.eq(self.result) + + def flush_buffer(self): + """ copies the *intermediate* register r_data into the output + """ + return self.o_data.eq(self.r_data) + + def ports(self): + return [self.i_data, self.o_data] + +class IOAckIn: + + def __init__(self): + self.p_valid = Signal() # >>in - comes in from PREVIOUS stage + self.n_ready = Signal() # in<< - comes in from the NEXT stage + + +class IOAckOut: + + def __init__(self): + self.n_valid = Signal() # out>> - goes out to the NEXT stage + self.p_ready = Signal() # <>in stage o_n_stb out>> stage+1 - stage-1 o_p_busy <>in stage o_data out>> stage+1 + stage-1 i.p_valid >>in stage o.n_valid out>> stage+1 + stage-1 o.p_ready <>in stage o_data out>> stage+1 | | +-------> process | | +-- r_data ---+ """ def __init__(self): - # input - #self.i_p_rst = Signal() # >>in - comes in from PREVIOUS stage - self.i_p_stb = Signal() # >>in - comes in from PREVIOUS stage - self.i_n_busy = Signal() # in<< - comes in from the NEXT stage - self.i_data = Signal(32) # >>in - comes in from the PREVIOUS stage - #self.i_rst = Signal() + # input: strobe comes in from previous stage, ready comes in from next + self.i = IOAckIn() + #self.i.p_valid = Signal() # >>in - comes in from PREVIOUS stage + #self.i.n_ready = Signal() # in<< - comes in from the NEXT stage - # buffered - self.r_data = Signal(32) - - # output - self.o_n_stb = Signal() # out>> - goes out to the NEXT stage - self.o_p_busy = Signal() # <> - goes out to the NEXT stage - - def pre_process(self, d_in): - return d_in | 0xf0000 - - def process(self, d_in): - return d_in + 1 + # output: strobe goes out to next stage, ready comes in from previous + self.o = IOAckOut() + #self.o.n_valid = Signal() # out>> - goes out to the NEXT stage + #self.o.p_ready = Signal() # <