Verilog編寫技巧!
時(shí)間:2025-11-30 20:56:11
手機(jī)看文章
掃描二維碼
隨時(shí)隨地手機(jī)看文章
好的設(shè)計(jì)者一般都要對(duì)電路要實(shí)現(xiàn)的功能有清晰的認(rèn)識(shí),對(duì)數(shù)據(jù)流很清楚,知道數(shù)據(jù)如何從一個(gè)點(diǎn)移動(dòng)到另一個(gè)點(diǎn),這就是所謂的“勾劃”(walk-through)。一旦設(shè)計(jì)藍(lán)圖在腦海中變得清晰,此后釆用Verilog編寫數(shù)據(jù)路徑和控制邏輯就會(huì)變得思路清晰。
腦海中的模擬
正如大多數(shù)人玩過(guò)的象棋游戲,我們都知道提前謀劃是何等重要,要在下一次移動(dòng)棋子之前考慮好此后的幾步棋應(yīng)該怎么走,以確保不會(huì)出錯(cuò),不被對(duì)手捕捉到機(jī)會(huì)。電路設(shè)計(jì)過(guò)程與下棋非常相似。當(dāng)設(shè)計(jì)狀態(tài)機(jī)、數(shù)據(jù)路徑或者控制邏輯時(shí),我們知道它們的功能。在進(jìn)行設(shè)計(jì)仿真之前,我們需要思考代碼在不同輸入和邊界條件下如何工作。如果用心去做好這一步工作,并且分析可能出現(xiàn)的問(wèn)題,驗(yàn)證工作將會(huì)變得非常高效。另外,這一步也給我們建立了自信,使我們確信整個(gè)設(shè)計(jì)非常扎實(shí),可以很好地工作。否則很可能出現(xiàn)的情況是在驗(yàn)證階段反復(fù)發(fā)現(xiàn)問(wèn)題并進(jìn)行電路修改,不斷進(jìn)行補(bǔ)救T.作,并且最終也不能確定設(shè)計(jì)足否還隱含著沒(méi)有被發(fā)現(xiàn)的問(wèn)題。哪種風(fēng)格—數(shù)據(jù)流或算法
描述組合邏輯有兩種方式—使用wire(對(duì)應(yīng)數(shù)據(jù)流描述方式)或者使用reg(對(duì)應(yīng)算法描述方式)。這兩種方式都能實(shí)現(xiàn)相同的邏輯功能,綜合后得到相同的門電路,具體使用哪一種方式可以根據(jù)個(gè)人喜好。數(shù)據(jù)流—短表達(dá)式舉例
wire [7:0] regl0_nxt;
assign regl0_nxt = wren ? data_in : regl0;
算法—短表達(dá)式舉例
reg [7:0] regl0_nxt;
always @(*) begin
reglO_nxt = reglO; if (wren)
regl0_nxt = data_in;
end
當(dāng)表達(dá)式非常簡(jiǎn)單時(shí),一般更傾向于使用數(shù)據(jù)流風(fēng)格來(lái)實(shí)現(xiàn),此時(shí)代碼行數(shù)很少。然而,當(dāng)表達(dá)式很長(zhǎng)并且與很多條件有關(guān)時(shí),數(shù)據(jù)流風(fēng)格閱讀起來(lái)較為費(fèi)力。此時(shí)可以使用算法風(fēng)格,可以采用if-else語(yǔ)句進(jìn)行描述,以易于閱讀和減少錯(cuò)誤發(fā)生。
數(shù)據(jù)流-長(zhǎng)表達(dá)式舉例
wire count255_nxt;
assign count255_nxt=
enable_cnt_dn_risedge ? cut_preset_stored:
(enable_cnt_up_risedge ? ’d0:
(pause_counting ? count255:
((enable_cnt_dn && ctr_expired)? cnt_preset_stored:
(enable_cnt_dn ? (count255- l'bl):
((enable_cnt_up && ctr_expired) ? 'd0:
(enable_cnt_up ? (count255 + 1'bl): count255))))));
算法—長(zhǎng)表達(dá)式舉例
reg |7:0] count255_nxt;
always @(*) begin
count255_nxt = count255; if (enable_cnt_dn_risedge)
count255_nxt = cnt_preset_stored; // initialize to max else if (enable_cnt_up_risedge) // initialize to 0
count255_nxt = 'd0;
else if (pause_counting)
count255_nxt = count255;
else if (enable_cnt_chi && ctr_expired)
count255_nxt = cnt_preset_stored;// auto load
else if (enable_cnt_dn)
count255_nxt = count255 - l'bl; else if (enable_cnt_up && ctr_expired); // hasn’t expired
count255_nxt = 'dO; // auto load
else if (enable_cnt_up)
count255_nxt = count255 + 1’bl;// hasn’t expired
end
寄存器型輸出
大型設(shè)計(jì)可被分解為很多塊,每個(gè)塊可進(jìn)一步分解為多個(gè)模塊。模塊相互聯(lián)接形成完整設(shè)計(jì)。模塊的輸出或者驅(qū)動(dòng)另一個(gè)模塊的輸入,或者作為頂層芯片的輸出引腳。在結(jié)構(gòu)化和大型設(shè)計(jì)中,綜合后整個(gè)電路要保持層次化結(jié)構(gòu),而不是使整個(gè)設(shè)計(jì)扁平化。當(dāng)層次化得以 保留時(shí),輸出有時(shí)必須通過(guò)相對(duì)長(zhǎng)的路徑到達(dá)另一個(gè)模塊,這會(huì)導(dǎo)致互聯(lián)延時(shí)增大。為了更容易滿足芯片內(nèi)對(duì)建立時(shí)間的要求,內(nèi)部模塊應(yīng)盡量采用寄存器輸出,而不是組合邏輯輸出,如圖10.1和圖10.2所示。采用寄存器輸出時(shí),可以為后續(xù)電路留下相對(duì)寬裕的布線延遲和輸入組合邏輯延遲。盡量使用寄存器輸出是一個(gè)非常好的設(shè)計(jì)經(jīng)驗(yàn)。
使用狀態(tài)機(jī)而不是松散的控制邏輯
狀態(tài)機(jī)可以實(shí)現(xiàn)復(fù)雜的控制功能,它可以非常容易地將所有可能的情況和邊界條件考慮進(jìn)去。通過(guò)設(shè)置多個(gè)狀態(tài),并將它們相互聯(lián)接可實(shí)現(xiàn)復(fù)雜的、重復(fù)的控制流程。如果狀態(tài)機(jī)需要完成的功能很少,可以用2個(gè)或3個(gè)狀態(tài)來(lái)實(shí)現(xiàn)。采用狀態(tài)機(jī)來(lái)實(shí)現(xiàn)控制功能是一個(gè)好方 法,即使是只有2個(gè)或3個(gè)狀態(tài)的簡(jiǎn)單狀態(tài)機(jī),都可以有效減少設(shè)計(jì)差錯(cuò)。綜合和仿真不匹配
在Verilog 2001出現(xiàn)之前,設(shè)計(jì)者需要確定always塊敏感列表中包含了所有輸入信號(hào)。下面是一個(gè)例子:always @ (in1, in2, in3) // sensitivity list
begin
outl = inl - in2;
out2 = (in3 > 5) ? in1 :in2;
end
設(shè)計(jì)者必須確保所有輸入(位于表達(dá)式右邊)都出現(xiàn)在敏感信號(hào)列表中。對(duì)于小型always塊,這很簡(jiǎn)單。但是對(duì)于大型always塊來(lái)說(shuō),敏感信號(hào)列表中很可能會(huì)漏項(xiàng)。設(shè)計(jì)者必須付出額外的努力和時(shí)間,用于查找所有輸入并將它們放入敏感信號(hào)列表中。如果某個(gè)輸入從敏感信號(hào)列表中漏掉了,會(huì)發(fā)生什么情況呢?如果一個(gè)輸入發(fā)生了變化,僅當(dāng)它出現(xiàn)在敏感信號(hào)列表中時(shí),仿真器才進(jìn)入always塊中執(zhí)行其中的語(yǔ)句。如果漏掉了某個(gè)敏感信號(hào),那么該部分電路的仿真結(jié)果與綜合后的邏輯功能相比可能會(huì)有很大差別。由于Verilog 2001版標(biāo)準(zhǔn)中不再需要將這些輸入放在敏感信號(hào)列表中,仿真器會(huì)自動(dòng)弄清楚所有的敏感信號(hào),綜合后的結(jié)果也會(huì)與仿真結(jié)果相同。舉例如下。
//Verilog 2001
always @ (*) // sensitivity list
begin
outl = inl - in2;
out2 = (in3 > 5) ? in1 :in2;
end
設(shè)計(jì)的模塊化和參數(shù)化
結(jié)構(gòu)化設(shè)計(jì)比非結(jié)構(gòu)化設(shè)計(jì)更有用。管理者和決策者可以根據(jù)結(jié)構(gòu)化設(shè)計(jì)作出明智的判斷。類似地,結(jié)構(gòu)化或者模塊化設(shè)計(jì)不僅易于理解和維護(hù),而且有利于設(shè)計(jì)共享。某些電路單元(加法器、乘法器、CRC校驗(yàn)電路等)能在不同的設(shè)計(jì)中被復(fù)用,可以對(duì)一組邏輯電路使用門控時(shí)鐘,冗余或者相近的電路功能可以被合并或者消除。采用模塊化設(shè)計(jì)后,所有這些都可能成為現(xiàn)實(shí)。此外,標(biāo)準(zhǔn)電路模塊還能在多個(gè)設(shè)計(jì)或一個(gè)公司內(nèi)部不同部門之間共享。加法器、減法器的有效使用
在數(shù)字系統(tǒng)設(shè)計(jì)中,會(huì)經(jīng)常用到加法器、減法器、乘法器等,需要大量數(shù)學(xué)運(yùn)算時(shí)更是如此。需要特別提醒的是,數(shù)學(xué)運(yùn)算電路會(huì)占用大量的門/面積資源,此時(shí),有效使用這些電路以降低硬件資源消耗就會(huì)非常有意義。設(shè)計(jì)者可以通過(guò)改進(jìn)算法,使運(yùn)算電路在不同的時(shí) 間為不同的其他內(nèi)部電路提供運(yùn)算服務(wù),從而減少運(yùn)算電路的數(shù)量。另一種方法是優(yōu)化RTL代碼,減少運(yùn)算電路在具體實(shí)現(xiàn)時(shí)消耗的資源。下面的例子,展示了如何減少加法器/減法器的數(shù)量。case (selx)
3'b000: calx = base;
3'b001: calx = base + 16;
3'b010: calx = base + 32;
3'b011: calx = base + 48;
3'bl00: calx = base + 64;
3'bl0l: calx = base + 80;
3'b110: calx = base + 96;
3'blll: calx = base + 112;
endcase
上面的例子需要使用7個(gè)加法器和1個(gè)復(fù)用器。通過(guò)編寫不同的RTL代碼可以實(shí)現(xiàn)同樣的功能,此時(shí)只需要1個(gè)加法器和1個(gè)復(fù)用器。
case (selx)
3'b000: base_offset = 0;
3'b00l: base_offset = 16;
3'b010: base_offset = 32;
3'b011: base_offset = 48;
3'blOO: base_offset = 64;
3'bl01: base_offset = 80;
3'bl10: base_offset = 96;
3'bl11: base_offset = 112;
endcase
assign calx = base + base_offset;
圖10.3是兩段代碼的綜合結(jié)果。
需要避免的情況
不要形成組合邏輯環(huán)路
當(dāng)路徑中有反饋,卻沒(méi)有觸發(fā)器這類時(shí)序元件時(shí),有可能出現(xiàn)組合邏輯環(huán)路,如下圖所示。對(duì)于有經(jīng)驗(yàn)的設(shè)計(jì)者來(lái)說(shuō),模塊內(nèi)簡(jiǎn)單的組合邏輯環(huán)路易于發(fā)現(xiàn)和避免。然而,當(dāng)信號(hào)由組合邏輯電路產(chǎn)生,穿過(guò)多個(gè)模塊,最后返回到原模塊所形成的組合邏輯環(huán)路卻難以識(shí)別??赡苤钡綄?duì)整個(gè)電路進(jìn)行仿真時(shí)才會(huì)注意到該環(huán)路。此時(shí),仿真器無(wú)法仿真出有效的結(jié)果或者會(huì)因?yàn)檫M(jìn)入死循環(huán)而長(zhǎng)時(shí)間停留在某一仿真時(shí)刻。查找組合邏輯環(huán)路的起點(diǎn)較為困難,采用單步仿真方式查找環(huán)路起點(diǎn)的工作非常繁瑣且令人厭煩。采用寄存器輸出的方式可以有效地減少形成組合邏輯環(huán)路的機(jī)會(huì)。
避免意外生成鎖存器
在時(shí)序電路設(shè)計(jì)中,一般會(huì)選擇D觸發(fā)器作為存儲(chǔ)元件。鎖存器也能存儲(chǔ)數(shù)值,但有一些不好的特性,除非真的需要,否則不應(yīng)在設(shè)計(jì)中使用它。在編寫RTL代碼時(shí),如果沒(méi)有遵循一定的規(guī)則,可能會(huì)出現(xiàn)意想不到的鎖存器。下面是一些綜合后會(huì)生成鎖存器的例子。生成鎖存器并不是設(shè)計(jì)的初衷,因此要熟悉容易生成鎖存器的代碼結(jié)構(gòu)并加以避免。使用always塊生成組合邏輯電路時(shí),如果變量所有可能的取值沒(méi)有被考慮完全,那么綜合后可能會(huì)生成鎖存器,如下面代碼所示。always @ *
begin if (a < b) c = d; end
在上面的例子中,給出了a
always @ *
begin if (a < b) c=d; else c=0;
end
一種簡(jiǎn)單的避免產(chǎn)生鎖存器的方法是給變量賦初值,如下面代碼所示。這樣即使后面的代碼沒(méi)有覆蓋所有的取值,也不會(huì)生成鎖存器。
always @ *
begin
c = 0; if (a < b) c = d; end
不要采用基于延遲的設(shè)計(jì)
邏輯設(shè)計(jì)中要避免利用門延遲產(chǎn)生所需要的脈沖。
如上圖所示的電路,該電路利用反相器的門延遲在與門輸出端產(chǎn)生了一個(gè)正脈沖。延遲大小取決于PVT(集成電路生產(chǎn)工藝、工作 電壓和環(huán)境溫度),這意味著延遲不是固定的,將隨著工作電壓、溫度和制作工藝而改變。當(dāng)設(shè)計(jì)被移植到更先進(jìn)的工藝上生產(chǎn)時(shí)(例如,65~28nm集成電路工藝),脈沖寬度將減小,最終成為毛刺。因此,這樣的邏輯設(shè)計(jì)不推薦使用,應(yīng)該被避免。
不要對(duì)一個(gè)變量多次賦值
在Verilog中,不要在多個(gè)always塊內(nèi)對(duì)同一個(gè)變量賦值。綜合工具將產(chǎn)生兩個(gè)獨(dú)立的邏輯塊,并將它們用“線或”方式連接,從而造成設(shè)計(jì)錯(cuò)誤。對(duì)一個(gè)變量的賦值必須在一個(gè)always塊內(nèi)進(jìn)行,下面是一個(gè)進(jìn)行對(duì)比分析的例子。
在多個(gè)always塊內(nèi)對(duì)同一個(gè)變量賦值可能不會(huì)報(bào)錯(cuò),always塊較少時(shí)也很容易發(fā)現(xiàn)這一問(wèn)題,如下面代碼所示。但當(dāng)一個(gè)電路規(guī)模較大時(shí),內(nèi)部可能有多個(gè)always塊,此時(shí)容易犯的錯(cuò)誤是在一個(gè)always塊可能對(duì)某個(gè)變量賦了初值,而在另一個(gè)always塊中賦了實(shí)際值。應(yīng)仔細(xì)檢查,避免這類問(wèn)題出現(xiàn)。
初步完成RTL代碼之后
無(wú)論編寫代碼時(shí)有多么仔細(xì),代碼中都會(huì)出現(xiàn)bug,應(yīng)在完成代碼編寫后趁熱打鐵,趁著對(duì)代碼還熟悉及時(shí)查找問(wèn)題,不要留到以后再解決。很多情況下,你并沒(méi)有意識(shí)到代碼中存在錯(cuò)誤,也不是想把問(wèn)題留到以后去解決,而是設(shè)計(jì)團(tuán)隊(duì)的其他人告訴你時(shí),你才意識(shí)到這個(gè)代碼存在設(shè)計(jì)錯(cuò)誤。此時(shí)也許已經(jīng)過(guò)了2天、2個(gè)月甚至2年,再查找問(wèn)題的難度就會(huì)增大很多。代碼編寫完成并進(jìn)行第一遍修改后,應(yīng)再一次對(duì)代碼進(jìn)行查看,按照你腦海中的步驟重新對(duì)設(shè)計(jì)檢查一遍,這樣不止會(huì)避免較為明顯的錯(cuò)誤,還可能發(fā)現(xiàn)隱藏較深的邊界問(wèn)題,這將大大提高工作效率。最為重要的是,驗(yàn)證工程師的工作量會(huì)減少,設(shè)計(jì)者本人也不需要花花費(fèi)更多的時(shí)間來(lái)重新消化原來(lái)的設(shè)計(jì)并修改代碼中的問(wèn)題??傊瑔?wèn)題越早解決越好。多數(shù)情況下,進(jìn)行代碼編寫時(shí),我們會(huì)使用剪切和粘貼來(lái)避免重復(fù)和枯燥的代碼輸入工作,此后再對(duì)粘貼的代碼段進(jìn)行修改,比如修改地址、索引等。對(duì)于這類枯燥的工作,設(shè)計(jì)者本能地會(huì)感到厭煩并且注意力不容易集中,恰恰此時(shí)最容易出現(xiàn)低級(jí)錯(cuò)誤。代碼編寫完成 后,應(yīng)對(duì)代碼進(jìn)行目測(cè)檢查,一些簡(jiǎn)單的問(wèn)題,比如代碼縮進(jìn)、空格和對(duì)齊等,很容易進(jìn)行目測(cè)檢查,也很容易找出剪切和粘貼時(shí)出現(xiàn)的錯(cuò)誤。編寫完RTL代碼之后,應(yīng)及時(shí)目測(cè)檢查一遍。對(duì)發(fā)現(xiàn)bug感到驚喜我們不會(huì)因?yàn)樵O(shè)計(jì)中有bug就否定設(shè)計(jì)者的成績(jī),而是要讓設(shè)計(jì)者明白bug的確會(huì)存在,簡(jiǎn)單的bug比如拼寫錯(cuò)誤,在每個(gè)設(shè)計(jì)者身上都會(huì)發(fā)生,這在驗(yàn)證過(guò)程中很容易發(fā)現(xiàn)。然而,對(duì)于邊界問(wèn)題、由于沒(méi)有充分理解而造成的設(shè)計(jì)錯(cuò)誤,或者多個(gè)事件組合在一起時(shí)才會(huì)發(fā)生的錯(cuò)誤等,在仿真期間有時(shí)也難以發(fā)現(xiàn)。當(dāng)進(jìn)行代碼分析時(shí),你可能會(huì)發(fā)現(xiàn),在那么努力地進(jìn)行設(shè)計(jì)和檢查之后,仍然存在一些問(wèn)題。此時(shí),對(duì)問(wèn)題進(jìn)行深入分析有助于使設(shè)計(jì)者獲得更多的設(shè)計(jì)經(jīng)驗(yàn)從而變得更加成熟,并在以后的設(shè)計(jì)中避免類似的問(wèn)題。設(shè)計(jì)要面向未來(lái)使用需求
典型的芯片或電路內(nèi)部都有映射在處理器存儲(chǔ)空間中的寄存器,驅(qū)動(dòng)程序可以對(duì)寄存器進(jìn)行配置或者通過(guò)讀取狀態(tài)寄存器的值得到電路的工作狀態(tài)。進(jìn)行寄存器定義時(shí),建議將功能相同的比特位單獨(dú)分組并編址,不要將功能不同的寄存器合并在一起編址。例如,要定義3個(gè)狀態(tài)位和3個(gè)控制位,不要將它們放在一個(gè)字節(jié)中,應(yīng)將3個(gè)狀態(tài)位放到一個(gè)字節(jié)屮(剩下的5比特作為保留位),將3個(gè)控制位放到另一個(gè)字節(jié)中,這樣,狀態(tài)位和控制位就被編入不同的字節(jié)中,具有不同的內(nèi)存映射地址。另外應(yīng)將粘滯位(sticky bit)和常規(guī)比特位 (regular bit)放在不同的字節(jié)中,因?yàn)檎硿恍枰獑为?dú)的電源供電。在電路中恰當(dāng)?shù)卦O(shè)置寄存器并進(jìn)行合理的編址,會(huì)簡(jiǎn)化硬件電路和驅(qū)動(dòng)程序的設(shè)計(jì)。另外進(jìn)行電路設(shè)計(jì)時(shí)要考慮到今后的設(shè)計(jì)需求。在電路架構(gòu)設(shè)計(jì)時(shí),即使已經(jīng)做了充分的考慮,也可能會(huì)進(jìn)行設(shè)計(jì)修改。例如,目前所需的編碼數(shù)值為6,使用了一個(gè)3位寄存器,考慮到將來(lái)的需求,可以選擇4位寄存器,以便于今后增大編碼數(shù)值。另外,應(yīng)確保重要的比特位或寄存器可以被軟件訪問(wèn),雖然這會(huì)增加硬件資源消耗,但是如果不這樣做該數(shù)值就不能被軟件讀取,這會(huì)給調(diào)試帶來(lái)不便。





