Stream utilities

Splitting / merging streams

class tiliqua.dsp.Split(*args, src_loc_at=0, **kwargs)

Consumes payloads from a single stream and splits it into multiple independent streams. This component may be instantiated in 2 modes depending on the value of replicate:

  • Channel splitter (replicate == False):

    The incoming stream has an data.ArrayLayout signature. Each payload in the data.ArrayLayout becomes an independent outgoing stream. n_channels must match the number of payloads in the data.ArrayLayout.

  • Channel replicater (replicate == True):

    The incoming stream has a single payload. Each payload in the incoming stream is replicated and at the output appears as n_channels independent streams, which produce the same values, however may be synchronized/consumed independently.

This class is inspired by previous work in the lambdalib and LiteX projects.

__init__(n_channels, replicate=False, source=None)
n_channelsint

The number of independent output streams. See usage above.

replicatebool, optional

See usage above.

sourcestream, optional

Optional incoming stream to pass through to wiring.connect on elaboration. This argument means you do not have to hook up self.i and can make some pipelines a little easier to read.

wire_ready(m, channels)

Set out channels as permanently READY so they don’t block progress.

class tiliqua.dsp.Merge(*args, src_loc_at=0, **kwargs)

Consumes payloads from multiple independent streams and merges them into a single stream.

This class is inspired by previous work in the lambdalib and LiteX projects.

__init__(n_channels, sink=None)
n_channelsint

The number of independent incoming streams.

sinkstream, optional

Optional outgoing stream to pass through to wiring.connect on elaboration. This argument means you do not have to hook up self.o and can make some pipelines a little easier to read.

wire_valid(m, channels)

Set in channels as permanently VALID so they don’t block progress.

Connecting and remapping streams

tiliqua.dsp.connect_remap(m, stream_o, stream_i, mapping)

Connect 2 streams, bypassing normal wiring.connect() checks that the signatures match. This allows easily remapping fields when you are trying to connect streams with different signatures.

For example, say I have a stream with an ArrayLayout payload and want to map it to a different stream with a StructLayout payload, and the underlying bit-representation of both layouts do not match, I can remap using:

dsp.connect_remap(m, vca_merge2a.o, vca0.i, lambda o, i : [
    i.payload.x   .eq(o.payload[0]),
    i.payload.gain.eq(o.payload[1] << 2)
])

This is a bit of a hack. TODO perhaps implement this as a StreamConverter such that we can still use wiring.connect?.

tiliqua.dsp.channel_remap(m, stream_o, stream_i, mapping_o_to_i)

Connect 2 streams of type data.ArrayLayout, with different channel counts or channel indices. For example, to connect a source with 4 channels to a sink with 2 channels, mapping 0 to 0, 1 to 1, leaving 2 and 3 unconnected:

s1 = stream.Signature(data.ArrayLayout(ASQ, 4)).create()
s2 = stream.Signature(data.ArrayLayout(ASQ, 2)).create()
dsp.channel_remap(m, s1, s2, {0: 0, 1: 1})

This also works the other way around, to connect e.g. a source with 2 channels to a sink with 4 channels. The stream will make progress however the value of the payloads in any unmapped output channels is undefined.

Connecting streams in feedback loops

class tiliqua.dsp.KickFeedback(*args, src_loc_at=0, **kwargs)

Inject a single dummy (garbage) sample after reset between two streams. This is necessary to break infinite blocking after reset if streams are set up in a feedback loop.

tiliqua.dsp.connect_feedback_kick(m, o, i)