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。在線程之間做數據通信或者內部數據緩存是可以考慮這個
 
