寄存器
在 SpinalHDL 中创建寄存器与在 VHDL 或 Verilog 中创建寄存器有很大不同。
在 Spinal 中,没有 process/always 块。寄存器在声明时明确定义。这种与传统的事件驱动 HDL 的区别具有很大的影响:
您可以在同一范围内赋值寄存器和连线,这意味着代码不需要在 process/always 块之间拆分
它使事情变得更加灵活(参见 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()
寄存器组
至于连线,可以使用 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])
}
将线缆/信号转换为寄存器
有时将现有的连线转换为寄存器很有用。例如,当您使用线束(Bundle)时,如果您希望线束的某些输出成为寄存器,您可能更愿意编写 io.myBundle.PORT := newValue
而不用 val PORT = Reg( ...)
并将其输出连接到带有 io.myBundle.PORT := PORT
的端口。为此,您只需在要实例化为寄存器的端口上使用 .setAsReg()
:
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
}
请注意,在上面的代码中,您还可以指定初始化值。
备注
该寄存器是在线路/信号的时钟域中创建的,并且不依赖于使用 .setAsReg()
的位置。
在上面的示例中,线路在 io
线束中定义,与组件位于同一时钟域中。即使 io.apb.PADDR.setAsReg()
这条代码写在具有不同时钟域的 ClockingArea
中,寄存器也将使用组件的时钟域,而不是 ClockingArea
的时钟域。