iT邦幫忙

2023 iThome 鐵人賽

DAY 11
0

完成了一個模組後,要怎麼知道他的正確性?

第一步就是編譯檔案,編譯器會告訴你這份檔案是否有語法錯誤,或是其他能檢查出的錯誤。但是編譯成功代表這個模組 100% 正確嗎?未必吧!也許你的語法都正確,但是你所實作的邏輯和要求有一些出入,可能是因為你的想法和電路實際上的行為不太相同。這時候我們會寫一個測試檔 (testbench) 來檢測他的正確性。

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 中,我們的檢測方式可以分成兩種:

  1. 輸出波形圖 (今日目標)
  2. 透過終端機輸出,類似 C 語言的 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 大致分成幾個要點:

  1. 測試資料的傳入與輸出
  2. 建立目標模組的實體 (instantiate)
  3. 規定不同時間的輸入測資
  4. 輸出成波形圖 (xxx.vcd)

測試資料的傳入與輸出

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

程式碼說明:

  1. 凡是指令前有 $,代表這個指令是在與系統溝通。
  2. $dumpfile 的參數可自行命名,但是副檔名請維持 .vcd
  3. $dumpvars 中有兩個參數: (這兩個參數很彈性,可以客製化出不同的樣式)
  • 第一個參數代表紀錄的信號層數,也許目標模組下還有很多的小模組,而 0 代表記錄所有信號
  • 第二個參數指的是 testbench 代表的模組名稱。

Testbench 程式碼

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 右邊的圖是,即可展開波形圖,結果如下:
https://ithelp.ithome.com.tw/upload/images/20230703/20150982CHIeYfFeqv.png

從波形圖看來,結果是正確的。

  • 當 A, B, C 中只有一個變數為 1 ,Cout = 0, S = 1
  • 當 A, B, C 中有二個變數為 1 時,Cout = 1, S = 0
  • 當 A, B, C 皆為 1 時,Cout = 1, S = 1

下一篇,我們將用另一種方式來測試目標模組。


上一篇
Day 10: BCD 加法器
下一篇
Day 12: Testbench 新天地
系列文
數位 IC 設計起手式30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言