Rules
Introduction
The semantics behind SpinalHDL are important to learn so you understand what is really happening behind the scenes and how to control it.
These semantics are defined by multiple rules:
Signals and registers are concurrent to each other (Parallel behavioral, as in VHDL and Verilog)
An assignement to a combinatorial signal is like expressing a rule which is always true
An assignement to a register is like expressing a rule which is applied on each cycle of its clock domain
For each signal, the last valid assignement wins
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 of the following pieces of code are equivalent:
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 generally, when you use the :=
assignement operator, it’s like specifying a new rule for the left side signal/register.
Last valid assignement wins
If a combinatorial signal or register is assigned multiple times, the last valid one wins.
As an example:
val x, y = Bool //Define two combinatorial signals
val result = UInt(8 bits) //Define a combinatorial signal
result := 1
when(x){
result := 2
when(y){
result := 3
}
}
Will produce the following truth table:
x |
y |
=> |
result |
---|---|---|---|
False |
False |
1 |
|
False |
True |
1 |
|
True |
False |
2 |
|
True |
True |
3 |
Signal and register interactions with Scala (OOP reference + Functions)
In SpinalHDL, each hardware element is modeled by a class instance. This means you can manipulate them by using their referance, such as passing them as an argument to a function.
As an example, the following code implements a register which is incremented when inc
is True and cleared when clear
is True (clear has 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 wins (Last valid assignement rule)
}
You can implement exactly the same functionality by mixing the previous example with a function that assignes to 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 what should be assigned to the function:
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 previous examples are strictly equivalent in their generated RTL but also from an SpinalHDL compiler perspective. This is because SpinalHDL only cares about the Scala runtime, it doesn’t care about the Scala syntax itself.
In other words, from a generated RTL generation / SpinalHDL perspective, when you use functions in Scala which generate hardware, it is like the function was inlined. This is also true case for Scala loops, as they will appear like they were unrolled in the generated RTL.