1. 程式人生 > >system verilog中不可不小心的陷阱

system verilog中不可不小心的陷阱

  1. 任務和函式(task and function)
    任務可以呼叫函式以及其他任務,但是函式不可以呼叫任務。在verilog中函式必須有返回值,但是在system verilog中擴充套件了函式功能,其可以返回空,即void。同時task引數列表可以表示為類似C的形式,用括號括起來,如:
task test(input logic a, input reg[5:0]b, output logic c);

任務重可以有時序控制,但是函式不准許有時序控制。同時需要注意的坑是任務必須在所有語句執行完成後才會返回數值.
可以使用ref來傳遞引數,這有點類似C中指標傳參,相當於將引數地址傳遞給任務,任務修改是可見的。但是在system verilog手冊中要求ref只能用於動態任務中,即要宣告任務為automatic。

task automatic test(input logic a, ref reg[5:0]b, output logic c);

不宣告動態的任務或者函式,其預設為靜態即內部資料分配給記憶體中固定儲存區。如果任務被多次呼叫,那麼多個程式共享靜態儲存區很容易發生混亂。宣告為動態的任務和函式其內部變數自動為動態,但是可以通過static宣告為靜態的。
動態任務中的動態變數如果去讀取檔案,則不會獲取檔案資料,如:

task automatic test(input logic a, ref reg[5:0]b, output logic c);

    integer data;
fp=$fopen(filename, "rb") $fread(data, fp); endtask

data是沒有資料的。如果將data宣告為static,那麼就可以獲得資料了

task automatic test(input logic a, ref reg[5:0]b, output logic c);

    static integer data;
    fp=$fopen(filename, "rb")
    $fread(data, fp);
endtask

2.檔案讀寫
在寫激勵的時候,很多時候要用到資料的快取,由於採用RAM比較麻煩,還是利用檔案來儲存資料更為方便。用於讀寫資料的系統函式有很多,這裡僅僅介紹我最常用的:$

fread和$fwrite。
$fread函式主要用於讀取二進位制檔案,它不會對檔案的儲存內容進行任何格式轉化。呼叫形式為:
code=$fread(integral_var, fd);
code=$fread(mem, fd, start, count);
變數可以是任何壓縮變數或者是儲存變數,比如logic[7:0] a[256];start和count是可選的,表示開始地址和讀取數量。fd是檔案指標。code是返回值,如果讀取失敗會返回0,如果成功則返回讀取位元組個數。舉一個讀取檔案的例子:

fd=$fopen(FILENAME, "rb");
if(fd==-1)begin
    $display("failed to open file: %s", FILENAME);
    return;
end
seek_code=$fseek(fd, address_offset, 0);//設定讀取起始地址
read_code=$fread(data_array, fd);//讀取資料到陣列中
$fclose(fd);

system verilog中對檔案讀取比較簡單,但是想要寫入二進位制,目前我沒有找到直接的方式。使用$fwrite函式,會轉化為ASCII形式儲存到檔案中。從手冊中瞭解到可以設定寫入方式:
$fwrite(fd, “%u”, data);
u表示是以二進位制形式寫入,但是vivado模擬器不支援。不知道其它模擬器。於是改用字元形式寫入:
$fwrite(fd, “%c”, data);
在vivado下測試成功。但是聽說別人使用其它模擬器時,對於0會被判定為NULL,不會寫入。

3.vivado模擬
讀寫檔案需要將檔案加入工程,即點選新增檔案->新增模擬檔案->選擇待讀寫檔案->勾選include all design sources for simulation。
還有一個很大的坑,在initial或者always塊中迴圈輸出外部資料,總是會輸出資料前一個值或者第一個資料輸出兩次。比如對於:

module initial_tb;

  reg clk;
  reg [7:0] data;
  reg valid;

  initial begin
    clk=1'b0;
    data=8'h0;
    valid=1'b0;
    repeat(5) @(posedge clk);
    valid=1'b1;
  end


  always begin

     begin
       if(valid)
          $display("data is: %d", data);
       @(posedge clk);
    end
  end

  always @(posedge clk)
  begin
    if(valid)
      data++;
  end

  always #20 clk=~clk;
endmodule

這個會打印出0,1,2…..
如果將valid訊號延遲一個時鐘,即增加

 always @(posedge clk)begin
    valid_r <=  valid;
  end

用valid_r來判定。則打印出正確資料,1,2,3…,即
這裡寫圖片描述
但是valid_r和valid在同一個週期下。
因為valid有效和data++是同時執行的,之間沒有延時,這樣就會存在競爭。如果valid有效後,但是data++沒有檢測到;
如果將valid訊號有效延時2ns,#2 valid=1’b1;列印資料就正確了。或者做#0時延,相當於在阻塞此程序,等待在同等時間條件下其它程序執行完畢。此時,在always快中,valid_r檢測到valid為0。
其實在硬體電路中,valid是用於驅動data++的,也要保證滿足setup 和holdup時間才行。
4.執行緒排程
system verilog提供了3種建立執行緒的方法:
(1)fork…join:塊內語句同時執行;
(2)fork…join_any:塊內語句有一個執行,則join_any後的語句(父程序)和塊內程序(子程序)同時執行;
(3)fork…join_none:子程序和父程序同時執行。
對於以下語句:

initial begin
    for(int i=0;i<3;i++)
        fork
            $write(i);
        join_none
            #0 $display("\n");

end

列印結果為3,3,3…。因為#0時延阻塞了當前所有程序的排程。等到所有程序建立完成才執行。