Delay Lines
- class tiliqua.delay_line.DelayLine(*args, src_loc_at=0, **kwargs)
SRAM- or PSRAM-backed audio delay line.
This forms the backbone of many different types of effects - echoes, pitch shifting, chorus, feedback synthesis etc.
Usage
Each
DelayLine
instance operates in a single-writer, multiple-reader fashion - that is, for eachDelayLine
, there may be only one stream of samples being written, however from eachDelayLine
you may create N instances ofDelayLineTap
, which are submodules ofDelayLine
used to produce output streams (read operations) on theDelayLine
.For a simple, SRAM-backed delay line, the following is sufficient:
delayln = DelayLine( max_delay=8192, write_triggers_read=False, )
From this, you can create some read taps:
tap1 = delayln.add_tap() tap2 = delayln.add_tap()
The
DelayLine
instance requires a single incoming streamDelayLine.i
, on which incoming samples are taken and written to the backing store.Each
DelayLineTap
instance requires both an incoming and outgoing stream,DelayLineTap.i
,DelayLineTap.o
, where an output sample is only produced some time after the requested delay count has arrived onDelayLineTap.i
.This gives applications the flexibility to read multiple times per write sample (useful for example for fractional delay lines where we want to interpolate between two adjacent samples).
Fixed (simple) delay taps
It can be a bit cumbersome to need to provide each tap with an input stream if you just want some taps with fixed delays.
So, if you want a simple fixed delay tap, you can use the
write_triggers_read=True
option when creating theDelayLine
. Then, you can specify explicit fixed delay taps as follows:delayln = DelayLine(max_delay=8192, write_triggers_read=True) tap1 = delayln.add_tap(fixed_delay=5000) tap2 = delayln.add_tap(fixed_delay=7000)
Note
When used in this mode,
tap1
andtap2
will internally have their inputs (sample request streams) hooked up to the write strobe. This means you no longer need to hook uptapX.i
and will automatically get a single sample on eachtapX.o
after every write todelayln
.Backing store
The backing store is a contiguous region of memory where samples are written to a wrapped incrementing index (i.e circular buffer fashion).
The same memory space is shared by all read & write operations, however the way this works is slightly different when comparing SRAM- and PSRAM- backed delay lines. In both cases, all read & write operations go through an arbiter and share the same memory bus.
- SRAM-backed delay line
The memory bus is connected directly to an FPGA DPRAM instantiation and does not need to be connected to any external memory bus.
- PSRAM-backed delay line
Due to the memory access latency of PSRAM, simply forwarding each read/write access would quickly consume memory bandwidth simply due to the access latency. So, in the PSRAM case, a small cache is inserted between the internal delay line R/W bus and the memory bus exposed by DelayLine.bus (normally hooked up to the PSRAM). The purpose of this cache is to collect as many read & write operations into burstable transactions as possible.
Note
As each delayline contains completely different samples and individually has quite a predictable access pattern, it makes sense to have one cache per
DelayLine
, rather than one larger shared cache (which would likely perform worse considering area/bandwidth). The important factor is that all writes and reads on the same delayline share the same cache, as the write and read taps have the same working set.- Members:
i (
In(stream.Signature(ASQ))
) – Input stream for writing samples to the delay line.bus (
Out(wishbone.Signature)
) – Wishbone bus for connecting to external PSRAM (usually through an arbiter). Only present for PSRAM-backed delay lines.
- __init__(max_delay, psram_backed=False, addr_width_o=None, base=None, write_triggers_read=True, cache_kwargs=None)
- max_delayint
The maximum delay in samples. This exactly corresponds to the memory required in the backing store. Must be a power of 2.
- psram_backedbool, optional
If True, the delay line is backed by PSRAM. Otherwise, it is backed by SRAM.
- addr_width_oint, optional
Required for PSRAM-backed delay lines. The address width of the external memory bus.
- baseint, optional
Required for PSRAM-backed delay lines. The memory slice base address. This is the physical address offset in bytes.
- write_triggers_readbool, optional
If True, writing to the delay line triggers a read. This means the
DelayLineTap.i
stream does not need to be connected- cache_kwargsdict, optional
Relevant only for PSRAM-backed delay lines. Arguments to forward to creation of the internal memory cache.
- add_tap(fixed_delay=None)
Add and return a new
DelayLineTap
to stream samples read from thisDelayLine
.Note
Each tap automatically becomes a submodule of the
DelayLine
instance. That is, you only need to addDelayLine
itself tom.submodules
.- fixed_delayint
The
DelayLineTap.i
is automatically set to a fixed delay. Only used whenwrite_triggers_read=True
.
- class tiliqua.delay_line.DelayLineTap(*args, src_loc_at=0, **kwargs)
A single read tap of a parent
DelayLine
. SeeDelayLine
top-level comment for information on usage.DelayLineTap
should only be created usingDelayLine.add_tap()
.- Members:
i (
In(unsigned(N))
) – Stream of delays requested to be read from the delay line. The unit is in number of samples behind the last written sample to the delay line.o (
Out(stream.Signature(ASQ))
) – Stream of samples read from the delay line, one per request onDelayLineTap.i
.