Verilog 代碼設計完成后,還需要進行重要的步驟,即邏輯功能仿真。仿真激勵文件稱之為 testbench,放在各設計模塊的頂層,以便對模塊進行系統(tǒng)性的例化調(diào)用進行仿真。
毫不夸張的說,對于稍微復雜的 Verilog 設計,如果不進行仿真,即便是經(jīng)驗豐富的老手,99.9999% 以上的設計都不會正常的工作。不能說仿真比設計更加的重要,但是一般來說,仿真花費的時間會比設計花費的時間要多。有時候,考慮到各種應用場景,testbench 的編寫也會比 Verilog 設計更加的復雜。所以,數(shù)字電路行業(yè)會具體劃分設計工程師和驗證工程師。
下面,對 testbench 做一個簡單的學習。
testbench 結(jié)構(gòu)劃分
testbench 一般結(jié)構(gòu)如下。

其實 testbench 最基本的結(jié)構(gòu)包括信號聲明、激勵和模塊例化。
根據(jù)設計的復雜度,需要引入時鐘和復位部分。當然更為復雜的設計,激勵部分也會更加復雜。根據(jù)自己的驗證需求,選擇是否需要自校驗和停止仿真部分。
當然,復位和時鐘產(chǎn)生部分,也可以看做激勵,所以它們都可以在一個語句塊中實現(xiàn)。也可以拿自校驗的結(jié)果,作為結(jié)束仿真的條件。
實際仿真時,可以根據(jù)自己的個人習慣來編寫 testbench,這里只是做一份個人的總結(jié)。
testbench 仿真舉例
前面的章節(jié)中,已經(jīng)寫過很多的 testbench。其實它們的結(jié)構(gòu)也都大致相同。下面,列舉一個數(shù)據(jù)拼接的簡單例子,對 testbench 再做一個具體的分析。
◆ 一個 2bit 數(shù)據(jù)拼接成 8bit 數(shù)據(jù)的功能模塊描述如下。
module  data_consolidation
    (
        input           clk ,
        input           rstn ,
        input [1:0]     din ,          //data in
        input           din_en ,
        output [7:0]    dout ,
        output          dout_en        //data out
     );
   // data shift and counter
    reg [7:0]            data_r ;
    reg [1:0]            state_cnt ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            state_cnt     <= 'b0 ;
            data_r        <= 'b0 ;
        end
        else if (din_en) begin
            state_cnt     <= state_cnt + 1'b1 ;    //數(shù)據(jù)計數(shù)
            data_r        <= {data_r[5:0], din} ;  //數(shù)據(jù)拼接
        end
        else begin
            state_cnt <= 'b0 ;
        end
    end
    assign dout          = data_r ;
    // data output en
    reg                  dout_en_r ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            dout_en_r       <= 'b0 ;
        end
        //計數(shù)為 3 且第 4 個數(shù)據(jù)輸入時,同步輸出數(shù)據(jù)輸出使能信號
        else if (state_cnt == 2'd3 & din_en) begin 
            dout_en_r       <= 1'b1 ;
        end
        else begin
            dout_en_r       <= 1'b0 ;
        end
    end
    //這里不直接聲明dout_en為reg變量,而是用相關寄存器對其進行assign賦值
    assign dout_en       = dout_en_r;
endmodule
◆ 對應的 testbench 描述如下,增加了文件讀寫的語句。
`timescale 1ns/1ps
   //============== (1) ==================
   //signals declaration
module test ;
    reg          clk;
    reg          rstn ;
    reg [1:0]    din ;
    reg          din_en ;
    wire [7:0]   dout ;
    wire         dout_en ;
    //============== (2) ==================
    //clock generating
    real         CYCLE_200MHz = 5 ; //
    always begin
        clk = 0 ; #(CYCLE_200MHz/2) ;
        clk = 1 ; #(CYCLE_200MHz/2) ;
    end
    //============== (3) ==================
    //reset generating
    initial begin
        rstn      = 1'b0 ;
        #8 rstn      = 1'b1 ;
    end
    //============== (4) ==================
    //motivation
    int          fd_rd ;
    reg [7:0]    data_in_temp ;  //for self check
    reg [15:0]   read_temp ;     //8bit ascii data, 8bit \\n
    initial begin
        din_en    = 1'b0 ;        //(4.1)
        din       = 'b0 ;
        open_file("../tb/data_in.dat", "r", fd_rd); //(4.2)
        wait (rstn) ;    //(4.3)
        # CYCLE_200MHz ;
        //read data from file
        while (! $feof(fd_rd) ) begin  //(4.4)
            @(negedge clk) ;
            $fread(read_temp, fd_rd);
            din    = read_temp[9:8] ;
            data_in_temp = {data_in_temp[5:0], din} ;
            din_en = 1'b1 ;
        end
        //stop data
        @(posedge clk) ;  //(4.5)
        #2 din_en = 1'b0 ;
    end
    //open task
    task open_file;
        input string      file_dir_name ;
        input string      rw ;
        output int        fd ;
        fd = $fopen(file_dir_name, rw);
        if (! fd) begin
            $display("--- iii --- Failed to open file: %s", file_dir_name);
        end
        else begin
            $display("--- iii --- %s has been opened successfully.", file_dir_name);
        end
    endtask
    //============== (5) ==================
    //module instantiation
    data_consolidation    u_data_process
    (
      .clk              (clk),
      .rstn             (rstn),
      .din              (din),
      .din_en           (din_en),
      .dout             (dout),
      .dout_en          (dout_en)
     );
    //============== (6) ==================
    //auto check
    reg  [7:0]           err_cnt ;
    int                  fd_wr ;
    initial begin
        err_cnt   = 'b0 ;
        open_file("../tb/data_out.dat", "w", fd_wr);
        forever begin
            @(negedge clk) ;
            if (dout_en) begin
                $fdisplay(fd_wr, "%h", dout);
            end
        end
    end
    always @(posedge clk) begin
        #1 ;
        if (dout_en) begin
            if (data_in_temp != dout) begin
                err_cnt = err_cnt + 1'b1 ;
            end
        end
    end
    //============== (7) ==================
    //simulation finish
    always begin
        #100;
        if ($time >= 10000)  begin
            if (!err_cnt) begin
                $display("-------------------------------------");
                $display("Data process is OK!!!");
                $display("-------------------------------------");
            end
            else begin
                $display("-------------------------------------");
                $display("Error occurs in data process!!!");
                $display("-------------------------------------");
            end
            #1 ;
            $finish ;
        end
    end
endmodule // test
          
        
        
Verilog仿真激勵舉例