Verilog 代碼設計完成后,還需要進行重要的步驟,即邏輯功能仿真。仿真激勵文件稱之為 testbench,放在各設計模塊的頂層,以便對模塊進行系統性的例化調用進行仿真。
毫不夸張的說,對于稍微復雜的 Verilog 設計,如果不進行仿真,即便是經驗豐富的老手,99.9999% 以上的設計都不會正常的工作。不能說仿真比設計更加的重要,但是一般來說,仿真花費的時間會比設計花費的時間要多。有時候,考慮到各種應用場景,testbench 的編寫也會比 Verilog 設計更加的復雜。所以,數字電路行業會具體劃分設計工程師和驗證工程師。
下面,對 testbench 做一個簡單的學習。
testbench 結構劃分
testbench 一般結構如下。
其實 testbench 最基本的結構包括信號聲明、激勵和模塊例化。
根據設計的復雜度,需要引入時鐘和復位部分。當然更為復雜的設計,激勵部分也會更加復雜。根據自己的驗證需求,選擇是否需要自校驗和停止仿真部分。
當然,復位和時鐘產生部分,也可以看做激勵,所以它們都可以在一個語句塊中實現。也可以拿自校驗的結果,作為結束仿真的條件。
實際仿真時,可以根據自己的個人習慣來編寫 testbench,這里只是做一份個人的總結。
testbench 仿真舉例
前面的章節中,已經寫過很多的 testbench。其實它們的結構也都大致相同。下面,列舉一個數據拼接的簡單例子,對 testbench 再做一個具體的分析。
◆ 一個 2bit 數據拼接成 8bit 數據的功能模塊描述如下。
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 ; //數據計數
data_r <= {data_r[5:0], din} ; //數據拼接
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
//計數為 3 且第 4 個數據輸入時,同步輸出數據輸出使能信號
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