Verilog 提供了很多可以對文件進行操作的系統(tǒng)任務。經常使用的系統(tǒng)任務主要包括:
$fopen
?, ?$fclose
?, ?$ferror
?$fdisplay
?, ?$fwrite
?, ?$fstrobe
?, ?$fmonitor
?$sformat
?, ?$swrite
?$fgetc
?, ?$fgets
?, ?$fscanf
?, ?$fread
?$fseek
?, ?$ftell
?, ?$feof
?, ?$frewind
?$readmemh
?, ?$readmemb
?使用文件操作任務(尤其注意 ?$sforamt
?, ?$gets
?, ?$sscanf
? 等)對文件進行操作時,需要根據文件性質和變量內容確定使用哪一種系統(tǒng)任務,并保證參數及讀寫變量類型與文件內容的一致性,不要將字符串類型和多進制類型相混淆。
系統(tǒng)任務 | 調用格式 | 任務描述 |
---|---|---|
文件打開 | fd = $fopen("fname", mode) ; | fname 為打開文件的名字
fd 為返回的 32bit 文件描述符 --- 正確打開時,fd 為非零值 --- 打開出錯時,fd為零值 mode 用于指定文件打開的方式 |
文件關閉 | $fclose(fd) ; | 關閉 fd 描述的對應文件 |
文件錯誤 | err = $ferror(fd, str) ; | 正常打開文件時:
--- err 與 str 均為零值, 打開文件出錯時: --- err 返回非零值表示錯誤 --- str 返回非零值存儲錯誤類型 --- 官方建議 str 長度為 640bit 位寬 |
舉例代碼如下:
//open/close file
integer fd1, fd2 ;
integer err1, err2 ;
reg [320:0] str1, str2 ; //錯誤類型的變量也可以為可支持的 string 類型
initial begin
//existing file
fd1 = $fopen("./DATA_RD.HEX", "r"); //打開存在的文件
err1 = $ferror(fd1, str1);
$display("File1 descriptor is: %h.", fd1 );//非零值
$display("Error1 number is: %h.", err1 ); //0
$display("Error2 info is: %s.", str1 ); //0
$fclose(fd1);
//not existing file
fd2 = $fopen("../../FILE_NOEXIST.HEX", "r");//打開的文件不存在
err2 = $ferror(fd2, str2);
$display("File2 descriptor is: %h.", fd2 ); //0
$display("Error2 number is: %h.", err2 ); //非零值
$display("Error2 info is: %s.", str2 ); //非零值
$fclose(fd2);
end
文件打開方式 mode 類型及其描述如下:
r | 只讀打開一個文本文件,只允許讀數據。 |
---|---|
w | 只寫打開一個文本文件,只允許寫數據。如果文件存在,則原文件內容會被刪除。如果文件不存在,則創(chuàng)建新文件。 |
a | 追加打開一個文本文件,并在文件末尾寫數據。如果文件如果文件不存在,則創(chuàng)建新文件。 |
rb | 只讀打開一個二進制文件,只允許讀數據。 |
wb | 只寫打開或建立一個二進制文件,只允許寫數據。 |
ab | 追加打開一個二進制文件,并在文件末尾寫數據。 |
r+ | 讀寫打開一個文本文件,允許讀和寫 |
w+ | 讀寫打開或建立一個文本文件,允許讀寫。如果文件存在,則原文件內容會被刪除。如果文件不存在,則創(chuàng)建新文件。 |
a+ | 讀寫打開一個文本文件,允許讀和寫。如果文件不存在,則創(chuàng)建新文件。讀取文件會從文件起始地址的開始,寫入只能是追加模式。 |
rb+ | 讀寫打開一個二進制文本文件,功能與 "r+" 類似。 |
wb+ | 讀寫打開或建立一個二進制文本文件,功能與 "w+" 類似。 |
ab+ | 讀寫打開一個二進制文本文件,功能與 "a+" 類似。 |
寫文件的系統(tǒng)任務主要包括:?$fdisplay
?, ?$fwrite
?, ?$fstrobe
?, ?$fmonitor
?,以及它們對應的自帶格式的系統(tǒng)任務 ?$fdisplayb
?, ?$fdisplayh
?, ?$fdisplayo
? 等。
調用格式 | 任務描述 |
---|---|
$fdisplay(fd, arguments) ; | 按順序或條件寫文件,自動換行 |
$fwrite(fd, arguments) ; | 按順序或條件寫文件,不自動換行 |
$fstrobe(fd, arguments) ; | 語句執(zhí)行完畢后選通寫文件 |
$fmonitor(fd, arguments) ; | 只要數據有變化就寫文件 |
相對于標準顯示任務 ?$display
?, ?$write
?, ?$strobe
?, ?$monitor
?,寫文件系統(tǒng)任務除了用法格式上需要多指定文件描述符 fd,其余打印條件、時刻特性等均與其對應的顯示任務保持一致。
利用追加寫的方式,對文件進行寫操作的舉例如下:
//(2) write file
integer fd ;
integer err, str ;
initial begin
fd = $fopen("./DATA_RD.HEX", "a+"); //末尾追加的方式打開
err = $ferror(fd, str);
if (!err) begin
$fdisplay(fd, "New data1: %h", fd) ;
$fdisplay(fd, "New data2: %h", str) ;
$fdisplay(fd, "New data3: %h", err) ;
//$write(fd, "New data3: %h", err) ; //最后一行不換行打印
end
$fclose(fd);
end
打開文件 DATA_RD.HEX,則可以看到文件末端新增了 3 行數據。
Verilog 還提供了往字符串里寫數據的系統(tǒng)任務 ?$swrite
? 和 ?$sformat
?。
調用格式 | 任務描述 |
---|---|
$swrite(reg, list_of_arguments) ; | 按順序或條件寫字符串到變量 reg 中 |
len = $sformat(reg, format_str, arguments) ; | 按格式 format_str 寫字符串到變量 reg 中
格式與 $display 指定格式時一致 不建議省略第二個參數 format_str 可返回字符串長度 len |
?$sformat
? 第二個參數 format 為字符串類型,一般建議不要省略。該參數指定了輸入變量的類型,指定類型時也可以包含其他字符串信息,類型種類及用法可參考顯示函數 ?$display
?。該參數也可以為寄存器類型,但要求存儲的數據為正常的字符串數據。
寫字符串代碼舉例如下:
//(3) write string
reg [299:0] str_swrite, str_sformat;
reg [63:0] str_buf ;
integer len, age ;
initial begin
#20 ;
str_buf = "w3cschool" ;
age = 9 ;
//$swrite 指定格式寫包含變量的字符串
$swrite(str_swrite, "%s age is %d", str_buf, age) ;
$display("%s", str_swrite);
//$swrite 直接寫不含有變量的字符串
$swrite(str_swrite, "years ", "old.") ;
$display("%s", str_swrite);
//$swrite 不指定格式寫包含變量的字符串,不建議
$swrite(str_swrite, age) ;
$display("$swrite err test: %d", str_swrite);
$display();
//$sformat 指定格式寫包含變量的字符串
$sformat(str_sformat, "I have learnt in %s", str_buf) ;
$display("%s", str_sformat);
//$sformat 直接寫不含有變量的字符串,并獲取字符串長度
len = $sformat(str_sformat, "for 4 years!") ;
$display("%s", str_sformat);
$display("$sformat len: %d", len);
//$sformat 直接一次寫多個不含有變量的字符串,不建議
$sformat(str_sformat, "for", "4", "years!") ;
$display("$sformat err test: %s", str_sformat);
end
忽略打印信息的空格,調試信息輸出如下:
由此可知,?$sformat
? 與 ?$swrite
? 用法可以一致,例如 ?$sformat
? 可采用指定格式的寫字符串,或只寫一次不含變量的字符串。此時 ?$sformat
? 相當于在第二個參數中未指定變量類型,所以第三個參數應該忽略不寫。
?$swrite
? 還可以一次寫多個不包含變量的字符串,而 ?$sformat
? 不允許如此調用。
也建議,使用 ?$swrite
? 寫包含變量的字符串時要指定變量類型,否則結果可能不可預測。
系統(tǒng)任務 | 調用格式及說明 |
---|---|
按字符讀文件 | c = $fgetc( fd ) ;
按字符格式將 fd 數據輸出給變量 c,c 位寬最少為 8 讀取錯誤時 c 值為 EOF(-1),可以用 $ferror 檢查錯誤類型 |
按字符寫緩沖區(qū) | code = $ungetc(c, fd ) ;
向文件 fd 緩沖區(qū)寫字符 c c 值在下次調用 $fgetc 時返回,文件 fd 自身內容不會發(fā)生變化 正常寫緩沖時返回值 code 為 0,發(fā)生錯誤時返回值 code 為 EOF |
按行讀文件 | code = $fgets(str, fd)
按字符連續(xù)讀,直至變量 str 被填滿,或一行內容讀取完畢,或文件結束 正常讀取時返回值 code 為讀取行數(次數),發(fā)生錯誤時 code 為 0 |
按格式讀文件 | code = $fscanf(fd, format, args) ;
按格式 format 將文件 fd 中的數據讀取到變量 args 中 format 可參考 $display 指定格式說明 讀取一次的停止條件為空格或換行 讀取發(fā)生錯誤時返回值 code 為 0 |
按格式讀字符串 | code = $sscanf(str, format, args) ;
按格式 format 將字符串型變量 str 讀取到變量 args 中 調用格式方法和 $fscanf 一致 |
按二進制讀文件 | code = $fread(store, fd, start, count) ;
按二進制數據流格式將數據從文件 fd 讀取到數組或寄存器變量 store 中 start 為文件起始地址,count 為讀取長度 若 start/count 未指定,數據會全部填充至變量 store 中 若 store 為寄存器類型,則 start/count 參數無效,store 變量填充滿一次數據后便會停止讀取 |
以"文件寫入"仿真中的文件 DATA_RD.HEX 為讀取的參考文件,進行舉例,該文件內容如下。
c0dec0de
5555aaaa
12345678
aaaa5555
New data1: 80000003
New data2: 00000000
New data3: 00000000
//(4.1) read char
integer i ;
reg [31:0] char_buf ;
initial begin
#30 ;
fd = $fopen("DATA_RD.HEX", "r");
$write("Read char: ");
err = $ferror(fd, str);
if (!err) begin
for (i=0; i<13; i++) begin
char_buf[7:0] = $fgetc(fd) ; //按單個字符讀取
$write("%c", char_buf[7:0]) ; //不換行逐次打印單個字符
end
$write(".\n") ;
end
$ungetc("1", fd) ; //連續(xù)寫3次文件緩沖區(qū)
$ungetc("2", fd) ;
$ungetc("3", fd) ;
char_buf[7:0] = $fgetc(fd) ; //read 3
char_buf[15:8] = $fgetc(fd) ; //read 2
char_buf[23:16] = $fgetc(fd) ; //read 1,read buffer end
char_buf[31:24] = $fgetc(fd) ; //read a
$display("Read char after $ungetc: %s", char_buf);
$fclose(fd);
end
仿真結果如下。
由圖可知,?$fgetc
? 讀取的 13 個字符正確,讀取字符包括了換行符。
?$ungetc
? 向文件緩沖區(qū)寫字符數據后,再用 ?$fgetc
? 可讀取文件緩沖區(qū)的字符數據。讀寫遵循先寫后出(FILO, First in Last out)原則,相當于壓棧。字符數據先寫"123"時,讀出數據為"321"。
文件緩沖區(qū)讀取完畢后,再進行字符數據讀取時,讀出的數據依然緊隨上一次文件讀取的位置,即 log 中"a123"中的字符"a"。
此過程中,文件 DATA_RD.HEX 內容一直沒有改變。
//(4.2) read line
integer code ;
reg [99:0] line_buf [9:0] ;
initial begin
#31 ;
fd = $fopen("DATA_RD.HEX", "r");
err = $ferror(fd, str);
if (!err) begin
for (i=0; i<6; i++) begin //按字符串格式逐行讀取
code = $fgets(line_buf[i], fd) ; //末尾含"\n",將打印2行
$display("Get line data%d: %s", i, line_buf[i]) ;
end
end
//十六進制顯示,將顯示對應的 ASCIII 碼字
$display("Show hex line data%d: %h", 2, line_buf[2]) ;
$display("Show hex line data%d: %h", 4, line_buf[4]) ;
$fclose(fd) ;
end
仿真結果如下。
前 4 行數據按照字符串類型讀取和顯示,結果正常。
讀取文件第 5 行數據時,由于變量 line_buf 位寬 100 的限制,文件內容"New data1: 80000003 "需要分 2 次才能完成讀取。
因為每一行末尾包含換行符"\n",所以使用 ?$display
? 函數打印時,會多出一行空行。
按照字符串型讀取、并對數據進行十六進制顯示時,并不能直觀的顯示出文件對應的數據內容。例如第二行內容并沒有顯示"12345678,"而是顯示其對應的 ASCII 碼。所以 $fgets 任務讀取時是按照字符串類型讀取的,這里需要注意。
//(4.3) $fscanf/$sscanf
reg [31:0] data_buf [9:0] ;
reg [63:0] string_buf [9:0] ;
reg [31:0] data_get ;
reg [63:0] data_test ;
initial begin
#32 ;
fd = $fopen("DATA_RD.HEX", "r");
err = $ferror(fd, str);
if (!err) begin
for (i=0; i<4; i++) begin
//前4行數據按照十六進制讀取和顯示
code = $fscanf(fd, "%h", data_buf[i]);
$display("$fscanf read data%d: %h", i, data_buf[i]) ;
end
for (i=4; i<6; i++) begin
//后2行數據按照字符串類型讀取和顯示
code = $fscanf(fd, "%s", string_buf[i]);
$display("$fscanf read data%d: %s", i, string_buf[i]) ;
end
end
//(1) $sscanf 源變量 data_test 為字符串類型
data_test = "fedcba98" ;
code = $sscanf(data_test, "%h", data_get);
$display("$sscanf read data0: %h", data_get) ;
//(2) $sscanf: 將源變量 data_test 先轉為字符串變量
code = $sformat(data_test, "%h", data_buf[2]);
code = $sscanf(data_test, "%h", data_get);
//直接輸入十六進制變量是不建議的
//code = $sscanf(data_buf[2], "%h", data_get);
$display("$sscanf read data0: %h", data_get) ;
$fclose(fd) ;
end
仿真結果如下。
利用 ?$fscanf
? 對文件前 4 行內容按照十六進制讀取和顯示,后 2 行內容按照字符串型讀取和顯示,均正常。
利用 ?$sscanf
? 讀取源寄存器內容然后搬移到目的寄存器時,源寄存器中的內容應該為字符串型數據。
例如,利用 ?$sscanf
? 將十六進制的數據 data_buf[2] 搬移到寄存器變量 data_get 時,可以先利用寫字符串任務 ?$sformat
? 將源變量 data_buf[2] 的內容轉為字符串型,存放在變量 data_test 中。然后再利用 ?$sscanf
? 按照十六進制將 data_test 中的內容搬移到變量 data_get 中。此時按照十六進制格式打印變量 data_get 會顯示正常。
如果直接利用 ?$sscanf
? 將十六進制格式的數據 data_buf[2] 直接搬移到變量 data_get 中,則 data_get 中的內容將會是異常的。
偷偷告訴你,寄存器之間是可以直接賦值的?。?!
//(4.4) $fread
reg [71:0] bin_buf [3:0] ; //每行有8個字型數據和1個換行符
reg [143:0] bin_reg ;
initial begin
#40 ;
fd = $fopen("DATA_RD.HEX", "r");
err = $ferror(fd, str);
if (!err) begin
code = $fread(bin_buf, fd, 0, 4); //數組型讀取,讀取4次
$display("$fread read data %h", bin_buf[0]) ;//十六進制顯示
$display("$fread read data %h", bin_buf[1]) ;
$display("$fread read data %s", bin_buf[2]) ;//字符串顯示
$display("$fread read data %s", bin_buf[3]) ;
end
fd = $fopen("DATA_RD.HEX", "r");
code = $fread(bin_reg, fd); //單個寄存器讀取
$display("$fread read data %h", bin_reg) ;
$fclose(fd) ;
end
仿真結果如下。
?$fread
? 按二進制讀取文件時 ,起始地址和讀取長度都是設置數組型變量的參數。
如果存儲數據的變量類型是非數組的 reg 型,則只會進行一次讀取,直至 reg 型變量被填充完畢。
系統(tǒng)任務 | 調用格式 | 任務描述 |
---|---|---|
獲取文件位置 | pos = $ftell( fd ) ; | 返回文件當前位置距離文件首部的偏移量,初始地址為 0
偏移量按照字節(jié)為一單位(8bits) 配合 $fseek 使用 |
重定位 | code = $fseek(fd, offset, type) ; | 設置文件下一個輸入或輸出的位置
offset 為設置的偏移量 type 為偏移量的操作類型 --- 0: 設置位置到偏移地址 --- 1: 設置位置到當前位置加偏移量 --- 2: 設置位置到文件尾加偏移量,經常使用負數來表示文件尾向前的偏移量 |
無偏移重定位 | code = $rewind( fd ) ; | 等價于 $fseek( fd, 0, 0) ; |
判斷文件尾部 | code = $feof(fd) ; | 判讀是否到文件尾部
檢測到文件尾部時返回值為 1,否則為 0 |
文件 DATA_RD.HEX 內容可表示如下。
換行符"\n"為結束符,則文件大小為:4x9 + 3x20 = 96 byte。
文件定位測試代碼如下:
// file position
reg [31:0] data4 ; //寄存器變量長度為 4bytes
reg [199:0] str_long ;
integer pos ;
initial begin
#40 ;
fd = $fopen("DATA_RD.HEX", "r");
err = $ferror(fd, str);
if (!err) begin
//first read
code = $fscanf(fd, "%h", data4);//從0位置開始讀
pos = $ftell(fd); //讀8byte后位置為8,坐標為(0,8)
$display("Position after read: %d", pos) ;
$display("1st read data: %h", data4) ;
//type = 0
code = $fseek(fd, 4, 0) ; //從位置4、坐標(0,4)開始讀
code = $fscanf(fd, "%h", data4); //讀到換行符停止
pos = $ftell(fd); //讀4byte后位置為8,坐標為(0,8)
$display("type 0: current position: %d", pos) ;
$display("type 0: read data: %h", data4) ;
//type = 1
code = $fseek(fd, 4, 1) ; //從位置4+9=12、坐標(1,3)據開始讀
code = $fscanf(fd, "%h", data4); //讀到換行符停止
pos = $ftell(fd); //讀5byte后位置為17,坐標為(1,8)
$display("type 1: current position: %d", pos) ;
$display("type 1: read data: %h", data4) ;
//type = 2
code = $fseek(fd, -(96-31), 2) ; //從位置31、坐標(3,4)開始讀
code = $fscanf(fd, "%h", data4);
pos = $ftell(fd); //讀4byte后位置為35,坐標為(3,8)
$display("type 2: current position: %d", pos) ;
$display("type 2: read data: %h", data4) ;
//rewind read
code = $rewind(fd) ;//重新將文件指針的位置指向文件首部
pos = $ftell(fd); //此時位置為 0
$display("Position after $rewind: %d", pos) ;
//read all content of file
while (!$feof(fd)) begin
code = $fgets(str_long, fd);
$write("Read : %s", str_long) ;
end
$fclose(fd) ;
end
end
仿真結果如下。
由圖可知 log 末尾多打了一行數據,這是因為文件 DATA_RD.TXT 末尾還有一行空白行(換行操作之后的結果),系統(tǒng)任務 ?$feof
? 并不認為該空白行為文件尾部,所以返回值仍然為 0。但實際該行并沒有數據,所以讀取的數據具有不可控制性。
為消除文件最后一行數據中換行符的影響,可將"文件寫入"例子中最后一個寫文件系統(tǒng)任務 ?$fdisplay
? 替換為 ?$write
? 。
其余 log 結合代碼注釋可知仿真正確,這里不再做統(tǒng)一解釋。
系統(tǒng)任務 | 調用格式及說明 |
---|---|
加載十六進制文件 | $readmemh("fname", mem, start_addr, finish_addr) |
fname 為數據文件名字
mem 為數組型/存儲器型變量 start_addr、finish_addr 分別為起始地址和終止地址 start_addr、finish_addr 可以省略,此時加載數據的停止條件為存儲器變量 mem 被填充完畢,或文件讀取完畢 文件內容只應該有空白符(或換行、空格符)、二進制或十六進制數據 注釋用"http://"進行標注,數據間建議用換行符區(qū)分 |
|
加載二進制文件 | $readmemb("fname", mem, start_addr, finish_addr) |
用法格式同 $readmemb |
文件 DATA_WITHNOTE.HEX 內容如下,將此文件的內容加載到存儲器變量中。
舉例代碼如下:
//6 load mem
reg [31:0] mem_load [3:0] ;
initial begin
#50 ;
$readmemh("./DATA_WITHNOTE.HEX", mem_load);
$display("Read memory1: %h", mem_load[0]) ;
$display("Read memory2: %h", mem_load[1]) ;
$display("Read memory3: %h", mem_load[2]) ;
$display("Read memory4: %h", mem_load[3]) ;
end
仿真結果如下:
點擊這里下載源碼
更多建議: