概述
弄清楚阻塞赋值和非阻塞赋值的区别非常重要,否则我们就没有办法理解verilog里面的for循环的执行结果。
简单来说,阻塞赋值是给变量的现态赋值,非阻塞赋值是给变量的次态赋值。
所谓的现态,就是执行代码时变量的状态,也就是当前状态。次态,就是当前整个always代码块执行完了之后,变量是什么值,也就是下一个状态。
注意:在同一分支下,对同一变量不能同时使用非阻塞赋值和阻塞赋值,否则编译不通过。
例如,下面的代码无法编译通过:
reg [13:0] a = 0;
reg [3:0] state = 0;
always @(posedge refresh) begin // 这个always块每秒执行一次
if (state == 0) begin
a = 14'd10; // 指定a的现态为10
a <= 14'd20; // 指定a的次态为20
number <= a; // 数码管显示a的现态
end
end
错误提示:Error (10110): Verilog HDL error at main.v(22): variable "a" has mixed blocking and nonblocking Procedural Assignments -- must be all blocking or all nonblocking assignments
不能混合使用两种赋值方式。
但是,下面的代码就可以编译通过,因为对a的两种赋值放在了不同的分支:
reg [13:0] a = 0;
reg [3:0] state = 0;
always @(posedge refresh) begin // 这个always块每秒执行一次
if (state == 0) begin
a = 14'd10; // 指定a的现态为10
number <= a; // 数码管显示a的现态
state <= 1;
end
else if (state == 1) begin
a <= 14'd20; // 指定a的次态为20
number <= a; // 数码管显示a的现态
state <= 0;
end
end
程序运行结果是:程序运行一秒后,数码管显示number的值就一直是10。
这是因为,当state==0时,a的现态被修改为10,然后number赋值的是a的现态,所以第一秒末number被改为了10。
当state==1时,指定了a的次态为20,然而a的现态是10,所以number赋值的是10,而不是20。代码执行完毕后(也就是refresh的上升沿结束后),a的值才变为20,这个时候number已经赋值完毕了,肯定就显示不出来20了。
【测试代码的框架】
网上很多代码都有复位功能,而下面这段代码偏偏就没有定义复位输入引脚。那么,模块定不定义复位引脚,有什么区别呢?
模块没有做复位功能的话,编译出来的电路就没有办法自己复位。必须把FPGA的重配置引脚nCONFIG拉低再拉高,重新花时间从外部Flash加载程序,才能重头开始执行程序。具体花多少时间就很难说了。
如果模块做了复位功能的话,就能利用这个复位引脚直接复位,复位时间可以很短,因为FPGA不需要重新加载程序了。
`define SYSCLK 50000000 // 晶振大小为50MHz
module main(
input clock, // 晶振提供的时钟
output [3:0] digits, // 数码管位选
output [7:0] segments // 数码管段选
);
reg [13:0] number = 0; // 数码管显示的数字
SegmentDisplay segdisp(clock, digits, segments, number);
// 每秒refresh产生一次上升沿
integer counter = 0;
wire refresh = (counter == `SYSCLK - 1);
always @(posedge clock) begin
if (refresh)
counter <= 0;
else
counter <= counter + 1;
end
reg [13:0] a = 0;
reg [3:0] state = 0;
always @(posedge refresh) begin // 这个always块每秒执行一次
if (state == 0) begin
a = 14'd10; // 指定a的现态为10
number <= a; // 数码管显示a的现态
state <= 1;
end
else if (state == 1) begin
a <= 14'd20; // 指定a的次态为20
number <= a; // 数码管显示a的现态
state <= 0;
end
end
endmodule
【例1】
reg [13:0] a = 0, b = 0;
reg [3:0] state = 0;
always @(posedge refresh) begin // 这个always块每秒执行一次
case (state)
0: begin
a <= 14'd3;
b <= 14'd7;
number <= (a + b) * 5; // 0 or 150
end
1: begin
a = 14'd14;
b = 14'd6;
number <= (a + b) * 3; // 60
end
2: begin
a = 14'd20;
b = 14'd10;
number <= (a + b) * 4; // 120
end
endcase
if (state == 2)
state <= 0;
else
state <= state + 1'b1;
end
数码管(number)的显示顺序:0 0 60 120 150 60 120 150 60 120 ……
最开始number为0,1秒过后refresh信号产生上升沿,always块得到执行,此时state的现态为0,于是执行case 0分支。代码中指定a和b的次态分别为3和7,然后给number赋值的时候,是将a和b的现态相加,再乘5,a和b的现态都是0,于是number=(0+0)*5=0
再过1秒,always块再次执行,此时state的现态为1,执行case 1分支。代码指定a和b的现态分别为14和6,给number赋值的时候用的就是a和b的现态,于是number=(14+6)*3=60。
再过一秒,同理,a和b的现态分别变成了20和10,number赋完值是120。
再过一秒,回到了state=0分支,此时又是指定a和b的次态,而a和b的现态是20和10,所以这次算出来number的值是(20+10)*5=150。
看了这段代码,我们就很容易理解阻塞和非阻塞赋值到底是什么区别了。
【例2】
reg [13:0] a = 0;
reg [3:0] state = 0;
always @(posedge refresh) begin // 这个always块每秒执行一次
if (state == 0) begin
a = 14'd10;
number <= a;
a = 14'd20;
state <= state + 1'b1; // 不能写成state<=1, 否则整个这段代码都会被优化掉
end
end
程序运行结果为number=10。首先a的现态赋值为10,然后把a的现态赋值给number,number=10,然后a的现态改为20,这并没有影响number的值。
最后一句话本来该写state<=1的,但是笔者发现整个if语句都会被Quartus II优化掉,无论if里面写什么都得不到执行(哪怕只是简单的一句话)。这可能是因为state变量被定义为了reg型,编译器觉得没用就把整个代码块删掉了。写成state<=state+1'b1就没问题了。
【例3】
程序中多次指定一个变量的次态,那么只有最后一次的值有效。
reg [13:0] a = 0;
reg [3:0] state = 0;
always @(posedge refresh) begin // 这个always块每秒执行一次
if (state == 0) begin
a <= 14'd100;
a <= 14'd200;
end
else if (state == 1) begin
a <= a + 14'd10;
number <= a;
a <= a + 14'd20;
end
else if (state == 2)
number <= a;
if (state <= 2)
state <= state + 1'b1;
end
程序运行结果:两秒过后数码管显示200,再过一秒显示220。
分析:第一秒末,a指定了两次次态,但只有第二次的200有效,于是a的次态为200。
第二秒末,a的现态为200,于是number被赋值为200,数码管显示200。a的次态被赋值了两次,一次是a的现态加上10等于200+10=210,另一次是a的现态加上20等于200+20=220,只有第二次赋值有效,所以a的次态是220。
第三秒末,number赋值为a的现态220。
【例4】
reg [13:0] a = 0;
reg [3:0] state = 0;
always @(posedge refresh) begin // 这个always块每秒执行一次
if (state == 0) begin
a = 14'd10;
end
else if (state == 1) begin
a <= 14'd20;
number = a;
a <= 14'd30;
end
if (state <= 1)
state <= state + 1'b1;
end
程序运行结果:第二秒末,数码管显示10。
分析:第二秒末,a的现态为10,所以number被赋值为10。两次对a赋值赋的是a的次态,且只有第二次有效,所以a的次态是30,但没有赋给number,没有显示出来。
【例5】
终于可以讲一下for循环了。在Verilog里面,for循环的主要作用是在单个时钟周期内立即得出运算结果。
比如,笔算乘法需要算出多个部分积,再把这几个积相加得到最终结果。计算机的乘法器执行乘法运算也是同样的方法。
always语句块虽然也有循环的功能,但是完成多次循环需要多个时钟周期,每个时钟周期执行一次循环。多周期乘法器就是每个时钟周期计算一下部分积,最后再相加,效率不是很高。
for循环可以在一个时钟周期内执行完整个循环,代价是把相应的电路复制几遍,比较浪费FPGA内部的资源。单周期乘法器就是一个时钟周期一下子算出来所有的部分积,直接相加,一个周期直接得出乘法运算的结果。
reg [13:0] a = 0, i;
reg [3:0] state = 0;
always @(posedge refresh) begin // 这个always块每秒执行一次
case (state)
0: begin
a = 100;
number <= a; // 100
end
1: begin
for (i = 0; i < 4; i = i + 1'b1)
a <= a + i * 10;
number <= a; // 100
end
2: begin
number <= a; // 130
end
endcase
if (state == 2)
state <= 0;
else
state <= state + 1'b1;
end
for循环括号里面的i不能用非阻塞赋值语句赋值,否则编译不通过。道理很简单,因为那样的话全是在指定i的次态,i的现态一直不变,那不就成了死循环了。
在上面的代码中,循环体内部用的是非阻塞赋值,指定a的次态。
循环一共执行4遍,第一遍循环i=0,a的现态是100,赋值a的次态100+0*10=100。第二遍循环i=1,a的现态还是100,赋值a的次态100+1*10=110。
第三遍循环i=2,赋值a的次态100+2*10=120。第四遍循环i=3,a的现态还是没变仍然是100,赋值a的次态100+3*10=130。
a的次态赋了四次,只有最后一次有效,是130。
接着把a的现态赋值给number,数码管显示100。
到了第三秒,执行state==2那个分支的时候,a的现态是130,所以赋给number后数码管显示130。
【例6】
把例5循环体里面的a改成阻塞赋值:a = a + i * 10,情况就不一样了。
第一秒末数码管显示100。第二秒末,执行state==1分支,第一遍循环i=0,a的现态是100,赋值a的现态100+0*10=100。第二遍循环i=1,a的现态还是100,赋值a的现态100+1*10=110。
第三遍循环i=2,这回a的现态是110,赋值a的现态110+2*10=130。第四遍循环i=3,a的现态是130,赋值a的现态130+3*10=160。然后把a的现态赋给number显示出来,数码管显示的是160。
当执行state==2分支时,a的现态还是160,赋给number后数码管的显示保持160不变。
最后
以上就是结实大叔为你收集整理的通过几段代码理解Verilog里面阻塞赋值和非阻塞赋值的区别,以及Verilog的for循环的使用的全部内容,希望文章能够帮你解决通过几段代码理解Verilog里面阻塞赋值和非阻塞赋值的区别,以及Verilog的for循环的使用所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复