Testbench文件編寫紀要(Verilog)


之前在使用Verilog做FPGA項目中、以及其他一些不同的場合下,零散的寫過一些練手性質的testbench文件,開始幾次寫的時候,每次都會因為一些基本的東西沒記住、寫的很不熟練,后面寫的時候稍微熟練了一點、但是整體編寫下來比較零碎不成體系,所以在這里簡要記錄一下一般情況下、針對小型的verilog模塊進行測試時所需要使用到的testbench文件的編寫要點。

本文主要參考了在網上找到的Lattice公司的“A Verilog HDL Test Bench Primer”手冊中的有關內容。謝謝!

模塊實例化、reg&wire聲明、initial和always塊的使用

需要測試的模塊(Verilog-module)被稱為DUT(Design Under Test),在testbench中需要對一個或者多個DUT進行實例化。

Testbench中的頂層module不需要定義輸入和輸出。

Testbench中連接到DUT instance的輸入的為reg類型、連接到DUT instance的輸出的為wire類型。

對於DUT的inout類型變量,在testbench中需要分別使用reg、wire類型的變量進行調用。

例如,對於下面這樣一個待測試module:

module bidir_infer (DATA, READ_WRITE);
input READ_WRITE ;
inout [1:0] DATA ;
reg [1:0] LATCH_OUT ;

always @ (READ_WRITE or DATA) begin
    if (READ_WRITE == 1)
        LATCH_OUT <= DATA;
end

assign DATA = (READ_WRITE == 1) ? 2'bZ : LATCH_OUT;

endmodule

為其設計的testbench文件可以是:

module test_bidir_ver;
reg read_writet;
reg [1:0] data_in;
wire [1:0] datat, data_out;
bidir_infer uut (datat, read_writet);

assign datat = (read_writet == 1) ? data_in : 2'bZ;
assign data_out = (read_writet == 0) ? datat : 2'bZ;

initial begin
read_writet = 1;
data_in = 11;
#50 read_writet = 0;
end

endmodule

和普通的Verilog模塊中一樣、使用assign對wire類型的變量進行賦值。

需要留意的一點是:對於沒有在代碼中賦初始值的變量,wire類型變量被初始化為Z、reg類型變量被初始化為X。

always和initial是兩種對reg變量進行操作的串行控制塊。每個initial和always塊都會在仿真開始時同時開始運行。

常見的,可以利用它們生成模塊所需的時鍾和復位信號,如下:

‘timescale 1 ns / 100 ps

reg clk_50, rst_l;

initial
begin
$display($time, " << Starting the Simulation >>");
clk_50 = 1’b0; // at time 0
rst_l = 0; // reset is active
#20 rst_l = 1’b1; // at time 20 release reset
end

always
#10 clk_50 = ~clk_50; // every ten nanoseconds invert

首行定義了時間單位/時間精度。時間單位為1ns,這樣生成的clk_50時鍾周期就是20ns、也就是頻率為50MHz。

復位信號rst_l在初始為0復位態、在20ns之后為1解除復位。

仿真中的停止、變量監視和輸出

有兩種仿真控制函數:$finish和$stop。其中,$finish任務用於終止仿真並跳出仿真器;$stop任務則用於中止仿真。在Modelsim中,$stop任務則是返回到交互模式。

如果需要監視仿真中某個變量的變化情況,可以使用$monitor函數:

$monitor($time, " clk_50=%b, rst_l=%b, enable_l=%b, load_l=%b, count_in=%h, cnt_out=%h, oe_l=%b, count_tri=%h", clk_50, rst_l, enable_l, load_l, count_in, cnt_out, oe_l, count_tri);

每當變量列表中的任一變量發生變化,就會產生輸出。

如果需要在仿真控制台屏幕打印輸出,可以使用$display函數:

$display($time, "<< count = %d - Turning OFF count enable >>",cnt_out);

 

 

任務Task的用法

可以將一組重復性的或者相關的命令組合到一起構成一個任務。

任務通常可以在initial或者always塊中被調用。

一個任務可以擁有輸入、輸出、以及inouts,也可以包含計時或延時元素。

以一個在FPGA上實現的簡單SPI接口為例。外部設備為主、FPGA為從,命令一共32bit,構成為“1位讀寫命令字(1讀0寫)+14位地址+1位NO CARE+16位數據”,片選信號拉低之后通信開始,時序如下圖:

數據流由外設到FPGA時(FPGA為接收),外設在SCLK的下降沿更新MOSI;FPGA在SCLK的上升沿將MOSI上的值抓取到移位寄存器。

當FPGA為發送方時,FPGA在SCLK的下降沿更新MISO線上的輸出,外設在SCLK的上升沿將MISO上的值抓取過來。

外設可以通過該SPI接口訪問FPGA內部生成的寄存器。

當對FPGA上的spi模塊進行讀測試時,外設發給FPGA的讀指令為:

{1'b1,address,1'b0,data(讀取到的16bit數據)}

為此編寫的任務spi_read可以是:

task spi_read;    
input[13:0]    address;
output[15:0]    data;
reg [31:0] output_register;
reg [15:0]input_register;
integer i;
    begin
       $display("time:%t----------------task spi_read",$time );
            #100;    
            spi_clk = 1'b0;
            spi_csn = 1'b1;
            spi_mosi =1'b0;
            output_register = {1'b1,address,1'b0,16'd0};
            
            $display("time:%t,testbench read output_register: %h,",$time,output_register );
            $display("time:%t,testbench read address: %h",$time,address );
            
            spi_csn = 1'b1; 
            for(i = 0 ; i < 16 ; i=i+1)
                begin
                    spi_csn = 1'b0;
                    spi_clk = 1'b0;
                    spi_mosi = output_register[31-i];
                    #100;
                    spi_clk = 1'b1;
                    #100;
                end
                
            for(i = 0 ; i < 16 ; i=i+1)
                begin
                    spi_csn = 1'b0;
                    spi_clk = 1'b0;
                    #100;
                    spi_clk = 1'b1;
                    input_register[15-i] = spi_miso;
                    #100;
                end
            spi_csn = 1'b1;    
            
            
            data = input_register;    
            $display("time:%t,testbench spi_read read data: %h,",$time,input_register );            
            
            $display("time:%t----------------",$time );
            #100;
    end

endtask
View Code

(其中仿真的時間單位為1ns,spi時鍾頻率為10MHz)

示例及匯總

根據前述內容,自我總結一般簡單的testbench文件的結構形式可以是如下:

`timescale 1 ns / 1 ns

module testbench_module_top;
reg 
reg
……
wire
wire
……

//reset and clock definition
initial beginend
initial beginend

//actual testing flows
initial 
begin 
//variables initialization
a = 
b = 
…

task_1(var_1… var_N)
…
task_N(var_1… var_N)
$stop;
…end


//dut module instance
module_top U1
(
.var1(),
.var2(),
…
.varN()
)

//necessary control logic for testbench module test flow
always@(...)

//tasks definition
task task_1;
input …;
output …;
……
//action flow
……
endtask

……

task task_N;
……
endtask

endmodule

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM