testbench常用語句 很詳細相當實用


內容

與可綜合Verilog代碼所不同的是,testbench Verilog是在計算機主機上的仿真器中執行的。testbench Verilog的許多構造與C語言相似,我們可在代碼中包括復雜的語言結構和順序語句的算法。

1 always塊和initial

Verilog有兩種進程語句:always塊和initial塊。always塊內的進程語句,可用來模擬抽象的電路。

出於模擬的目的,always塊可以包括:用以指定與不同結構之間的傳播延遲等同的時序結構;或等待指定事件的時序結構。敏感列表有時可忽略。比方說,我們用下面的代碼片段來模擬時鍾信號,該信號每20個時間單位在0~1間變換一次,且永遠執行下去。

 

 

always

begin

  clk=1;

  #20;

  clk=0;

  #20);

end

 

initial塊內也有進程語句,但是僅在仿真之初被執行。其簡單語法如下:

 

 

initial

begin

 進程語句;

end

 

initail塊常用於設置變量的初始值。注意,initial塊不可被綜合。

2 進程語句

進程語句應用於initial塊、always塊、function和task之中。最常用的進程語句為:

·         阻塞賦值

·         非阻塞賦值

·         if表達式

·         case表達式

·         循環表達式

我們討論過阻塞和阻塞賦值if和case語句

Verilog支持的循環結構有:for、while、repeat和forever。for循環的簡單語法為:

 

 

for([initial_assignment]; [end_condition]; [step_assignment])

begin

  [procedural_statements;]

end

 

舉個例子,我們可以使用下面的語句來清除16位寄存器文件的內容:

 

 

integer i;

. . .

for(i=0; i<16; i=i+1)

  reg_file[i]=0;

 

注意當循環體內只有一條語句的話,begin和end限定詞可以略去。

while循環的簡單語法如下:

 

 

while([end_condition])

begin

  [procedural_statements;]

end

 

循環體內的語句連續重復執行,直到達到指定的終止條件[end_condition]為止。比方說上面的清寄存器文件的操作可以使用while循環來描述:

 

 

integer i;

. . .

while(i<16)

begin

  reg_file[i]=0;

  i=i+1;

en

 

repeat循環的簡單語法如下:

 

 

repeat([number])

begin

  [procedual_statements;]

end

 

循環體內的語句被重復執行指定數次,該數可通過[number]來指定。比方說,我們可以將上面的操作替換為repeat循環:

 

 

integer i;

. . .

repeat(16)

begin

  [procedural_statements;]

end

 

forever循環,正如其名,重復執行其主體直至仿真結束位置。循環體內常包括一定的時序控制結構,以致周期性推遲執行。比方說,我們換一種方式來描述時鍾信號,該信號每10個時間單位翻轉一次,且永遠運行下去。

 

 

initial

begin

  clk=1'b0;

  forever

    #10 clk=~clk;

end

 

3 時序控制

在testbench中,必須指定不同信號有效和無效或等待某事件或條件的時間。有三種時序控制結構:

·         時延控制:#[delay_time]

·         事件控制:@([event], [event], …]

·         等待語句:wait([boolean_expression])

此外還有一個編譯器指令,`timescale,也與時序規范有關。

4 時延控制

時延控制使用#符號來指示,其后為延遲的時間單位數值。

如果時延控制放置在左手邊,那么整條語句的執行都會被延遲。比方說,

 

 

 

. . .

#10 a=1'b0;

#5  y=a|b;

. . .

 

假設當前時間為t,上面的語句表示,a於t+10時刻得到0值;又過了5個時間單位后(即於t+15時刻)a|b表達式被計算,其結果被賦給y。

如果實驗控制被放置在右手邊,那么表達式將會被立即運算,但是延遲后再賦給左手邊。如:

 

 

. . .

#10 a=1'b0;

    y=#5 a|b;

. . .

 

a於t+10時刻得到0值;a|b表達式被立即運算(即在t+10時刻),但其結果卻在t+15時刻才賦給y。

一般情況下,我們使用時延控制生成激勵的方式來替代傳播延遲的模擬。下面的格式使得代碼顯得更加直觀。

 

 

 

 

 

. . .

a=1'b0;// a gets 0

#10;   // the 0 value lasts 10 time units

a-1'b1;// a changes to 10

#5;    // the 1 value lasts 5 time units

a=1'b0;// a changes to 0

#20    // the 0 value lasts 20 time units

. . .

 

5 事件控制

事件控制使用@符號來指示,其后為敏感列表,用於指定所需事件。其使用與always塊內的事件類似。事件即敏感列表中的信號改變其值(信號跳變)的時刻。可加入posedge和negedge關鍵字以指定所需的跳變邊沿(上升沿和下降沿)。在testbench中,直到指定事件發生,語句才可跳過延遲,繼續執行。事件控制的一個常見應用為:使用時鍾信號來同步激勵的生成。比方說,下面的代碼片段中,en信號被激活持續一個時鍾周期。

 

 

 

 

 

 

 

 

 

localparam delta=1;

. . .

@(posedgeclk);// wait for the rising edge of clk

#delta;        // wait for delta to avoid hold=time violation

en=1'b1;       // assert en to 1

@(posedgeclk);// wait for the next rising edge of clk

#delta;        // wait for delta to avoid hold-time violation

en=1'b0;       // assert en to 0     

 

換一種方式,我們可以在時鍾信號的下降沿斷言或解除斷言en。

 

 

 

 

 

 

 

. . .

@(negedgeclk)// wait for the falling edge of clk

en=1'b1;      // assert en to 1

@(negedgeclk)// wait for the next falling edge of clk

en=1'b0;

. . .

 

6 等待語句

wait語句用以等待指定條件。其簡單語法如下:

 

 

wait[boolean_expression]

 

直到[boolean_expression]被計算為真,后面語句才可跳過延遲,繼續執行。比方說,我們可以這樣寫代碼:

 

 

wait(state==READ && mem_ready==1'b1) [statement_to_get_data];

 

我們也可以使用wait語句來延遲執行。比方說,我們可以等計數器數到15才激活某信號:

 

 

 

 

. . .

wait(counter==4'b1111);// wait until counter is 15

. . .                  // continue

 

wait語句有時很想事件控制。后者是等待某信號的跳變邊沿,而前者是等待指定條件,有時可理解為電平敏感。

7 timescale指令

編譯器指令用以控制編譯和預處理verilog代碼,他們通過重音符號(`)來指明。重音符號常位於鍵盤的左上角。與時間有關的指令是`timescale指令

`timescale [time_unit] / [time_precision]

time_unit指定計時和延時的測量單位,time_precision則是指定仿真器的精度。

比方說,指令

 

 

`timescale10ns/1ns

 

則說明仿真單位為10ns,精度為1ns。當指定如下代碼中的延時,

 

 

#5 y = a & b; 

表明實際上的延時為50ns(即5*10ns)。

也可以指定小數形式的單位延時,比方說

 

 

#5.12345 y = a & b;

 

則說明實際延時為51.2345ns。因為精度是1ns, 所以在仿真中就取整為51ns。精度越少,仿真的准確性越高,但是會減慢仿真的速度。

time_unit和time_precision的數字部分可以為1、10和100,時間單元可以是s(秒)、ms(毫秒)、us(微秒)、ns(納秒)和ps(皮秒)。

8 系統控制函數和任務

Verilog有一組預定義的系統函數,以$打頭,執行與系統相關的操作,如仿真控制、文件讀取等。下面我們講一下一些常用的函數和任務。

數據類型轉換函數

$unsigned和$signed函數執行介於無符號數和有符號數類型之間的轉換。

仿真時間函數

仿真時間函數返回當前的仿真時間,如$time、$stime和$realtime函數分別以64位整數、32位整數和實數的形式返回時間。

仿真控制任務

有兩種仿真控制函數:$finish和$stop。其中,$finish任務用於終止仿真並跳出仿真器;$stop任務則用於中止仿真。在Modelsim中,$stop任務則是返回到交互模式。在開發流程中,我們有時會停在Modelsim環境中,來進一步編輯或測試波形,因此代碼中使用的是$stop。

顯示任務

在Modelsim中,仿真的結果可以以波形的形式顯示,也可以以文本的形式顯示。四種主要的顯示任務有$display、$write、$strobe和$monitor,它們語法類似。在Modelsim中,文本是在控制面板顯示的。

$display的語法與C語言中的打印函數類似。其簡單語法為:

 

 

$display([format_string], [argument], [argument], ...);

 

如:

 

 

$display("at %d; signal x = %b", $time, x);

 

其結果的形式如下:

 

 

at 5100; signal x = 00110001

 

最常用的轉移符號有%d、%b、%o、%h、%c、%s和%g,對應分別為十進制、二進制、八進制、十六進制、字符、字符串和實數。

$write任務幾乎和$diplay等同,除了其執行之后並不跳到下一行顯示。而是一直顯示在當前位置。顯示下一行字符\n,必須手動添加,以創建一個行中斷。

Verilog可結合time step的概念來塑造仿真延時。每個time step中可以發生很多活動。$strobe與$display任務類似。代替立即執行的是,$strobe任務是在當前仿真的time step的結尾執行的。它可以規避由於競爭冒險造成的不匹配的數據顯示。

$monitor任務是非常通用的命令。鑒於$displat、$write、$strobe任務是在一旦它們被執行的情況下才顯示文本,$monitor任務則是當其參數發生變化時即顯示文本。$monitor任務提供了簡單的富有彈性的方式來跟蹤仿真。比方說,我們可以在testbench中添加如下的代碼:

 

 

 

 

 

 

initial

begin

  $display("time   test_in0   test_in1   test_out");

  $monitot("%d       %b          %b         %b",

            $time, test_in0, test_in1, test_out);

end

 

Modelsim的控制面板中顯示的文本仿真結果如下(示例):

 

 

 

 

 

 

time   test_in0   test_in1   test_out

   0      00         00          1

 200      01         00          0

 400      01         11          0

. . .

 

文件I/O系統函數和任務

Veirlog提供一組用於訪問外部數據文件的函數和任務。文件可以通過$fopen和$fclose函數來打開和關閉。$fopen的語法為:

 

 

[mcd_names] = $fopen("[file_name]"); 

$fopen函數返回一個與文件相關的32位的多通道描述子。這個描述子我們可以認為是一個32位的標志,它代表一個文件(亦即一個通道)。最低位LSB保留,用以只是標准輸出(console)。當使用函數調用的文件被成功打開,則返回的描述子的值得某位會被置一。例如,0…0010表示打開第一個文件,0…0100表示打開第二個文件,依次類推。若函數的返回值為0,則表示文件未能成功打開。

一旦某個文件被打開,我們就可以向其內寫入數據。可用的四種顯示系統任務為:$fdisplay,$fwrite,$fstrobe和$fmonitor。這些任務的用法類似於先前的$display等,除了其第一參數為描述子以外。

 

 

$fdisplay([mcd_name], [format_string], ...); 

下面給出一個簡單的代碼片段。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

integer log_file, both_file;

localparam con_file =32'h0000_0001;// console

 

initialbegin

    log_file  = $fopenZ("my_log");

    if(log_file == 0)

        $display("Fail to open log file");// write console

    both_file = log_file | con_file;

    

    // write to both console and log_file

    $fdisplay(both_file,"Simulation started");

    . . .

    // write to log_file only

    $fdisplay(log_file, ...);

    . . .

    // write to

    $fdisplay(both_file,"Simulation ended");

    $fclose(log_file);

end

 

注意我們可以通過對多個描述子進行位運算來創建一個描述子,比方說both_file變量。當both_file被使用時,就可以同時對console和log_file進行操作。

有兩個任務可以從文件中載入數據,分別為:$readmemb,$readmemh。這些任務假設外置文件中存儲了memory-array的內容,然后讀出這些內容存到一個變量中。$readmemb,$readmemh所假設的文件格式分別為二進制和十六進制,相應地,它們的語法格式為:

 

 

 

$readmemb("[file_name]", [mem_variable]);

$readmemh("{file_name]", [mem_variable]);

 

下面的片段描述如何載入一個8x4的存儲陣列:

 

 

 

 

reg[3:0] v_mem [0:7];

. . .

$readmemb("vector.txt", v_mem);

 

vector.txt應該包含八個4bit的使用空格分割的二進制數據。

有了文件操作函數和任務,就可以使用外部文件來指定測試模型,以及記錄仿真結果。下面給出一個案例。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

`timescale1ns/1ns

