Verilog畢竟是硬件描述語言,使用Verilog這類HDL語言的目的始終是對電路的建模,並最終獲得工具轉換出來的實際電路,因此寫代碼的過程當中要能抽象出對應的電路。算法
但同時,Verilog畢竟仍是一種程序語言,就像其餘程序語言同樣,因此仍然須要熟悉它的語法特性,這樣才能夠在遇到不熟悉的代碼風格時候,能抽象出相應電路或者找出建模過程當中出錯的地方。工具
本文關注於常見代碼風格的建模效果以及綜合是否改變仿真結果。post
---
系統環境:Centos 6.5
測試工具:Questasim 10.1b
綜合工具:DC 2013.03(針對ASIC)
波形工具:Verdi 2013測試
----優化
以3-8譯碼器做爲例子來看case的建模效果。代碼和tb分別以下:spa
module case_endcase_basic( input [2:0] data_in, output [7:0] data_out ); reg [7:0] data_out_reg; always @ (*) begin case( data_in ) 3'h0: data_out_reg = 8'b0000_0001; 3'h1: data_out_reg = 8'b0000_0010; 3'h2: data_out_reg = 8'b0000_0100; 3'h3: data_out_reg = 8'b0000_1000; 3'h4: data_out_reg = 8'b0001_0000; 3'h5: data_out_reg = 8'b0010_0000; 3'h6: data_out_reg = 8'b0100_0000; 3'h7: data_out_reg = 8'b1000_0000; endcase end assign data_out = data_out_reg; endmodule
module case_endcase_basic_tb; reg [2:0] data_in; wire [7:0] data_out; initial begin data_in = 0; #10 data_in = 1; #10 data_in = 2; #10 data_in = 3; #10 data_in = 4; #10 data_in = 5; #10 data_in = 6; #10 data_in = 7; #10 data_in = 8; #10 $finish; end `ifdef DUMP_FSDB initial begin $fsdbDumpfile("test.fsdb"); $fsdbDumpvars; end `endif case_endcase_basic case_endcase_basic_instance( .data_in ( data_in ), .data_out( data_out ) ); endmodule
使用QuestaSim仿真後,獲得的波形圖:3d
DC綜合以後效果圖:code
使用 綜合後的網標文件和sdf文件進行後仿真,波形圖以下:blog
能夠看出在信號穩定的時候,綜合後的仿真效果和綜合前的是同樣的。而對於兩個不一樣輸入值以前出現的「0」值,能夠判斷是cell的1->0 比0->1的傳播速度要快。get
下面討論下case語句中常見的集中代碼風格差生的影響
仿照上面的例子。只是取消掉data_in=3’h7的狀況。代碼以下。
module case_endcase_basic1( input [2:0] data_in, output [7:0] data_out ); reg [7:0] data_out_reg; always @ (*) begin case( data_in ) 3'h0: data_out_reg = 8'b0000_0001; 3'h1: data_out_reg = 8'b0000_0010; 3'h2: data_out_reg = 8'b0000_0100; 3'h3: data_out_reg = 8'b0000_1000; 3'h4: data_out_reg = 8'b0001_0000; 3'h5: data_out_reg = 8'b0010_0000; 3'h6: data_out_reg = 8'b0100_0000; endcase end assign data_out = data_out_reg; endmodule
tb沿用上一個。測試後的波形以下:
能夠看出來data_in=7的時候,data_out的值並無變化。猜想simulator在這種狀況下,保持以前的值。
雖然rtl仿真結果只是簡單的一點變化,可是綜合的效果卻會有比較大的差別:
明顯感受到要複雜了不少,同時出現了latch,並且latch前的邏輯會比較複雜。除此,代碼中去掉的那種狀況,電路的變更也對應於圈出的地方,這條路徑最後輸出爲data_out[7],可從工具綜合出來的電路看出,data_out[7]將一直爲0。
進行綜合後仿真,波形圖以下:
能夠看出後仿後的波形中,data_out延時要比第一種狀況下的延時長。
若是能告訴工具 在沒有列出的條件狀況下 的輸出值,就能夠避免產生latch。好比最經常使用的default關鍵詞。代碼以下:
module case_endcase_basic2( input [2:0] data_in, output [7:0] data_out ); reg [7:0] data_out_reg; always @ (*) begin case( data_in ) 3'h0: data_out_reg = 8'b0000_0001; 3'h1: data_out_reg = 8'b0000_0010; 3'h2: data_out_reg = 8'b0000_0100; 3'h3: data_out_reg = 8'b0000_1000; 3'h4: data_out_reg = 8'b0001_0000; 3'h5: data_out_reg = 8'b0010_0000; 3'h6: data_out_reg = 8'b0100_0000; default: data_out_reg = 8'b1000_0000; endcase end assign data_out = data_out_reg; endmodule
tb仍然同上。波形以下:
綜合後的效果:
能夠看出,和第一種狀況下的電路結構是同樣的。由於電路效果是相同的。綜合後仿真的波形圖,也如第一種狀況。
除了使用default語句,也能夠在case以前對輸出值賦一遍值,達到相同效果。代碼以下:
module case_endcase_basic3( input [2:0] data_in, output [7:0] data_out ); reg [7:0] data_out_reg; always @ (*) begin data_out_reg = 8'b1000_0000; case( data_in ) 3'h0: data_out_reg = 8'b0000_0001; 3'h1: data_out_reg = 8'b0000_0010; 3'h2: data_out_reg = 8'b0000_0100; 3'h3: data_out_reg = 8'b0000_1000; 3'h4: data_out_reg = 8'b0001_0000; 3'h5: data_out_reg = 8'b0010_0000; 3'h6: data_out_reg = 8'b0100_0000; endcase end assign data_out = data_out_reg; endmodule
tb仍然如上。wave:
綜合效果:
相比較於default,這種風格的代碼,綜合的效果多少有點不同(圈出來的地方),但從下面的綜合後仿真來看邏輯功能仍是同樣的。單純從電路圖上能夠估計此種風格電路的效果關鍵路徑的延遲會較前一種要多一些。
從DC粗略綜合後的簡略時序報告來看,也確實後者的延遲長一點。
再回頭看綜合後仿真的波形圖,當data_in=7的狀況下,data_out的輸出要比default時候的長一些。
同時查看生成latch時候的關鍵路徑的延遲,單純latch就有1.31 。
至於爲什麼有這種差別,由於對DC的綜合算法不瞭解,也很差下定論。至於再複雜些狀況會不會差別小或者後者效果好,並無驗證,不過我的仍是建議使用default。
最近看opencores上uart的代碼時候,看到一種寫法,測試一下。
module case_endcase_basic2_m( input [2:0] data_in, output [7:0] data_out ); reg [7:0] data_out_reg; always @ (*) begin case( data_in ) 3'h0: data_out_reg = 8'b0000_0001; 3'h1: data_out_reg = 8'b0000_0010; 3'h2: data_out_reg = 8'b0000_0100; 3'h3: data_out_reg = 8'b0000_1000; 3'h4: data_out_reg = 8'b0001_0000; 3'h5: data_out_reg = 8'b0010_0000; 3'h6: data_out_reg = 8'b0100_0000; default: ; endcase end assign data_out = data_out_reg; endmodule
tb仍不變,wave以下:
綜合效果:
看出,和case不完整條件的效果是同樣的。綜合後仿真wave:
這種風格代碼,建模純粹的組合電路不可取,可是用來建模時序電路卻是效果還不錯。
在使用case建模譯碼、選擇電路的時候,經常會碰到這些代碼風格:只在相應的條件下,只對相應的輸出值賦值。代碼以下:
module case_endcase_basic_m( input [2:0] data_in, output [7:0] data_out ); reg [7:0] data_out_reg; always @ (*) begin case( data_in ) 3'h0: data_out_reg[0] = 1'b1; 3'h1: data_out_reg[1] = 1'b1; 3'h2: data_out_reg[2] = 1'b1; 3'h3: data_out_reg[3] = 1'b1; 3'h4: data_out_reg[4] = 1'b1; 3'h5: data_out_reg[5] = 1'b1; 3'h6: data_out_reg[6] = 1'b1; 3'h7: data_out_reg[7] = 1'b1; endcase end assign data_out = data_out_reg; endmodule
tb不變,wave:
在使用dc綜合的時候碰到了一個問題:
綜合出了這樣的電路。經dxzhang幫忙,發現這是優化過得電路,在log中對這有所提示:
dc工具在默認狀況下對電路進行了優化,須要關閉:
set compile_seqmap_propagate_constants false set compile_delete_unloaded_sequential_cells false
綜合效果:
會發現,這種建模風格一樣也產生了latch。綜合的電路能夠分紅兩部分,前面圈出來的的部分和第一種譯碼器同樣,第二部分是一組譯碼器。RS-latch中R接地,恆等於0,只有S有效,s=0時保持,s=1時候賦值爲1。最後全部位都會變成1。
綜合後仿真wave:
如今回過頭來看原來的代碼,每一種狀況只對指定位賦值爲1,其餘的位並無說起,這時候工具認爲是」dont care」,即x。能夠粗略想到最簡單的化簡結果就是每個輸出恆爲1(能夠大致參照卡諾圖化簡過程)。這時候會發現,原來代碼自己就是寫錯了的。但同時這也提供了另外一種構建譯碼器的代碼風格,只須要改動一點便可,以下面一種狀況。
代碼中只在case前一行有所改動。
module case_endcase_basic_mr( input [2:0] data_in, output [7:0] data_out ); reg [7:0] data_out_reg; always @ (*) begin data_out_reg = 8'h00; case( data_in ) 3'h0: data_out_reg[0] = 1'b1; 3'h1: data_out_reg[1] = 1'b1; 3'h2: data_out_reg[2] = 1'b1; 3'h3: data_out_reg[3] = 1'b1; 3'h4: data_out_reg[4] = 1'b1; 3'h5: data_out_reg[5] = 1'b1; 3'h6: data_out_reg[6] = 1'b1; 3'h7: data_out_reg[7] = 1'b1; endcase end assign data_out = data_out_reg; endmodule
tb不變,wave以下:
綜合後電路:
容易看出,這和第一個版本的電路是徹底同樣的,至於一樣使用這種多重賦值的第4種電路中爲什麼多出來一部分邏輯還不清楚。
不過,我我的認爲,可使用這種多重賦值來替換default。或者在某些狀況下,既有default的存在,又有這種多重賦值。