本文首發(fā)于微信公眾號“芯片學堂”焙矛,作者JKZHAN
多線程、并發(fā)村斟、并行計算等這些概念我們在很多計算機相關(guān)的領(lǐng)域都會聽到抛猫。具體去看孩灯,并行計算是一個非常廣泛的課題峰档,涵蓋了計算機體系結(jié)構(gòu)各個層面的眾多計算機技術(shù),包括了成熟的讥巡、先進的。而本文將展開介紹的SV多線程归榕,只是在軟件應(yīng)用層面去看多線程吱涉,不必也不會去深挖仿真細節(jié)。
01 多線程概念
在介紹SystemVerilog語言支持的多線程開發(fā)之前特石,有必要先來看看一些基本概念鳖链,什么是多線程芙委,以及為什么在芯片開發(fā)或者驗證中會需要用到多線程。
先明確兩個概念灌侣,進程(process)和線程(thread)侧啼。簡單來說,進程(process)就是程序的某一次執(zhí)行痊乾,也可以說成進程是有了執(zhí)行進度的程序。它是操作系統(tǒng)進行資源分配和調(diào)度的一個獨立單位蛾魄,也就是說進程是競爭計算機資源的基本單位。線程(thread)則屬于某一個進程缴川,一個進程可以有一個或者多個線程描馅。線程不獨立擁有計算機資源而线,它只能跟同一進程中的其他線程共享這個進程所分配到的資源。線程是程序執(zhí)行流的最小單元嘹狞,或者叫操作系統(tǒng)進行執(zhí)行調(diào)度的基本單元誓竿。
在SV中,LRM(Language Reference Manual)將其支持的并發(fā)結(jié)構(gòu)叫并行塊(parallel blocks)涧偷,將創(chuàng)建出來的執(zhí)行流叫并發(fā)進程(concurrent processes)毙死。同時將“a process”定義成了“A thread of one or more programming statements that can be executed independently of other programming statements”。乍一看感覺跟計算機原本對進程和線程的定義有點混淆扼倘。但總之,SV多線程編程中的這個“線程”再菊,可以理解成是共享了一個仿真進程所擁有的計算機資源(比如內(nèi)存地址空間)的執(zhí)行流。
另外秉剑,SV的多線程到底是并行執(zhí)行的(parallel)還是并發(fā)執(zhí)行的(concurrent)绿语,這是仿真器、操作系統(tǒng)和硬件的事情种柑。對于仿真程序和驗證平臺開發(fā)者匹耕,能感知到的只是創(chuàng)建出來的這些線程是同時在跑的,后文中提到的“同時執(zhí)行”驶赏、“并行執(zhí)行”等詞就都指的是用戶層面的感知,而不嚴格區(qū)別并行和并發(fā)煤傍。
02 多線程使用場景
在我們使用SV的時候蚯姆,其實已經(jīng)不知不覺的用到了多線程。比如在最簡單的testbench中龄恋,一般會用always塊或者在initial塊中用forever循環(huán)來產(chǎn)生時鐘郭毕,然后在另一個initial塊中給DUT(design under test)的接口信號做初始化和灌激勵。實際上显押,這就是兩個線程乘碑,一個是產(chǎn)生時鐘的執(zhí)行流,一個是端口信號驅(qū)動的執(zhí)行流蝉仇,這兩個執(zhí)行流是相互獨立的。
在SV中沉迹,有靜態(tài)(static)線程和動態(tài)(dynamic)線程害驹。靜態(tài)線程指的是從仿真一開始就一直存在的線程,它的創(chuàng)建方式就是使用initial過程塊或者always過程塊(包括always, always_comb, always_ff等)葫松。這在使用Verilog等硬件描述語言做設(shè)計的時候就涉及到了底洗。仿真的時候調(diào)度器(systemverilog scheduler)會根據(jù)敏感信號列表等觸發(fā)事件來對線程進行調(diào)度執(zhí)行亥揖。
本文重點要介紹的是SV的動態(tài)線程圣勒,也就是在仿真過程當中動態(tài)地創(chuàng)建出來的線程摧扇。動態(tài)多線程的編程方法在下一節(jié)會介紹。動態(tài)多線程的在芯片驗證中吁峻,特別是在面向?qū)ο螅∣OP)的驗證環(huán)境開發(fā)中非常有用在张,可以在仿真過程中動態(tài)地創(chuàng)建給多個接口灌激勵的線程、可以用來創(chuàng)建復(fù)雜的斷言等。當你試圖寫一個復(fù)雜的順序執(zhí)行流程凡傅,但卻感到哪個地方礙手礙腳的時候,不妨試一試創(chuàng)建多個線程哼转,拆分下任務(wù)槽华,讓它們各干各的。
03 SV多線程編程框架
SV的多線程編程采用Fork/Join框架佣蓉,這跟Java中由Doug Lea引入的并行任務(wù)執(zhí)行框架類似亲雪。Fork/Join框架的基本思想就是將一個大而功能復(fù)雜的任務(wù)拆解成能夠獨立運行的小任務(wù)义辕,最后再將這些個小任務(wù)的計算結(jié)果組合起來,實現(xiàn)跟執(zhí)行一個大任務(wù)一樣的效果灌砖。像Fork/Join這種分而治之的思想在很多地方都可以看到它的影子基显,比如面向?qū)ο蟮木幊谭椒ǎ直热缬糜诖髷?shù)據(jù)計算的MapReduce編程模型撩幽。分而治之可以使得程序結(jié)構(gòu)易于擴展和維護,同時又能充分發(fā)揮計算機的并行計算能力制跟,提高程序運行速度雨膨。
SV多線程編程的框架,包括線程創(chuàng)建聊记、線程同步和通信排监、線程控制和線程銷毀等方法,總結(jié)如下舆床。
SV的有三種創(chuàng)建動態(tài)線程的方法:fork ... join fork ... join_any fork ... join_none挨队。在Fork/Join結(jié)構(gòu)中的每一個過程塊都會對應(yīng)創(chuàng)建出一個線程,這些線程是同時執(zhí)行的湿弦。三種創(chuàng)建線程的方法區(qū)別就在于最后那個關(guān)鍵詞腾夯,join指的是fork出來的線程都執(zhí)行結(jié)束之后,join后面的語句才會繼續(xù)往下執(zhí)行竟秫;join_any指的是fork出來的線程中只要有一個執(zhí)行結(jié)束跷乐,join_any后面的語句就會繼續(xù)往下執(zhí)行;join_none指的是fork出來的線程創(chuàng)建之后馒稍,join_none后面的語句就可以繼續(xù)往下執(zhí)行浅侨;
線程控制的常用方法有wait fork,disable fork鼓黔。在Fork/Join結(jié)構(gòu)之后,加一句wait fork崔步,會阻塞后續(xù)語句缎谷,直到fork出來的進程都執(zhí)行完;如果加一句disable fork瑞你,則是直接結(jié)束掉fork出來的進程希痴,后續(xù)語句往下執(zhí)行。
SV內(nèi)建的這個Process類(fork出來的線程都屬于這個類)提供了各種方法虏缸,包括self()纺铭、kill()刀疙、await()谦秧、suspend()、resume()锥累、status()集歇。這些方法的功能分別是獲得線程句柄,殺死線程际歼、等待線程執(zhí)行完姑蓝、線程掛起、線程恢復(fù)執(zhí)行纺荧、獲得線程狀態(tài)。利用這些方法可以實現(xiàn)線程之間的同步和控制输枯。
線程的同步和通信的方法有event(事件)用押、semaphore(旗語)和mailbox(信箱)。event可以用來傳遞簡單的狀態(tài)或者控制信號池充,比如一個線程可以觸發(fā)一個事件缎讼,另一個線程等待該事件觸發(fā)。semaphore可以通過分發(fā)key的方式來管理公共資源卧惜。mailbox就比較厲害了夹纫,可以用來傳遞更多的數(shù)據(jù)。
若有必要舰讹,再開一篇文章寫一些例程茅姜,方便學習和借鑒。
參考文獻
[1] IEEE Standard Association. "IEEE Standard for SystemVerilog-Unified Hardware Design, Specification, and Verification Language." (2013).
[2] Breshears, Clay. The art of concurrency: A thread monkey's guide to writing parallel applications. " O'Reilly Media, Inc.", 2009.
[3] Spear, Chris. SystemVerilog for verification: a guide to learning the testbench language features. Springer Science & Business Media, 2008.