• 吃透Chisel语言.21.Chisel时序电路(一)——Chisel寄存器(Register)详解


    Chisel时序电路(一)——Chisel寄存器(Register)详解

    上一部分我们学习了简单的组合电路,这一部分介绍时序电路,与组合电路相比多了时序和状态,但也相比来说也并不是很难的东西。而时序电路对于数字设计来说特别重要,我们当然可以构造一个单周期的复杂系统,比如实现一个单周期的RISC-V处理器。但是由于这一个周期要做的事情太多太多,因此周期很长,时钟频率上不去,性能自然高不了。而时序电路是流水线的基础,通过将复杂电路划分为若干流水线阶段,既可以提高时钟频率,又可以利用指令级并行,能够极大提升数字电路性能。因此,我们必须深刻理解时序,这一篇文章我们就从最基本的寄存器开始。

    关于时序电路

    时序电路是输出依赖于输入和先前状态的电路,而组合电路的输出仅依赖于输入。由于我们专注于同步设计(带时钟的设计),所以我们谈及时序电路的时候通常都指的是同步时序电路。我们当然也可以用异步逻辑和反馈来构建时序电路,但是这是特定领域的话题,在Chisel中也没法表达异步时序电路。为了构建时序逻辑,我们需要用于存储电路状态信息的元素,这个元素就是寄存器,也就是这部分第一篇文章的内容。

    寄存器

    构建时序电路最基础的元素就是寄存器。寄存器在数字逻辑电路中,本质上就是个D触发器的集合。D触发器会在时钟的上升沿捕获输入并存放输入作为输出。换句话来说,寄存器会在时钟的上升沿用输入的值更新输出的值。

    下面的示意图就是一个寄存器:

    在这里插入图片描述

    它包含了一个输入D和一个输出Q,而且每个寄存器都有一个clock时钟信号。因为这个全局时钟信号在同步电路中是连接到所有的寄存器的,所以在我们的示意图中一般不会特别标识出来。方框下面的三角形符号就表示时钟输入,而且指示这个一个寄存器。在后面的示意图里面就不显式标识寄存器的时钟信号了。在Chisel中,我们不需要显式地连接寄存器的时钟输入,这一点跟示意图不画同步时钟信号是异曲同工之处。

    Chisel中的寄存器

    前面以及使用过寄存器了,在Chisel中,输入为d输出为q的寄存器表示如下:

    val q = RegNext(d)
    
    • 1

    需要注意的是,我们不需要给寄存器连接时钟信号,Chisel会隐式地自己连接。寄存器的输入输出可以是任意的复杂类型,比如vector或者bundle这种。

    寄存器也可以分两步来定义:

    val delayReg = Reg(UInt(4.W))
    delayReg := delayIn
    
    • 1
    • 2

    首先我们定义寄存器并命名,然后我们连接信号delayIn到寄存的输入。注意寄存器命名的时候应该包含一个Reg后缀,这个命名习惯才好,这主要是为了区分组合电路和时序电路。还需要注意,Scala(也包括Chisel)中的命名都是用的驼峰命名法(CamelCase)。变量名通常小写字母开通,类通常用大写字母开头。

    寄存器也可以在复位的时候初始化。reset信号和clock信号一样,在Chisel中是隐式的。我们可以给寄存器一个复位值,比如说零,把这个复位值通过寄存器构造器RegInit的参数给定即可。而寄存器的输入连接到一个Chisel赋值语句。

    val valReg = RegInit(0.U(4.W))
    valReg := inVal
    
    • 1
    • 2

    Chisel中复位信号的默认是实现是同步复位信号。对于同步复位而言不需要对D触发器进行修改,只需要在输入端加一个Mux就行了,这个Mux在复位的初始值和数据之间进行选择。下图就是具有同步复位功能的寄存器示意图,复位信号用于驱动寄存器输入端的Mux:

    在这里插入图片描述

    Chisel后面也会支持异步复位了,但是相关内容还在开发。不过由于同步复位其实很常用,因此FPGA触发器会包含一个同步复位(和设置)输入信号来不浪费LUT资源用于创建Mux。

    寄存器的波形图和时序特性

    时序电路的值随着时间的变化而变化,因此他们的行为可以用信号随时间变化的图来描述,这个图就叫做波形图或时序图。下图就是一个具有复位输入并输入了一些数据的寄存器的波形图,时间是从左向右前进的:

    在这里插入图片描述

    波形图的顶端是驱动电路的时钟信号。在第一个时钟周期,复位之前,寄存器的内容时未定义的,在第二个时钟周期,复位信号置为高,这个时钟周期的上升沿(位置B)寄存器接受了初始值0,而输入信号3被忽略了。在下一个时钟周期,复位信号置为低,inVal的值在下一个时钟周期的上升沿(位置C)捕获。在之后的时钟周期中,复位信号保持为0,在复位信号为0的情况下,寄存器的输出会在寄存器输入的一个周期延时后更新。

    波形图是图形化地确定电路行为的牛逼工具,尤其是在更复杂的电路中,比如有很多操作并行执行、数据在电路中流水线地传输等,时序图都很方便。前面讲解过,Chisel测试器也可以在测试中生成波形文件,可用于后面用波形图查看器显示并调试。

    带使能信号的寄存器

    寄存器的典型设计模式是带使能信号的寄存器,只有在使能信号为true(高电平)的时候,寄存器才会捕获输入值,否则维持旧值。使能信号的实现和同步复位是类似的,也只需要在寄存器的输入端加一个Mux,这个Mux的一个输入是输入数据,另一个输入是寄存器的输出反馈值。下面就是带使能信号的寄存器的示意图:

    在这里插入图片描述

    因为这也是个常见的设计模式,因此现代FPGA的触发器也都包含一个专用使能信号输入,不需要额外的资源。而下图是带使能信号的寄存器的波形图:

    在这里插入图片描述

    绝大多数时间,使能信号enable都是高电平的(true),此时寄存器输出会以一个时钟周期延时跟随输入变化。而在第4个时钟周期,使能信号置低电平,此时寄存器输出会在D处的上升沿保持值5

    带是使能信号的寄存器可以在Chisel中以几行代码表示,用条件更新语句就行了:

    val enableReg = Reg(UInt(4.W))
    
    when (enable) {
        enbaleReg := inVal
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    当然了,因为带使能信号的寄存器太常见了,所以Chisel中也有直接构造这种寄存器的函数RegEnable,它的第一个参数是寄存器输入,第二个参数就是使能信号:

    val enableReg2 = RegEnable(inVal, enable)
    
    • 1

    带使能信号的寄存器也是可以有复位信号的:

    val resetEnableReg = RegInit(0.U(4.W))
    
    when (enable) {
        resetEnableReg := inVal
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这种寄存器也很常见,因此Chisel中的RegEnable函数还有个三参数版本,第一个参数是寄存器输入,第二个参数是复位初始值,而第三个信号则是使能信号:

    val resetEnableReg2 = RegEnable(inVal, 0.U(4.W), enable)
    
    • 1

    寄存器还可以在不命名的情况下作为表达式的一部分,比如下面的电路就可以检测一个信号的上升沿,方法是比较该信号当前值和上个时钟周期的值:

    val risingEdge = din & !RegNext(din)
    
    • 1

    这个表达式中,只有当前时钟周期该信号为1,且上个时钟周期该信号为0时,表达式结果才为1。这个RegNext就是将参数作为输入,返回结果是寄存器的输出。

    结语

    到目前为止,我们已经讲完了寄存器的所有基本用法。作为时序电路的基础,寄存器非常重要,不是光靠学会基本用法就能拿捏的,而且还有很多特定用法的寄存器,比如计数器、移位寄存器等。从下一篇文章开始,我们将学习计数器、移位寄存器等特殊寄存器,以便更好地从例子中理解寄存器的用法,也为后面打下坚实基础。

  • 相关阅读:
    Mac/Wins Matlab如何查看APPs源码
    深度剖析动态规划算法:原理、优势与实战
    【数字IC验证快速入门】7、验证岗位中必备的数字电路基础知识(含常见面试题)
    java毕业设计校园互助平台校园帮帮网站源码+lw文档+mybatis+系统+mysql数据库+调试
    shell多线程的资料
    rem响应式布局
    使用 SwiftUI 构建可搜索列表,为您的 iOS 应用程序创建具有自动完成功能的可搜索列表(教程含源码)
    编写CUDA程序经验教训,CUDA计算结果有误
    PMP 11.27 考试倒计时16天!冲刺啦!
    docker系列(6) - docker数据卷
  • 原文地址:https://blog.csdn.net/weixin_43681766/article/details/125890456