淺析狀態機編碼問題

FPGA越往底層走,越發現很多問題只是知其然,而不知其所以然。狀態機編碼原則就是其中之一。我們在實際開發中,只記住了建議使用獨熱碼(one hot)作爲狀態編碼,至於爲什麼(大概也就記得不容易跑飛),可能早就忘了。

以經典的案例來說明其中的一些問題:

  • 序列檢測,每檢測到一組“11011”,然後輸出一個高電平。

狀態轉移圖如下圖所示:

狀態機的Verilog代碼如下:

module FSM_test(
    input       clk,
    input       rst_n,
    input       d_in,
    output      d_out
    );
    
/*     parameter   S0  = 5'b000000,
                S1  = 5'b000001,
                S2  = 5'b000011,
                S3  = 5'b000010,
                S4  = 5'b000110,
                S5  = 5'b000111; */
                
    parameter   S0  = 5'b00000,
                S1  = 5'b00001,
                S2  = 5'b00010,
                S3  = 5'b00100,
                S4  = 5'b01000,
                S5  = 5'b10000;  
/* parameter   S0  = 5'd0,
            S1  = 5'd1,
            S2  = 5'd2,
            S3  = 5'd3,
            S4  = 5'd4,
            S5  = 5'd5; */
                 
    reg         r_d_out;
    reg [4:0]   cs,ns;
    
    always@(posedge clk or negedge rst_n) begin
        if(rst_n == 1'b0)   begin
            cs <= S0;
        end else begin
            cs <= ns;
        end
    end
    
    always@(*) begin
        ns = 5'dx;
        case(cs) 
            S0: begin
                if(d_in == 1'b1) 
                    ns = S1;
                else
                    ns = S0;
            end
            S1: begin
                if(d_in == 1'b1) 
                    ns = S2;
                else
                    ns = S0;
            end
            S2: begin
                if(d_in == 1'b1) 
                    ns = S2;
                else
                    ns = S3;
            end
            S3: begin
                if(d_in == 1'b1) 
                    ns = S4;
                else
                    ns = S0;
            end
            S4: begin
                if(d_in == 1'b1) 
                    ns = S5;
                else
                    ns = S0;
            end
            S5: begin
                if(d_in == 1'b1) 
                    ns = S2;
                else
                    ns = S3;
            end
            default:
                ns = S0;
        
        endcase
    end
    
    always@(posedge clk or negedge rst_n) begin
        if(rst_n == 1'b0)   begin
            r_d_out <= 1'b0;
        end else if(cs == S5)begin
            r_d_out <= 1'b1;
        end else begin
            r_d_out <= 1'b0;
        end
    end
    
    assign  d_out = r_d_out;
endmodule

上面代碼中,定義了格雷碼、獨熱碼以及二進制碼(序列碼)的狀態編碼方式,本想在vivado下看綜合後的原理圖,發現三種編碼方式綜合後的結果一樣!不符合理論啊,所以想是不是被vivado優化了。果然,查書發現在綜合選項中,“-fsm_extraction”選項爲auto,在auto下,本段狀態就會被優化成格雷碼的編碼方式。

取消該優化之後,僅對比格雷碼和獨熱碼的綜合結果,原理圖如下:

  • 格雷碼

  • 獨熱碼

格雷碼消耗4個LUT和3個Register,而獨熱碼消耗7個LUT和6個Register。不是說獨熱碼更省組合邏輯嗎??爲什麼反而消耗LUT更多。到了這一層,還是不能往下理解,是代碼設計問題還是綜合問題?

做了一個嘗試之後,還是把前人結論和經驗總結一下:

  • 二進制碼:

有過渡狀態,容易跑飛。

  • 格雷碼:

減少過渡狀態,每次只有一位變化,因此可以降低功耗。但是如果當一個狀態到下一個狀態有多種轉換路徑時,就不能保證狀態跳轉時只有一個位變化,這樣就無法發揮格雷碼的特點了。

  • 獨熱碼:

少用組合邏輯,多了寄存器。速度更快、可靠性更好。

至於二進制碼咱不用討論,因爲對於狀態少的情況(小於4),也可以使用,因爲跑飛的概率極其小。

對於格雷碼和獨熱碼,到底應該怎麼選擇才能達到最優綜合?查閱書籍及網絡資料,總結起來就是:

  • 格雷碼:適合所有狀態是順序序列,可以用格雷碼來消除毛刺,但如果有複雜分支判斷,則格雷碼也不能達到消毛刺的目的,簡單的說,格雷碼適合條件不復雜,狀態多的情況;
  • 獨熱碼:消耗較少組合邏輯,消耗更多寄存器,因此在FPGA中有利於速度和可靠性。適合條件複雜,狀態少的情況。

另外,在CPLD中由於組合邏輯多,而寄存器少,所以可能不適合獨熱碼,更適合格雷碼和二進制編碼。

參考文獻

https://www.zhihu.com/question/40994717

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