iT邦幫忙

2023 iThome 鐵人賽

DAY 12
0
Software Development

數位 IC 設計起手式系列 第 12

Day 12: Testbench 新天地

  • 分享至 

  • xImage
  •  

我們來介紹另一種測試方式!

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 中。

Testbench of FAdder.v

今天的任務就是要完成 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 才真正改變,也就是第二個輸出。
透過輸出,我們才比較容易理解電的行為,否則這很難直接被看出來吧!

Testbench of BCD_Adder.v

// 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

https://ithelp.ithome.com.tw/upload/images/20230703/20150982deagw0TRCE.png

透過這些範例,希望大家能對 testbench 有基本的認識,並於之後的練習時做相對應的 testbench 。


上一篇
Day 11: 初探 Testbench
下一篇
Day 13: 循序電路之序章
系列文
數位 IC 設計起手式30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言