moduleeq2_file_tb;

// signal declaration

reg[1:0] test_in0, test_in1;

wiretest_out;

integer log_file, console_file, out_file;

reg[3:0] v_mem [0:7];

integer i;

 

// instantiate the circuit under test

eq2_sop eq2_sop_inst (

    .a(test_in0),

    .b(test_in1),

    .aeqb(test_out)

);

 

initialbegin

    // setup output fil

    log_file = $fopen("eqlog.txt"

    if(!log_file) $fdisplay("Cannot open log file");

    console_file =32'h0000_0001;

    out_file = log_file | console_file;

    

    // read test vector

    #readmemb("vector.txt", v_mem);   

    // test generator iterating throught 8 pattens

    for(i=0; i<8; i=i+1)begin

        {test_in0, test_in1} = v_mem[i];

    end

    

    // stop simulation

    $fclose(log_file);

    $stop;

end

 

// text display

initialbegin

    $fdisplay(out_file,"   time    test_in0    test_in1    test_out");

    $fdisplay(out_file,"           (a)         (b)         (aeqb)");

    $fmoitor(out_file,"    d    %b          %b          %b",

        $time, test_in0, test_in1, test_out);

end

 

endmodule

 

指定的test pattern為4位二進制格式,存儲在vector.txt中。文件內容為:

 

1

2

3

4

5

6

7

8

00_00

01_00

01_11

10_10

10_00

11_11

11_01

00_10

 

注意:“_”只起連接數字,分隔開容易區分位數的作用,和verilog其他地方的用法一致。

上面的文件被載入到二維的v_mem變量中。仿真的結果被寫入console和log_file中。log_file的內容為:

 

 

 

 

 

 

 

 

 

 

 

time    test_in0    test_in1    test_out

        (a)         (b)         (aeqb)

   0    00          00          1

 200    01          00          0

 400    01          11          0

 600    10          10          1

 800    10          11          0

1000    11          00          1

1200    11          01          0

1400    00          10          0

 

log_file為一般的文本文件,可使用其他文本編輯器編輯。

9 用戶自定義函數和任務

待續

 

轉載自:http://blog.sina.com.cn/s/blog_78699cbf01016mvt.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM