寄存器(Registers)
一、简介(Introduction)
在SpinalHDL中创建寄存器与VHDL和Verilog中区别很大。
在SpinalHDL中, 没有process/always块, 寄存器在声明的时候就清晰地定义出来。这种和传统事件驱动HDL的不同带来了巨大的影响:
你可以在同一区域对寄存器和wire赋值, 意味着代码不需要再分成process/always块
这让很多事情变得灵活多变, 参考Functions
时钟和复位是分别处理的, 参考时钟域章节。
二、例化(Instantiation)
有四种方式例化寄存器:
语句 | 描述 |
---|---|
Reg(type: Data) | 给定类型的寄存器 |
RegInit(resetValue: Data) | 当复位信号有效, 复位值是resetValue 的寄存器 |
RegNext(nextValue: Data) | 每周期采样给定nextValue 的寄存器 |
RegNextWhen(nextValue: Data, cond: Bool) | 当条件触发时采样给定nextValue 的寄存器 |
以下是一些寄存器声明的例子:
//4 bits UInt寄存器
val reg1 = Reg(UInt(4 bits))
//每周期采样reg1的寄存器
val reg2 = RegNext(reg1 + 1)
//当复位信号有效初始化为0的4 bits寄存器
val reg3 = RegInit(U"0000")
reg3 := reg2
when(reg2 === 5) {
reg3 := 0xF
}
//当条件真时采样reg3的寄存器
val reg4 = RegNextWhen(reg3, cond)
以上代码会产生如下电路图:
备注:上述的
reg3
例子展示了如何给RegInit
赋值。同样的方法也适用于给其他寄存器类型赋值(Reg
,RegNext
,RegNextWhen
)。正如在组合逻辑赋值中, 最后被赋值的语句有效, 但是如果没有赋值语句, 寄存器会保留原值。
同样的, RegNext
是在Reg
语句上建立的抽象层次, 以下两种时序代码是完全等价的:
//标准方式
val something = Bool()
val value = Reg(Bool())
value := something
//简短方式
val something = Bool()
val value = RegNext(something)
三、复位值(Reset value)
除了RegInit(value: Data)
语句可以直接创建带有复位值的寄存器, 也可以通过在寄存器上调用init(value: Data)
函数设置复位值。
//当reset发生被初始化为0的UInt 4bits寄存器
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) //只有寄存器包有效, 才会有复位值
四、仿真下的例化(Initialization value for simulation)
对于在RTL中不需要复位值但是在仿真时需要初始值的寄存器(避免传递x值), 你可以通过调用randBoot()
函数产生随机初始值。
//随机初始化的UInt寄存器
val reg1 = Reg(UInt(4 bits)) randBoot()
五、寄存器向量(Register vectors)
对于wire, 可以用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))
对于当init
值未知时初始化必须被推迟的情况, 可以用以下函数:
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.ouutput := 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 wire into a register)
有时把wire转换为register很有用。例如当你用包(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
}
在上述代码你需要注意的是, 你也可以给定一个初始值
备注:寄存器的时钟域用的是wire的时钟域, 并不取决于
.setAsReg()
在哪里调用。 在上述例子中, wire定义在io
包中, 和模块有着相同的时钟域。即便io.apb.PADDR.setAsReg()
在有着不同时钟域的ClockingArea
中书写, 寄存器还是会采用模块的时钟而非ClockingArea
的时钟。