Verilog十大基本功2(testbench的設計 檔案讀取和寫入操作 原始碼)
阿新 • • 發佈:2019-01-10
需求說明:Verilog設計基礎
內容 :testbench的設計 讀取檔案 寫入檔案
來自 :時間的詩
十大基本功之 testbench
1. 激勵的產生
對於 testbench 而言,埠應當和被測試的 module 一一對應。
埠分為 input,output 和 inout 型別產生激勵訊號的時候,
input 對應的埠應當申明為 reg,
output 對應的埠申明為 wire,
inout 埠比較特殊,下面專門講解。
1)直接賦值
一般用 initial 塊給訊號賦初值,initial 塊執行一次,always 或者 forever 表示由事件激發反覆執行。
舉例,一個 module
`timescale 1ns/1ps
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
endmodule
大家應該注意到有個#符號,該符號的意思是指延遲相應的時間單位。該時間單位由 timscale 決定.
一般在 testbench 的開頭定義時間單位和模擬精度,比如`timescale 1ns/1ps
前面一個是代表時間單位,後面一個代表模擬時間精度。
以上面的例子而言,一個時鐘週期是 20 個單位,也就是 20ns。
而模擬時間精度的概念就是,你能看到 1.001ns 時對應的訊號值,
而假如 timescale 1ns/1ns,1.001ns 時候的值就無法看到。
對於一個設計而言,時間刻度應該統一,如果設計檔案和 testbench 裡面的時間刻度不一致,
模擬器預設以 testbench 為準。
一個較好的辦法是寫一個 global.v 檔案,然後用 include 的辦法,可以防止這個問題。
對於反覆執行的操作,可寫成 task,然後呼叫,比如
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); // 呼叫 task end
其他像 forever,for,function 等等語句用法類似,雖然不一定都能綜合,但是用在 testbench 裡面很方
便,大家可以自行查閱參考文件
2) 檔案輸入
有時候,需要大量的資料輸入,直接賦值的話比較繁瑣,可以先生成資料,再將資料讀入到暫存器中,
需要時取出即可。
用 $readmemb 系統任務從文字檔案中讀取二進位制向量(可以包含輸入激勵和輸出期望值)。
$readmemh 用於讀取十六進位制檔案。例如:
reg [7:0] mem[256:1] // a 8-bit, 256-word 定義儲存器 mem
initial $readmemh ( "E:/readhex/mem.dat", mem ) // 將.dat 檔案讀入暫存器 mem 中
initial $readmemh ( "E:/readhex/mem.dat", mem, 128, 1 ) // 引數為暫存器載入資料的地址始終
檔案調入和列印中,我們 $fread $fwrite 用的更多一些, 大家可以自行查閱參考文件
2. 檢視模擬結果
對於簡單的 module 來說,要在 modelsim 的模擬窗口裡面看波形,就用 add wave ..命令
比如, testbench 的頂層 module 名叫 tb,要看時鐘訊號,就用 add wave tb.clk
要檢視所有訊號的時候,就用 add wave /*
當然,也可以在 workspace 下的 sim 窗口裡面右鍵單擊 instance 來新增波形
對於複雜的模擬,免不了要記錄波形和資料到檔案裡面去。
1)波形檔案記錄(這部分暫時我沒用到呢!)
常見的波形檔案一般有兩種, vcd 和 fsdb, debussy 是個很好的工具,支援 fsdb,所以最好是 modelsim+debussy 的組合預設情況下, modelsim 不認識 fsdb,所以需要先裝 debussy,再生成 fsdb 檔案。
$dumpfile 和$dumpvar 是 verilog 語言中的兩個系統任務, 可以呼叫這兩個系統任務來建立和將指定資訊匯入 VCD 檔案.
對於 fsdb 檔案來說,對應的命令是 fsdbDumpfile,dumpfsdbvars
(什麼是 VCD 檔案? 答: VCD 檔案是在對設計進行的模擬過程中,記錄各種訊號取值變化情況的資訊記錄檔案。 EDA
工具通過讀取 VCD 格式的檔案,顯示圖形化的模擬波形,所以,可以把 VCD 檔案簡單地視為波形記錄檔案.)下面分別
描述它們的用法並舉例說明之。
$dumpfile 系統任務:為所要建立的 VCD 檔案指定檔名。
舉例( "//"符號後的內容為註釋文字):
initial
$dumpfile ("myfile.dump"); //指定 VCD 檔案的名字為 myfile.dump,模擬資訊將記錄到此檔案
$dumpvar 系統任務:指定需要記錄到 VCD 檔案中的訊號,可以指定某一模組層次上的所有訊號,也可以單獨指定某一
個訊號。
典型語法為$dumpvar(level, module_name); 引數 level 為一個整數, 用於指定層次數, 引數 module 則指定要記錄的模組。
整句的意思就是,對於指定的模組,包括其下各個層次(層次數由 level 指定)的訊號,都需要記錄到 VCD 檔案中去。
舉例:
initial
$dumpvar (0, top); //指定層次數為 0,則 top 模組及其下面各層次的所有訊號將被記錄
initial
$dumpvar (1, top); //記錄模組例項 top 以下一層的訊號
//層次數為 1,即記錄 top 模組這一層次的訊號
//對於 top 模組中呼叫的更深層次的模組例項,則不記錄其訊號變化
initial
$dumpvar (2, top); //記錄模組例項 top 以下兩層的訊號
//即 top 模組及其下一層的訊號將被記錄
假設模組 top 中包含有子模組 module1,而我們希望記錄 top.module1 模組以下兩層的訊號,則語法舉例如下:
initial
$dumpvar (2, top.module1); //模組例項 top.module1 及其下一層的訊號將被記錄
假設模組 top 包含訊號 signal1 和 signal2(注意是變數而不是子模組), 如我們希望只記錄這兩個訊號,則語法舉例如下:
initial
$dumpvar (0, top.signal1, top.signal2); //雖然指定了層次數,但層次數是不影響單獨指定的訊號的
//即指定層次數和單獨指定的訊號無關
我們甚至可以在同一個$dumpvar 的呼叫中,同時指定某些層次上的所有訊號和某個單獨的訊號,假設模組 top 包含信
號 signal1,同時包含有子模 塊 module1,如果我們不但希望記錄 signal1 這個獨立的訊號,而且還希望記錄子模組 module1
以下三層的所有訊號,則語法舉例如下:
initial
$dumpvar (3, top.signal1, top.module1); //指定層次數和單獨指定的訊號無關
//所以層次數 3 只作用於模組 top.module1, 而與訊號
top.signal1 無關
上面這個例子和下面的語句是等效的:
initial
begin
$dumpvar (0, top.signal1);
$dumpvar (3, top.module1);
end
$dumpvar 的特別用法(不帶任何引數):
initial
$dumpvar; //無引數,表示設計中的所有訊號都將被記錄
最後,我們將$dumpfile 和$dumpvar 這兩個系統任務的使用方法在下面的例子中綜合說明,假設我們有一個設計例項,
名為 i_design,此設計中包含模組 module1,模組 module1 下面還有很多層次,我們希望對這個設計進行模擬,並將仿
真過程中模組 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)檔案輸出結果
integer out_file; // out_file 是一個檔案描述,需要定義為 integer 型別out_file = $fopen ( " cpu.data " ); // cpu.data 是需要開啟的檔案,也就是最終的輸出文字
設計中的訊號值可以通過$fmonitor, $fdisplay,$fwrite
其中$fmonitor 只要有變化就一直記錄, $fdisplay 和$fwrite 需要觸發條件才記錄
例子:
initial begin
$fmonitor(file_id, "%m: %t in1=%d o1=%h", $time, in1, o1);
end
[email protected](a or b)
begin
$fwrite(file_id,"At time%t a=%b b=%b",$realtime,a,b);
end
3 參考“A Verilog HDL Test Bench Primier.pdf”
1) DUT(Design Under Test)部分
//-------------------------------------------------
// File: count16.v
// Purpose: Verilog Simulation Example
//-------------------------------------------------
`timescale 1 ns / 100 ps
module count16 (
clk,
rst_n,
load_l,
enable_l,
cnt_in,
oe_l,
count,
count_tri
);
input clk;
input rst_n;
input load_l;
input enable_l;
input [3:0] cnt_in;
input oe_l;
output [3:0] count;
output [3:0] count_tri;
reg [3:0] count;
// tri-state buffers
assign count_tri = (!oe_l) ? count : 4'bZZZZ;
// synchronous 4 bit counter
always @ (posedge clk or negedge rst_n)
if (!rst_n) begin
count <= 4'd0;
end
else begin
if (!load_l) begin
count <= cnt_in;
end
else if (!enable_l) begin
count <= count + 1;
end
end
endmodule //of count16
2) Test Bench
//-------------------------------------------------
// File: tb.v
// Purpose: Verilog Simulation Example
// Test Bench
//-----------------------------------------------------------
`timescale 1ns / 100ps
module tb ();
//---------------------------------------------------------
// inputs to the DUT are reg type
reg clk_50;
reg rst_n;
reg load_l;
reg enable_l;
reg [3:0] count_in;
reg oe_l;
//--------------------------------------------------------
// outputs from the DUT are wire type
wire [3:0] cnt_out;
wire [3:0] count_tri;
//----------------------------------------------------------
// create a 50Mhz clock
always #10 clk_50 = ~clk_50; // every ten nanoseconds invert
//-----------------------------------------------------------
// initial blocks are sequential and start at time 0
initial
begin
$display($time, " << Starting the Simulation >>");
clk_50 = 1'd0;
// at time 0
rst_n = 0;
// reset is active
enable_l = 1'd1;
// disabled
load_l = 1'd1;
// disabled
count_in = 4'h0;
oe_l = 4'b0;
// enabled
#20 rst_n = 1'd1;
// at time 20 release reset
$display($time, " << Coming out of reset >>");
@(negedge clk_50); // wait till the negedge of
// clk_50 then continue
load_count(4'hA);
// call the load_count task
@(negedge clk_50);
$display($time, " << Turning ON the count enable >>");
enable_l = 1'b0;
// turn ON enable
// let the simulation run,
// the counter should roll
wait (cnt_out == 4'b0001); // wait until the count
// equals 1 then continue
$display($time, " << count = %d - Turning OFF the count enable >>",cnt_out);
enable_l = 1'b1;
#40;
// let the simulation run for 40ns
// the counter shouldn't count
$display($time, " << Turning OFF the OE >>");
oe_l = 1'b1;
// disable OE, the outputs of
// count_tri should go high Z.
#20;
$display($time, " << Simulation Complete >>");
$stop;
// stop the simulation
end
//--------------------------------------------------------------
// This initial block runs concurrently with the other
// blocks in the design and starts at time 0
initial begin
// $monitor will print whenever a signal changes
// in the design
$monitor(
$time,
" clk_50=%b, rst_n=%b, enable_l=%b, load_l=%b, count_in=%h, cnt_out=%h, oe_l=%b, count_tri=%h",
clk_50, rst_n, enable_l, load_l, count_in, cnt_out, oe_l, count_tri
);
end
//--------------------------------------------------------------
// The load_count task loads the counter with the value passed
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
//---------------------------------------------------------
// instantiate the Device Under Test (DUT)
// using named instantiation
count16 count16_m0 (
.clk (clk_50),
.rst_n (rst_n),
.load_l (load_l),
.cnt_in (count_in),
.enable_l (enable_l),
.oe_l (oe_l),
.count (cnt_out),
.count_tri (count_tri)
);
//---------------------------------------------------------
// read and write data
reg [7:0] mem[10:1];//read data from file
initial $readmemh ("F:/IC/prj/testbench/prj0/data/mem.dat", mem ); // 將.dat 檔案讀入暫存器 mem 中
//設計中的訊號值可以通過$fmonitor, $fdisplay,$fwrite
//其中$fmonitor 只要有變化就一直記錄, $fdisplay 和$fwrite 需要觸發條件才記錄
integer file_out; // out_file 是一個檔案描述,需要定義為 integer 型別
initial file_out = $fopen("F:/IC/prj/testbench/prj0/data/wr_mem.dat", "w");
// wr_mem.dat 是需要開啟的檔案,也就是最終的輸出文字
always @(posedge clk_50)
if (/*tb.count16_m0.*/enable_l == 1'd0) begin
$fwrite (file_out, "%h\n", cnt_out[3:0]);
// $fdisplay(file_out, "%h", cnt_out[3:0]);
end
endmodule //of cnt16_tb
3) sim.do檔案
#Time: 2016-07-26
#By : times_poem
quit -sim
cd F:/IC/prj/testbench/prj0
if [file exists work] {
vdel -all
}
vlib work
vlog ./*.v
vlog ./src/*.v
vsim -t ps -novopt work.tb
log -r /*
do wave.do
run -all