.. role:: raw-html-m2r(raw)
:format: html
.. _stream:
Stream
======
Specification
-------------
| The Stream interface is a simple handshake protocol to carry payload.
| It could be used for example to push and pop elements into a FIFO, send requests to a UART controller, etc.
.. list-table::
:header-rows: 1
:widths: 1 1 1 10 1
* - Signal
- Type
- Driver
- Description
- Don't care when
* - valid
- Bool
- Master
- When high => payload present on the interface
-
* - ready
- Bool
- Slave
- When low => transaction are not consumed by the slave
- valid is low
* - payload
- T
- Master
- Content of the transaction
- valid is low
.. wavedrom::
{ signal: [
{name:'clk', wave: 'p.........' },
{name: 'valid' , wave: '0101..01.0'},
{name: 'ready' , wave: 'x1x0.1x1.x'},
{name: 'payload', wave: 'x=x=..x==x',data:['D0','D1','D2','D3']},
]}
There is some examples of usage in SpinalHDL :
.. code-block:: scala
class StreamFifo[T <: Data](dataType: T, depth: Int) extends Component {
val io = new Bundle {
val push = slave Stream (dataType)
val pop = master Stream (dataType)
}
...
}
class StreamArbiter[T <: Data](dataType: T,portCount: Int) extends Component {
val io = new Bundle {
val inputs = Vec(slave Stream (dataType),portCount)
val output = master Stream (dataType)
}
...
}
.. note::
Each slave can or can't allow the payload to change when valid is high and ready is low. For examples:
* An priority arbiter without lock logic can switch from one input to the other (which will change the payload).
* An UART controller could directly use the write port to drive UART pins and only consume the transaction at the end of the transmission.
Be careful with that.
Functions
---------
.. list-table::
:header-rows: 1
:widths: 5 5 1 1
* - Syntax
- Description
- Return
- Latency
* - Stream(type : Data)
- Create a Stream of a given type
- Stream[T]
-
* - master/slave Stream(type : Data)
- | Create a Stream of a given type
| Initialized with corresponding in/out setup
- Stream[T]
-
* - x.fire
- Return True when a transaction is consumed on the bus (valid && ready)
- Bool
-
* - x.isStall
- Return True when a transaction is stall on the bus (valid && ! ready)
- Bool
-
* - x.queue(size:Int)
- Return a Stream connected to x through a FIFO
- Stream[T]
- 2
* - | x.m2sPipe()
| x.stage()
- | Return a Stream drived by x
| through a register stage that cut valid/payload paths
| Cost = (payload width + 1) flop flop
- Stream[T]
- 1
* - x.s2mPipe()
- | Return a Stream drived by x
| ready paths is cut by a register stage
| Cost = payload width * (mux2 + 1 flip flop)
- Stream[T]
- 0
* - x.halfPipe()
- | Return a Stream drived by x
| valid/ready/payload paths are cut by some register
| Cost = (payload width + 2) flip flop, bandwidth divided by two
- Stream[T]
- 1
* - | x << y
| y >> x
- Connect y to x
-
- 0
* - | x <-< y
| y >-> x
- Connect y to x through a m2sPipe
-
- 1
* - | x < y
| y >/> x
- Connect y to x through a s2mPipe
-
- 0
* - | x <-/< y
| y >/-> x
- | Connect y to x through s2mPipe().m2sPipe()
| Which imply no combinatorial path between x and y
-
- 1
* - x.haltWhen(cond : Bool)
- | Return a Stream connected to x
| Halted when cond is true
- Stream[T]
- 0
* - x.throwWhen(cond : Bool)
- | Return a Stream connected to x
| When cond is true, transaction are dropped
- Stream[T]
- 0
The following code will create this logic :
.. image:: /asset/picture/stream_throw_m2spipe.svg
:align: center
.. code-block:: scala
case class RGB(channelWidth : Int) extends Bundle{
val red = UInt(channelWidth bit)
val green = UInt(channelWidth bit)
val blue = UInt(channelWidth bit)
def isBlack : Bool = red === 0 && green === 0 && blue === 0
}
val source = Stream(RGB(8))
val sink = Stream(RGB(8))
sink <-< source.throwWhen(source.payload.isBlack)
Utils
-----
There is many utils that you can use in your design in conjunction with the Stream bus, This chapter will document them.
StreamFifo
^^^^^^^^^^
On each stream you can call the .queue(size) to get a buffered stream. But you can also instantiate the FIFO component itself :
.. code-block:: scala
val streamA,streamB = Stream(Bits(8 bits))
//...
val myFifo = StreamFifo(
dataType = Bits(8 bits),
depth = 128
)
myFifo.io.push << streamA
myFifo.io.pop >> streamB
.. list-table::
:header-rows: 1
:widths: 1 1 2
* - parameter name
- Type
- Description
* - dataType
- T
- Payload data type
* - depth
- Int
- Size of the memory used to store elements
.. list-table::
:header-rows: 1
:widths: 1 4 5
* - io name
- Type
- Description
* - push
- Stream[T]
- Used to push elements
* - pop
- Stream[T]
- Used to pop elements
* - flush
- Bool
- Used to remove all elements inside the FIFO
* - occupancy
- UInt of log2Up(depth + 1) bits
- Indicate the internal memory occupancy
StreamFifoCC
^^^^^^^^^^^^
You can instanciate the dual clock domain version of the fifo by the following way :
.. code-block:: scala
val clockA = ClockDomain(???)
val clockB = ClockDomain(???)
val streamA,streamB = Stream(Bits(8 bits))
//...
val myFifo = StreamFifoCC(
dataType = Bits(8 bits),
depth = 128,
pushClock = clockA,
popClock = clockB
)
myFifo.io.push << streamA
myFifo.io.pop >> streamB
.. list-table::
:header-rows: 1
:widths: 1 1 2
* - parameter name
- Type
- Description
* - dataType
- T
- Payload data type
* - depth
- Int
- Size of the memory used to store elements
* - pushClock
- ClockDomain
- Clock domain used by the push side
* - popClock
- ClockDomain
- Clock domain used by the pop side
.. list-table::
:header-rows: 1
:widths: 1 4 5
* - io name
- Type
- Description
* - push
- Stream[T]
- Used to push elements
* - pop
- Stream[T]
- Used to pop elements
* - pushOccupancy
- UInt of log2Up(depth + 1) bits
- Indicate the internal memory occupancy (from the push side perspective)
* - popOccupancy
- UInt of log2Up(depth + 1) bits
- Indicate the internal memory occupancy (from the pop side perspective)
StreamCCByToggle
^^^^^^^^^^^^^^^^
| Component that provide a Stream cross clock domain bridge based on toggling signals.
| This way of doing cross clock domain bridge is characterized by a small area usage but also a low bandwidth.
.. code-block:: scala
val clockA = ClockDomain(???)
val clockB = ClockDomain(???)
val streamA,streamB = Stream(Bits(8 bits))
//...
val bridge = StreamCCByToggle(
dataType = Bits(8 bits),
inputClock = clockA,
outputClock = clockB
)
bridge.io.input << streamA
bridge.io.output >> streamB
.. list-table::
:header-rows: 1
:widths: 1 1 2
* - parameter name
- Type
- Description
* - dataType
- T
- Payload data type
* - inputClock
- ClockDomain
- Clock domain used by the push side
* - outputClock
- ClockDomain
- Clock domain used by the pop side
.. list-table::
:header-rows: 1
:widths: 1 1 2
* - io name
- Type
- Description
* - input
- Stream[T]
- Used to push elements
* - output
- Stream[T]
- Used to pop elements
But you can also use a this shorter syntax which directly return you the cross clocked stream:
.. code-block:: scala
val clockA = ClockDomain(???)
val clockB = ClockDomain(???)
val streamA = Stream(Bits(8 bits))
val streamB = StreamCCByToggle(
input = streamA,
inputClock = clockA,
outputClock = clockB
)
StreamArbiter
^^^^^^^^^^^^^
When you have multiple Streams and you want to arbitrate them to drive a single one, you can use the StreamArbiterFactory.
.. code-block:: scala
val streamA, streamB, streamC = Stream(Bits(8 bits))
val arbitredABC = StreamArbiterFactory.roundRobin.onArgs(streamA, streamB, streamC)
val streamD, streamE, streamF = Stream(Bits(8 bits))
val arbitredDEF = StreamArbiterFactory.lowerFirst.noLock.onArgs(streamD, streamE, streamF)
.. list-table::
:header-rows: 1
:widths: 1 5
* - Arbitration functions
- Description
* - lowerFirst
- Lower port have priority over higher port
* - roundRobin
- Fair round robin arbitration
* - sequentialOrder
- | Could be used to retrieve transaction in a sequancial order
| First transaction should come from port zero, then from port one, ...
.. list-table::
:header-rows: 1
:widths: 1 5
* - Lock functions
- Description
* - noLock
- The port selection could change every cycle, even if the transaction on the selected port is not consumed.
* - transactionLock
- The port selection is locked until the transaction on the selected port is consumed.
* - fragmentLock
- | Could be used to arbitrate Stream[Flow[T]].
| In this mode, the port selection is locked until the selected port finish is burst (last=True).
.. list-table::
:header-rows: 1
:widths: 2 1
* - Generation functions
- Return
* - on(inputs : Seq[Stream[T]])
- Stream[T]
* - onArgs(inputs : Stream[T]*)
- Stream[T]
StreamFork
^^^^^^^^^^
This utile take its input stream and duplicate it outputCount times.
.. code-block:: scala
val inputStream = Stream(Bits(8 bits))
val dispatchedStreams = StreamDispatcherSequencial(
input = inputStream,
outputCount = 3
)
StreamDispatcherSequencial
^^^^^^^^^^^^^^^^^^^^^^^^^^
This utile take its input stream and route it to ``outputCount`` stream in a sequential order.
.. code-block:: scala
val inputStream = Stream(Bits(8 bits))
val dispatchedStreams = StreamDispatcherSequencial(
input = inputStream,
outputCount = 3
)