where data will flow on *every* clock when the conditions are right.
input acceptance conditions are when:
- * incoming previous-stage strobe (i.p_valid) is HIGH
- * outgoing previous-stage ready (o.p_ready) is LOW
+ * incoming previous-stage strobe (p.i_valid) is HIGH
+ * outgoing previous-stage ready (p.o_ready) is LOW
output transmission conditions are when:
- * outgoing next-stage strobe (o.n_valid) is HIGH
- * outgoing next-stage ready (i.n_ready) is LOW
+ * outgoing next-stage strobe (n.o_valid) is HIGH
+ * outgoing next-stage ready (n.i_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
from collections.abc import Sequence
-class IOAckIn:
+class PrevControl:
def __init__(self):
- self.p_valid = Signal() # >>in - comes in from PREVIOUS stage
- self.n_ready = Signal() # in<< - comes in from the NEXT stage
+ self.i_valid = Signal(name="p_i_valid") # >>in
+ self.o_ready = Signal(name="p_o_ready") # <<out
-class IOAckOut:
+class NextControl:
def __init__(self):
- self.n_valid = Signal() # out>> - goes out to the NEXT stage
- self.p_ready = Signal() # <<out - goes out to the PREVIOUS stage
+ self.o_valid = Signal(name="n_o_valid") # out>>
+ self.i_ready = Signal(name="n_i_ready") # <<in
def eq(o, i):
if ever the input is ready and the output is not, processed data
is stored in a temporary register.
- stage-1 i.p_valid >>in stage o.n_valid out>> stage+1
- stage-1 o.p_ready <<out stage i.n_ready <<in stage+1
- stage-1 i.data >>in stage o.data out>> stage+1
+ stage-1 p.i_valid >>in stage n.o_valid out>> stage+1
+ stage-1 p.o_ready <<out stage n.i_ready <<in stage+1
+ stage-1 p.data >>in stage n.data out>> stage+1
| |
process --->----^
| |
+-- r_data ->-+
- input data i_data is read (only), is processed and goes into an
+ input data p.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
* ispec: returns output signals to the output specification
* process: takes an input instance and returns processed data
- i_data -> process() -> result --> o.data
+ p.data -> process() -> result --> n.data
| ^
| |
+-> r_data -+
self.stage = stage
# set up input and output IO ACK (prev/next ready/valid)
- self.i = IOAckIn()
- self.o = IOAckOut()
+ self.p = PrevControl()
+ self.n = NextControl()
# set up the input and output data
- self.i.data = stage.ispec() # input type
+ self.p.data = stage.ispec() # input type
self.r_data = stage.ospec() # all these are output type
self.result = stage.ospec()
- self.o.data = stage.ospec()
+ self.n.data = stage.ospec()
def connect_next(self, nxt):
""" helper function to connect to the next stage data/valid/ready.
data/valid is passed *TO* nxt, and ready comes *IN* from nxt.
"""
- return [nxt.i.p_valid.eq(self.o.n_valid),
- self.i.n_ready.eq(nxt.o.p_ready),
- eq(nxt.i.data, self.o.data),
+ return [nxt.p.i_valid.eq(self.n.o_valid),
+ self.n.i_ready.eq(nxt.p.o_ready),
+ eq(nxt.p.data, self.n.data),
]
def connect_in(self, prev):
""" helper function to connect stage to an input source. do not
use to connect stage-to-stage!
"""
- return [self.i.p_valid.eq(prev.i.p_valid),
- prev.o.p_ready.eq(self.o.p_ready),
- eq(self.i.data, prev.i.data),
+ return [self.p.i_valid.eq(prev.p.i_valid),
+ prev.p.o_ready.eq(self.p.o_ready),
+ eq(self.p.data, prev.p.data),
]
def connect_out(self, nxt):
""" helper function to connect stage to an output source. do not
use to connect stage-to-stage!
"""
- return [nxt.o.n_valid.eq(self.o.n_valid),
- self.i.n_ready.eq(nxt.i.n_ready),
- eq(nxt.o.data, self.o.data),
+ return [nxt.n.o_valid.eq(self.n.o_valid),
+ self.n.i_ready.eq(nxt.n.i_ready),
+ eq(nxt.n.data, self.n.data),
]
def set_input(self, i):
""" helper function to set the input data
"""
- return eq(self.i.data, i)
+ return eq(self.p.data, i)
def update_buffer(self):
""" copies the result into the intermediate register r_data,
def update_output(self):
""" copies the (combinatorial) result into the output
"""
- return eq(self.o.data, self.result)
+ return eq(self.n.data, self.result)
def flush_buffer(self):
""" copies the *intermediate* register r_data into the output
"""
- return eq(self.o.data, self.r_data)
+ return eq(self.n.data, self.r_data)
def ports(self):
- return [self.i.data, self.o.data]
+ return [self.p.data, self.n.data]
def elaborate(self, platform):
m = Module()
if hasattr(self.stage, "setup"):
- self.stage.setup(m, self.i.data)
+ self.stage.setup(m, self.p.data)
# establish some combinatorial temporaries
o_n_validn = Signal(reset_less=True)
i_p_valid_o_p_ready = Signal(reset_less=True)
- m.d.comb += [o_n_validn.eq(~self.o.n_valid),
- i_p_valid_o_p_ready.eq(self.i.p_valid & self.o.p_ready),
+ m.d.comb += [o_n_validn.eq(~self.n.o_valid),
+ i_p_valid_o_p_ready.eq(self.p.i_valid & self.p.o_ready),
]
# store result of processing in combinatorial temporary
- with m.If(self.i.p_valid): # input is valid: process it
- m.d.comb += eq(self.result, self.stage.process(self.i.data))
+ with m.If(self.p.i_valid): # input is valid: process it
+ m.d.comb += eq(self.result, self.stage.process(self.p.data))
# if not in stall condition, update the temporary register
- with m.If(self.o.p_ready): # not stalled
+ with m.If(self.p.o_ready): # not stalled
m.d.sync += self.update_buffer()
- #with m.If(self.i.p_rst): # reset
- # m.d.sync += self.o.n_valid.eq(0)
- # m.d.sync += self.o.p_ready.eq(0)
- with m.If(self.i.n_ready): # next stage is ready
- with m.If(self.o.p_ready): # not stalled
+ #with m.If(self.p.i_rst): # reset
+ # m.d.sync += self.n.o_valid.eq(0)
+ # m.d.sync += self.p.o_ready.eq(0)
+ with m.If(self.n.i_ready): # next stage is ready
+ with m.If(self.p.o_ready): # not stalled
# nothing in buffer: send (processed) input direct to output
- m.d.sync += [self.o.n_valid.eq(self.i.p_valid),
+ m.d.sync += [self.n.o_valid.eq(self.p.i_valid),
self.update_output(),
]
- with m.Else(): # o.p_ready is false, and something is in buffer.
+ with m.Else(): # p.o_ready is false, and something is in buffer.
# Flush the [already processed] buffer to the output port.
- m.d.sync += [self.o.n_valid.eq(1),
+ m.d.sync += [self.n.o_valid.eq(1),
self.flush_buffer(),
# clear stall condition, declare register empty.
- self.o.p_ready.eq(1),
+ self.p.o_ready.eq(1),
]
- # ignore input, since o.p_ready is also false.
+ # ignore input, since p.o_ready is also false.
- # (i.n_ready) is false here: next stage is ready
+ # (n.i_ready) is false here: next stage is ready
with m.Elif(o_n_validn): # next stage being told "ready"
- m.d.sync += [self.o.n_valid.eq(self.i.p_valid),
- self.o.p_ready.eq(1), # Keep the buffer empty
+ m.d.sync += [self.n.o_valid.eq(self.p.i_valid),
+ self.p.o_ready.eq(1), # Keep the buffer empty
# set the output data (from comb result)
self.update_output(),
]
- # (i.n_ready) false and (o.n_valid) true:
+ # (n.i_ready) false and (n.o_valid) true:
with m.Elif(i_p_valid_o_p_ready):
# If next stage *is* ready, and not stalled yet, accept input
- m.d.sync += self.o.p_ready.eq(~(self.i.p_valid & self.o.n_valid))
+ m.d.sync += self.p.o_ready.eq(~(self.p.i_valid & self.n.o_valid))
return m
def ports(self):
- return [self.i.p_valid, self.i.n_ready,
- self.o.n_valid, self.o.p_ready,
+ return [self.p.i_valid, self.n.i_ready,
+ self.n.o_valid, self.p.o_ready,
]
self.stage = stage
self._data_valid = Signal()
# set up input and output IO ACK (prev/next ready/valid)
- self.i = IOAckIn()
- self.o = IOAckOut()
+ self.p = PrevControl()
+ self.n = NextControl()
# set up the input and output data
- self.i.data = stage.ispec() # input type
+ self.p.data = stage.ispec() # input type
self.r_data = stage.ispec() # input type
self.result = stage.ospec() # output data
- self.o.data = stage.ospec() # output type
- self.o.data.name = "outdata"
+ self.n.data = stage.ospec() # output type
+ self.n.data.name = "outdata"
def set_input(self, i):
""" helper function to set the input data
"""
- return eq(self.i.data, i)
+ return eq(self.p.data, i)
def elaborate(self, platform):
m = Module()
if hasattr(self.stage, "setup"):
self.stage.setup(m, self.r_data)
m.d.comb += eq(self.result, self.stage.process(self.r_data))
- m.d.comb += self.o.n_valid.eq(self._data_valid)
- m.d.comb += self.o.p_ready.eq(~self._data_valid | self.i.n_ready)
- m.d.sync += self._data_valid.eq(self.i.p_valid | \
- (~self.i.n_ready & self._data_valid))
- with m.If(self.i.p_valid & self.o.p_ready):
- m.d.sync += eq(self.r_data, self.i.data)
- m.d.comb += eq(self.o.data, self.result)
+ m.d.comb += self.n.o_valid.eq(self._data_valid)
+ m.d.comb += self.p.o_ready.eq(~self._data_valid | self.n.i_ready)
+ m.d.sync += self._data_valid.eq(self.p.i_valid | \
+ (~self.n.i_ready & self._data_valid))
+ with m.If(self.p.i_valid & self.p.o_ready):
+ m.d.sync += eq(self.r_data, self.p.data)
+ m.d.comb += eq(self.n.data, self.result)
return m
def ports(self):
- return [self.i.data, self.o.data]
+ return [self.p.data, self.n.data]
class ExampleCombPipe(CombPipe):
from nmigen.compat.sim import run_simulation
from example_buf_pipe import ExampleBufPipe, ExampleBufPipeAdd
from example_buf_pipe import ExampleCombPipe, CombPipe
-from example_buf_pipe import IOAckIn, IOAckOut
+from example_buf_pipe import PrevControl, NextControl
from random import randint
def check_o_n_valid(dut, val):
- o_n_valid = yield dut.o.n_valid
+ o_n_valid = yield dut.n.o_valid
assert o_n_valid == val
def testbench(dut):
#yield dut.i_p_rst.eq(1)
- yield dut.i.n_ready.eq(0)
- yield dut.o.p_ready.eq(0)
+ yield dut.n.i_ready.eq(0)
+ yield dut.p.o_ready.eq(0)
yield
yield
#yield dut.i_p_rst.eq(0)
- yield dut.i.n_ready.eq(1)
- yield dut.i.data.eq(5)
- yield dut.i.p_valid.eq(1)
+ yield dut.n.i_ready.eq(1)
+ yield dut.p.data.eq(5)
+ yield dut.p.i_valid.eq(1)
yield
- yield dut.i.data.eq(7)
+ yield dut.p.data.eq(7)
yield from check_o_n_valid(dut, 0) # effects of i_p_valid delayed
yield
yield from check_o_n_valid(dut, 1) # ok *now* i_p_valid effect is felt
- yield dut.i.data.eq(2)
+ yield dut.p.data.eq(2)
yield
- yield dut.i.n_ready.eq(0) # begin going into "stall" (next stage says ready)
- yield dut.i.data.eq(9)
+ yield dut.n.i_ready.eq(0) # begin going into "stall" (next stage says ready)
+ yield dut.p.data.eq(9)
yield
- yield dut.i.p_valid.eq(0)
- yield dut.i.data.eq(12)
+ yield dut.p.i_valid.eq(0)
+ yield dut.p.data.eq(12)
yield
- yield dut.i.data.eq(32)
- yield dut.i.n_ready.eq(1)
+ yield dut.p.data.eq(32)
+ yield dut.n.i_ready.eq(1)
yield
yield from check_o_n_valid(dut, 1) # buffer still needs to output
yield
def testbench2(dut):
- #yield dut.i.p_rst.eq(1)
- yield dut.i.n_ready.eq(0)
- #yield dut.o.p_ready.eq(0)
+ #yield dut.p.i_rst.eq(1)
+ yield dut.n.i_ready.eq(0)
+ #yield dut.p.o_ready.eq(0)
yield
yield
- #yield dut.i.p_rst.eq(0)
- yield dut.i.n_ready.eq(1)
- yield dut.i.data.eq(5)
- yield dut.i.p_valid.eq(1)
+ #yield dut.p.i_rst.eq(0)
+ yield dut.n.i_ready.eq(1)
+ yield dut.p.data.eq(5)
+ yield dut.p.i_valid.eq(1)
yield
- yield dut.i.data.eq(7)
+ yield dut.p.data.eq(7)
yield from check_o_n_valid(dut, 0) # effects of i_p_valid delayed 2 clocks
yield
yield from check_o_n_valid(dut, 0) # effects of i_p_valid delayed 2 clocks
- yield dut.i.data.eq(2)
+ yield dut.p.data.eq(2)
yield
yield from check_o_n_valid(dut, 1) # ok *now* i_p_valid effect is felt
- yield dut.i.n_ready.eq(0) # begin going into "stall" (next stage says ready)
- yield dut.i.data.eq(9)
+ yield dut.n.i_ready.eq(0) # begin going into "stall" (next stage says ready)
+ yield dut.p.data.eq(9)
yield
- yield dut.i.p_valid.eq(0)
- yield dut.i.data.eq(12)
+ yield dut.p.i_valid.eq(0)
+ yield dut.p.data.eq(12)
yield
- yield dut.i.data.eq(32)
- yield dut.i.n_ready.eq(1)
+ yield dut.p.data.eq(32)
+ yield dut.n.i_ready.eq(1)
yield
yield from check_o_n_valid(dut, 1) # buffer still needs to output
yield
send = True
else:
send = randint(0, send_range) != 0
- o_p_ready = yield self.dut.o.p_ready
+ o_p_ready = yield self.dut.p.o_ready
if not o_p_ready:
yield
continue
if send and self.i != len(self.data):
- yield self.dut.i.p_valid.eq(1)
- yield self.dut.i.data.eq(self.data[self.i])
+ yield self.dut.p.i_valid.eq(1)
+ yield self.dut.p.data.eq(self.data[self.i])
self.i += 1
else:
- yield self.dut.i.p_valid.eq(0)
+ yield self.dut.p.i_valid.eq(0)
yield
def rcv(self):
stall_range = randint(0, 3)
for j in range(randint(1,10)):
stall = randint(0, stall_range) != 0
- yield self.dut.i.n_ready.eq(stall)
+ yield self.dut.n.i_ready.eq(stall)
yield
- o_n_valid = yield self.dut.o.n_valid
- i_n_ready = yield self.dut.i.n_ready
+ o_n_valid = yield self.dut.n.o_valid
+ i_n_ready = yield self.dut.n.i_ready
if not o_n_valid or not i_n_ready:
continue
- o_data = yield self.dut.o.data
+ o_data = yield self.dut.n.data
self.resultfn(o_data, self.data[self.o], self.i, self.o)
self.o += 1
if self.o == len(self.data):
send = True
else:
send = randint(0, send_range) != 0
- o_p_ready = yield self.dut.o.p_ready
+ o_p_ready = yield self.dut.p.o_ready
if not o_p_ready:
yield
continue
if send and self.i != len(self.data):
- yield self.dut.i.p_valid.eq(1)
+ yield self.dut.p.i_valid.eq(1)
for v in self.dut.set_input(self.data[self.i]):
yield v
self.i += 1
else:
- yield self.dut.i.p_valid.eq(0)
+ yield self.dut.p.i_valid.eq(0)
yield
def rcv(self):
stall_range = randint(0, 3)
for j in range(randint(1,10)):
stall = randint(0, stall_range) != 0
- yield self.dut.i.n_ready.eq(stall)
+ yield self.dut.n.i_ready.eq(stall)
yield
- o_n_valid = yield self.dut.o.n_valid
- i_n_ready = yield self.dut.i.n_ready
+ o_n_valid = yield self.dut.n.o_valid
+ i_n_ready = yield self.dut.n.i_ready
if not o_n_valid or not i_n_ready:
continue
- o_data = yield self.dut.o.data
+ o_data = yield self.dut.n.data
self.resultfn(o_data, self.data[self.o], self.i, self.o)
self.o += 1
if self.o == len(self.data):
while True:
stall = randint(0, 3) != 0
send = randint(0, 5) != 0
- yield dut.i.n_ready.eq(stall)
- o_p_ready = yield dut.o.p_ready
+ yield dut.n.i_ready.eq(stall)
+ o_p_ready = yield dut.p.o_ready
if o_p_ready:
if send and i != len(data):
- yield dut.i.p_valid.eq(1)
- yield dut.i.data.eq(data[i])
+ yield dut.p.i_valid.eq(1)
+ yield dut.p.data.eq(data[i])
i += 1
else:
- yield dut.i.p_valid.eq(0)
+ yield dut.p.i_valid.eq(0)
yield
- o_n_valid = yield dut.o.n_valid
- i_n_ready = yield dut.i.n_ready
+ o_n_valid = yield dut.n.o_valid
+ i_n_ready = yield dut.n.i_ready
if o_n_valid and i_n_ready:
- o_data = yield dut.o.data
+ o_data = yield dut.n.data
assert o_data == data[o] + 2, "%d-%d data %x not match %x\n" \
% (i, o, o_data, data[o])
o += 1
v v
i_p_valid >>in pipe1 o_n_valid out>> i_p_valid >>in pipe2
o_p_ready <<out pipe1 i_n_ready <<in o_p_ready <<out pipe2
- i_data >>in pipe1 o_data out>> i_data >>in pipe2
+ p_data >>in pipe1 o_data out>> p_data >>in pipe2
"""
def __init__(self):
self.pipe1 = ExampleBufPipe()
self.pipe2 = ExampleBufPipe()
# input
- self.i = IOAckIn()
- self.i.data = Signal(32) # >>in - comes in from the PREVIOUS stage
+ self.p = PrevControl()
+ self.p.data = Signal(32) # >>in - comes in from the PREVIOUS stage
# output
- self.o = IOAckOut()
- self.o.data = Signal(32) # out>> - goes out to the NEXT stage
+ self.n = NextControl()
+ self.n.data = Signal(32) # out>> - goes out to the NEXT stage
def elaborate(self, platform):
m = Module()