Skip to content

Commit 1940353

Browse files
committed
interface/stream_utils: add the LastOnTimeout module
1 parent e719732 commit 1940353

File tree

2 files changed

+72
-0
lines changed

2 files changed

+72
-0
lines changed

lambdalib/interface/stream_utils.py

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"Splitter",
1212
"Merger",
1313
"LastInserter",
14+
"LastOnTimeout",
1415
"Arbiter",
1516
]
1617

@@ -252,6 +253,54 @@ def elaborate(self, platform):
252253
return m
253254

254255

256+
class LastOnTimeout(Elaboratable):
257+
""" This module injects a `last` signal into the source stream
258+
if no more data is received from the sink stream since more than
259+
`timeout` clock cycles.
260+
261+
Typically this module can be connected to a UART receiver to delimit
262+
packets based on the idle time between packets, or to to realign
263+
a converter when no more data is received.
264+
"""
265+
def __init__(self, layout, timeout=1):
266+
self.timeout = timeout
267+
self.sink = stream.Endpoint(layout)
268+
self.source = stream.Endpoint(layout)
269+
270+
def elaborate(self, platform):
271+
sink = self.sink
272+
source = self.source
273+
274+
m = Module()
275+
276+
m.submodules.timer = timer = WaitTimer(self.timeout)
277+
m.d.comb += timer.wait.eq(~sink.valid)
278+
279+
last_r = Signal()
280+
loaded = Signal()
281+
282+
with m.If(sink.valid & sink.ready):
283+
m.d.sync += [
284+
source.data.eq(sink.data),
285+
last_r .eq(sink.last),
286+
loaded .eq(1),
287+
]
288+
with m.Elif(source.valid & source.ready):
289+
m.d.sync += loaded.eq(0)
290+
291+
with m.If(~source.valid | source.ready):
292+
m.d.comb += sink.ready.eq(1)
293+
294+
# We send the data to the source stream immediately
295+
# when it was the last data, or when more data is incoming,
296+
# or if we waited until the timeout expired.
297+
with m.If(loaded & (last_r | sink.valid | timer.done)):
298+
m.d.comb += source.valid.eq(1)
299+
m.d.comb += source.last.eq(last_r | timer.done)
300+
301+
return m
302+
303+
255304
class RoundRobin(Elaboratable):
256305
""" Fair round robin.
257306
"""

lambdalib/tests/test_interface_stream.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,30 @@ def test_last_inserter():
117117
sim.run()
118118

119119

120+
def test_last_timeout():
121+
lot = LastOnTimeout([("data", 8)], timeout=10)
122+
sim = Simulator(lot)
123+
124+
data = {
125+
"data": range(20),
126+
"last": [0]*3 + [1] + [0]*10 + [1] + [0]*5,
127+
}
128+
129+
length = len(data["data"])
130+
sender = StreamSimSender(lot.sink, data, speed=0.2)
131+
receiver = StreamSimReceiver(lot.source,
132+
length=length,
133+
speed=0.8, verbose=True)
134+
135+
sim.add_clock(1e-6)
136+
sim.add_sync_process(sender.sync_process)
137+
sim.add_sync_process(receiver.sync_process)
138+
with sim.write_vcd("tests/test_stream_last_on_timeout.vcd"):
139+
sim.run()
140+
141+
120142
if __name__ == "__main__":
121143
test_splitter(); print()
122144
test_merger(); print()
123145
test_last_inserter(); print()
146+
test_last_timeout(); print()

0 commit comments

Comments
 (0)