Skip to content

Commit

Permalink
Add pipelining API documentation
Browse files Browse the repository at this point in the history
  • Loading branch information
Dolu1990 committed Nov 7, 2023
1 parent 00b9fe3 commit 58d0417
Show file tree
Hide file tree
Showing 6 changed files with 308 additions and 0 deletions.
8 changes: 8 additions & 0 deletions source/SpinalHDL/Libraries/Pipeline/index.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
============
Pipeline
============

.. toctree::
:glob:

*
299 changes: 299 additions & 0 deletions source/SpinalHDL/Libraries/Pipeline/introduction.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,299 @@

Introduction
============

spinal.lib.misc.pipeline provide a pipelining API. The main advantages over manual pipelining are :

- You don't have to pre define data structure with all the element which goes through each individual stage
- You can compose the pipeline (as a consequence of the above point)
- Manual retiming is much easier, as you don't have to handle the registers / arbitration manualy
- Manage the arbitration by itself

The API is composed of 4 main things :

- Node : which represent a layer in the pipeline
- Connector : which allows to connect nodes to each others
- Builder : which will generate the hardware required for a whole pipeline
- Stageable : which represent a data thing that you can retrieve Nodes the pipeline

It is important to understande that Stageable isn't a hardware data instance, but a key to retrieve a data along the pipeline on nodes.

Here is an example to illustrate :

.. image:: /asset/image/pipeline/intro_pip.png
:scale: 70 %

Simple example
----------------

Here is a simple example


.. code-block:: scala
import spinal.core._
import spinal.core.sim._
import spinal.lib._
import spinal.lib.misc.pipeline._
class TopLevel extends Component {
val io = new Bundle{
val up = slave Stream (UInt(16 bits))
val down = master Stream (UInt(16 bits))
}
// Let's define 3 Nodes for our pipeline
val n0, n1, n2 = Node()
// Let's connect those nodes by using simples registers
val s01 = StageConnector(n0, n1)
val s12 = StageConnector(n1, n2)
// Let's define a few stageable things that can go through the pipeline
val VALUE = Stageable(UInt(16 bits))
val RESULT = Stageable(UInt(16 bits))
// Let's bind io.up to n0
io.up.ready := n0.ready
n0.valid := io.up.valid
n0(VALUE) := io.up.payload
// Let's do some processing on n1
n1(RESULT) := n1(VALUE) + 0x1200
// Let's bind n2 to io.down
n2.ready := io.down.ready
io.down.valid := n2.valid
io.down.payload := n2(RESULT)
// Let's ask the builder to generate all the required hardware
Builder(s01, s12)
}
This will produce the following hardware :

.. image:: /asset/image/pipeline/simple_pip.png
:scale: 70 %

Here is a simulation wave :

.. image:: /asset/image/pipeline/simple_wave.png



Stageable
============

Stageable class can be instanciated to represent some data which can go through the pipeline. Technicaly speaking, Stageable is a HardType which has a name and is used as a "key" to retrieve stuff.

.. code-block:: scala
val PC = Stageable(UInt(32 bits))
val PC_PLUS_4 = Stageable(UInt(32 bits))
val n0, n1 = Node()
val s01 = StageConnector(n0, n1)
n0(PC) := 0x42
n1(PC_PLUS_4) := n1(PC) + 4
Node
============

Node mostly host the valid/ready arbitration signal, and the hardware signal required for all the Stageable values going through it.

You can access its arbitration signals via :

- node.valid / node.isValid
- node.ready / node.isReady
- node.isFireing which is a shortcut for `valid && ready`

You can access its stageable's signals via :

.. list-table::
:header-rows: 1
:widths: 2 5

* - API
- Description
* - node(Stageable)
- Return the corresponding hardware signal
* - node(Stageable, Any)
- Same as above, but include a second argument which is used as a "secondary key". This ease the construction of multi lane hardware. For instance, when you have a multi issue CPU pipeline, you can use the lane Int id as secondary key
* - node.insert(Data)
- Return a new Stageable instance which is connected to the given Data hardware signal



.. code-block:: scala
val n0, n1 = Node()
val PC = Stageable(UInt(32 bits))
n0(PC) := 0x42
n0(PC, "true") := 0x42
n0(PC, 0x666) := 0xEE
val SOMETHING = n0.insert(myHardwareSignal) //This create a new Stageable
when(n1(SOMETHING) === 0xFFAA){ ... }
Connectors
============

