通過幾段程式碼理解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不變。
相關文章
- (12)非阻塞賦值與阻塞賦值區別(以簡單例子說明)賦值單例
- Verilog連續賦值、過程賦值、過程連續賦值總結賦值
- 教你認識Verilog 連續賦值賦值
- php之普通變數賦值、物件賦值、引用賦值的區別PHP變數賦值物件
- 怎樣理解阻塞非阻塞與同步非同步的區別?非同步
- 同步、非同步、阻塞、非阻塞的區別非同步
- IO - 同步 非同步 阻塞 非阻塞的區別非同步
- 阻塞式程式設計和非阻塞式程式設計區別程式設計
- 對於同步、非同步、阻塞、非阻塞的幾點淺薄理解非同步
- Verilog有符號數、無符號數之間的賦值與運算符號賦值
- 從linux原始碼看socket的阻塞和非阻塞Linux原始碼
- 從 Linux 原始碼看 socket 的阻塞和非阻塞Linux原始碼
- 常被新手忽略的值賦值和引用賦值(偏redux向)賦值Redux
- 從賦值看基本型別和引用型別的區別賦值型別
- 同步、非同步,阻塞、非阻塞理解非同步
- 理解阻塞、非阻塞、同步、非同步非同步
- jquery取值和賦值(包含部分是原生js的取值和賦值)jQuery賦值JS
- 對執行緒、協程和同步非同步、阻塞非阻塞的理解執行緒非同步
- 理解Golang多重賦值Golang賦值
- 變數的賦值 指標間接賦值變數賦值指標
- int型別是無法通過&獲取地址的,那物件裡的*int應該如何賦值型別物件賦值
- JS中的變數賦值深入理解JS變數賦值
- 自己對Java中if變數賦值的理解Java變數賦值
- 阻塞非阻塞和同步非同步的區分 參考一些書籍非同步
- 淺析賦值、淺拷貝、深拷貝的區別賦值
- Python - 物件賦值、淺拷貝、深拷貝的區別Python物件賦值
- Verilog HDL迴圈語句簡介
- Verilog中genvar 和 generate的使用
- python的賦值傳遞Python賦值
- async與defer的作用與區別以及阻塞優化優化
- 你真的理解Python中的賦值、傳參嗎?Python賦值
- 通道阻塞的理解
- 【OS】同步非同步/阻塞非阻塞、併發並行序列的區分非同步並行
- MySQL中變數的定義和變數的賦值使用MySql變數賦值
- 請教一個切片遞迴賦值的問題遞迴賦值
- 談談對不同I/O模型的理解 (阻塞/非阻塞IO,同步/非同步IO)模型非同步
- 微信小程式--data的賦值與取值微信小程式賦值
- 同步阻塞、同步非阻塞、多路複用的介紹