博文目录
- 参考资料
写在前面
由于刚毕业,又发生了很多事,在进入工作的准备期,就这样一停更很多日,接下来几天也将如此,直到生活稳定下来。
这个简单的教程还没有完成,继续吧。
正文
Verilog中的always块是Verilog中最常用的一个语法点,可以这么说,你稍微进行一个正常的设计都会用到always块,时序逻辑一定会用到,组合逻辑也很可能会用到。
参考互联网资料,我们从如下几个方面讲解always块语法。
- 语法
- 什么是敏感列表?
- always块是用来做什么的?
- 如果没有敏感列表会怎样?
- 例子
- 时序逻辑设计实例
- 组合逻辑设计实例
语法
语法很简单,如何按照结构划分可以分为:
always @ (event)
[statement]
always @ (event) begin
[multiple statements]
end
第一种是块内只有一条语句,不需要使用begin end;第二种是有多条语法,需要使用begin end包裹起来。
这其实都是废话。我们推荐全部都用begin end包裹起来,这样形式比较固定,比较方面阅读以及形成固定风格。
我们按照下面方式划分,时序逻辑和组合逻辑区别划分:
划分组合逻辑以及时序逻辑的依据是敏感列表里面的内容有没有边沿事件?
- 组合逻辑语法:
always @ (level event) begin
[multiple statements]
end
括号内部的敏感列表仅仅为电平事件,例如:
always@(a,b,c,d) begin
out = a&b&c&d;
end
这样写的缺点在于有的时候,敏感列表过多,一个一个加入太麻烦,容易忘掉,为了解决这个问题,verilog 2001标准说可以使用*替换敏感列表,表示缺省,编译器会根据always块内部的内容自动识别敏感变量。
如:
always@(*) begin
out = a&b&c&d;
end
这样就方便很多。
- 时序逻辑语法:
时序逻辑的always块将内部敏感列表包括了边沿事件,一般是时钟边沿。
always @ (edge event) begin
[multiple statements]
end
例如我们描述一个同步复位的D触发器,可以这样描述:
always@(posedge i_clk) begin
if(i_rst) begin
q <= 0;
end
else begin
q <= d;
end
end
这表示当检测到时钟上升沿时,判断是否复位有效,如果有效对输出复位,否则采样输入d值。
当然时序逻辑敏感列表中的边沿事件不一定只是时钟,也可以是其他边沿,例如描述一个异步复位的D触发器:
always@(posedge i_clk or negedge i_rst) begin
if(i_rst) begin
q <= 0;
end
else begin
q <= d;
end
end
这表示当检测到时钟上升沿或者复位的上升沿时,都会触发always块内部的语句。
但是明显看出,复位的优先级更高,也就是说,当检测到复位的上升沿时,无论时钟边沿是否检测到都执行复位操作,否则,当检测到时钟上升沿时,采样输入值d。
什么是敏感列表?
这里提一嘴吧,敏感列表就是触发always块内部语句的条件。
在下面的代码中,每当信号a或b的值发生变化时,always块中的所有语句都会被执行。
// Execute always block whenever value of "a" or "b" change
always @ (a or b) begin
[statements]
end
always块是用来干什么的?
always块是Verilog中用来描述组合逻辑以及时序逻辑的语法。
在这上面的语法小节中也说过了。
需要补充的是一个设计中可以有多个always块,或者说一定有很多个always块。
这些硬件块都是相互独立同时工作的。每个块之间的连接是决定数据流的原因。为了模拟这种行为,一个always块被做成一个连续的过程(硬件不可能断断续续工作),当敏感列表中的一个信号变化时,它就会被触发并执行一些动作(always块内的语句)。
如果没有敏感列表怎么办?
这个话题比较有意思,你可能说怎么可能没有敏感列表?其实,还真的可以没有敏感列表,这是仿真中的用法。我们经常使用没有敏感列表的always来表示不断的触发,用此特性来生成时钟。
例如:
always #10 clk = ~clk;
其实,always块内的敏感列表就是为了控制内部语句什么时候触发的。那么可以理解为一种定时,如果没有了敏感列表,则为零延迟,那么就会不断的触发,如下:
// always block is started at time 0 units
// But when is it supposed to be repeated ?
// There is no time control, and hence it will stay and
// be repeated at 0 time units only. This continues
// in a loop and simulation will hang !
always clk = ~clk;
上面显示的示例是一个always块,它试图反转信号clk的值。该语句每0个时间单位执行一次。因此,由于语句中没有延迟,因此它将永远执行。
这是没有意义的,我们正确的做法应该给一个定时或延迟:
即使敏感度列表为空,也应该有其他形式的时间延迟。always如下所示,通过构造中的延迟语句来提前仿真时间。现在,每10个时间单位完成一次时钟反转。
always #10 clk = ~clk;
当然,这种没有敏感列表的显示延迟,是不能综合的,只能用于仿真。
时序逻辑实例
下面显示的代码定义了一个名为tff的模块,该模块接受数据输入,时钟和低电平有效复位。每当在时钟的上升沿发现d为1 时,输出就会反相。此处,该always块在clk的上升沿或rstn的下降沿触发。
module tff (input d,
clk,
rstn,
output reg q);
always @ (posedge clk or negedge rstn) begin
if (!rstn)
q <= 0;
else begin
if (d)
q <= ~q;
else
q <= q;
end
end
endmodule
组合逻辑实例
开始的语法讲解中其实也举了一些组合逻辑以及时序逻辑的例子,这里在给出使用always块设计组合逻辑的一个例子:
如下图:
使用always块描述这个逻辑:
module combo ( input a,
input b,
input c,
input d,
output reg o);
always @ (a or b or c or d) begin
o <= ~((a & b) | (c^d));
end
endmodule
注意事项
- always块内被赋值的变量(左值)都应该为reg类型。
- 个人微信公众号: FPGA LAB
- 个人博客首页
- 注:学习交流使用!