SystemVerilog_線程


SV線程

語句塊

用來將多個語句組織在一起,使得他們在語法上如同一個語句。

  • 順序塊:語句置於關鍵字begin和end之間,塊中的語句以順序方式執行。
  • 並行塊:關鍵字fork和join/join_any/join_none之間的是並行塊語句,塊中的語句並行執行。

命名塊

給每個塊定義的標識名稱,將塊名加在begin或fork后面;

  • 可以定義塊內局部變量
  • 允許定義的塊被其他語句調用,如disable語句;

程序和模塊

  • 對於硬件的過程塊,他們之間的通信可以理解為不同邏輯、時序塊之間的通信或者同步,是通過信號的變化來完成
  • 從硬件實現角度看,verilog 通過always,initial過程語句塊和信號數據連接實現進程間通信
  • 可以將不同的module作為獨立的程序塊,他們之間的同步通過信號的變化(event觸發)、等待待定事件(時鍾周期)或者時間延時來完成
  • 從軟件思維理解硬件仿真,仿真中的各個模塊首先是獨立運行的線程
  • 模塊在仿真一開始便並行執行,除了每個線程會按照自身內部產生的事件來觸發過程語句塊之外,也同時依靠相鄰模塊間的信號變化來完成模塊之間的線程同步

線程

  • 線程是可以獨立運行的程序
  • 線程需要被觸發,可以結束或者不結束
  • 在module中的 initial 和 always ,都可以看成獨立的線程,他們在仿真 0 時刻開始,而選擇結束或者不結束
  • 硬件模型中由於都是always 語句塊,所以可以看成是多個獨立運行的線程,而這些線程會一直占用仿真資源
  • 軟件測試平台中的驗證環境都需要有initial 語句塊去創建,而在仿真過程中,驗證環境中的對象可以創建和銷毀,因此,軟件測試端的資源占用是動態的
  • 線程的執行軌跡呈樹狀結構,任何線程都有父線程
    • 當子線程終止時,父線程可以繼續執行 
    • 當父線程終止時,其所開辟的所有子線程都應當會終止 
  • 軟件環境中的initial  塊對語句有兩種方式:
    • begin ...  end:內部語句以順序方式執行
    • fork ... join:內部語句以並發方式執行

線程的控制

創建線程

fork...join/join_any/join_none:   能夠從它的每一個並行語句中產生並發進程。

  • join: 父進程阻塞到這個分支產生的所有進程結束;
  • join_any:  父進程阻塞到這個分支產生的任意一個進程結束;
  • join_none:    父進程會與其生成的所有子進程並發執行。在父進程執行一條阻塞語句或者結束之前,所生成的子進程不會啟動執行。

 wait fork

  • 在SV中,當程序中的initial 塊全部執行完畢,仿真器就退出了。
  • 如果需要等待fork 中的所有線程執行完畢再退出結束initial 塊,可以使用 wait fork 語句來等待所有子線程結束
initial begin: fork_join_none
  $display("@%0t, fork_join_none_thread entered", $time);
  fork: fork_join_none_thread
    thread(0, 10);   // 線程1
    thread(1, 20);   // 線程2
    thread(2, 30);   // 線程3
  join_none
  $display("@%0t, fork_join_none_thread exited", $time);
  wait fork;     // 等待所有線程退出
  $display("@%0t, fork_join_none_thread's all sub-threads finished", $time);
end

disable

  • 在使用fork ... join  或者fork ... join_none 以后,可以使用disable 來制定需要停止的線程
  • disable fork 可以停止從當前線程中衍生出來的所有子線程
  • 如果給某一個任務或者線程指明標號,那么當這個線程被調用多次以后,如果通過disable 去禁止這個線程標號,所有衍生的同名線程都將被禁止
initial begin: fork_join_any
  $display("@%0t, fork_join_any_thread entered", $time);
  fork: fork_join_any_thread
    thread(0, 10);
    thread(1, 20);
    thread(2, 30);
  join_any 
  $display("@%0t, fork_join_any_thread exited", $time);
  disable fork_join_any_thread;
  $display("@%0t, disabled fork_join_any_thread", $time);
end

線程間的通信:IPC

  •  測試平台中的所有賢臣都需要同步並交換數據
  • 一個線程等待另一個線程
  • 多個線程可能同時訪問同一個資源
  • 線程之間可能需要交換數據

