不經(jīng)意間看到幾年前自己寫的 FGPA 設(shè)計,代碼風格勉強說的過去,但是邏輯設(shè)計方面的安全隱患比比皆是。許多初學者編寫 Verilog 代碼,基本都是按照 C 語言的思維和風格去設(shè)計,造成了很多不規(guī)范的共性問題。
本節(jié)主要總結(jié)一些不規(guī)范且危險的 Verilog 設(shè)計。主要針對可綜合的數(shù)字設(shè)計,testbench 是仿真程序,一般情況下要求不是很嚴格。
代碼規(guī)范要講述的內(nèi)容與編碼風格是不一樣的。編碼風格只是建議,設(shè)計者可以不按照本教程編碼風格的建議,隨心所欲的暢寫代碼。只要邏輯正確,電路安全,哪怕寫成柳絮滿天飛的風格,編譯器能正常編譯正常仿真即可。設(shè)計者可以高傲的說,寫自己的代碼,讓別人猜去吧!
代碼規(guī)范是在一定程度上必須要遵從的規(guī)則,否則可能會對數(shù)字電路邏輯的正確性造成一定影響。除非針對某種特殊的設(shè)計,或個人輕車熟路、把握十足,可以稍微的越界 Verilog 代碼規(guī)范,否則在設(shè)計中還是建議多注意這些規(guī)范。尤其初學者特別容易觸犯此類問題。
變量聲明時不要對變量進行賦初值操作。如果變量聲明時設(shè)置初始值,仿真時變量會有期望的初值,但綜合后電路的初始值是不確定的。如果信號初值會影響邏輯功能,則仿真過程可能會因驗證不充分而錯過查找出邏輯錯誤的機會。例如下面描述是不建議的:
reg [31:0] wdata = 32'b0 ;
賦初值操作應該在復位狀態(tài)下完成,也建議寄存器變量都使用復位端,以保證系統(tǒng)上電或紊亂時,可以通過復位操作讓系統(tǒng)恢復初始狀態(tài)。
建議設(shè)計時,時鐘采用正邊沿邏輯,復位采用負邊沿邏輯。復位設(shè)計詳見《Verilog 復位簡介》。
復位時語句塊中所有的信號都應該賦予初值,不要漏掉相關(guān)信號。
always @(posedge clk or negedge rstn) begin
if (!rstn) begin
cnt <= 'b0 ; //漏掉 cout 賦初值,很危險
end
else if (cnt == 10) begin
cnt <= 4'b0 ;
cout <= 1'b1 ;
end
else begin
cnt <= cnt + 1'b1 ;
cout <= 1'b0 ;
end
end
不到萬不得已不要在 2 個 always 塊中分別使用同一時鐘的上升沿和下降沿邏輯,否則會引入相對復雜的時鐘質(zhì)量和時序約束的問題。
//建議盡量避免 2 個 always 塊 2 個時鐘邊沿的邏輯
always @(posedge clk) begin
a <= b ;
end
always @(negedge clk) begin
c <= d ;
end
禁止在一個 always 塊中同時將時鐘的雙邊沿作為觸發(fā)條件,編譯、仿真可能會按照設(shè)計人員的思想進行,但此類電路往往不可綜合,或綜合后電路功能不會符合預期。
//禁止一個 always 塊中使用雙邊沿邏輯
always @(posedge clk or negedge clk) begin
a <= b ;
end
禁止在 2 個 always 塊中為同一個變量賦值,這是很多初學者容易犯的錯誤。
//此設(shè)計是錯誤的
always @(posedge clk) begin
a <= b ;
end
always @(negedge clk) begin
a <= d ;
end
一個 always 塊中不要存在多個并行或不相關(guān)的條件語句,使用多個 always 分別描述。
當一個 always 語句中存在多個并行或不相關(guān)的條件語句時,仿真的執(zhí)行結(jié)果或綜合的實際電路中,不相關(guān)的條件語句都是并行執(zhí)行的。但是仿真過程可能是順序執(zhí)行的,如果有延遲信息可能會導致不可以預知的錯誤結(jié)果。且該寫法可讀性較差,功能結(jié)構(gòu)劃分不明顯。
//不推薦
always @(posedge clk) begin
if (a == b)
data_t1 <= data1 ;
if (a == b && c == d)
data_t2 <= data2 ;
else
data_t2 <= 'b0 ;
end
//推薦分開寫
always @(posedge clk) begin
if (a == b)
data_t1 <= data1 ;
end
always @(posedge clk) begin
if (a == b && c == d)
data_t2 <= data2 ;
else
data_t2 <= 'b0
end
設(shè)計中盡量使用同步設(shè)計。
必須要使用異步邏輯時,一定要對不同時鐘域之間的信號進行同步處理,不能直接使用相關(guān)信號,否則會產(chǎn)生亞穩(wěn)態(tài)電路。同步處理具體實現(xiàn)請參考《Verilog 同步與異步》及其后面相關(guān)章節(jié)。
盡量不要直接將時鐘信號與普通變量信號做邏輯操作,或?qū)r鐘信號進行電平信號的檢測判斷。例如下列描述都是不建議的。
assign clk_gate = clk & clken ;
assign dout = (clk == 1'b1) ? din : 0 ;
always @(posedge clk) begin
if (clk = 1'b1)
data_t1 <= data1 ;
end
不同條件下對時鐘進行選擇時,不能直接使用選擇邏輯,否則會出現(xiàn)毛刺現(xiàn)象。詳見《Verilog 時鐘切換》。
一般情況下信號變量不要直接使用乘法 ?*
?、除法 ?/
?、求余數(shù) ?%
? 等操作。這些操作符被綜合后,結(jié)構(gòu)和時序往往不易控制。應該使用相關(guān)優(yōu)化后的 ip 模塊或工藝庫中的集成模塊。但是 parameter 類型的常量就可以使用此類操作符,因為在編譯之初編譯器就會計算出常量運算的結(jié)果,不會消耗多余的硬件資源。
組合邏輯的條件語句中條件補充完整,組合邏輯的 always 語句中敏感信號要羅列完全,以避免不期望的 Latch 產(chǎn)生。詳見《Verilog 教程》章節(jié)《Verilog 避免Latch》。
邏輯設(shè)計時要考慮代碼能不能綜合成實際電路,會綜合成什么樣的電路。詳見《Verilog 可綜合性設(shè)計》。
例化時,連接輸入端的信號可以是 reg 型或 wire 型變量,連接輸出端的信號一定是 wire 型變量。但是端口信號聲明時,輸入信號必須是 wire 型變量,輸出信號可以是 reg 型或 wire 型變量。
多個模塊例化時,模塊名字在前,例化名字在后,且例化名字不能相同。
更多建議: