完成了一個模組後,要怎麼知道他的正確性?
第一步就是編譯檔案,編譯器會告訴你這份檔案是否有語法錯誤,或是其他能檢查出的錯誤。但是編譯成功代表這個模組 100% 正確嗎?未必吧!也許你的語法都正確,但是你所實作的邏輯和要求有一些出入,可能是因為你的想法和電路實際上的行為不太相同。這時候我們會寫一個測試檔 (testbench) 來檢測他的正確性。
準確來說,什麼是測試檔?
就是我們在另一個檔案 (測試檔) 中提供一些測試資料,並呼叫欲測試的模組。根據測試檔的輸出,我們可以知道模組的正確性。
以 C 語言來舉例吧!
假設我們有一個 func.h 長這樣:
int max(int x, int y) {
return (x >= y) ? x : y;
}
我們會怎麼測試呢?寫一個 test.c ,其內包含一些測資,我們可以透過輸出的結果來檢查正確性。
#include <stdio.h>
#include "func.h"
int main() {
int a = 5, b = 3, c = -5, d = -3, e = 0;
printf("x: %d, y: %d, max: %d\n", a, b, max(a, b));
printf("x: %d, y: %d, max: %d\n", d, c, max(d, c));
printf("x: %d, y: %d, max: %d\n", c, e, max(c, e));
}
補充個 C 語言好習慣:
正常來說,我們會將標頭檔 (.h) 和函式庫 (.c) 分開撰寫,而測試檔只會將標頭檔引入。
(因為篇幅較長,因此上面的程式碼把函式庫融合進函式庫)
編譯時的指令可以上網自行搜尋 (提示關鍵字:gcc
,函式庫編譯
)
在 Verilog 中,我們的檢測方式可以分成兩種:
printf()
(明日目標)我們以 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
在開始之前,我們先下載一個軟體 GTKWave,他可以幫助我們顯示輸出的波形圖 (MacOS 載點:GTKWave)。使用 Windows 作業系統的讀者,可以透過 Quartus II 內部的波形模擬器,也可以達到相同的效果。
撰寫 testbench 大致分成幾個要點:
Testbench 本身可以視為一個模組,但是他不包含輸入與輸出的端口 (port) 。
我們會在模組內部宣告與欲測試模組相連的變數,輸入的變數型態為 reg
,輸出的變數型態為 wire
。
因為欲測試模組本身也是一個電路,想要把數值輸入進去,我們也需要透過電路。
至於為什麼要使用reg
?不妨這樣思考,輸入代表的是測資,如果使用wire
我們不就只能給定一筆測資,因此我們需要使用reg
來取代wire
,但是這也不完全是原因,只是比較好理解。
註:reg 的賦值不需要加上assign
關鍵字,可以直接給值,如:a = 1'b0;
有了輸入與輸出,我們應該將他們連到什麼位置呢?
我們會先對目標模組實體化,這樣就會產生相對應的端口讓我們接線。
這個部分最為重要,我們先來介紹一個新的 Verilog 語法: initial
。
initial block 的特色是內部的程式碼都只會執行一次,因此我們不會重複執行測資。另外,內部的變數只支援 reg
,這也是為什麼輸入的變數型態需為 reg
的另一個原因。
initial begin
// 參數型態需為 reg
// 測資在 initial block 中指定
end
接著,我們來說明測資要怎麼指定。先來說說時間的概念吧!在電路中,電流的速度不是人的時間觀所能感覺的。如果我們在設定測資後,沒有給予延遲,輸入馬上會被換成下一個測資。也就是說,在沒有延遲的情況下只有最後一個測資會被真正的執行。
時間延遲的語法是 #n
,其中, n
是一個數字,代表你將延遲幾個時間單位,如:#5
。各個波動圖模擬軟體都有他的預設間單位,當然也可以由使用者在程式中指定,詳見 timescale
關鍵字。
#n
之後,我們可以隨意指定測資 (當然要合法!),如:#5 a = 1'b0; b = 1'b1;
initial block 最前面,我們可以加上如下的指令,告知編譯器生成波形圖的需求。
initial begin
$dumpfile("FAdder.vcd");
$dumpvars(0, FAdder_tb);
/*
測資指定放這裡
*/
end
程式碼說明:
$
,代表這個指令是在與系統溝通。.vcd
Testbench 檔案我們命名為 FAdder_tb.v
:
module FAdder_tb;
reg A, B, C;
wire S, Cout;
Full_Adder F(A, B, C, S, Cout);
initial begin
$dumpfile("FAdder.vcd");
$dumpvars(1, FAdder_tb);
#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; // 延遲 5 個時間單位後,終止程式
end
endmodule
編譯與執行:
iverilog -o fadder FAdder_tb.v FAdder.v
vvp fadder
終端機顯示:
VCD info: dumpfile FAdder.vcd opened for output.
,代表成功產生 .vcd
最後,打開 GTKWave ,並將 FAdder.vcd
拖曳到視窗中。點選 GTKWave 右邊的圖是,即可展開波形圖,結果如下:
從波形圖看來,結果是正確的。
下一篇,我們將用另一種方式來測試目標模組。