FPGA學習筆記---時序邏輯與組合邏輯分析比較

       在學習FPGA使用Verilog HDL語言編程時,開始遇到時序邏輯和組合邏輯時概念一看就明白,但是實際使用時還是不清楚到底要用哪個。現在用就一個例子來體會一下這兩者的區別。

     首先先看組合邏輯和時序邏輯的定義。

看完以後還是感覺雲裏霧裏搞不清楚,那麼就不用管它了,直接用例子來說明。

        在這裏設計一個0---9計數器,clk爲輸入時鐘信號,cin爲計數有效信號,也就是說只有當cin爲高電平時,計數器才計數一次。cout爲計數進位信號,當計數值爲9時,計數值再加1的話,就輸出一個進位信號,同時計數值清零。q輸出計數值,輸出值的範圍是0--9。這個計數器類似於數碼管顯示數字時每一個數碼管的顯示範圍,每個數碼管顯示範圍爲0--9,當低位滿10之後,向前一位進1,同時低位清零。

下面開始編寫代碼

首先定義輸入輸出端口

module bcd_counter(
    input   clk,        //時鐘
    input   rst_n,      //復位
    input   cin,        //計數使能
    
    output  cout,       //進位輸出
    output [3:0] q      //計數輸出
);
endmodule

輸入信號有三個 時鐘 clk、復位 rst_n、計數使能 cin。

輸出信號有兩個 進位輸出cout、計數值輸出q,q的計數範圍是0--9,所以q設置爲4位計數器。

下面編寫計數代碼

reg [3:0] cnt;
//BCD碼計數
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        cnt <= 4'd0;
    else if(cin == 1'b1) begin      //計數使能信號有效時 計數器加1
        if(cnt < 4'd9)
            cnt <= cnt + 1'b1;
        else
            cnt <= 4'd0;
     end
     else 
        cnt <= cnt;
end

         cnt存儲計數值,復位後默認值爲0,每次當cin爲高電平時,計數值加1,當計數到9時,計數值清零。cin爲低電平時,計數值保持不變。這樣計數寄存器的值就在0-到9之間循環。

下面編寫進位代碼

always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        cout <= 1'b0;
    else if(cnt == 4'd9 && cin == 1'b1)
        cout <= 1'b1;
    else
        cout <= 1'b0;
end

當計數寄存器的值爲9,同時計數使能信號爲1時,說明已經計夠10次了,需要進位一次,這時cout輸出1。其餘情況下輸出爲0。

最後將寄存器的值連接到輸出端口上

assign q = cnt; 

整體代碼如下

module bcd_counter(
    input   clk,        //時鐘
    input   rst_n,      //復位
    input   cin,        //計數使能
    
    output  cout,       //進位輸出
    output [3:0] q      //計數輸出
);

reg [3:0] cnt;
//BCD碼計數
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        cnt <= 4'd0;
    else if(cin == 1'b1) begin      //計數使能信號有效時 計數器加1
        if(cnt < 4'd9)
            cnt <= cnt + 1'b1;
        else
            cnt <= 4'd0;
     end
     else 
        cnt <= cnt;
end
//進位信號輸出
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        cout <= 1'b0;
    else if(cnt == 4'd9 && cin == 1'b1)
        cout <= 1'b1;
    else
        cout <= 1'b0;
end

assign q = cnt;        //輸出計數值

endmodule

代碼的功能比較簡單,一個always語句產生計數信號,一個always語句產生進位信號。

下面編寫測試文件

`timescale 1ns/1ns
module bcd_counter_tb;
parameter T = 20;

reg sys_clk;
reg sys_rst_n;
reg cin;

wire cout;
wire [3:0] q;

bcd_counter bcd_counter0(
    .clk        (sys_clk),        //時鐘
    .rst_n      (sys_rst_n),      //復位
    .cin        (cin),            //計數使能
    
    .cout       (cout),           //進位輸出
    .q          (q)               //計數輸出
);

initial begin
    sys_rst_n = 1'b0;
    sys_clk = 1'b1;
    #200;
    sys_rst_n = 1'b1;
    repeat(100) begin      
        cin <= 1'b0;              //輸出4個週期低電平
        #(T * 4);   
        cin <= 1'b1;              //輸出1個週期高電平
        #(T);        
    end
    cin <= 1'b0;
    #(200 * T);
    $stop;
end

always #(T/2) sys_clk = ~sys_clk;

endmodule

       測試文件產生一個時鐘信號 sys_clk 和一個計數使能信號 cin,cin信號爲4個時鐘的低電平,然後1個時鐘的高電平.也是說沒5個時鐘週期計數器就會計數一次。

      下來仿真一下,看看輸出波形。

放大波形看看cin和計數值關係

可以看到每個cin信號爲高時,計數值加1,當計數值爲9時,輸出cout信號輸出一個高脈衝。

再放大波形看看cout和cin的輸出時序

可以看到當計數值爲9時,cin信號出現高電平,cout延時一個時鐘週期才輸出的一個高電平。按照正常計數邏輯來說,當低位9再加1時,低位變爲0,同時向高位進1,計數和進位是同時發生的。而這個卻出現了計數和進位不同步的情況。出現這種情況是爲什麼呢?那就要分析分析代碼中的進位信號。

always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        cout <= 1'b0;
    else if(cnt == 4'd9 && cin == 1'b1)
        cout <= 1'b1;
    else
        cout <= 1'b0;
end

進位的always語句執行塊,是在時鐘的上升沿或者復位信號的下降沿纔會進入。

在波形中可以看出,在藍色光標處,時鐘的上升沿cin信號到來,計數值加1,此時計數值爲9,同時cin信號爲1,此時cout信號應該要輸出高電平了,但是由於cout是由時序邏輯控制的,只有在時鐘的上升邊沿always語句纔會執行,所以只有等到下一個時鐘上升沿cout纔有機會輸出高電平。

說明cout信號通過時序邏輯來實現的話,會有延時,不符合設計的實時變化要求,那麼就把cout的實現改成用組合邏輯來實現,看看是什麼效果。

在代碼中將cout改爲組合邏輯

/*
always @(posedge clk or negedge rst_n) begin
    if(rst_n == 1'b0)
        cout <= 1'b0;
    else if(cnt == 4'd9 && cin == 1'b1)
        cout <= 1'b1;
    else
        cout <= 1'b0;
end
*/
將時序邏輯改爲組合邏輯

assign  cout = (cnt == 4'd9 && cin == 1'b1) ? 1'b1 : 1'b0;  

將cout改爲組合邏輯實現,當計數值cnt爲9,同時cin爲高電平時,cout值爲1,否則cout值爲0。

這樣當cin和cnt的值由任何變化時,cout值也跟着會變化,不受到時鐘上升沿的影響。

注意將cout由時序邏輯改爲組合邏輯時,要在初始化中將cout由寄存器類型改爲線網類型。

重新編譯代碼後,查看波形。

這時可以看到cout信號和cin信號會同時變爲高電平,不會有一個時鐘週期的延遲。

當計數值變爲9時,cin由低電平變爲高電平繼續計數時,計數值清0,同時進位輸出也變爲高電平。符合設計的要求。

通過上面的例子可以看到,當輸出信號需要實時跟隨輸入信號變化時,就必須用組合邏輯來實現。其餘情況下用時序邏輯來實現。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章