內容
與可綜合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表達式
· 循環表達式
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