寄存器
在 SpinalHDL 中创建寄存器与在 VHDL 或 Verilog 中创建寄存器有很大不同。
在 Spinal 中,没有 process/always 块。寄存器在声明时明确定义。这种与传统的事件驱动 HDL 的区别具有很大的影响:
You can assign registers and signals in the same scope, meaning the code doesn’t need to be split between process/always blocks
它使事情变得更加灵活(参见 Functions)
时钟和复位是分开处理的,有关详细信息,请参阅 时钟域 <clock_domain> 章节。
实例化
实例化寄存器有4种方法:
语法 |
描述 |
---|---|
|
创建给定类型的寄存器 |
|
当发生复位时,为寄存器加载给定的 |
|
创建寄存器,且每个周期对给定的 |
|
创建寄存器,当条件发生时对 |
这是声明一些寄存器的示例:
// UInt register of 4 bits
val reg1 = Reg(UInt(4 bits))
// Register that updates itself every cycle with a sample of reg1 incremented by 1
val reg2 = RegNext(reg1 + 1)
// UInt register of 4 bits initialized with 0 when the reset occurs
val reg3 = RegInit(U"0000")
reg3 := reg2
when(reg2 === 5) {
reg3 := 0xF
}
// Register that samples reg3 when cond is True
val reg4 = RegNextWhen(reg3, cond)
上面的代码将推断出以下逻辑:
备注
上面 reg3
示例显示了如何为 RegInit
创建寄存器赋值。也可以使用相同的语法赋值其他寄存器类型(“Reg”、“RegNext”、“RegNextWhen”)。就像组合赋值一样,规则是“最后一个赋值生效”,但如果没有完成赋值,寄存器将保留其值。如果在设计中声明 Reg 并且没有适当地赋值和使用, EDA 流程中的工具会在它认为该寄存器不必要时裁剪寄存器(从设计中删除)。
另外,RegNext
是一个基于 Reg
语法构建的抽象。下面两个代码序列严格等效:
// Standard way
val something = Bool()
val value = Reg(Bool())
value := something
// Short way
val something = Bool()
val value = RegNext(something)
可以通过其他方式同时拥有多个选项,因此可在上述基本理解的基础上构建稍微更高级的组合:
// UInt register of 6 bits (initialized with 42 when the reset occurs)
val reg1 = Reg(UInt(6 bits)) init(42)
// Register that samples reg1 each cycle (initialized with 0 when the reset occurs)
// using Scala named parameter argument format
val reg2 = RegNext(reg1, init=0)
// Register that has multiple features combined
// My register enable signal
val reg3Enable = Bool()
// UInt register of 6 bits (inferred from reg1 type)
// assignment preconfigured to update from reg1
// only updated when reg3Enable is set
// initialized with 99 when the reset occurs
val reg3 = RegNextWhen(reg1, reg3Enable, U(99))
// when(reg3Enable) {
// reg3 := reg1; // this expression is implied in the constructor use case
// }
when(cond2) { // this is a valid assignment, will take priority when executed
reg3 := U(0) // (due to last assignment wins rule), assignment does not require
} // reg3Enable condition, you would use `when(cond2 & reg3Enable)` for that
// UInt register of 8 bits, initialized with 99 when the reset occurs
val reg4 = Reg(UInt(8 bits), U(99))
// My register enable signal
val reg4Enable = Bool()
// no implied assignments exist, you must use enable explicitly as necessary
when(reg4Enable) {
reg4 := newValue
}
复位值
除了直接创建具有复位值的寄存器的 RegInit(value : Data)
语法之外,您还可以通过在寄存器上调用 init(value : Data)
函数来设置复位值。
// UInt register of 4 bits initialized with 0 when the reset occurs
val reg1 = Reg(UInt(4 bits)) init(0)
如果您有一个包含线束(Bundle)的寄存器,则可以对线束的每个元素使用 init
函数。
case class ValidRGB() extends Bundle {
val valid = Bool()
val r, g, b = UInt(8 bits)
}
val reg = Reg(ValidRGB())
reg.valid init(False) // Only the valid if that register bundle will have a reset value.
用于仿真目的的初始化值
对于在 RTL 中不需要复位值,但需要仿真初始化值(以避免未知状态X传播)的寄存器,您可以通过调用 randBoot()
函数来请求随机初始化值。
// UInt register of 4 bits initialized with a random value
val reg1 = Reg(UInt(4 bits)) randBoot()
寄存器组
As for signals, it is possible to define a vector of registers with Vec
.
val vecReg1 = Vec(Reg(UInt(8 bits)), 4)
val vecReg2 = Vec.fill(8)(Reg(Bool()))
初始化可以像往常一样使用 init
方法完成,它可以与寄存器上的 foreach
迭代相结合。
val vecReg1 = Vec(Reg(UInt(8 bits)) init(0), 4)
val vecReg2 = Vec.fill(8)(Reg(Bool()))
vecReg2.foreach(_ init(False))
如果由于初始化值未知而必须推迟初始化,请使用如下例所示的函数。
case class ShiftRegister[T <: Data](dataType: HardType[T], depth: Int, initFunc: T => Unit) extends Component {
val io = new Bundle {
val input = in (dataType())
val output = out(dataType())
}
val regs = Vec.fill(depth)(Reg(dataType()))
regs.foreach(initFunc)
for (i <- 1 to (depth-1)) {
regs(i) := regs(i-1)
}
regs(0) := io.input
io.output := regs(depth-1)
}
object SRConsumer {
def initIdleFlow[T <: Data](flow: Flow[T]): Unit = {
flow.valid init(False)
}
}
class SRConsumer() extends Component {
// ...
val sr = ShiftRegister(Flow(UInt(8 bits)), 4, SRConsumer.initIdleFlow[UInt])
}
Transforming a signal into a register
Sometimes it is useful to transform an existing signal into a register. For
instance, when you are using a Bundle
, if you want some outputs of the bundle to
be registers, you might prefer to write io.myBundle.PORT := newValue
without
declaring registers with val PORT = Reg(...)
and connecting their output to
the port with io.myBundle.PORT := PORT
. To do this, you just need to use
.setAsReg()
on the ports you want to control as registers:
val io = new Bundle {
val apb = master(Apb3(apb3Config))
}
io.apb.PADDR.setAsReg()
io.apb.PWRITE.setAsReg() init(False)
when(someCondition) {
io.apb.PWRITE := True
}
请注意,在上面的代码中,您还可以指定初始化值。
备注
The register is created in the clock domain of the signal, and does not depend
on the place where .setAsReg()
is used.
In the example above, the signal is defined in the io
Bundle, in the same
clock domain as the component. Even if io.apb.PADDR.setAsReg()
was
written in a ClockingArea
with a different clock domain, the register
would use the clock domain of the component and not the one of the
ClockingArea
.