前言:这一章主要讲硬件描述语言,书中给出了VHDL和verilog两种,笔者选择的是verilog进行学习。同时语法和c语言很相似,笔者只挑了一些对于自己比较重要且容易在实际操作中比较容易困惑的知识点进行总结记载。在小标题上此笔记和书中对齐,方便后续翻阅。
5.1.2 .1
1、module,称为模块,而module模块包含两大部分,分别是模块接口部分和模块实现部分。
模块接口部分:包含模块命名,参数定义,端口列表,其中参数定义是可选部分,关键词是parameter,此举意义是增加代码的重用性和可读性。在端口列表处注意:默认端口类都是wire型,而实际我们经常要用到reg型的输出,所以可以如:output reg a ;
除此之外,在定义端口范围的时候,尽量从大到小 如[15:0] ,方便初始化和规范性。
2、always块:always块有三种类型:纯组合逻辑的always、纯同步时序逻辑的always、和具有异步复位时序逻辑always。
先说纯组合逻辑,这个always里面只描述组合逻辑,并且敏感列表中一般应该包含always全部的输入信号。如何判断是否是输入型号,满足一下三个点:
① 出现在赋值等号右边
② 出现在条件判断中的信号
③ 没有出现在赋值等号左边的信号
不过第三点一般可以忽略,这三点主要是帮助看代码。
纯同步:即敏感列表中只有同步时序逻辑的时钟信号即可。
异步复位时序逻辑always:敏感列表包含同步时钟和异步复位,用的也比较多。但是注意这三种不要混用。
3、连续赋值语句
assign 针对线网型变量的一种赋值语句,这一点很容易出错。
5.1.2.3 数据类型
verilog中,有三大数据类型,分别是寄存器型,线网型,和参数数据类型,其中在数字电路中真正起作用的是寄存器型和线网型,它们遵守四值逻辑系统即高电平、低电平、不定态x、和高阻态z。
寄存器型类型:verilog中规定,凡是被赋值的变量,都必须是寄存器型的,而在实际的电路中,如果在时序逻辑中,reg类型对应为寄存器,在组合逻辑中对应为连线,在不完全的组合逻辑中对应为锁存器。所以,在综合时,寄存器型的变量不一定会综合成寄存器。寄存器变量的一些子类型如下:
reg、integer、real 后两种不常用,也不推荐使用,偶尔如循环语句的循环变量可以用integer。
线网型:首先注意,input和inout必须用线网型,对应到实际的逻辑电路就是连线。
关于数组:verilog中的数组访问只支持到元素的访问,而如果想查看某个数据的某个bit,则需要用以下方法:
assign dout = myram [256] ;
assign signbit = dout [31] ;
5.1.2.4
初始化:初始化主要是针对那些有记忆功能的单元,有分布式和集中式两种方法,其中分布式就是在变量声明的时候顺便赋值,不推荐,推荐集中式,即用initial 块进行初始化,这个块只进行一次,且只允许寄存器类型的变量进行赋值,对应那些有记忆功能的单元,所以无论是语法还是物理意义都说明了这一点。除此之外,这里用<= 还是 = 赋值都无所谓,不涉及具体电路的实现。
5.1.2.5
连续赋值:首先连续赋值是什么?
连续赋值所描述的硬件功能赋值电路不需要等待任何事件触发,也不会被任何事件中断,连续不断的赋值,显然,这种赋值只能用在组合逻辑,这也是为什么要求是线网型变量。
阻塞赋值和非阻塞赋值如何理解?
阻塞赋值主要在组合逻辑中使用,非阻塞赋值主要在时序逻辑中使用,阻塞赋值可以理解成一根线的两端,一端变了另一端也会跟着变,准确说就是,上一条赋值语句完成且 赋值操作完成 后才开始下一条语句执行,而非阻塞赋值(<=)则是上一条语句执行 但是 赋值操作还没完成 就开始执行下一条。举个例子:
a<=c ;
b<=a ;
假如上一个时钟触发后,a=1,b=1,此时让c为0,那么在下一个时钟触发后,a为0,而b还是为1。
除此之外,阻塞和非阻塞赋值最好不要混用。
映射赋值,笔者之前的困惑是如何在实例化的时候确定谁是input谁是output,书中给出的解答是,编译器会自己根据上下文判断。
关于运算符:注意区分归约运算符和按位运算符,前者都是单目运算符,后者一般是双目预算符。
迭代连接运算符:{ } 连接功能很简单 如 a b {a,b},迭代功能举个例子 :
wire [1:0] a = 2'b01 ;
wire [7:0] b = {4{a}} ;
其实b就等于 {a,a, a, a} = 8‘b0101_0101
5.1.2.6
关于always的敏感列表:
敏感列表中只能出现一种信号的一个边沿事件, (DDR是通过若干上升沿和若干下降沿协同实现的) 除此之外,敏感变量中用边沿事件+电平判断理论上说得通但是verilog不支持(笔者曾犯过此类发明创造的错误)如:always @(posedge clk or rst)
纯组合always推荐用=,时许always推荐用<= 。
关于实例化:
注意:实例化时实例的输出端口只能连接线网类型的变量,而输入端口都可以,
同时书中还介绍了数组实例化,这里直接看原文容易理解。
2/28日更新-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
5.1.2.9
仿真雷区
阻塞赋值在使用时要注意顺序,因为从阻塞赋值的原理上来看,不同的顺序对于语句功能的影响是巨大的。
敏感列表的缺失问题
此问题一般出现在组合always,如果敏感列表有缺失,就可能会引入锁存器,(这里先讲一下为什么我们尽量不要引入锁存器latch。第一,锁存器对毛刺敏感,所以很容易在信号上产生毛刺,而且也没有时钟信号,不容易进行静态时序分析。)不过编译器会自动补全敏感列表,而仿真器不会,所以在仿真上会有影响,至于为什么要避免锁存器,书中给出了解释:首先锁存器是毛刺敏感的,如果不能保证敏感列表中信号的质量,则会引起输出信号的不稳定,其次FPGA芯片中一般没有锁存器资源,那么就要使用触发器和逻辑门来实现,比较浪费资源,其三,锁存器的引入也会对时序分析造成困难。
锁存器具备下列特点,摘自:https://blog.csdn.net/gordon_77/article/details/79437758:
(1)对毛刺敏感(使能信号有效时,输出状态可能随输入多次变化,产生空翻,对下一级电路很危险),不能异步复位,因此在上电后处于不确定的 状态。
(2)锁存器会使静态时序分析变得非常复杂,不具备可重用性。 (首先, 锁存器没有时 钟参与信号传递,无法做 STA;其次,综合工具会将 latch 优化掉,造成前后仿真结果不一 致)
(3)在FPGA中基本的单元是由查找表和触发器组成的,若生成锁存器反而需要 更多的资源。根据锁存器的特点可以看出,在电路设计中,要对锁存器特别谨慎,如果设计 经过综合后产生出和设计意图不一致的锁存器,则将导致设计错误,包括仿真和综合。因此, 在设计中需要避免产生意想不到的锁存器。 如果组合逻辑的语句完全不使用 always 语句块,就可以保证综合器不会综合出锁存器。
(4)但如果锁存器和触发器两者都由与非门搭建的话,锁存器耗用的逻辑资源要比D触发器少(D触发器需要12个MOS管,锁存器只需6个MOS管),锁存器的集成度更高。所以在的ASIC设计中会用到锁存器。但锁存器对毛刺敏感,无异步复位端,不能让芯片在上电时处在确定的状态;另外,锁存器会使静态时序分析变得很复杂,不利于设计的可重用,所以,在ASIC设计中,除了CPU这高速电路,或者RAM这种对面积很敏感的电路,一般不提倡用锁存器。
5.2.6 正确的变量访问思路
简而言之就是“一写多读”,即如果有多个并行语句要对一个变量进行操作,那么有且只有一个语句对该变量进行写操作,其余的可以进行读操作。举个例子,老师就是驱动源,一节课上有且只有一个,如果一大群老师各讲各的,学生应该听谁的呢?
而“一写”要注意:每次动作只能修改一次变量的值
5.2.6.3 总线
总线(bus)
例如SPI PCI PCIe PXI PXIe USB 等等,注意系统的各个部件只能分时间使用总线,这也是总线的一个缺点。书中讲述了几种总线的实例,利用三态门或选择器实现的总线,这里直接看书。
5.2.7
数字电路中的隐患
1、寄存器输出的不稳定态:由于不同线路的延迟不一致导致的非预期的中间状态,如8位寄存器输出从0111_1111变到1000_0000时过渡部分会出现毛刺部分,这不是我们想要的,尤其是在异步逻辑中,不稳定态的影响是巨大的。书中提到单触发器寄存器的输出转变时不会有这种情况,但是多触发器寄存器在一次变换一个以上 bit 的时候就会出现不稳定态,所以书中提到的解决思路是,利用格雷码 ,(格雷码在一定范围内每个数值的变化只改变一个bit的值)但是格雷码很难直接进行数字运算,所以在设计的时候要注意将格雷码转换成自然二进制码再参与运算,书中提到的方法只能算作改进,不稳定态是不能完全消除的,所以在这里提供了一种很好的思路。
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------3.4更新------------------------------------
5.2.10
复位,作为fpga设计中一个必要的信号,书中介绍了同步复位和异步复位两种不同的方法,以下通过对比的方式介绍一下两种模式。
同步复位(瞬间的、离散的) | 异步复位(持续的、连续的) | ||
优点 | 缺点 | 优点 | 缺点 |
有利于仿真器仿真 | 要注意复位信号 的长度设计 | 可以节省资源 | 复位和时钟有效沿接近时容易导致亚稳态 |
时钟沿来临时才生效 所以有效滤除毛刺 | 受到各种延迟的影响大 | 设计相对简单 | 寄存器多是异步,所以各寄存器延迟不同 |
可做成纯同步时序 有利于时序分析和提高性能 | 大多寄存器只有异步 会浪费资源建立同步复位 | 非常容易受到毛刺干扰影响 |
所以,一般还是推荐使用同步复位,当然,如果资源紧张时,可以采用低电平有效的异步复位,同步释放机制 。因为,大多数的触发器都是具有低电平有效的复位引脚,所以,复位信号设计为低电平有效更加节省资源。
复位信号高扇出怎么办?
由于复位信号基本要覆盖设计的每一个角落,所以大扇出是很正常的,书中给出了两种解决思路,下面一起来看看。
1、复制寄存器
假如现在有1000个寄存器需要复位信号R,那么这个R的扇出就是1000,此时,我们可以让10个寄存器和R保持一致,用它们去控制这1000个寄存器,不过主要工作一般都有编译器帮我们完成。
2、正确利用全局时钟树
书中接下来介绍了各存储,如fifo、ram网上资料很多,不做过多赘述。
5.2.12 FPGA 的灵魂--状态机
状态机六要素 | |||||
状态集合 | 初态 | 终态 | 输入符号集 | 输出符号集 | 状态转移函数 |
这里解释一下输入符号集和输出符号集,其实就是输入会有哪些情况,输出会有哪些情况,而状态转移函数就是带有说明的箭头。
状态机的模型:moore和mealy,前者输出仅由输入决定,后者则是由现态和输入共同决定。其中每种又分为三种子类型,这里只介绍最好的一种类型。
当然,这两大类也可以共用组成九种不同的mix型状态机。书中后续用了较大篇幅介绍状态机设计思想,有需要直接看书,或者在实践中配合书本慢慢学习。