大家好,又到了每日學(xué)習(xí)的時間了,今天我們來聊一聊FPGA中測試文件編寫的相關(guān)知識,聊一聊激勵仿真。
? 1. 激勵的產(chǎn)生
對于testbench而言,端口應(yīng)當(dāng)和被測試的module一一對應(yīng)。端口分為input,output和inout類型產(chǎn)生激勵信號的時候,input對應(yīng)的端口應(yīng)當(dāng)申明為reg, output對應(yīng)的端口申明為wire,inout端口比較特殊,下面專門講解。
1)直接賦值。
一般用initial塊給信號賦初值,initial塊執(zhí)行一次,always或者forever表示由事件激發(fā)反復(fù)執(zhí)行。
舉例,一個module
module exam();
reg rst_n;
reg clk;
reg data;
initial
begin
clk=1‘b0;
rst=1’b1;
#10
rst=1‘b0;
#500
rst=1’b1;
end
always
begin
#10
clk=~clk;
end
大家應(yīng)該注意到有個#符號,該符號的意思是指延遲相應(yīng)的時間單位。該時間單位由timscale決定。一般在testbench的開頭定義時間單位和仿真 精度,比如`timescale 1ns/1ps,前面一個是代表時間單位,后面一個代表仿真時間精度。以上面的例子而言,一個時鐘周期是20個單位,也就是20ns。而仿真時間精度的概 念就是,你能看到1.001ns時對應(yīng)的信號值,而假如timescale 1ns/1ns,1.001ns時候的值就無法看到。對于一個設(shè)計而言,時間刻度應(yīng)該統(tǒng)一,如果設(shè)計文件和testbench里面的時間刻度不一致,仿真 器默認以testbench為準。一個較好的辦法是寫一個global.v文件,然后用include的辦法,可以防止這個問題。
對于反復(fù)執(zhí)行的操作,可寫成task,然后調(diào)用,比如
task load_count;
input [3:0] load_value;
begin
@(negedge clk_50);
$display($time, “ 《《 Loading the counter with %h 》》”, load_value);
load_l = 1’b0;
count_in = load_value;
@(negedge clk_50);
load_l = 1’b1;
end
endtask //of load_count
initial
begin
load_count(4’hA); // 調(diào)用task
end
其他像forever,for,function等等語句用法類似,雖然不一定都能綜合,但是用在testbench里面很方便,大家可以自行查閱參考文檔
2) 文件輸入
有時候,需要大量的數(shù)據(jù)輸入,直接賦值的話比較繁瑣,可以先生成數(shù)據(jù),再將數(shù)據(jù)讀入到寄存器中,需要時取出即可。用 $readmemb系統(tǒng)任務(wù)從文本文件中讀取二進制向量(可以包含輸入激勵和輸出期望值)。$readmemh 用于讀取十六進制文件。例如:
reg [7:0] mem[1:256] // a 8-bit, 256-word 定義存儲器mem
initial $readmemh ( “E:/readhex/mem.dat”, mem ) // 將.dat文件讀入寄存器mem中
initial $readmemh ( “E:/readhex/mem.dat”, mem, 128, 1 ) // 參數(shù)為寄存器加載數(shù)據(jù)的地址始終
2. 查看仿真結(jié)果
對于簡單的module來說,要在modelsim的仿真窗口里面看波形,就用add wave 。.命令
比如,testbench的頂層module名叫tb,要看時鐘信號,就用add wave tb.clk
要查看所有信號的時候,就用 add wave /*
當(dāng)然,也可以在workspace下的sim窗口里面右鍵單擊instance來添加波形
對于復(fù)雜的仿真,免不了要記錄波形和數(shù)據(jù)到文件里面去。
1)波形文件記錄
常見的波形文件一般有兩種,vcd和fsdb,debussy是個很好的工具,支持fsdb,所以最好是modelsim+debussy的組合
默認情況下,modelsim不認識fsdb,所以需要先裝debussy,再生成fsdb文件。
$dumpfile和$dumpvar是verilog語言中的兩個系統(tǒng)任務(wù),可以調(diào)用這兩個系統(tǒng)任務(wù)來創(chuàng)建和將指定信息導(dǎo)入VCD文件。
對于fsdb文件來說,對應(yīng)的命令是fsdbDumpfile,dumpfsdbvars
(什么是VCD文件? 答:VCD文件是在對設(shè)計進行的仿真過程中,記錄各種信號取值變化情況的信息記錄文件。EDA工具通過讀取VCD格式的文件,顯示圖形化的仿真波形,所以,可以把VCD文件簡單地視為波形記錄文件。)下面分別描述它們的用法并舉例說明之。
$dumpfile系統(tǒng)任務(wù):為所要創(chuàng)建的VCD文件指定文件名。
舉例(“//”符號后的內(nèi)容為注釋文字):
initial
$dumpfile (“myfile.dump”); //指定VCD文件的名字為myfile.dump,仿真信息將記錄到此文件
$dumpvar系統(tǒng)任務(wù):指定需要記錄到VCD文件中的信號,可以指定某一模塊層次上的所有信號,也可以單獨指定某一個信號。
典型語法為$dumpvar(level, module_name); 參數(shù)level為一個整數(shù),用于指定層次數(shù),參數(shù)module則指定要記錄的模塊。整句的意思就是,對于指定的模塊,包括其下各個層次(層次數(shù)由 level指定)的信號,都需要記錄到VCD文件中去。
舉例:
initial
$dumpvar (0, top); //指定層次數(shù)為0,則top模塊及其下面各層次的所有信號將被記錄
initial
$dumpvar (1, top); //記錄模塊實例top以下一層的信號
//層次數(shù)為1,即記錄top模塊這一層次的信號
//對于top模塊中調(diào)用的更深層次的模塊實例,則不記錄其信號變化
initial
$dumpvar (2, top); //記錄模塊實例top以下兩層的信號
//即top模塊及其下一層的信號將被記錄
假設(shè)模塊top中包含有子模塊module1,而我們希望記錄top.module1模塊以下兩層的信號,則語法舉例如下:
initial
$dumpvar (2, top.module1); //模塊實例top.module1及其下一層的信號將被記錄
假設(shè)模塊top包含信號signal1和signal2(注意是變量而不是子模塊), 如我們希望只記錄這兩個信號,則語法舉例如下:
initial
$dumpvar (0, top.signal1, top.signal2); //雖然指定了層次數(shù),但層次數(shù)是不影響單獨指定的信號的
//即指定層次數(shù)和單獨指定的信號無關(guān)
我們甚至可以在同一個$dumpvar的調(diào)用中,同時指定某些層次上的所有信號和某個單獨的信號,假設(shè)模塊top包含信號signal1,同時包含有子模 塊module1,如果我們不但希望記錄signal1這個獨立的信號,而且還希望記錄子模塊module1以下三層的所有信號,則語法舉例如下:
initial
$dumpvar (3, top.signal1, top.module1); //指定層次數(shù)和單獨指定的信號無關(guān)
//所以層次數(shù)3只作用于模塊top.module1, 而與信號
top.signal1無關(guān)
上面這個例子和下面的語句是等效的:
initial
begin
$dumpvar (0, top.signal1);
$dumpvar (3, top.module1);
end
$dumpvar的特別用法(不帶任何參數(shù)):
initial
$dumpvar; //無參數(shù),表示設(shè)計中的所有信號都將被記錄
最后,我們將$dumpfile和$dumpvar這兩個系統(tǒng)任務(wù)的使用方法在下面的例子中綜合說明,假設(shè)我們有一個設(shè)計實例,名為 i_design,此設(shè)計中包含模塊module1,模塊module1下面還有很多層次,我們希望對這個設(shè)計進行仿真,并將仿真過程中模塊 module1及其以下所有層次中所有信號的變化情況,記錄存儲到名為mydesign.dump的VCD文件中去,則例示如下:
initial
begin
$dumpfile (“mydesign.dump”); //指定VCD文件名為mydesign.dump
$dumpvar (0, i_design.module1); //記錄i_design.module1模塊及其下面層次中所有模塊的所有信號
end
對于生成fsdb文件而言,也是類似的
initial
begin
$fsdbDumpfile(“tb_xxx.fsdb”);
$fsdbDumpvars(0,tb_xxx);
end
2)文件輸出結(jié)果
integer out_file; // out_file 是一個文件描述,需要定義為 integer類型
out_file = $fopen ( “ cpu.data ” ); // cpu.data 是需要打開的文件,也就是最終的輸出文本
設(shè)計中的信號值可以通過$fmonitor, $fdisplay,$fwrite
其中$fmonitor只要有變化就一直記錄,$fdisplay和$fwrite需要觸發(fā)條件才記錄
例子:
initial begin
$fmonitor(file_id, “%m: %t in1=%d o1=%h”, $time, in1, o1);
end
always@(a or b)
begin
$fwrite(file_id,“At time%t a=%b b=%b”,$realtime,a,b);
end
3 testbench的技巧
1)。如果激勵中有一些重復(fù)的項目,可以考慮將這些語句編寫成一個task,這樣會給書寫和仿真帶來很大方便。例如,一個存儲器的testbench的激勵可以包含write,read等task。
2)。如果DUT中包含雙向信號(inout),在編寫testbench時要注意。需要一個reg變量來表示其輸入,還需要一個wire變量表示其輸出。
3)。如果initial塊語句過于復(fù)雜,可以考慮將其分為互補相干的幾個部分,用數(shù)個initial塊來描述。在仿真時,這些initial塊會并發(fā)運行。這樣方便閱讀和修改。
4)。每個testbench都最好包含$stop語句,用以指明仿真何時結(jié)束。
5)。加載測試向量時,避免在時鐘的上下沿變化,比如數(shù)據(jù)最好在時鐘上升沿之前變化,這也符合建立時間的要求。
4.一個簡單的例子
module counter (clk, reset, enable, count);
input clk, reset, enable;
output [3:0] count;
reg [3:0] count;
always @ (posedge clk)
if (reset == 1‘b1) begin
count 《= 0;
end else if ( enable == 1’b1) begin
count 《= count + 1;
end
endmodule
testbench
module counter_tb;
reg clk, reset, enable;
wire [3:0] count;
counter U0 (
.clk (clk),
.reset (reset),
.enable (enable),
.count (count)
);
initial begin
clk = 0;
reset = 0;
enable = 0;
end
always
#5 clk = ! clk;
initial begin
$dumpfile (“counter.vcd”);
$dumpvars;
end
initial begin
$display(“ time, clk, reset, enable, count”);
$monitor(“‰d, ‰b, ‰b, ‰b, ‰d”,$time, clk,reset,enable,count);
end
initial
#100 $finish;
//Rest of testbench code after this line
endmodule
5 雙向端口
芯片外部引腳很多都使用inout類型的,為的是節(jié)省管腿。一般信號線用做總線等雙向數(shù)據(jù)傳輸?shù)臅r候就要用到INOUT類型了。就是一個端口同時做輸入和 輸出。inout在具體實現(xiàn)上一般用三態(tài)門來實現(xiàn)。三態(tài)門的第三個狀態(tài)就是高阻‘Z’。當(dāng)inout端口不輸出時,將三態(tài)門置高阻。這樣信號就不會因為兩端同時 輸出而出錯了,更詳細的內(nèi)容可以搜索一下三態(tài)門tri-state的資料。
1 使用inout類型數(shù)據(jù),可以用如下寫法:
inout data_inout;
input data_in;
reg data_reg;//data_inout的映象寄存器
reg link_data;
assign data_inout=link_data?data_reg:1’bz;//link_data控制三態(tài)門
//對于data_reg,可以通過組合邏輯或者時序邏輯根據(jù)data_in對其賦值。通過控制link_data的高低電平,從而設(shè)置data_inout是輸出數(shù)據(jù)還是處于高阻態(tài),如果處于高阻態(tài),則此時當(dāng)作輸入端口使用.link_data可以通過相關(guān)電路來控制。
2 編寫測試模塊時,對于inout類型的端口,需要定義成wire類型變量,而其它輸入端口都定義成reg類型,這兩者是有區(qū)別的。
當(dāng)上面例子中的data_inout用作輸入時,需要賦值給data_inout,其余情況可以斷開。此時可以用assign語句實現(xiàn):assign data_inout=link?data_in_t:1’bz;其中的link ,data_in_t是reg類型變量,在測試模塊中賦值。
另外,可以設(shè)置一個輸出端口觀察data_inout用作輸出的情況:
Wire data_out;
Assign data_out_t=(!link)?data_inout:1’bz;
else,in RTL
inout use in top module(PAD)
dont use inout(tri) in sub module
也就是說,在內(nèi)部模塊最好不要出現(xiàn)inout,如果確實需要,那么用兩個port實現(xiàn),到頂層的時候再用三態(tài)實現(xiàn)。理由是:在非頂層模塊用雙向口的話,該 雙向口必然有它的上層跟它相連。既然是雙向口,則上層至少有一個輸入口和一個輸出口聯(lián)到該雙向口上,則發(fā)生兩個內(nèi)部輸出單元連接到一起的情況出現(xiàn),這樣在 綜合時往往會出錯。
對雙向口,我們可以將其理解為2個分量:一個輸入分量,一個輸出分量。另外還需要一個控制信號控制輸出分量何時輸出。此時,我們就可以很容易地對雙向端口建模。
例子:
CODE:
module dual_port (
。
inout_pin,
。
);
inout inout_pin;
wire inout_pin;
wire input_of_inout;
wire output_of_inout;
wire out_en;
assign input_of_inout = inout_pin;
assign inout_pin = out_en ? output_of_inout : 高阻;
endmodule
可見,此時input_of_inout和output_of_inout就可以當(dāng)作普通信號使用了。
在仿真的時候,需要注意雙向口的處理。如果是直接與另外一個模塊的雙向口連接,那么只要保證一個模塊在輸出的時候,另外一個模塊沒有輸出(處于高阻態(tài))就可以了。
如果是在ModelSim中作為單獨的模塊仿真,那么在模塊輸出的時候,不能使用force命令將其設(shè)為高阻態(tài),而是使用release命令將總線釋放掉
很多初學(xué)者在寫testbench進行仿真和驗證的時候,被inout雙向口難住了。仿真器老是提示錯誤不能進行。下面是我個人對inout端口寫 testbench仿真的一些總結(jié),并舉例進行說明。在這里先要說明一下inout口在testbench中要定義為wire型變量。
先假設(shè)有一源代碼為:
module xx(data_inout , 。.);
inout data_inout;
assign data_inout=(! link)?datareg:1‘bz;
endmodule
方法一:使用相反控制信號inout口,等于兩個模塊之間用inout雙向口互連。這種方法要注意assign 語句只能放在initial和always塊內(nèi)。
module test();
wire data_inout;
reg data_reg;
reg link;
initial begin
。
end
assign data_inout=link?data_reg:1’bz;
endmodule
方法二:使用force和release語句,但這種方法不能準確反映雙向端口的信號變化,但這種方法可以反在塊內(nèi)。
module test();
wire data_inout;
reg data_reg;
reg link;
#xx; //延時
force data_inout=1‘bx; //強制作為輸入端口
。..
#xx;
release data_inout; //釋放輸入端口
endmodule
很多讀者反映仿真雙向端口的時候遇到困難,這里介紹一下雙向端口的仿真方法。一個典型的雙向端口如圖1所示。
其中inner_port與芯片內(nèi)部其他邏輯相連,outer_port為芯片外部管腳,out_en用于控制雙向端口的方向,out_en為1時,端口為輸出方向,out_en為0時,端口為輸入方向。
用Verilog語言描述如下:
module bidirection_io(inner_port,out_en,outer_port);
input out_en;
inout[7:0] inner_port;
inout[7:0] outer_port;
assign outer_port=(out_en==1)?inner_port:8’hzz;
assign inner_port=(out_en==0)?outer_port:8‘hzz;
endmodule
用VHDL語言描述雙向端口如下:
library ieee;
use IEEE.STD_LOGIC_1164.ALL;
entity bidirection_io is
port ( inner_port : inout std_logic_vector(7 downto 0);
out_en : in std_logic;
outer_port : inout std_logic_vector(7 downto 0) );
end bidirection_io;
architecture behavioral of bidirection_io is
begin
outer_port《=inner_port when out_en=’1‘ else (OTHERS=》’Z‘);
inner_port《=outer_port when out_en=’0‘ else (OTHERS=》’Z‘);
end behavioral;
仿真時需要驗證雙向端口能正確輸出數(shù)據(jù),以及正確讀入數(shù)據(jù),因此需要驅(qū)動out_en端口,當(dāng)out_en端口為1時,testbench驅(qū)動 inner_port端口,然后檢查outer_port端口輸出的數(shù)據(jù)是否正確;當(dāng)out_en端口為0時,testbench驅(qū)動 outer_port端口,然后檢查inner_port端口讀入的數(shù)據(jù)是否正確。由于inner_port和outer_port端口都是雙向端口(在 VHDL和Verilog語言中都用inout定義),因此驅(qū)動方法與單向端口有所不同。
驗證該雙向端口的testbench結(jié)構(gòu)如圖2所示。
這是一個self-checking testbench,可以自動檢查仿真結(jié)果是否正確,并在Modelsim控制臺上打印出提示信息。圖中Monitor完成信號采樣、結(jié)果自動比較的功能。
testbench的工作過程為
1)out_en=1時,雙向端口處于輸出狀態(tài),testbench給inner_port_tb_reg信號賦值,然后讀取outer_port_tb_wire的值,如果兩者一致,雙向端口工作正常。
2)out_en=0時,雙向端口處于輸如狀態(tài),testbench給outer_port_tb_reg信號賦值,然后讀取inner_port_tb_wire的值,如果兩者一致,雙向端口工作正常。
用Verilog代碼編寫的testbench如下,其中使用了自動結(jié)果比較,隨機化激勵產(chǎn)生等技術(shù)。
`timescale 1ns/10ps
module tb();
reg[7:0] inner_port_tb_reg;
wire[7:0] inner_port_tb_wire;
reg[7:0] outer_port_tb_reg;
wire[7:0] outer_port_tb_wire;
reg out_en_tb;
integer i;
initial
begin
out_en_tb=0;
inner_port_tb_reg=0;
outer_port_tb_reg=0;
i=0;
repeat(20)
begin
#50
i=$random;
out_en_tb=i[0]; //randomize out_en_tb
inner_port_tb_reg=$random; //randomize data
outer_port_tb_reg=$random;
end
end
//**** drive the ports connecting to bidirction_io
assign inner_port_tb_wire=(out_en_tb==1)?inner_port_tb_reg:8’hzz;
assign outer_port_tb_wire=(out_en_tb==0)?outer_port_tb_reg:8‘hzz;
//instatiate the bidirction_io module
bidirection_io bidirection_io_inst(.inner_port(inner_port_tb_wire),
.out_en(out_en_tb),
.outer_port(outer_port_tb_wire));
//***** monitor ******
always@(out_en_tb,inner_port_tb_wire,outer_port_tb_wire)
begin
#1;
if(outer_port_tb_wire===inner_port_tb_wire)
begin
$display(“ **** time=%t ****”,$time);
$display(“OK! out_en=%d”,out_en_tb);
$display(“OK! outer_port_tb_wire=%d,inner_port_tb_wire=%d”,
outer_port_tb_wire,inner_port_tb_wire);
end
else
begin
$display(“ **** time=%t ****”,$time);
$display(“ERROR! out_en=%d”,out_en_tb);
$display(“ERROR! outer_port_tb_wire != inner_port_tb_wire” );
$display(“ERROR! outer_port_tb_wire=%d, inner_port_tb_wire=%d”,
outer_port_tb_wire,inner_port_tb_wire);
end
end
endmodule
原文標題:簡談FPGA Verilog testbench
文章出處:【微信公眾號:FPGA設(shè)計論壇】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
責(zé)任編輯:haq
-
FPGA
+關(guān)注
關(guān)注
1629文章
21738瀏覽量
603461 -
文件
+關(guān)注
關(guān)注
1文章
566瀏覽量
24749
原文標題:簡談FPGA Verilog testbench
文章出處:【微信號:gh_9d70b445f494,微信公眾號:FPGA設(shè)計論壇】歡迎添加關(guān)注!文章轉(zhuǎn)載請注明出處。
發(fā)布評論請先 登錄
相關(guān)推薦
評論