You're reading the documentation for a development version.
For the latest stable release version, please have a look at master.

Fiber framework

Currently in developpement.

The Fiber to run the hardware elaboration in a out of order manner, a bit similarly to Makefile, where you can define rules and dependencies which will then be solved when you run a make command. It is very similar to the Scala Future feature.

Such framework complexify simple things but provide some strong feature for complex cases :

  • You can define things before even knowing all their requirements, ex : instanciating a interruption controller, before knowing how many lines of interrupt you need

  • Abstract/lazy/partial SoC architecture definition allowing the creation of SoC template for further specialisations

  • Automatic requirements negotiation between multiple agents in a decentralized way, ex : between masters and slaves of a memory bus

The framework is mainly composed of :

  • Handle[T], which can be used later to store a value of type T.

  • handle.load which allow to set the value of a handle (will reschedule all tasks waiting on it)

  • handle.get, which return the value of the given handle. Will block the task execution if that handle isn’t loaded yet

  • Handle{ code }, which fork a new task which will execute the given code. The result of that code will be loaded into the Handle

  • soon(handle), which allow the current task to announce that soon it will load that handle with a value (used to track which handle will

Warning, this is realy not usual RTL description and aim large system generation. It is currently used as toplevel integration tool in SaxonSoC.

Simple dummy example

There is a simple example :

import spinal.core.fiber._

// Create two empty Handles
val a, b = Handle[Int]

// Create a Handle which will be loaded asynchronously by the given body result
val calculator = Handle {
    a.get + b.get // .get will block until they are loaded
}

// Same as above
val printer = Handle {
    println(s"a + b = ${calculator.get}") // .get is blocking until the calculator body is done
}

// Synchronously load a and b, this will unblock a.get and b.get
a.load(3)
b.load(4)

Its runtime will be :

  • create a and b

  • fork the calculator task, but is blocked when executing a.get

  • fork the printer task, but is blocked when executing calculator.get

  • load a and b, which reschedule the calculator task (as it was waiting on a)

  • calculator do its a + b sum, and load its Handle with that result, which reschedule the printer task

  • printer task print its stuff

  • everything done

So, the main point of that example is to show that we kind of overcome the sequential execution of things, as a and b are loaded after the definition of the calculator.

Handle[T]

Handle[T] are a bit like scala’s Future[T], they allow to talk about something before it is even existing, and wait on it.

val x,y = Handle[Int]
val xPlus2 : Handle[Int] = x.produce(x.get + 2) //x.produce can be used to generate a new Handle when x is loaded
val xPlus3 : Handle[Int] = x.derivate(_ + 3)    //x.derivate is as x.produce, but also provide the x.get as argument of the lambda function
x.load(3) //x will now contain the value 3

soon(handle)

In order to maintain a proper graph of dependencies between tasks and Handle, a task can specify in advance that it will load a given handle. This is very usefull in case of a generation starvation/deadlock for SpinalHDL to report accuratly where is the issue.