Skip to content

Commit

Permalink
gateware.iostream.IOStreamer: let o_stream transfer if i_stream not rdy
Browse files Browse the repository at this point in the history
This change allows o_stream to take at least one transfer, as long as
there are no sample transfers in flight. This will prevent deadlocks,
for example if i_stream is implemented with something like:
```m.d.comb += ready.eq(valid)```
Which is valid according to stream rules.

This commit also adds tests for this.
  • Loading branch information
purdeaandrei committed Aug 24, 2024
1 parent 2bf1b26 commit 2cff955
Show file tree
Hide file tree
Showing 2 changed files with 29 additions and 5 deletions.
13 changes: 8 additions & 5 deletions software/glasgow/gateware/iostream.py
Original file line number Diff line number Diff line change
Expand Up @@ -162,15 +162,18 @@ def elaborate(self, platform):
m.d.comb += buffer_parts.oe.eq(latch_parts.oe)

def delay(value, name):
delayed_values = []
for stage in range(latency):
next_value = Signal.like(value, name=f"{name}_{stage}")
m.d.sync += next_value.eq(value)
value = next_value
return value
delayed_values.append(next_value)
return delayed_values

i_en = delay(self.o_stream.valid & self.o_stream.ready &
self.o_stream.p.i_en, name="i_en")
meta = delay(self.o_stream.p.meta, name="meta")
i_en_delays = delay(self.o_stream.valid & self.o_stream.ready &
self.o_stream.p.i_en, name="i_en")
i_en = i_en_delays[-1]
meta = delay(self.o_stream.p.meta, name="meta")[-1]

# This skid buffer is organized as a shift register to avoid any uncertainties associated
# with the use of an async read memory. On platforms that have LUTRAM, this implementation
Expand All @@ -195,7 +198,7 @@ def delay(value, name):

m.d.comb += self.i_stream.payload.eq(skid[skid_at])
m.d.comb += self.i_stream.valid.eq(i_en | (skid_at != 0))
m.d.comb += self.o_stream.ready.eq(self.i_stream.ready & (skid_at == 0))
m.d.comb += self.o_stream.ready.eq(self.i_stream.ready | ~((skid_at!=0) | Cat(*i_en_delays).any()))

return m

Expand Down
21 changes: 21 additions & 0 deletions software/tests/gateware/test_iostream.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,27 @@ def _subtest_sdr_and_ddr_input_sampled_correctly(self, o_valid_bits, i_en_bits,
self._subtest_sdr_input_sampled_correctly(o_valid_bits, i_en_bits, i_ready_bits, timeout_clocks, iready_comb_path)
self._subtest_ddr_input_sampled_correctly(o_valid_bits, i_en_bits, i_ready_bits, timeout_clocks, iready_comb_path)

def test_i_ready_low_doesnt_block_when_not_sampling_inputs(self):
"""
This testcase ensures that if i_stream is blocked, we can still perform
non-sampling updates, as long as we never try to sample.
"""
self._subtest_sdr_and_ddr_input_sampled_correctly(
o_valid_bits = "010101010",
i_en_bits = "000000000",
i_ready_bits = "")

def test_i_ready_low_doesnt_block_when_iready_combinatorial(self):
"""
This testcase ensures that an i_stream consumer, which is following
stream rules, and is allowed to generate ready like this:
```m.d.comb += ready.eq(valid)```, will not cause the system to lock up
"""
self._subtest_sdr_and_ddr_input_sampled_correctly(
o_valid_bits = "0111100000" + (("0" * MAX_SKIDBUFFER_SIZE) + "0") * 4,
i_en_bits = "0111100000" + (("0" * MAX_SKIDBUFFER_SIZE) + "0") * 4,
i_ready_bits = "0000000000" + (("0" * MAX_SKIDBUFFER_SIZE) + "1") * 4, iready_comb_path = True)

def _subtest_fill_skid_buffer_to_level_static(self, level):
"""
This testcase fills up the skid buffer up to a given level
Expand Down

0 comments on commit 2cff955

Please sign in to comment.