我們來介紹另一種測試方式!
Verilog 提供了一系列的指令,讓我們能與系統溝通 (System Task) ,如上一篇使用過的 $dumpfile()
, $dumpvars()
, $finish
。而常用的指令還有 $display()
, $monitor()
等,這兩個指令的意義就像是 C 語言的 printf()
。
為什麼這些指令要透過系統來處理呢?學過作業系統就會知道,所有的 I/O 裝置,如:螢幕等,都必須要交由作業系統來接管,因此如果你想要輸出任何東西,你必須要告知作業系統你欲執行的事項。這也是為什麼他叫做 System Task 。C 語言的
printf()
,scanf()
也是運用差不多的道理來執行輸入與輸出。
$display()
和 $monitor()
意義上來說是相同的,但是他們的觸發時間不相同。$monitor()
只需要被觸發一次,之後只要要觀察的變數有所變化,指定的語句就會被輸出。$display()
則只會做一次,即便是欲觀察的參數有變化,也不會主動輸出。
還有一個指令叫做 $write()
,他與 $display()
功用和觸發時機相同,差別在於 $display()
, $monitor()
會於結尾自動換行,$write()
則否。
$display()
, $monitor()
, $write()
的參數為一個字串,字串中支援跳脫字元和變數。如果要放置變數於字串中,我們會以 %b, %d, %o, %h 等取代變數,並於字串後方依序指定變數。
舉幾個例子來說明:
$display("The value of A is %d", A);
$monitor("A is %b, B is %h", A, B);
$write("A is %d\n", A);
介紹一個很特別的參數 %g
,他需要搭配 $time
做使用,這個參數會被指定為距離開始到現在經過多少個時間單位。在 Verilog 中,這是一個很重要的參數。為求格式一致,我們也可以在 % 和 g 之間加入數字,代表字串寬度,這個語法同樣適用於 %b, %d, %h 等參數。
$monitor("Time: %2g, A is %b, B is %h", $time, A, B);
最重要的是 System Task 的指令都要被放置在 always block 或 initial block 中。
initial block 和 always block 的差別在於執行次數,initial block 只會執行一次,而 always block 會執行多次。always block 的語法如下:
always @ (var) begin // some code end
always block 中的 var 只要有任何的變化,always block 中的程式碼就會被執行一次。因此
$display()
,$write()
通常會被放置於 always block 中,而$monitor()
則會被放置在 initial block 中。
今天的任務就是要完成 FAdder.v 的第二次測試!
// FAdder.v
module Full_Adder(
input A, B, Cin,
output Sum, Cout
);
assign Sum = A ^ B ^ Cin;
assign Cout = (A & B) | (A & Cin) | (B & Cin);
endmodule
先以 $monitor()
測試:
module FAdder_tb;
reg A, B, C;
wire S, Cout;
Full_Adder F(A, B, C, S, Cout);
initial begin
$monitor("Time: %2g, A: %b, B: %b, C: %b, S: %b, O: %b", $time, A, B, C, S, Cout);
#5 A = 1'b0; B = 1'b0; C = 1'b0;
#5 A = 1'b0; B = 1'b1; C = 1'b0;
#5 A = 1'b1; B = 1'b0; C = 1'b0;
#5 A = 1'b1; B = 1'b1; C = 1'b0;
#5 A = 1'b0; B = 1'b0; C = 1'b1;
#5 A = 1'b0; B = 1'b1; C = 1'b1;
#5 A = 1'b1; B = 1'b0; C = 1'b1;
#5 A = 1'b1; B = 1'b1; C = 1'b1;
#5 $finish;
end
endmodule
結果為:
Time: 0, A: x, B: x, C: x, S: x, O: x Time: 5, A: 0, B: 0, C: 0, S: 0, O: 0 Time: 10, A: 0, B: 1, C: 0, S: 1, O: 0 Time: 15, A: 1, B: 0, C: 0, S: 1, O: 0 Time: 20, A: 1, B: 1, C: 0, S: 0, O: 1 Time: 25, A: 0, B: 0, C: 1, S: 1, O: 0 Time: 30, A: 0, B: 1, C: 1, S: 0, O: 1 Time: 35, A: 1, B: 0, C: 1, S: 0, O: 1 Time: 40, A: 1, B: 1, C: 1, S: 1, O: 1
改以 $display()
來測試:
module FAdder_tb;
reg A, B, C;
wire S, Cout;
Full_Adder F(A, B, C, S, Cout);
initial begin
#5 A = 1'b0; B = 1'b0; C = 1'b0;
#5 A = 1'b0; B = 1'b1; C = 1'b0;
#5 A = 1'b1; B = 1'b0; C = 1'b0;
#5 A = 1'b1; B = 1'b1; C = 1'b0;
#5 A = 1'b0; B = 1'b0; C = 1'b1;
#5 A = 1'b0; B = 1'b1; C = 1'b1;
#5 A = 1'b1; B = 1'b0; C = 1'b1;
#5 A = 1'b1; B = 1'b1; C = 1'b1;
#5 $finish;
end
always @ (*) // * 代表整份程式碼中任意變數有變化,就執行 always block
$display(
"Time: %2g, A: %b, B: %b, C: %b, S: %b, O: %b",
$time, A, B, C, S, Cout
);
endmodule
結果為:
Time: 5, A: 0, B: 0, C: 0, S: x, O: x Time: 5, A: 0, B: 0, C: 0, S: 0, O: 0 Time: 10, A: 0, B: 1, C: 0, S: 0, O: 0 Time: 10, A: 0, B: 1, C: 0, S: 1, O: 0 Time: 15, A: 1, B: 0, C: 0, S: 1, O: 0 Time: 20, A: 1, B: 1, C: 0, S: 1, O: 0 Time: 20, A: 1, B: 1, C: 0, S: 0, O: 1 Time: 25, A: 0, B: 0, C: 1, S: 0, O: 1 Time: 25, A: 0, B: 0, C: 1, S: 1, O: 1 Time: 25, A: 0, B: 0, C: 1, S: 1, O: 0 Time: 30, A: 0, B: 1, C: 1, S: 1, O: 0 Time: 30, A: 0, B: 1, C: 1, S: 0, O: 1 Time: 35, A: 1, B: 0, C: 1, S: 0, O: 1 Time: 40, A: 1, B: 1, C: 1, S: 0, O: 1 Time: 40, A: 1, B: 1, C: 1, S: 1, O: 1
為什麼每個時間點都會有兩種輸出呢?而且第一種輸出大多有誤?
因為變數在每一個時間點有兩個變化,時間的變化和 A, B, C 的變化。但是當時間改變時,S, Cout 都還未被改變,因此維持前者的結果,直到 A, B, C 的變化時,S, Cout 才真正改變,也就是第二個輸出。
透過輸出,我們才比較容易理解電的行為,否則這很難直接被看出來吧!
// BCD_Adder.v
module BCD_Adder(
input [3:0] A, B,
input C,
output [3:0] Sum,
output Cout
);
wire [3:0] Z;
wire Cout0, Cout1;
Adder4 A0(A, B, C, Z, Cout0);
assign Cout = Cout0 | (Z[3] & Z[2]) | (Z[3] & Z[1]);
Adder4 A1 (Z, {1'b0, {2{Cout}}, 1'b0}, 1'b0, Sum, Cout1);
endmodule
module Adder4(
input [3:0] A, B,
input Cin,
output [3:0] Sum,
output Cout
);
wire [3:1] Ctemp;
Full_Adder F0(A[0], B[0], Cin, Sum[0], Ctemp[1]);
Full_Adder F1(A[1], B[1], Ctemp[1], Sum[1], Ctemp[2]);
Full_Adder F2(A[2], B[2], Ctemp[2], Sum[2], Ctemp[3]);
Full_Adder F3(A[3], B[3], Ctemp[3], Sum[3], Cout);
endmodule
module Full_Adder(
input A, B, Cin,
output Sum, Cout
);
assign Sum = A ^ B ^ Cin;
assign Cout = (A & B) | (A & Cin) | (B & Cin);
endmodule
我們來為這 BCD 加法器寫一個 testbench ,希望他同時能顯示波形圖,又可以把數值輸出在終端機上!
以下為我們的程式碼:
// BCD_Adder_tb.v
module BCD_tb;
reg [3:0] A, B;
reg C;
wire [3:0] S;
wire Cout;
BCD_Adder F(A, B, C, S, Cout);
initial begin
$dumpfile("BCD.vcd");
$dumpvars(1, BCD_tb);
$monitor("Time: %3g\tA: %4d\tB: %4d\tCin: %b\tCout: %b\tSum: %4d", $time, A, B, C, Cout, S);
#5 A=4'h0; B=4'h9; C=1'b0;
#5 A=4'h1; B=4'h9; C=1'b0;
#5 A=4'h2; B=4'h9; C=1'b0;
#5 A=4'h3; B=4'h9; C=1'b0;
#5 A=4'h4; B=4'h9; C=1'b0;
#5 A=4'h5; B=4'h9; C=1'b0;
#5 A=4'h6; B=4'h9; C=1'b0;
#5 A=4'h7; B=4'h9; C=1'b0;
#5 A=4'h8; B=4'h9; C=1'b0;
#5 A=4'h9; B=4'h9; C=1'b0;
#5 A=4'h0; B=4'h9; C=1'b1;
#5 A=4'h1; B=4'h9; C=1'b1;
#5 A=4'h2; B=4'h9; C=1'b1;
#5 A=4'h3; B=4'h9; C=1'b1;
#5 A=4'h4; B=4'h9; C=1'b1;
#5 A=4'h5; B=4'h9; C=1'b1;
#5 A=4'h6; B=4'h9; C=1'b1;
#5 A=4'h7; B=4'h9; C=1'b1;
#5 A=4'h8; B=4'h9; C=1'b1;
#5 A=4'h9; B=4'h9; C=1'b1;
#5 $finish;
end
endmodule
編譯並執行的結果應為:
VCD info: dumpfile BCD.vcd opened for output.
Time: 0 A: x B: x Cin: x Cout: x Sum: x
Time: 5 A: 0 B: 9 Cin: 0 Cout: 0 Sum: 9
Time: 10 A: 1 B: 9 Cin: 0 Cout: 1 Sum: 0
Time: 15 A: 2 B: 9 Cin: 0 Cout: 1 Sum: 1
Time: 20 A: 3 B: 9 Cin: 0 Cout: 1 Sum: 2
Time: 25 A: 4 B: 9 Cin: 0 Cout: 1 Sum: 3
Time: 30 A: 5 B: 9 Cin: 0 Cout: 1 Sum: 4
Time: 35 A: 6 B: 9 Cin: 0 Cout: 1 Sum: 5
Time: 40 A: 7 B: 9 Cin: 0 Cout: 1 Sum: 6
Time: 45 A: 8 B: 9 Cin: 0 Cout: 1 Sum: 7
Time: 50 A: 9 B: 9 Cin: 0 Cout: 1 Sum: 8
Time: 55 A: 0 B: 9 Cin: 1 Cout: 1 Sum: 0
Time: 60 A: 1 B: 9 Cin: 1 Cout: 1 Sum: 1
Time: 65 A: 2 B: 9 Cin: 1 Cout: 1 Sum: 2
Time: 70 A: 3 B: 9 Cin: 1 Cout: 1 Sum: 3
Time: 75 A: 4 B: 9 Cin: 1 Cout: 1 Sum: 4
Time: 80 A: 5 B: 9 Cin: 1 Cout: 1 Sum: 5
Time: 85 A: 6 B: 9 Cin: 1 Cout: 1 Sum: 6
Time: 90 A: 7 B: 9 Cin: 1 Cout: 1 Sum: 7
Time: 95 A: 8 B: 9 Cin: 1 Cout: 1 Sum: 8
Time: 100 A: 9 B: 9 Cin: 1 Cout: 1 Sum: 9
透過這些範例,希望大家能對 testbench 有基本的認識,並於之後的練習時做相對應的 testbench 。