書寫testbench是數字電路設計中不可或缺的一項設計方法,主要是提供的是激勵。儘管現在各種開發工具都通過繪製波形圖的方法生成測試激勵,測試書寫的代碼,但是其不可移植性,不可通用性,還有有些功能無法是實現,如監視變量的值的變化,顯示數據的狀態等。
一個完整的testbench包含下列幾個部分:
(1)module的定義,一般無輸入輸出端口。
(2)信號的定義,定義哪些是你要輸入,輸入的定義爲reg類型,輸出的定義爲wire型
(3)實例化待測試的模塊
(4)提供測試激勵
1.如何書寫測試激勵;
(1)時鐘信號的產生
時間單位設置:`timescale 1 ns/ 1 ns
或者是
(2)復位信號的產生
復位信號就是在復位電平下延時一段時間,然後再將復位電平信號取反即可。如:
在實際應用將其封裝爲task,使用時掉調用即可。
task
endtask
(3)產生一種複雜的信號。如下面的實例產生一個vs和hs信號
//-------------------------------------------------------------------------------------
//-------------------------------------------------------------------------------------
這樣基本上就可以完成一些簡單的測試testbench了。
2.如何將我們的測試儘可能的簡單明瞭化
下面介紹幾種常用的系統函數
作用:
如:
作用:監視變量的變化,一旦變量變化,則將變量顯示出來
如:$monitor
作用:打開一個文件面,對文件的操作
作用:在打開的文件裏,寫入顯示的內容
作用:在打開的文件裏,寫入監視的變量變化時的內容
作用:關閉當前的內容
如:initial
我們爲什麼要寫testbench?
經常看到論壇裏有人問我們爲什麼要寫testbench,總是覺得不好回答,下面是整理出來的一些理由供大家參考。
與寫testbench相對應的功能手段還有畫波形圖,兩者相比,畫波形圖的方法更加直觀和易於入門,那爲什麼我們還要寫Testbench呢?原因有以下五點:
第一,畫波形圖只能提供極低的功能覆蓋。
第二,畫波形無法實現驗證自動化。
第三,畫波形圖難於定位錯誤。
第四,畫波形的可重用性和平臺移植性極差。
第五,通過畫波形的驗證速度極慢。
Testbench巧解!
=================================概念=========================
testbench是一種驗證的手段。
首先,任何設計都是會有輸入輸出的。但是在軟環境中沒有激勵輸入,也不會對你設計的輸出正確性進行評估。那麼此時便有一種,模擬實際環境的輸入激勵和輸出校驗的一種“虛擬平臺”的產生。在這個平臺上你可以對你的設計從軟件層面上進行分析和校驗,這個就是testbench的含義。
==============================================================
======================初步認識=================================
就初學而言,testbench更像一個激勵的產生器。
舉例:一個ram,可能有幾個input和output。分別列在下面。
clk,時鐘輸入
addr,地址輸入
wen,寫使能
data,數據輸入
然後還有一個dataout的數據輸出。
那麼你可以寫一個文件,給clk,addr,wen,data送入你預想的一些信號,然後觀察q的輸出,看看ram是否工作正常。那麼這個文件從一定意義上可以叫做"testbench"。
聯想(幫助理解):從quartus裏面你仿真,你可能對着那個畫圖一樣的東西畫上輸入,然後編譯以後看他的輸出。對吧。那麼在modelsim裏面,我告訴你,可以不用畫圖了~,你只需要按照一定規則寫一個.v或者.vhd的文件,這個文件可以給你的設計提供你預想的輸入。這個就是testbench的文件。然後在modelsim這個特定的軟件環境下,這個軟件能根據你的代碼給你的設計提供輸入,又可以把你設計的輸出在屏幕上顯示出來給你debug。那麼這個時候,一個在modelsim上的testbench就完成了。
狹義的總結一下:FPGA的testbench就是一個.v(verilog)或者.vhd(vhdl)的文件。這個文件能給你的設計提供激勵,並能在一些專用的軟件中提供良好的debug接口。這個就是一個testbench。
==============================================================
=====================高級應用================================
關於testbench的高級應用。
剛纔說了初步的testbench。其實testbench是verification(驗證)中的一個手段。
驗證是什麼呢?舉例:做魚了,你往裏面加了調料,然後再嚐嚐味道,這個就是驗證的過程。同樣你可以分成幾個部分,一條魚,好比你的設計,然後你給他一定的激勵,也就是調料啦。然後你再嘗一嘗,看看魚是不是達到你想要的味道了。那就是一種驗證的手段,如果淡了。那麼加點鹽,再嚐嚐,這個就是反覆驗證。
testbench裏面包含了三個東西:
1、激勵生成。也就是我們剛纔初級時候說的所謂的“testbench”。英文麼就是simulator,這個只用來生成輸出,他自己沒有輸入,只是按照一定的規律去給你的設計激勵,激勵通過設計的輸入端口送到你的設計中。其餘的事情不管。這裏的激勵,都是預先設想好的,比如根據某個協議,或者某種通信方式傳遞。
2、你的設計。英文可以叫做DUT:design
under testbench或者DUV:design
under verification。當然咯。這個是你主要目標。
3、輸出校驗。校驗你的輸出。英文叫markerboard,他所管的事情就是,接收你設計的輸入,然後通過校驗,找出對應的問題。然後報錯,或者統計錯誤。等等。通俗的講,你設計它就是把你自己解脫出來,讓他來幫你找錯誤。他輸出給你的可能就是通過打印啊,通知啊,等等方法瞭解你設計的正確性。
那麼你有可能問了,這個東西用verilog或者VHDL能寫麼,modelsim裏能用麼?的確是可以的,有寫甚至可以用c的代碼通過程序接口來轉換到modelsim裏面來幫助驗證。
========================高級應用結束==========================
最後小說兩句:testbench是一個平臺,幫助你從軟件方面驗證的。對於這個概念不需強求,等你自己的驗證寫多了,自然而然就會了解其中深刻的含義。先開始慢慢的寫一些激勵,然後再寫寫校驗。到時候你收穫的東西自然而然的能幫助你理解testbench和verification 。
Testbench編寫技巧
測試平臺是個沒有輸入輸出端口的模塊。仿真在一個模塊設計中是很關鍵的步驟,而testbench是仿真的很好工具。
與待測模塊接口
與輸入端口相連接的變量定義爲reg
與輸出端口相連的定義爲wire
initial塊中初始化變量,必須的。
用$stop或$finish暫停或結束仿真
wait(z==1’b1);//等待變量值改變,變量可以是待測試模塊的輸出或者內部變量
時鐘產生:
always # 10 clk =~clk;產生時鐘
initial repeat(13) #5 clk =~clk; //控制只產生13個時鐘。
同步數據:
initial forever @ (posedge clk) #3 x = $random;
爲了降低多個輸入同時翻轉的概率,對時序電路的輸入一般採用素數作爲時間間隔。
同步顯示:
l
一般在initial中調用,採用$monitor顯示模塊MUT內部current的值以及發生變化的時間,$monitor是一個後臺運行任務函數,多個模塊下,任意時間只能有一個$monitor起作用,可用$monitoron $monitoroff來控制。
l
l
隨機數據
initial repeat(5) #7 x = $random;
a = $random`; //產生-59~59之間隨機數
a = {$random}`; //產生0~59之間隨機數
產生隨機時間間隔
always begin
#(t) x = $random;
end
初學TestBench
初學TestBench
`timescale 1ns/10ps //單位時間/精度
`include "adder.v"
module adder_testbench;
小結:
以上寫法產生的時鐘如下:
如何編寫testbench的總結
如何編寫testbench的總結(非常實用的總結) 1.激勵的設置 相應於被測試模塊的輸入激勵設置爲reg型,輸出相應設置爲wire類型,雙向端口inout在測試中需要進行處理。 方法1:爲雙向端口設置中間變量inout_reg作爲該inout的輸出寄存,inout口在testbench中要定義爲wire型變量,然後用輸出使能控制傳輸方向。 eg: inout [0:0] bi_dir_port; wire [0:0] bi_dir_port; reg [0:0] bi_dir_port_reg; reg bi_dir_port_oe; assign bi_dir_port=bi_dir_port_oe?bi_dir_port_reg:1'bz; 用bi_dir_port_oe控制端口數據方向,並利用中間變量寄存器改變其值。等於兩個模塊之間用inout雙向口互連。往端口寫(就是往模塊裏面輸入) 方法2:使用force和release語句,這種方法不能準確反映雙向端口的信號變化,但這種方法可以反映塊內信號的變化。具體如示: module test(); wire data_inout; reg data_reg; reg link; #xx; //延時 force data_inout=1'bx; //強制作爲輸入端口 ............... #xx; release data_inout; //釋放輸入端口 endmodule 從文本文件中讀取和寫入向量 1)讀取文本文件:用 $readmemb系統任務從文本文件中讀取二進制向量(可以包含輸入激勵和輸出期望值)。$readmemh 用於讀取十六進制文件。例如: reg [7:0] mem[1:256] // a 8-bit, 256-word定義存儲器mem initial $readmemh ( "mem.data", mem ) // 將.dat文件讀入寄存器mem中 initial $readmemh ( "mem.data", mem, 128, 1 ) // 參數爲寄存器加載數據的地址始終 2)輸出文本文件:打開輸出文件用?$fopen 例如: integer out_file; // out_file 是一個文件描述,需要定義爲 integer類型 out_file = $fopen ( " cpu.data " ); // cpu.data 是需要打開的文件,也就是最終的輸出文本 設計中的信號值可以通過$fmonitor, $fdisplay,
2. Verilog和Ncverilog命令使用庫文件或庫目錄 3.Verilog Testbench信號記錄的系統任務:
1). SHM數據庫可以記錄在設計仿真過程中信號的變化. 它只在probes有效的時間內記錄你set
probe on的信號的變化. 在記錄信號或者波形時需要指出被記錄信號的路徑,如:tb.module.u1.clk. ……………………………………………………………………………………………………… 關於信號記錄的系統任務的說明: 在testbench中使用信號記錄的系統任務,就可以將自己需要的部分的結果以及波形文件記錄下來(可採用sigalscan工具查看),適用於對較大的系統進行仿真,速度快,優於全局仿真。使用簡單,在testbench中添加:initial begin $shm_open("waves.shm"); $shm_probe("要記錄信號的路徑“,”AS“); #10000 $shm_close; 即可。
4. ncverilog編譯的順序:
ncverilog file1 file2 .... 6.加載測試向量時,避免在時鐘的上下沿變化 爲了模擬真實器件的行爲,加載測試向量時,避免在時鐘的上下沿變化,而是在時鐘的上升沿延時一個時間單位後,加載的測試向量發生變化。如: assign #5 c="a"^b …… @(posedge clk) #(0.1*`cycle) A=1; ****************************************************************************** //testbench的波形輸出 module top; ... initial begin $dumpfile("./top.vcd"); //存儲波形的文件名和路徑,一般是.vcd格式. $dumpvars(1,top); //存儲top這一層的所有信號數據 $dumpvars(2,top.u1); //存儲top.u1之下兩層的所有數據信號(包含top.u1這一層) $dumpvars(3,top.u2); //存儲top.u2之下三層的所有數據信號(包含top.u2這一層) $dumpvars(0,top.u3); //存儲top.u3之下所有層的所有數據信號 end endmodule //產生隨機數,seed是種子 $random(seed); ex: din <= $random(20); //仿真時間,爲unsigned型的64位數據 $time ex: ... time condition_happen_time; ... condition_happen_time = $time; ... $monitor($time,"data output = %d", dout); ... //參數 parameter para1 = 10, para2 = 20, para3 = 30; //顯示任務 $display(); //監視任務 $monitor(); //延遲模型 specify ... //describ pin-to-pin delay endspecify ex: module nand_or(Y,A,B,C); input A,B,C; output Y; AND2 #0.2 (N,A,B); OR2 #0.1 (Y,C,N); specify (A*->Y) = 0.2; (B*->Y) = 0.3; (C*->Y) = 0.1; endspecify endmodule //時間刻度 `timescale 單位時間/時間精確度 //文件I/O 1.打開文件 integer file_id; file_id = fopen("file_path/file_name"); 2.寫入文件 //$fmonitor只要有變化就一直記錄 $fmonitor(file_id, "%format_char", parameter); eg:$fmonitor(file_id, "%m: %t in1=%d o1=%h", $time, in1, o1); //$fwrite需要觸發條件才記錄 $fwrite(file_id, "%format_char", parameter); //$fdisplay需要觸發條件才記錄 $fdisplay(file_id, "%format_char", parameter); $fstrobe(); 3.讀取文件 integer file_id; file_id = $fread("file_path/file_name", "r"); 4.關閉文件 $fclose(fjile_id); 5.由文件設定存儲器初值 $readmemh("file_name", memory_name"); //初始化數據爲十六進制 $readmemb("file_name", memory_name"); //初始化數據爲二進制 //仿真控制 $finish(parameter); //parameter = 0,1,2 $stop(parameter); //讀入SDF文件 $sdf_annotate("sdf_file_name", module_instance, "scale_factors"); //module_instance: sdf文件所對應的instance名. //scale_factors:針對timming delay中的最小延時min,典型延遲typ,最大延時max調整延遲參數 //generate語句,在Verilog-2001中定義.用於表達重複性動作 //必須事先聲明genvar類型變量作爲generate循環的指標 eg: genvar i; generate for(i = 0; i < 4; i = i + 1) begin assign = din[i] = i % 2; end endgenerate //資源共享 always @(A or B or C or D) sum = sel ? (A+B):(C+D); //上面例子使用兩個加法器和一個MUX,面積大 //下面例子使用一個加法器和兩個MUX,面積小 always @(A or B or C or D) begin tmp1 = sel ? A:C; tmp2 = sel ? B:D; end always @(tmp1 or tmp2) sum = tmp1 + tmp2; ****************************************************************************** 模板: module testbench; //定義一個沒有輸入輸出的module reg …… //將DUT的輸入定義爲reg類型 …… wire…… //將DUT的輸出定義爲wire類型 …… //在這裏例化DUT initial begin …… //在這裏添加激勵(可以有多個這樣的結構) end always…… //通常在這裏定義時鐘信號 initial //在這裏添加比較語句(可選) end initial //在這裏添加輸出語句(在屏幕上顯示仿真結果) end endmodule 以下介紹一些書寫Testbench的技巧: 1.如果激勵中有一些重複的項目,可以考慮將這些語句編寫成一個task,這樣會給書寫和仿真帶來很大方便。例如,一個存儲器的testbench的激勵可以包含write,read等task。 2.如果DUT中包含雙向信號(inout),在編寫testbench時要注意。需要一個reg變量來表示其輸入,還需要一個wire變量表示其輸出。 3.如果initial塊語句過於複雜,可以考慮將其分爲互補相干的幾個部分,用數個initial塊來描述。在仿真時,這些initial塊會併發運行。這樣方便閱讀和修改。 4.每個testbench都最好包含$stop語句,用以指明仿真何時結束。 最後提供一個簡單的示例(轉自Xilinx文檔): DUT: module shift_reg (clock, reset, load, sel, data, shiftreg); input clock; input reset; input load; input [1:0] sel; input [4:0] data; output [4:0] shiftreg; reg [4:0] shiftreg; always @ (posedge clock) begin if (reset) shiftreg = 0; else if (load) shiftreg = data; else case (sel) 2’b00 : shiftreg = shiftreg; 2’b01 : shiftreg = shiftreg << 1; 2’b10 : shiftreg = shiftreg >> 1; default : shiftreg = shiftreg; endcase end endmodule Testbench: module testbench; // declare testbench name reg clock; reg load; reg reset; // declaration of signals wire [4:0] shiftreg; reg [4:0] data; reg [1:0] sel; // instantiation of the shift_reg design below shift_reg dut(.clock (clock), .load (load), .reset (reset), .shiftreg (shiftreg), .data (data), .sel (sel)); //this process block sets up the free running clock initial begin clock = 0; forever #50 clock = ~clock; end initial begin// this process block specifies the stimulus. reset = 1; data = 5’b00000; load = 0; sel = 2’b00; #200 reset = 0; load = 1; #200 data = 5’b00001; #100 sel = 2’b01; load = 0; #200 sel = 2’b10; #1000 $stop; end initial begin// this process block pipes the ASCII results to the //terminal or text editor $timeformat(-9,1,"ns",12); $display(" Time Clk Rst Ld SftRg Data Sel"); $monitor("%t %b %b %b %b %b %b", $realtime, clock, reset, load, shiftreg, data, sel); end endmodule |
有關testbench的一些問題
task test_task;//task的定義,以task關鍵字開始,緊接後面是task的id,名字
input [1:0] a;
output [1:0] b;
begin
end
endtask
reg [1:0] task_a;
reg [1:0] task_b;
initial
initial
reg [1:0] task1_a;
reg [1:0] task1_b;
task test_task1;
output [1:0] b;
assign b = ~task1_a;//可以使用全局變量,不一定是task內部的變量
endtask
initial
task
還是有區別的。
1,task只能定義在module內部,不能單獨在一個文件中,不能定義在module外面。
2,在task調用的是必須在過程性語句內部使用,initial,begin
... end
3,task可以沒有參數,直接使用全局變量來實現功能。
4,可以使用延時控制,可以調用其他task和函數。
函數:可以調用函數,不能調用任務,不可有時序控制。
function [1:0] invert;
//input [1:0] a;
//begin
invert = 2'd3;
//end
endfunction
reg [1:0] fun_a;
reg [1:0] fun_out;
initial
task test_task1;
output [1:0] b;
b = ~add(task1_a);
endtask
function [1:0] invert;//函數調用函數
input [1:0] a;
endfunction
//顯示和文件操作
integer i;
initial
begin
a1 = 0;
end
initial
integer f_id;
initial
initial
integer cool;
initial
testbench的實現方法多樣,而且還不斷涌現出新方法,這些都是人們在爲更好的驗證設計做的努力。如VHDL,verilog,systemC,systemverilog均可以,但是真正的實際應用中絕對不是單獨應用,而是將他們結合起來,使你的驗證更方便,更全面。
由於系統驗證的龐大,我們還是從最簡單的,最熟悉的上手,就先單獨用VHDL語言來寫仿真語言,當然VHDL語言我們已經比較熟悉了,但是有個比較大的區別是,我們以前都是儘量學習能夠綜合的語言,但是在仿真中經常會用到一些行爲級描述的語言,他們是不能被綜合成邏輯的,但是卻絕對可以讓我們的驗證更加高效方便,但是我們也不單獨來討論VHDL中哪些語言是能夠綜合,哪些語言是不能綜合的,重點將會放在testbench的一般結構,編寫testbench的一般思想,以及更多的是實際接觸,以實際的例子與前面的可綜合邏輯相結合達到完整的系統設計的上的!
testbench的幾種思路:
一、只在testbench中實例化DUT(design
under test),激勵輸入是在testbench中臨時產生的,只能用於簡單邏輯。優點:簡單,易操作。缺點:複用性差 ,效率低
二、DUT的輸入由單獨的一個文件產生,在testbench中實例化兩上entity,可以複雜輸入,簡單輸出的模塊。模型如圖2所示
三、DUT的輸入與測試輸出各由單獨文件產生,在testbench由三個實例化模塊產生,用於具有複雜輸入以及輸出的模塊,模型如圖3所示。
四、可以根據仿真輸出來修改輸入激勵的,可以自動通過輸出來修改輸入,使驗證更加準確。如圖4所示的模型
五、有文件做爲testbench的輸入,輸出的模型。仿真中需要順序的輸入大量數據,以及接收相應的數據,可以通過從文件中讀入數據,然後將產生的數據存入文件,使複雜系統驗證更加方便。模型如圖5所示。
六、可以將激勵同時輸入自己設計的模塊和已經驗證了相同模塊,比較二者輸出。模型如圖6所示。
圖1
圖2
圖6
本文來源於:電子工程世界
描述測試信號的變化和測試過程的模塊叫做測試平臺(Testbench),它可以對電路模塊進行動態的測試。通過觀測被測試模塊的輸出信號是否符合要求,可以調試和驗證邏輯系統的設計和結構是否正確,便於發現問題並修改。
Testbench用於測試模塊的示意圖如圖所示:
由示意圖可知,Testbench要對被測模塊進行測試,需要產生被測模塊所需的激勵信號(比如時鐘信號,復位信號等),這個就像我們用Quartus波形仿真時拖波形一樣,只是Testbench裏需要我們用代碼來實現波形的變化。
產生的激勵信號需要與被測模塊對口(比如產生的時鐘信號要送入時鐘輸入口,產生的復位信號要送入復位輸入口等),如何實現對口,這就需要對被測試模塊的例化來實現。例化的寫法如下:
被測模塊名
(
.被測模塊輸入口
.被測模塊輸出口
);
上面的示意圖對應的例化寫法爲:
被測模塊名
(
);