0引言
verilog的always块内的边沿电平、同步异步、阻塞非阻塞问题,往往令初学者头大,下面我记录一下上述三种条件不同情况下的verilog代码描述、综合结果和资源占用情况,以供参考。
平台1:xilinx
工具: vivado 2019.2
芯片选型:zynq-7020(ps:zynq-7000系列 为lut6)
1.always块多边沿触发问题
Q1:一个always块内是如何实现异步复位的?
code case 1.1:
module block(clk,clk1,rstn,A, B,C);
input clk,clk1,rstn,A,B;
output reg C;
//case1.1
always@(posedge clk or negedge rstn)
if(~rstn )
C <= B&A;
else
C <=1'b0;
endmodule
上为一般的带有异步复位的always逻辑块,时钟边沿获取A&B的值赋给reg C,异步低复位置C零。
综合电路如下:
消耗资源情况:
LUT - 3 FF - 1
可以看到真正的触发边沿信号只有一个clk(PDFE),而另一个边沿rstn被处理为组合逻辑,异步复位体现为把D触发器的输出与带有复位的组合逻辑并列,值得注意的是通常原理图助记符号对异步的描述很简单,即直接加在D触发器上,如下图,但实际上一个D触发器不具备这种异步复位功能,而必须依赖额外的组合逻辑资源来实现,这从消耗Lut资源为3而不是1也可以侧面看出。
Q2:一个异步复位always块内是如何识别哪个是触发时钟哪个是复位?
code case1.2
module block(clk,clk1,rstn,A, B,C);
input clk,clk1,rstn,A,B;
output reg C;
//case1.2
always@(posedge clk or negedge rstn)
if(~clk )
C <= B&A;
else
C <=1'b0;
endmodule
上述相比case1.1 将判断信号从rstn换成clk,综合结果如下:
资源消耗:LUT-3 FF-1
结果显示rstn为边沿触发,clk为复位。
这说明,双边沿触发信号,参与逻辑运算的信号可以作为异步信号,默认的不参与的作为时钟触发。
Q3:always块内3个或者以上的边沿信号如何处理?
code case 1.3
module block(clk,clk1,rstn,A, B,C);
input clk,clk1,rstn,A,B;
output reg C;
//case1.3
always@(posedge clk or negedge rstn or posedge clk1)
if(~clk )
C <= B&A;
else
C <=1'b0;
endmodule
在case1.2基础上再引入触发信号clk1,并在判断逻辑中对rstn和clk1均不限定,而仅限定一个clk。
综合结果:错误
[Synth 8-91] ambiguous clock in event control (模糊时钟)
Q4:进一步限定clk1会怎样?
code case 1.4:
module block(clk,clk1,rstn,A, B,C);
input clk,clk1,rstn,A,B;
output reg C;
//case1.4
always@(posedge clk or negedge rstn or posedge clk1)
if(~clk && clk1 )
C <= B&A;
else
C <=1'b0;
endmodule
综合结果如下:
说明,默认未限定的rstn,为当作边沿触发信号,其他两个clk,clk1被条件判断限定成电平方式的组合逻辑。
Q5:对所有的边沿触发都加入条件判断限定会怎样?
code case 1.5:
module block(clk,clk1,rstn,A, B,C);
input clk,clk1,rstn,A,B;
output reg C;
//case1.5
always@(posedge clk or negedge rstn or posedge clk1)
if( ~rstn && clk1 && ~clk )
C <= B&A;
else
C <=1'b0;
endmodule
综合结果:
此时仍然有一个信号clk被选作出发边沿,此时3个信号都参与到条件逻辑判断运算中。可以进一步验证,更换信号位置
if( ~rstn && clk1 && ~clk ) 为 if( clk1 && ~clk && ~rstn ) , rstn 会被选作边沿触发。 也即,与表达式中的最后一个被编译器选作D触发器的边沿触发信号。
小节:
多边沿触发时,实际只有一个作为D触发器的边沿触发信号,该信号为代码块中默认未提及的或者与表达式最后的一个(其他情况未验证),不能留1个以上不约束边沿触发信号,否则出现ambiguous clock in event control 错误。
2.always块同步复位
code case 2:
module block(clk,clk1,rstn,A, B,C);
input clk,clk1,rstn,A,B;
output reg C;
//case2
always@(posedge clk)
if(~rstn )
C <= B&A;
else
C <= 1'b0;
endmodule
同步复位即触发信号仅为clk,rstn不加入触发。
综合结果:
占用资源:
LUT - 1 FF - 1
结果显示
复位信号经过反相后直接接到FF中。这进一步说明,一个FF自带的复位为同步复位,如果要异步复位需要额外的组合逻辑资源。同时,表明该型号fpga的复位信号默认高有效,如果需要低电平有效,必须要引入一个反相器。有论坛讨论,大部分xilinx的fpga都是高电平复位,altera则是低电平复位,遵守这个规则能够降低延时和资源占用。
3.always块边沿触发、阻塞赋值
code case 3:
module block(clk,clk1,rstn,A, B,C);
input clk,clk1,rstn,A,B;
output reg C;
//case3
always@(posedge clk)
if(~rstn )
C = B&A;
else
C = 1'b0;
endmodule
注意,此时的reg型信号 C 使用了 阻塞赋值“=” 。
综合结果:
资源占用:
LUT - 1 FF - 1
结果显示
边沿触发、阻塞赋值,综合结果与case 2 中的非阻塞赋值一样。这说明:
- always块内的reg变量可以进行阻塞赋值,而我们知道assign语句中,reg变量不能放在等号左边被赋值,会报错[Synth 8-1852] concurrent assignment to a non-net C is not permitted 。
- 结果与case 2的非阻塞赋值一样,生成带有FF的时序逻辑。
4.always块电平触发、阻塞赋值
code case 4:
module block(clk,clk1,rstn,A, B,C);
input clk,clk1,rstn,A,B;
output reg C;
//case4
always@(*)
if(~rstn )
C = B&A;
else
C = 1'b0;
endmodule
即去掉触发信号的关键词 posedge 和 negedge,仅用信号名,或者“ * ”代表所有信号。
综合结果:
资源占用:
LUT - 1 FF - 0
结果显示
1.电平触发+阻塞赋值,always块生成纯组合逻辑。
2.reg型的变量,生成的可以不带FF,可以是纯组合逻辑。
3.把触发电平信号换成 A、B、rstn、clk都不影响综合结果,说明编译器判断规则是只要没有posedge和negedge关键词,都一律判定为无需触发,时时生效,即组合逻辑。
5.always块电平触发、非阻塞赋值
code case 5
module block(clk,clk1,rstn,A, B,C);
input clk,clk1,rstn,A,B;
output reg C;
//case5
always@(*)
if(~rstn )
C <= B&A;
else
C <= 1'b0;
endmodule
同样,*可以是 A、B、rstn、clk
综合结果:
资源占用:
LUT - 1 FF - 0
结果显示
- 为纯组合逻辑,与case4 结果一模一样。
- always块内的reg变量的非阻塞赋值也可能生成不带FF的组合逻辑。
总结
- 同步异步复位问题:FF自带的复位默认为同步复位,要实现异步必须消耗额外的组合逻辑资源,而同步则不用。
- 多边沿触发:只允许一个实际触发时钟接入FF的触发端,其他的触发信号都必须给出限制从而被综合成电平信号组合逻辑。
- always块内生成的带不带DFF,取决于触发信号是否带有 posedge 和 negedge关键字,在可综合情况下,有则必然为带触发器的时序逻辑,无则为无触发器的组合逻辑,而无关是否阻塞非阻塞。(PS: 语法上,assign 的被赋值变量是必须是wire, always块内的被赋值变量必须是reg)