寄存器

在 SpinalHDL 中创建寄存器与在 VHDL 或 Verilog 中创建寄存器有很大不同。

在 Spinal 中,没有 process/always 块。寄存器在声明时明确定义。这种与传统的事件驱动 HDL 的区别具有很大的影响:

  • 您可以在同一范围内赋值寄存器和连线,这意味着代码不需要在 process/always 块之间拆分

  • 它使事情变得更加灵活(参见 Functions

时钟和复位是分开处理的,有关详细信息,请参阅 时钟域 <clock_domain> 章节。

实例化

实例化寄存器有4种方法:

语法

描述

Reg(type : Data)

创建给定类型的寄存器

RegInit(resetValue : Data)

当发生复位时,为寄存器加载给定的 resetValue

RegNext(nextValue : Data)

创建寄存器,且每个周期对给定的 nextValue 进行采样

RegNextWhen(nextValue : Data, cond : Bool)

创建寄存器,当条件发生时对 nextValue 进行采样

这是声明一些寄存器的示例:

// 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)

上面的代码将推断出以下逻辑:

../../_images/register.svg

备注

上面 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 的时钟域。