CO-Verilog
CO-Verilog
Verilog Summary
verilog的小总结,有部分内容来自于讨论区,在此收纳,防止突然讨论区像教程一样在计组上机结束后就被关了。
1.$signed的注意事项
首先我们要了解,一个表达式由操作数(Operand
)、运算符(Operator
)组成,且可以递归定义。
针对符号,表达式的最终计算主要有两步:①符号确定
②向内传播
。
首先存在如下一些特殊的运算符(包括但不限于),它们的符号仅由其中部分操作数确定:
(1).移位运算符(<<、>>、>>>等)生成的表达式(形如i >>> j
)的符号只由i
的符号决定,j
恒视作无符号且不参与该表达式的符号确定。
(2).三目运算符生成的表达式(形如 i ? j :k )中的i
不参与该表达式的符号确定。
其次,各类常量的符号性也要注意。
比如0
等常量是有符号的,而4b'0
、2'd0
等给定基数形式的常量是无符号的(形如4'sd12
的除外,为有符号常量,其中s
即表示Signed
)。
再看对符号确定
的解释:
如果一个表达式在排除不参与其符号确定的操作数后,剩下的子表达式中任意一个被确定为无符号,那么整个表达式都被确定为无符号,否则有符号。
符号确定后,现在开始向内传播
部分,依然是标准文本:
这部分与教程解释一致:在确定整体表达式的符号性后,将确定好的符号性由外向内地传递给上下文决定的子表达式(自决定的子表达式不受影响)。类似递归的过程,遇到原子表达式则强制转换原子表达式的符号。
如果你还是觉得难以理解,那就直接通过一个例子(简化自896-837)来感受一下:
1 1 ? $signed(a)>>>b : 4'b0
首先Verilog不存在表达式短路这样的优化(至少仿真时没有),所以我们不能把该表达式进一步简化成$signed(a)>>>b
,仍然要看整体。
符号确定
:首先1
不参与符号确定,排除;$signed(a)>>>b
和4'b0
,其中4'b0
显然无符号,$signed(a)>>>b
符号由$signed(a)
决定,是有符号的;则表达式1 ? 有符号 : 无符号
,根据Verilog对无符号的偏爱,表达式整体无符号。
向内传播
:首先1
不受影响,排除;原子表达式4'b0
本就是无符号,无需转换;$signed(a)>>>b
中b
不受影响,再次递归进入$signed(a)
,是有符号原子表达式,强制转化成无符号,即$unsigned($signed(a))
。
最终表达式等价a >> b
,相当于仍对a
进行逻辑右移。
简单理解及一些规避方法:
1.扫描表达式中不在$signed()
中也不属于不受影响的无符号原子操作数,找到一个则直接鉴定整个表达式无符号。如果这些操作数一个都找不到,恭喜,直接按正常思维就能算对表达式;如果鉴定为无符号,那么就要把表达式中所有不属于不受影响(几乎就是所有)的有符号原子操作数强制转化成无符号再计算。
2.如果你不能确定到底哪些会变成无符号,那就尽一切办法阻止它,具体方法就是多用括号。
1 | assign a = ((b + c) >>> d) + e; // 原式 |
此外还有分离变量、自己实现易出问题的部分等方法(见教程,都用于自己写代码)。
最后提一嘴$signed()
,与$unsigned()
一样是一类系统函数,不仅能强制转换符号性,还能起到类似屏障的作用,函数内部的表达式是单独计算的,既不影响函数外部符号的确定,外部符号向内传播时也不会进入函数内部,这点与教程推荐的分离变量的作用一致。因此可以把$signed(a+b)
看成一个原子表达式或变量/常量,但其本身依然有可能受到向内传播的影响,被转换成无符号形式,失去转换效果,但能保住其内部的表达式的计算结果。 比如下面的例子(修改自教程):
1 | // wrong !!! |
2.wire和reg变量类型
1、从仿真角度来说,HDL语言面对的是编译器,相当于使用软件思路,此时:
wire对应于连续赋值,如assign;
reg对应于过程赋值,如always,initial;
2、从综合角度,HDL语言面对的是综合器,相当于从电路角度来思考,此时:
wire型变量综合出来一般情况下是一根导线。
reg变量在always中有两种情况,根据是否时钟沿(posedge clk)综合成时序逻辑(会包含触发器)或者组合逻辑,所以reg型并不表示综合出来就是暂存器register,在组合电路中使用reg,综合后只是net(线网)。
3.赋值
在Verilog中有以下三种赋值方法:
1.连续赋值(assign x=y;):不能在过程块内使用;
2.过程阻塞性赋值(x=y;):只能在过程块中使用;
3.过程非阻塞性复制(x<=y):只能在过程块内使用。
在always中不能对wire变量赋值,wire为无逻辑连线,所以输入什么就的输出什么。那 assign c = a & b;不是对wire的赋值吗?不然,虽然称为连续赋值,但可能更多带有连接的意思。综合时是将a & b综合成 a、b经过一个与门,而c只是连接到与门输出线,真正综合出来的是与门&,不是c。
4.always过程块
我们知道数字电路是由导线连接的逻辑门组成,因此任何电路都可以表示为module和assign语句的某种组合。但是,有时候这不是描述电路最简便的方法。过程块(比如always块)提供了一种用于替代assign语句描述电路的方法。有两种always块是可以综合出电路硬件的:
组合逻辑:
1 | always @(*) |
组合always块相当于assign语句,因此组合电路存在两种表达方法。具体使用哪个主要取决于使用哪个更方便。过程块内的代码与外部的assign代码不同。always过程块中可以使用更丰富的语句(比如if-then,case)。以下两个代码均创造出了相同的组合逻辑电路,只要任何输入(右侧)改变值,两者都将重新计算输出。
1 | assign out1 = a & b | c ^ d; |
对于组合always块,敏感变量列表总是使用(*)。如果把所有的输入都列出来也是可以的,但容易出错的(可能少列出了一个),并且在硬件综合时会忽略少列了一个,仍按原电路综合。但仿真器将会按少列一个来仿真,这导致了仿真与硬件不匹配。
时序逻辑:
1 | always @(posedge clk) |
时序always块也会像组合always块一样生成一系列的组合电路,但同时在组合逻辑的输出还生成了一组触发器(Flip-Flop)或寄存器。该输出在下一个时钟上升沿(posedge clk)后可见,而不是之前的立即可见。
5.阻塞和非阻塞赋值的不同
我们知道在描述组合逻辑的always块中用阻塞赋值,在描述时序逻辑的always块中用非阻塞赋值,为什么一定要这样做呢?
回答是:这是因为要使综合前仿真和综合后仿真一致的缘故。如果不按照上面两个要点来编写Verilog代码,也有可能综合出正确的逻辑,但前后仿真的结果就会不一致。
先做以下定义
RHS– 方程式右手方向的表达式或变量可分别缩写为:RHS表达式或RHS变量。
LHS– 方程式左手方向的表达式或变量可分别缩写为:LHS表达式或LHS变量。
IEEE Verilog标准定义了有些语句有确定的执行时间,有些语句没有确定的执行时间。若有两条或两条以上语句准备在同一时刻执行,但由于语句的排列次序不同(而这种排列次序的不同是IEEE Verilog标准所允许的),却产生了不同的输出结果。这就是造成Verilog模块冒险和竞争现象的原因。为了避免产生竞争,理解阻塞和非阻塞赋值在执行时间上的差别是至关重要的。
阻塞赋值
赋值时先计算一条语句等号右手方向(RHS)部分的值,这时赋值语句不允许任何别的Verilog语句的干扰,直到现行的赋值完成时刻,即把RHS赋值给 LHS的时刻,它才允许别的赋值语句的执行。
如果在一个过程块中阻塞赋值的RHS变量正好是另一个过程块中阻塞赋值的LHS变量,这两个过程块又用同一个时钟沿触发,这时阻塞赋值操作会出现问题,即如果阻塞赋值的次序安排不好,就会出现竞争。若这两个阻塞赋值操作用同一个时钟沿触发,则执行的次序是无法确定的。下面的例子可以说明这个问题:
用阻塞赋值的反馈振荡器
1 | module fbosc1 (y1, y2, clk, rst); |
两个always块是并行执行的,与前后次序无关,。如果前一个 always块的复位信号先到 0 时刻,则 y1 和 y2 都会取 1,而如果后一个 always 块的复位信号先到 0 时刻,则y1 和 y2 都会取 0。
非阻塞赋值
在计算非阻塞赋值的RHS表达式和更新LHS期间,其他的Verilog语句,包括其他的Verilog非阻塞赋值语句都能同时计算RHS表达式和更新LHS。非阻塞赋值允许其他的Verilog语句同时进行操作。非阻塞赋值的操作可以看作为两个步骤的过程:
在赋值时刻开始时,计算非阻塞赋值RHS表达式。
在赋值时刻结束时,更新非阻塞赋值LHS表达式。
1 | module fbosc2 (y1, y2, clk, rst); |
此时复位信号到0时,y1为1而y2为0是确定的。
Verilog模块编程要点:
使用以下规范可避免综合后仿真中出现的的冒险竞争问题。
1.时序电路建模时,用非阻塞赋值。
2.锁存器电路建模时,用非阻塞赋值。
3.用always块写组合逻辑时,采用阻塞赋值。
4.在同一个always块中同时建立时序和组合逻辑电路时,用非阻塞赋值。
5.在同一个always块中不要同时使用非阻塞赋值和阻塞赋值。
6.不要在多个always块中为同一个变量赋值。
6.Verilog综合出锁存器的问题
Warning (10240): … inferring latch(es)
上述这类警告通常情况下代表错误,除非锁存器是故意生成的。
时序电路无论如何不会产生锁存器(锁存器只能是电平敏感的),组合电路只要条件不完备就会产生锁存器,如if语句未用 else 语句覆盖所有情况,case 语句未使用 default 语句覆盖所有可能的情况,组合电路输出必须在所有输入的情况下都有值,即为语句块中所有的变量赋值。若没else、default语句,电路输出仍需保持不变,这就意味着电路需要记住当前状态,从而产生锁存器。组合逻辑(比如逻辑门)不能记住任何状态。
1 | A: always @(*)begin |
代码A:
是一个always语句块构成的组合逻辑,其中缺少else分支。当d=1’b0时,综合工具会默认保持a的值,即生成锁存器.
代码B:
虽然补全了else分支语句。但是,其代码依然有保持功能,即会生成锁存器;也就是说,组合逻辑是否会生成锁存器,其根本原因是该组合逻辑存在保持功能!
代码C:
在always语句块内使用了case语句,并且case语句中含有default分支。但是,我们可以发现:d=2’b00时,没有说明c的赋值;d=2’b01时,没有说明a的赋值。
代码D:
和代码C的不同是,它在always语句块的开始,对a和c进行了默认赋值;这就类似于软件里面的初始化,当然只是类似而已,不会生成锁存器。
7.X和Z
x是不定态,也就是说,其状态不可知。z是高阻态,在实际电路中,开关断开(没有驱动)就意味着高阻态(断路电阻视为很大)。casex和casez说的是如果情况中的某一位值是x或z,那么就不比较他们了,就比较其他的不是x或z的位。
当声明变量的时候,如果不赋值,那么wire类型初值是z,整数和寄存器都是x,这很符合实际情况,wire没有驱动的时候是高阻态,而综合后与门电路相关的整数和寄存器,都是与门电路相关,而门电路是有驱动的(这里说的是不是输入的驱动),所以应该是不定态x。
8.模块例化引用
模块引用相当于C语言中的函数调用,可将引用的模块看作一个有定向输入输出的黑盒。
1 | module top_module (input x, input y, output z); |
9.同步复位与异步复位
1 | 同步复位 |
10.generate 用法
generate 语句可以动态地生成 Verilog 代码,常用于编写许多结构相同但参数不同的赋值语句或逻辑语句,方便参数化模块的生成。generate 语句主要有以下三种用途:
1.对矢量中的多个位进行重复操作
2.重复操作多个模块的实例引用
3.根据参数定义来确定程序中是否应该包括某段 Verilog 代码
举个第二个用途的例子(generate-for结构)
在使用前必须先声明一个 genvar 变量,用于 for 循环判断,需要给generate 块命名。
1 | genvar i; |
11.有限状态机
三段式
三段式状态机虽然代码会长一些,但能够更方便地修改,并更清晰地表达状态机的跳变与输出规则。
三段式分别指
1.状态跳转逻辑,根据输入信号以及当前状态确定状态的次态状态
2.触发器实现,在时钟边沿实现状态寄存器的跳变以及状态复位
3.输出逻辑,根据当前状态(moore)实现输出
举个栗子
1 | module top_module ( |
Moore型和Mealy型
根据状态机的输出是否与输入条件相关来区分Moore状态机和Mealy状态机.
实现相同功能时,Moore型状态机需要比Mealy型状态机多一个状态,且Moore型状态机的输出比Mealy型延后一个时钟周期。
假设Moore型有3个状态(A,B,C)其中当输入in为1时状态B将转移到状态C,而Mealy型仅仅需要2个状态(A,B),这是因为Moore型的输出仅仅与状态有关,若想输出C状态的值,则必须形成C状态。而对于Mealy型,输出是由状态和输入共同决定的,即如果在状态B时,输入in为1,那么输出电路就可以输出C状态的值了,不需要等到状态C形成,因此会提前一个周期输出,也不需要状态C。
或者可以这么理解,状态机的次态由当前状态和输入决定,Moore的输出其实是由次态(例如上述C状态)决定的,所以需要等待一个时钟沿,输出相较于状态转移滞后一个周期。而mealy型在相同的周期内反应,所以不需要等待时钟。
- Title: CO-Verilog
- Author: Charles
- Created at : 2022-12-27 13:04:49
- Updated at : 2023-11-05 21:36:02
- Link: https://charles2530.github.io/2022/12/27/co-verilog/
- License: This work is licensed under CC BY-NC-SA 4.0.