目录
Verilog 高级知识点
1、阻塞赋值(Blocking)
2、非阻塞赋值(Non-Blocking)
3 、assign 和 always 区别
4、什么是 latch
Verilog 高级知识点
本节给大家介绍一些高级的知识点。高级知识点包括阻塞赋值和非阻塞赋值、assign 和 always 语句差异、什么是锁存器、状态机、模块化设计等。
1、阻塞赋值(Blocking)
阻塞赋值,顾名思义即在一个 always 块中,后面的语句会受到前语句的影响,具体来说就是在同一个 always 中,一条阻塞赋值语句如果没有执行结束,那么该语句后面的语句就不能被执行,即被“阻塞”。也就是说 always 块内的语句是一种顺序关系,这里和 C 语言很类似。符号“=”用于阻塞的赋值(如:b = a;),阻塞赋值“=”在 begin 和 end 之间的语句是顺序执行,属于串行语句。
在这里定义两个缩写:
- RHS:赋值等号右边的表达式或变量可以写作 RHS 表达式或 RHS 变量;
- LHS:赋值等号左边的表达式或变量可以写作 LHS 表达式或 LHS 变量;
阻塞赋值的执行可以认为是只有一个步骤的操作,即计算 RHS 的值并更新 LHS,此时不允许任何其他语句的干扰,所谓的阻塞的概念就是值在同一个 always 块中,其后面的赋值语句从概念上来讲是在前面一条语句赋值完成后才执行的。
为了方便大家理解阻塞赋值的概念以及阻塞赋值和非阻塞赋值的区别,我们这里以在时序逻辑下使用阻塞赋值为例来实现这样一个功能:在复位的时候,a=1,b=2,c=3;而在没有复位的时候,a 的值清零,同时将 a 的值赋值给 b,b 的值赋值给 c,代码以及信号波形图如下图所示:
代码中使用的是阻塞赋值语句,从波形图中可以看到,在复位的时候(rst_n=0),a=1,b=2,c=3;而结束复位之后(波形图中的 0 时刻),当 clk 的上升沿到来时(波形图中的 2 时刻),a=0,b=0,c=0。这是因为阻塞赋值是在当前语句执行完成之后,才会执行后面的赋值语句,因此首先执行的是 a=0,赋值完成后将 a 的值赋值给 b,由于此时 a 的值已经为 0,所以 b=a=0,最后执行的是将 b 的值赋值给 c,而 b 的值已经赋值为 0,所以 c 的值同样等于 0。
2、非阻塞赋值(Non-Blocking)
符号“<=”用于非阻塞赋值(如:b <= a;),非阻塞赋值是由时钟节拍决定,在时钟上升到来时,执行赋值语句右边,然后将 begin-end 之间的所有赋值语句同时赋值到赋值语句的左边,注意:是 begin—end 之间的所有语句,一起执行,且一个时钟只执行一次,属于并行执行语句。这个是和 C 语言最大的一个差异点,大家要逐步理解并行执行的概念。
非阻塞赋值的操作过程可以看作两个步骤:
- (1)赋值开始的时候,计算 RHS;
- (2)赋值结束的时候,更新 LHS。
所谓的非阻塞的概念是指,在计算非阻塞赋值的 RHS 以及 LHS 期间,允许其它的非阻塞赋值语句同时计算 RHS 和更新 LHS。
我们下面使用非阻塞赋值同样来实现这样一个功能:在复位的时候,a=1,b=2,c=3;而在没有复位的时候,a 的值清零,同时将 a 的值赋值给 b,b 的值赋值给 c,代码以及信号波形图如下图所示:
代码中使用的是非阻塞赋值语句,从波形图中可以看到,在复位的时候(rst_n=0),a=1,b=2,c=3;而结束复位之后(波形图中的 0 时刻),当 clk 的上升沿到来时(波形图中的 2 时刻),a=0,b=1,c=2。这是因为非阻塞赋值在计算 RHS 和更新 LHS 期间,允许其它的非阻塞赋值语句同时计算 RHS 和更新 LHS。在波形图中的 2 时刻,RHS 的表达是 0、a、b,分别等于 0、1、2,这三条语句是同时更新 LHS,所以 a、b、c 的值分别等于 0、1、2。
在了解了阻塞赋值和非阻塞赋值的区别之后,有些朋友可能还是对什么时候使用阻塞赋值,什么时候使用非阻塞赋值有些疑惑,在这里给大家总结如下。
在描述组合逻辑电路的时候,使用阻塞赋值,比如 assign 赋值语句和不带时钟的 always 赋值语句,这种电路结构只与输入电平的变化有关系,代码如下:
在描述时序逻辑的时候,使用非阻塞赋值,综合成时序逻辑的电路结构,比如带时钟的 always 语句;这种电路结构往往与触发沿有关系,只有在触发沿时才可能发生赋值的变化,代码如下:
always @(posedge sys_clk or negedge sys_rst_n) begin
if (!sys_rst_n) begin
a <= 1'b0;
b <= 1'b0;
end
else begin
a <= c;
b <= d;
end
end
3 、assign 和 always 区别
assign 语句和 always 语句是 Verilog 中的两个基本语句,这两个都是经常使用的语句。
assign 语句使用时不能带时钟。
always 语句可以带时钟,也可以不带时钟。在 always 不带时钟时,逻辑功能和 assign 完全一致,都是只产生组合逻辑。比较简单的组合逻辑推荐使用 assign 语句,比较复杂的组合逻辑推荐使用 always 语句。
示例如下:
assign counter_en = (counter == (COUNT_MAX - 1'b1)) ? 1'b1 : 1'b0;
always @(*) begin
case (led_ctrl_cnt)
2'd0 : led = 4'b0001;
2'd1 : led = 4'b0010;
2'd2 : led = 4'b0100;
2'd3 : led = 4'b1000;
default : led = 4'b0000;
endcase
end
带时钟和不带时钟的 always
always 语句可以带时钟,也可以不带时钟。在 always 不带时钟时,逻辑功能和 assign 完全一致,虽然产生的信号定义还是 reg 类型,但是该语句产生的还是组合逻辑。
reg [3:0] led;
always @(*) begin
case (led_ctrl_cnt)
2'd0 : led = 4'b0001;
2'd1 : led = 4'b0010;
2'd2 : led = 4'b0100;
2'd3 : led = 4'b1000;
default : led = 4'b0000;
endcase
end
在 always 带时钟信号时,这个逻辑语句才能产生真正的寄存器,如下示例 counter 就是真正的寄存器。
//用于产生 0.5 秒使能信号的计数器
always @(posedge sys_clk or negedge sys_rst_n) begin
if (sys_rst_n == 1'b0)
counter <= 1'b0;
else if (counter_en)
counter <= 1'b0;
else
counter <= counter + 1'b1;
end
4、什么是 latch
latch 是指锁存器,是一种对脉冲电平敏感的存储单元电路。锁存器和寄存器都是基本存储单元,锁存器是电平触发的存储器,寄存器是边沿触发的存储器。两者的基本功能是一样的,都可以存储数据。锁存器是组合逻辑产生的,而寄存器是在时序电路中使用,由时钟触发产生的。
latch 的主要危害是会产生毛刺(glitch),这种毛刺对下一级电路是很危险的。并且其隐蔽性很强,不易查出。因此,在设计中,应尽量避免 latch 的使用。
代码里面出现 latch 的两个原因是在组合逻辑中,if 或者 case 语句不完整的描述,比如 if 缺少 else 分支,case 缺少 default 分支,导致代码在综合过程中出现了 latch。解决办法就是 if 必须带 else 分支,case 必须带default 分支。
大家需要注意下,只有不带时钟的 always 语句 if 或者 case 语句不完整才会产生 latch,带时钟的语句 if 或者 case 语句不完整描述不会产生 latch。
下面为缺少 else 分支的带时钟的 always 语句和不带时钟的 always 语句,通过实际产生的电路图可以看到第二个是有一个 latch 的,第一个仍然是普通的带有时钟的寄存器。