There is few different connectors already implemented (but you could also create your own custom one) :

DirectConnector
------------------

Very simple, it connect two nodes with wires only. Here is an example :


.. code-block:: scala
val c01 = DirectConnector(n0, n1)
StageConnector
------------------

This connect two nodes using registers on the data / valid signals and some arbitration on the ready.

.. code-block:: scala
val c01 = StageConnector(n0, n1)
S2mConnector
------------------

This connect two nodes using registers on the ready signal, which can be usefull to improve backpresure combinatorial timings.

.. code-block:: scala
val c01 = S2mConnector(n0, n1)
CtrlConnector
------------------

This is kind of a special connector, as connect two nodes with optional flow control / bypass logic. Its API should be flexible enough to implement a CPU stage with it.

Here is its flow control API (The Bool argument enable the feature) :

.. list-table::
:header-rows: 1
:widths: 2 5

* - API
- Description
* - haltWhen(Bool)
- Allows to block the current transaction (clear up.ready down.valid)
* - throwWhen(Bool)
- Allows to remove the current transaction from the pipeline (clear down.valid and remove the transaction driver)
* - removeSeedWhen(Bool)
- Allows to remove the transaction driver (but doesn't clear the down.valid)
* - duplicateWhen(Bool)
- Allows to duplicate the current transaction (clear up.ready)
* - terminateWhen(Bool)
- Allows to hide the current transaction from downstream (clear down.valid)

Also note that if you want to do flow control in a conditional scope (ex in a when statement), you can call the following functions :

- haltIt(), duplicateIt(), terminateIt(), removeSeedIt(), throwIt()

.. code-block:: scala
val c01 = CtrlConnector(n0, n1)
c01.haltWhen(something)
when(somethingElse){
c01.haltIt()
}
You can retrieve which node are connected using node.up / node.down.

The CtrlConnector also provide an API to access stageable :

.. list-table::
:header-rows: 1
:widths: 2 5

* - API
- Description
* - connector(Stageable)
- Same as connector.down(Stageable)
* - connector(Stageable, Any)
- Same as connector.down(Stageable, Any)
* - connector.insert(Data)
- Same as connector.down.insert(Data)
* - connector.bypass(Stageable)
- Allows to conditionaly override a stageable value between connector.up -> connector.down. This can be used to fix data hazard in CPU pipelines for instance.


.. code-block:: scala
val c01 = CtrlConnector(n0, n1)
val PC = Stageable(UInt(32 bits))
c01(PC) := 0x42
c01(PC, 0x666) := 0xEE
val DATA = Stageable(UInt(32 bits))
// Let's say Data is inserted in the pipeline before c01
when(hazard){
c01.bypass(DATA) := fixedValue
}
// c01(DATA) and below will get the hazard patch
Note that if you create a CtrlConnector without node arguements, it will create its own nodes internaly.

.. code-block:: scala
val decode = CtrlConnector()
val execute = CtrlConnector()
val d2e = StageConnector(decode.down, execute.up)
Other connectors
------------------------------------

There is also a JoinConnector / ForkConnector implemented.

Your custom connector
------------------------------------

You can implement your custom connectors by implementing the Connector base class.

.. code-block:: scala
trait Connector extends Area{
def ups : Seq[Node]
def downs : Seq[Node]
def propagateDown(): Unit
def propagateUp(): Unit
def build() : Unit
}
Builder
============

To generate the hardware of your pipeline, you need to give a list of all the connectors used in your pipeline.


.. code-block:: scala
// Let's define 3 Nodes for our pipeline
val n0, n1, n2 = Node()
// Let's connect those nodes by using simples registers
val s01 = StageConnector(n0, n1)
val s12 = StageConnector(n1, n2)
// Let's ask the builder to generate all the required hardware
Builder(s01, s12)
1 change: 1 addition & 0 deletions source/SpinalHDL/Libraries/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,5 +37,6 @@ To use features introduced in followings chapter you need, in most of cases, to
IO/index
Graphics/index
EDA/index
Pipeline/index
Misc/index

Binary file added source/asset/image/pipeline/intro_pip.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added source/asset/image/pipeline/simple_pip.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added source/asset/image/pipeline/simple_wave.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 58d0417

Please sign in to comment.