event

  • Verilog 中,一個線程總是要等待一個帶 @ 操作符的事件。這個操作符是邊沿敏感的,總是阻塞着、等待事件的變化
  • 其他線程可以通過  ->  操作法來觸發事件,結束對第一個線程的阻塞
  • event 是邊沿觸發
  • 可以使用電平敏感的 wait(e1.triggered()) , 來代替邊沿敏感的阻塞語句 @e1;只要event 被觸發過,就可以防止引起阻塞
  • 明確什么時候使用wait:wait 用於等待一次,如果需要多次等待,則需要在等待到一次后,就清除一次,如uvm中的wait;@ 可以等待多次
event e1, e2;
initial begin
    ->e2;
    @e1;    //  觸發e1,在event 邊沿觸發
end
initial begin
    ->e1;   //  等待e1
    @e2;
end
// e1, e2在同一時刻被觸發,由於delta cycle的時間差,可能使得兩個初始化無法等到e1 或者 e2

// 推薦使用
wait (e1.triggered());  

 線程間的通知

class car;
    bit start = 0;
    task launch();
        start = 1;
        $display(car is launched);
    endtask
    task move();
        wait(start == 1);
        $display("car is moving");
    endtask
    task driver();
        fork
            this.launch();
            this.move();
        join
    endtask
endclass

module road;
    initial begin
        automatic car byd = new();
        byd.driver();
    end
endmodule

envet 模式

class car;
    event     e_start;
    task launch();
        -> e_start;
        $display(car is launched);
    endtask
    task move();
        wait(e_start.triggered);
        $display("car is moving");
    endtask
    task driver();
        fork
            this.launch();
            this.move();
        join
    endtask
endclass

 semaphore

  •  可以實現對同一資源的訪問控制,互斥訪問
  • semaphore 的三種基本操作:
    • new()  可以創建一個帶單個或者多個鑰匙的semaphore
    • get()    可以獲取一個或者多個鑰匙
    • put()    可以返回一個或者多個鑰匙
  • try_get()  : 非阻塞式獲取鑰匙,函數返回 1 表示有足夠多的鑰匙,返回  0  表示鑰匙不夠

 mailbox信箱

  • 線程間如果傳遞消息,可以使用mailbox
  • mailbox和隊列有相近之處
  • mailbox 是一種對象,需要使用 new() 例化;例化時有一個可選的參數 size 來限定其存儲的最大數量
    • 如果size 是0 或者沒有指定 new(),則信箱是無限大的,可以容納任意多的條目
    • new(N), 設置容量為 N
  • put():可以把數據放入mailbox;如果信箱已滿,則 put 會阻塞;try_put() 非阻塞方法
  • get():可以從信箱移除數據;如果信箱為空,則 get 會阻塞;try_get()  非阻塞方法
  • peek():可以獲取對信箱里數據的拷貝而不移除它;try_peek 非阻塞方法
  • 線程之間的同步方法需要注意,哪些是阻塞方法,哪些是非阻塞方法
  • 如果要顯式的限定mailbox 中的元素類型,可以通過mailbox #(type=T)  的方式來聲明,例如 mailbox #(int)
mailbox 隊列
必須通過new()例化 只需要聲明隊列
可以同時存儲不同的數據類型 內部存儲的數據類型必須一致

put 和 get 是阻塞方法

調用阻塞方法只能在task中,因為阻塞是耗時的

push_back 和pop_front是非阻塞的

在使用queue 時,需要使用wait(queue.size()>0)才可以在其后對非空隊列做取數據操作

mailbox只能用作FIFO   queue 既可以用作FIFO, 也可以用作LIFO
mailbox變量的操作,在傳遞形式參數是,實際傳遞並拷貝的是mailbox指針

傳遞隊列的形式參數默認是input方向,傳遞的是數組的拷貝;

考慮使用ref方向,考慮對隊列是引用還是拷貝

 總結

  •  event:最小信息量的觸發,即單一的通知功能。可以用來做事件的觸發,也可以多個event組合起來做線程之間的同步
  • semaphore:共享資源的安全衛士。如果多線程之間要對某一公共資源做訪問
  • mailbox:精小的SV原生FIFO。在線程之間做數據通信或者內部數據緩存是可以考慮這個

 


 


免責聲明!

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



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