本文首發於微信公眾號「花螞蟻」,想要學習FPGA及Verilog的同學可以關注一下。

Verilog HDL語言中共有以下一些系統函數和任務:

$bitstoreal, $rtoi, $display, $setup, $finish, $skew, $hold,

$setuphold, $itor, $strobe, $period, $time, $printtimescale,

$timefoemat, $realtime, $width, $real tobits, $write, $recovery,

在Verilog HDL語言中每個系統函數和任務前面都用一個標識符$來加以確認。這些系統函數和任務提供了非常強大的功能。下面對一些常用的系統函數和任務逐一加以介紹。

注意:對於初學者來說任務函數和任務不是首要必須掌握的,可以以後慢慢理解。

1.$display和$write任務

格式:

$display(p1,p2,....pn);
$write(p1,p2,....pn);

這兩個函數和系統任務的作用是用來輸出信息,即將參數p2到pn按參數p1給定的格式輸出。參數p1通常稱為「格式控制」,參數p2至pn通常稱為「輸出表列」。

這兩個任務的作用基本相同。$display自動地在輸出後進行換行,$write則不是這樣。如果想在一行里輸出多個信息,可以使用$write。在$display和$write中,其輸出格式控制是用雙引號括起來的字元串,它包括兩種信息:

  • 格式說明,由"%"和格式字元組成。它的作用是將輸出的數據轉換成指定的格式輸出。格式說明總是由「%」字元開始的。對於不同類型的數據用不同的格式輸出。下表中給出了常用的幾種輸出格式。
  • 普通字元,即需要原樣輸出的字元。其中一些特殊的字元可以通過下表中的轉換序列來輸出。下面表中的字元形式用于格式字元串參數中,用來顯示特殊的字元。

在$display和$write的參數列表中,其「輸出表列」是需要輸出的一些數據,可以是表達式。下面舉幾個例子說明一下。

[例1]:

