1. 程式人生 > >Verilog十大基本功2(testbench的設計 檔案讀取和寫入操作 原始碼)

Verilog十大基本功2(testbench的設計 檔案讀取和寫入操作 原始碼)

需求說明: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