Verilog 幾種代碼風格的建模效果

Verilog畢竟是硬件描述語言,使用Verilog這類HDL語言的目的始終是對電路的建模,並最終獲得工具轉換出來的實際電路,因此寫代碼的過程當中要能抽象出對應的電路。算法

但同時,Verilog畢竟仍是一種程序語言,就像其餘程序語言同樣,因此仍然須要熟悉它的語法特性,這樣才能夠在遇到不熟悉的代碼風格時候,能抽象出相應電路或者找出建模過程當中出錯的地方。工具

本文關注於常見代碼風格的建模效果以及綜合是否改變仿真結果。post

---
系統環境:Centos 6.5
測試工具:Questasim 10.1b
綜合工具:DC 2013.03(針對ASIC) 
波形工具:Verdi 2013測試

----優化

case

1.譯碼器

以3-8譯碼器做爲例子來看case的建模效果。代碼和tb分別以下:spa

// 3-8 decoder
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
// 3-8 decoder testbench
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

case_endcase_basic

DC綜合以後效果圖:code

image

使用 綜合後的網標文件和sdf文件進行後仿真,波形圖以下:blog

case_endcase_basic_post

能夠看出在信號穩定的時候,綜合後的仿真效果和綜合前的是同樣的。而對於兩個不一樣輸入值以前出現的「0」值,能夠判斷是cell的1->0 比0->1的傳播速度要快。get

下面討論下case語句中常見的集中代碼風格差生的影響

2.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沿用上一個。測試後的波形以下:

case_endcase_basic1

能夠看出來data_in=7的時候,data_out的值並無變化。猜想simulator在這種狀況下,保持以前的值。

雖然rtl仿真結果只是簡單的一點變化,可是綜合的效果卻會有比較大的差別:

case_endcase_basic1_sch

 

明顯感受到要複雜了不少,同時出現了latch,並且latch前的邏輯會比較複雜。除此,代碼中去掉的那種狀況,電路的變更也對應於圈出的地方,這條路徑最後輸出爲data_out[7],可從工具綜合出來的電路看出,data_out[7]將一直爲0。

進行綜合後仿真,波形圖以下:

case_endcase_basic1_post

能夠看出後仿後的波形中,data_out延時要比第一種狀況下的延時長。

3 case 不徹底條件+default

若是能告訴工具 在沒有列出的條件狀況下 的輸出值,就能夠避免產生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仍然同上。波形以下:

case_endcase_basic2

綜合後的效果:

case_endcase_basic2_sch

能夠看出,和第一種狀況下的電路結構是同樣的。由於電路效果是相同的。綜合後仿真的波形圖,也如第一種狀況。

case_endcase_basic2_post

4 case 不徹底條件+輸出初始化

除了使用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:

case_endcase_basic3

綜合效果:

 

case_endcase_basic3_sch

相比較於default,這種風格的代碼,綜合的效果多少有點不同(圈出來的地方),但從下面的綜合後仿真來看邏輯功能仍是同樣的。單純從電路圖上能夠估計此種風格電路的效果關鍵路徑的延遲會較前一種要多一些。

case_endcase_basic3_post

從DC粗略綜合後的簡略時序報告來看,也確實後者的延遲長一點。

case_endcase_basic2_timing_rptcase_endcase_basic3_timing_rpt

再回頭看綜合後仿真的波形圖,當data_in=7的狀況下,data_out的輸出要比default時候的長一些。

同時查看生成latch時候的關鍵路徑的延遲,單純latch就有1.31 。

至於爲什麼有這種差別,由於對DC的綜合算法不瞭解,也很差下定論。至於再複雜些狀況會不會差別小或者後者效果好,並無驗證,不過我的仍是建議使用default。

5 case 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_endcase_basic2_m

綜合效果:

case_endcase_basic2_m_sch

看出,和case不完整條件的效果是同樣的。綜合後仿真wave:

case_endcase_basic2_m_post

這種風格代碼,建模純粹的組合電路不可取,可是用來建模時序電路卻是效果還不錯。

6 譯碼器變體(錯誤版本)

在使用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:

case_endcase_basic_m

在使用dc綜合的時候碰到了一個問題:

case_endcase_basic_m_sch_opt

綜合出了這樣的電路。經dxzhang幫忙,發現這是優化過得電路,在log中對這有所提示:

case_endcase_basic_m_dc_log

dc工具在默認狀況下對電路進行了優化,須要關閉:

set compile_seqmap_propagate_constants false
set compile_delete_unloaded_sequential_cells false

綜合效果:

case_endcase_basic_m_sch

會發現,這種建模風格一樣也產生了latch。綜合的電路能夠分紅兩部分,前面圈出來的的部分和第一種譯碼器同樣,第二部分是一組譯碼器。RS-latch中R接地,恆等於0,只有S有效,s=0時保持,s=1時候賦值爲1。最後全部位都會變成1。

綜合後仿真wave:

case_endcase_basic_m_post

 

如今回過頭來看原來的代碼,每一種狀況只對指定位賦值爲1,其餘的位並無說起,這時候工具認爲是」dont care」,即x。能夠粗略想到最簡單的化簡結果就是每個輸出恆爲1(能夠大致參照卡諾圖化簡過程)。這時候會發現,原來代碼自己就是寫錯了的。但同時這也提供了另外一種構建譯碼器的代碼風格,只須要改動一點便可,以下面一種狀況。

7 譯碼器變體(正確)

代碼中只在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以下:

case_endcase_basic_mr

綜合後電路:

case_endcase_basic_mr_sch

容易看出,這和第一個版本的電路是徹底同樣的,至於一樣使用這種多重賦值的第4種電路中爲什麼多出來一部分邏輯還不清楚。

不過,我我的認爲,可使用這種多重賦值來替換default。或者在某些狀況下,既有default的存在,又有這種多重賦值。

相關文章
相關標籤/搜索