module disp;
initial
begin
$display("\ %%
"123
");
end
endmodule

輸出結果為

\%
"S

從上面的這個例子中可以看到一些特殊字元的輸出形式(八進位數123就是字元S)。

[例2]:

module disp;
reg[31:0] rval;
pulldown(pd);
initial
begin
rval=101;
$display("rval=%h hex %d decimal", rval, rval);
$display("rval=%o otal %b binary", rval, rval);
$display("rval has %c ascii character value",rval);
$display("pd strength value is %v",pd);
$display("current scope is %m");
$display("%s is ascii value for 101",101);
$display("simulation time is %t",$time);
end
endmodule

其輸出結果為:

rval=00000065 hex 101 decimal
rval=00000000145 octal 00000000000000000000000001100101 binary
rval has e ascii character value
pd strength value is StX
current scope is disp
e is ascii value for 101
simulation time is 0

輸出數據的顯示寬度

在$display中,輸出列表中數據的顯示寬度是自動按照輸出格式進行調整的。這樣在顯示輸出數據時,在經過格式轉換以後,總是用表達式的最大可能值所佔的位數來顯示錶達式的當前值。

在用十進位數格式輸出時,輸出結果前面的0值用空格來代替。對於其它進位,輸出結果前面的0仍然顯示出來。例如對於一個值的位寬為12位的表達式,如按照十六進位數輸出,則輸出結果佔3個字元的位置,如按照十進位數輸出,則輸出結果佔4個字元的位置。

這是因為這個表達式的最大可能值為FFF(十六進位)、4095(十進位)。可以通過在%和表示進位的字元中間插入一個0自動調整顯示輸出數據寬度的方式。見下例:

$display("d=%0h a=%0h",data,addr);

這樣在顯示輸出數據時,在經過格式轉換以後,總是用最少的位數來顯示錶達式的當前值。下面舉例說明:

[例3]:

module printval;
reg[11:0]r1;
initial
begin
r1=10;
$display("Printing with maximum size=%d=%h",r1,r1);
$display("Printing with minimum size=%0d=%0h",r1,r1);
end
endmodule

輸出結果為:

printing with maximum size=10=00a:
printing with minimum size=10=a;

如果輸出列表中表達式的值包含有不確定的值或高阻值,其結果輸出遵循以下規則:

(1).在輸出格式為十進位的情況下:

  • · 如果表達式值的所有位均為不定值,則輸出結果為小寫的x。
  • · 如果表達式值的所有位均為高阻值,則輸出結果為小寫的z。
  • · 如果表達式值的部分位為不定值,則輸出結果為大寫的X。
  • · 如果表達式值的部分位為高阻值,則輸出結果為大寫的Z。

(2).在輸出格式為十六進位和八進位的情況下:

  • · 每4位二進位數為一組代表一位十六進位數,每3位二進位數為一組代表一位八進位數。
  • · 如果表達式值相對應的某進位數的所有位均為不定值,則該位進位數的輸出的結果為小寫的x。
  • · 如果表達式值相對應的某進位數的所有位均為高阻值,則該位進位數的輸出結果為小寫的z。
  • · 如果表達式值相對應的某進位數的部分位為不定值,則該位進位數輸出結果為大寫的X。
  • · 如果表達式值相對應的某進位數的部分位為高阻值,則該位進位數輸出結果為大寫的Z。

對於二進位輸出格式,表達式值的每一位的輸出結果為0、1、x、z。下面舉例說明:

語句輸出結果:

$display("%d", 1bx); 輸出結果為:x
$display("%h", 14bx0_1010); 輸出結果為:xxXa
$display("%h %o",12b001x_xx10_1x01,12b001_xxx_101_x01); 輸出結果為:XXX 1x5X

注意:因為$write在輸出時不換行,要注意它的使用。可以在$write中加入換行符
,以確保明確的輸出顯示格式。

2.系統任務$monitor

格式:

$monitor(p1,p2,.....pn);
$monitor;
$monitoron;
$monitoroff;

任務$monitor提供了監控和輸出參數列表中的表達式或變數值的功能。其參數列表中輸出控制格式字元串和輸出表列的規則和$display中的一樣。

當啟動一個帶有一個或多個參數的$monitor任務時,模擬器則建立一個處理機制,使得每當參數列表中變數或表達式的值發生變化時,整個參數列表中變數或表達式的值都將輸出顯示。

如果同一時刻,兩個或多個參數的值發生變化,則在該時刻只輸出顯示一次。但在$monitor中,參數可以是$time系統函數。這樣參數列表中變數或表達式的值同時發生變化的時刻可以通過標明同一時刻的多行輸出來顯示。如:

$monitor($time,,"rxd=%b txd=%b",rxd,txd);

在$display中也可以這樣使用。注意在上面的語句中,「,,"代表一個空參數。空參數在輸出時顯示為空格。

$monitoron和$monitoroff任務的作用是通過打開和關閉監控標誌來控制監控任務monitor的啟動和停止,這樣使得程序員可以很容易的控制$monitor何時發生。

其中$monitoroff任務用於關閉監控標誌,停止監控任務$monitor,$monitoron則用於打開監控標誌,啟動監控任務$monitor。通常在通過調用$monitoron啟動$monitor時,不管$monitor參數列表中的值是否發生變化,總是立刻輸出顯示當前時刻參數列表中的值,這用於在監控的初始時刻設定初始比較值。

在預設情況下,控制標誌在模擬的起始時刻就已經打開了。在多模塊調試的情況下,許多模塊中都調用了$monitor,因為任何時刻只能有一個$monitor起作用,因此需配合$monitoron與$monitoroff使用,把需要監視的模塊用$monitoron打開,在監視完畢後及時用$monitoroff關閉,以便把$monitor 讓給其他模塊使用。

$monitor與$display的不同處還在於$monitor往往在initial塊中調用,只要不調用$monitoroff,$monitor便不間斷地對所設定的信號進行監視。

3.時間度量系統函數$time

在Verilog HDL中有兩種類型的時間系統函數:$time和$realtime。用這兩個時間系統函數可以得到當前的模擬時刻。

系統函數$time

$time可以返回一個64比特的整數來表示的當前模擬時刻值。該時刻是以模塊的模擬時間尺度為基準的。下面舉例說明。

[例1]:

`timescale 10ns/1ns
module test;
reg set;
parameter p=1.6;
initial
begin
$monitor($time,,"set=",set);
#p set=0;
#p set=1;
end
endmodule

輸出結果為:

0 set=x
2 set=0
3 set=1

在這個例子中,模塊test想在時刻為16ns時設置寄存器set為0,在時刻為32ns時設置寄存器set為1。但是由$time記錄的set變化時刻卻和預想的不一樣。這是由下面兩個原因引起的:

1) $time顯示時刻受時間尺度比例的影響。在上面的例子中,時間尺度是10ns,因為$time輸出的時刻總是時間尺度的倍數,這樣將16ns和32ns輸出為1.6和3.2。

2) 因為$time總是輸出整數,所以在將經過尺度比例變換的數字輸出時,要先進行取整。在上面的例子中,1.6和3.2經取整後為2和3輸出。注意:時間的精確度並不影響數字的取整。

$realtime系統函數

$realtime和$time的作用是一樣的,只是$realtime返回的時間數字是一個實型數,該數字也是以時間尺度為基準的。下面舉例說明:

[例2]:

`timescale10ns/1ns
module test;
reg set;
parameter p=1.55;
initial
begin
$monitor($realtime,,"set=",set);
#p set=0;
#p set=1;
end
endmodule

輸出結果為:

0 set=x
1.6 set=0
3.2 set=1

從上面的例子可以看出,$realtime將模擬時刻經過尺度變換以後即輸出,不需進行取整操作。所以$realtime返回的時刻是實型數。

4.系統任務$finish

格式:

$finish;
$finish(n);

系統任務$finish的作用是退出模擬器,返回主操作系統,也就是結束模擬過程。任務$finish可以帶參數,根據參數的值輸出不同的特徵信息。如果不帶參數,默認$finish的參數值為1。下面給出了對於不同的參數值,系統輸出的特徵信息:

  • 不輸出任何信息
  • 輸出當前模擬時刻和位置
  • 輸出當前模擬時刻,位置和在模擬過程中所用memory及CPU時間的統計

5.系統任務$stop

格式:

$stop;
$stop(n);

$stop任務的作用是把EDA工具(例如模擬器)置成暫停模式,在模擬環境下給出一個互動式的命令提示符,將控制權交給用戶。這個任務可以帶有參數表達式。根據參數值(0,1或2)的不同,輸出不同的信息。參數值越大,輸出的信息越多。

6.系統任務$readmemb和$readmemh

在Verilog HDL程序中有兩個系統任務$readmemb和$readmemh用來從文件中讀取數據到存貯器中。這兩個系統任務可以在模擬的任何時刻被執行使用,其使用格式共有以下六種:

1) $readmemb("<數據文件名>",<存貯器名>);

2) $readmemb("<數據文件名>",<存貯器名>,<起始地址>);

3) $readmemb("<數據文件名>",<存貯器名>,<起始地址>,<結束地址>);

4) $readmemh("<數據文件名>",<存貯器名>);

5) $readmemh("<數據文件名>",<存貯器名>,<起始地址>);

6) $readmemh("<數據文件名>",<存貯器名>,<起始地址>,<結束地址>);

在這兩個系統任務中,被讀取的數據文件的內容只能包含:空白位置(空格,換行,製表格(tab)和form-feeds),注釋行(//形式的和/*...*/形式的都允許),二進位或十六進位的數字。

數字中不能包含位寬說明和格式說明,對於$readmemb系統任務,每個數字必須是二進位數字,對於$readmemh系統任務,每個數字必須是十六進位數字。

數字中不定值x或X,高阻值z或Z,和下劃線(_)的使用方法及代表的意義與一般Verilog HDL程序中的用法及意義是一樣的。另外數字必須用空白位置或注釋行來分隔開。

在下面的討論中,地址一詞指對存貯器(memory)建模的數組的定址指針。當數據文件被讀取時,每一個被讀取的數字都被存放到地址連續的存貯器單元中去。存貯器單元的存放地址範圍由系統任務聲明語句中的起始地址和結束地址來說明,每個數據的存放地址在數據文件中進行說明。當地址出現在數據文件中,其格式為字元「@」後跟上十六進位數。如:

@hh...h

對於這個十六進位的地址數中,允許大寫和小寫的數字。在字元「@」和數字之間不允許存在空白位置。可以在數據文件里出現多個地址。當系統任務遇到一個地址說明時,系統任務將該地址後的數據存放到存貯器中相應的地址單元中去。

對於上面六種系統任務格式,需補充說明以下五點:

1) 如果系統任務聲明語句中和數據文件里都沒有進行地址說明,則預設的存放起始地址為該存貯器定義語句中的起始地址。數據文件里的數據被連續存放到該存貯器中,直到該存貯器單元存滿為止或數據文件里的數據存完。

2) 如果系統任務中說明了存放的起始地址,沒有說明存放的結束地址,則數據從起始地址開始存放,存放到該存貯器定義語句中的結束地址為止。

3) 如果在系統任務聲明語句中,起始地址和結束地址都進行了說明,則數據文件里的數據按該起始地址開始存放到存貯器單元中,直到該結束地址,而不考慮該存貯器的定義語句中的起始地址和結束地址。

4) 如果地址信息在系統任務和數據文件里都進行了說明,那麼數據文件里的地址必須在系統任務中地址參數聲明的範圍之內。否則將提示錯誤信息,並且裝載數據到存貯器中的操作被中斷。

5) 如果數據文件里的數據個數和系統任務中起始地址及結束地址暗示的數據個數不同的話,也要提示錯誤信息。

下面舉例說明:

先定義一個有256個地址的位元組存貯器 mem:

reg[7:0] mem[1:256];

下面給出的系統任務以各自不同的方式裝載數據到存貯器mem中。

initial $readmemh("mem.data",mem);
initial $readmemh("mem.data",mem,16);
initial $readmemh("mem.data",mem,128,1);

第一條語句在模擬時刻為0時,將裝載數據到以地址是1的存貯器單元為起始存放單元的存貯器中去。

第二條語句將裝載數據到以單元地址是16的存貯器單元為起始存放單元的存貯器中去,一直到地址是256的單元為止。

第三條語句將從地址是128的單元開始裝載數據,一直到地址為1的單元。在第三種情況中,當裝載完畢,系統要檢查在數據文件里是否有128個數據,如果沒有,系統將提示錯誤信息。

7.系統任務 $random

這個系統函數提供了一個產生隨機數的手段。當函數被調用時返回一個32bit的隨機數。它是一個帶符號的整形數。

$random一般的用法是:$ramdom % b ,其中 b>0.它給出了一個範圍在(-b+1):(b-1)中的隨機數。下面給出一個產生隨機數的例子:

reg[23:0] rand;
rand = $random % 60;

上面的例子給出了一個範圍在-59到59之間的隨機數,

下面的例子通過位並接操作產生一個值在0到59之間的數。

reg[23:0] rand;
rand = {$random} % 60;

利用這個系統函數可以產生隨機脈衝序列或寬度隨機的脈衝序列,以用於電路的測試。

下面例子中的Verilog HDL模塊可以產生寬度隨機的隨機脈衝序列的測試信號源,在電路模塊的設計模擬時非常有用。同學們可以根據測試的需要,模仿下例,靈活使用$random系統函數編製出與實際情況類似的隨機脈衝序列。

[例]

`timescale 1ns/1ns
module random_pulse( dout );
output [9:0] dout;
reg dout;
integer delay1,delay2,k;

initial
begin
#10 dout=0;
for (k=0; k< 100; k=k+1)
begin
delay1 = 20 * ( {$random} % 6);
// delay1 在0到100ns間變化
delay2 = 20 * ( 1 + {$random} % 3);
// delay2 在20到60ns間變化
#delay1 dout = 1 << ({$random} %10);
//dout的0--9位中隨機出現1,並出現的時間在0-100ns間變化
#delay2 dout = 0;
//脈衝的寬度在在20到60ns間變化
end
end
endmodule

本文首發於微信公眾號「花螞蟻」,想要學習FPGA及Verilog的同學可以關注一下。


推薦閱讀:
相关文章