通过几段代码理解Verilog里面阻塞赋值和非阻塞赋值的区别,以及Verilog的for循环的使用

弄清楚阻塞赋值和非阻塞赋值的区别非常重要,否则我们就没有办法理解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不变。

  • 6
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

巨大八爪鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值