Introduction
The semantic behind SpinalHDL is an important point to understand what is realy happening behind the scene and how you can twist it.
This semantic is defined by multiples rules :
- Signals and register are concurrent to each other (Parallel behavioral, as in VHDL and Verilog)
- An assignement on a combinatorial signal is like expressing a rule which is always true
- An assignement on a register is like expressing a rule which applied on each cycle of its clock domain
- For each signal, the last valid assignement win
- Each signal and register can be manipulated as an object during the hardware elaboration in a OOP manner
Concurrency
The order in which you assign each combinatorial or register signals as no behavioral impact.
For example both following code are equivalents :
val a, b, c = UInt(8 bits) // Define 3 combinatorial signals
c := a + b // c will be set to 7
b := 2 // b will be set to 2
a := b + 3 // a will be set to 5
Is equivalent to :
val a, b, c = UInt(8 bits) // Define 3 combinatorial signals
b := 2 // b will be set to 2
a := b + 3 // a will be set to 5
c := a + b // c will be set to 7
More generaly speaking, when you use the :=
assignement operator, it’s like specifying a new rule for the left side signal/register.
Last valid assignement win
If a combinatorial signal or register is assigned multiple time, the last valid one win.
As example :
val x, y = Bool //Define two combinatorial signal
val result = UInt(8 bits) //Define one combinatorial signal
result := 1
when(x){
result := 2
when(y){
result := 3
}
}
Will produce the following truth table on result:
x | y | => | result |
---|---|---|---|
False | False | 1 | |
False | True | 1 | |
True | False | 2 | |
True | True | 3 |
Signals and register interactions with Scala (OOP reference + Functions)
In SpinalHDL, each hardware elements is modelised by an class instance. Which mean you can manipulate each of them by using their referance, give them as argument of an function and then work on them (read/write).
As reference example, the following code implement an register which is incremented when inc
is True and cleared when clear
is True (clear has the priority over inc) :
val inc, clear = Bool //Define two combinatorial signal/wire
val counter = Reg(UInt(8 bits)) //Define a 8 bits register
when(inc){
counter := counter + 1
}
when(clear){
counter := 0 //If inc and clear are True, then this assignement win (Last valid assignement rule)
}
You can do exactly the same functionality by mixing the precedent example with a function to assign the counter :
val inc, clear = Bool
val counter = Reg(UInt(8 bits))
def setCounter(value : UInt): Unit = {
counter := value
}
when(inc){
setCounter(counter + 1) // Set counter with counter + 1
}
when(clear){
counter := 0
}
You can also integrate the conditional check inside the function :
val inc, clear = Bool
val counter = Reg(UInt(8 bits))
def setCounterWhen(cond : Bool,value : UInt): Unit = {
when(cond) {
counter := value
}
}
setCounterWhen(cond = inc, value = counter + 1)
setCounterWhen(cond = clear, value = 0)
And also specify to the function, who should be assigned :
val inc, clear = Bool
val counter = Reg(UInt(8 bits))
def setSomethingWhen(something : UInt,cond : Bool,value : UInt): Unit = {
when(cond) {
something := value
}
}
setSomethingWhen(something = counter, cond = inc, value = counter + 1)
setSomethingWhen(something = counter, cond = clear, value = 0)
All precedent examples are strictly equivalent in their generated RTL but also from an SpinalHDL compiler perspective. It is the case, because SpinalHDL only care about the Scala runtime, it doesn’t care about the Scala syntax itself.
In other word, from an generated RTL generation / SpinalHDL perspective, when you use function in Scala which generate hardware, it is like if that function where inlined. It is also the case for Scala loop, they will appear as unrolled in the generated RTL.