Verilog 狀態(tài)機

2022-05-17 13:41 更新

關(guān)鍵詞:狀態(tài)機,售賣機

有限狀態(tài)機(Finite-State Machine,F(xiàn)SM),簡稱狀態(tài)機,是表示有限個狀態(tài)以及在這些狀態(tài)之間的轉(zhuǎn)移和動作等行為的數(shù)學(xué)模型。狀態(tài)機不僅是一種電路的描述工具,而且也是一種思想方法,在電路設(shè)計的系統(tǒng)級和 RTL 級有著廣泛的應(yīng)用。

狀態(tài)機類型

Verilog 中狀態(tài)機主要用于同步時序邏輯的設(shè)計,能夠在有限個狀態(tài)之間按一定要求和規(guī)律切換時序電路的狀態(tài)。狀態(tài)的切換方向不但取決于各個輸入值,還取決于當(dāng)前所在狀態(tài)。 狀態(tài)機可分為 2 類:?Moore ?狀態(tài)機和 ?Mealy ?狀態(tài)機。

Moore 型狀態(tài)機

?Moore ?型狀態(tài)機的輸出只與當(dāng)前狀態(tài)有關(guān),與當(dāng)前輸入無關(guān)。

輸出會在一個完整的時鐘周期內(nèi)保持穩(wěn)定,即使此時輸入信號有變化,輸出也不會變化。輸入對輸出的影響要到下一個時鐘周期才能反映出來。這也是 ?Moore ?型狀態(tài)機的一個重要特點:輸入與輸出是隔離開來的。


Mealy 型狀態(tài)機

?Mealy ?型狀態(tài)機的輸出,不僅與當(dāng)前狀態(tài)有關(guān),還取決于當(dāng)前的輸入信號。

?Mealy ?型狀態(tài)機的輸出是在輸入信號變化以后立刻發(fā)生變化,且輸入變化可能出現(xiàn)在任何狀態(tài)的時鐘周期內(nèi)。因此,同種邏輯下,?Mealy ?型狀態(tài)機輸出對輸入的響應(yīng)會比 ?Moore ?型狀態(tài)機早一個時鐘周期。


狀態(tài)機設(shè)計流程

根據(jù)設(shè)計需求畫出狀態(tài)轉(zhuǎn)移圖,確定使用狀態(tài)機類型,并標(biāo)注出各種輸入輸出信號,更有助于編程。一般使用最多的是 ?Mealy ?型 3 段式狀態(tài)機,下面用通過設(shè)計一個自動售賣機的具體實例來說明狀態(tài)機的設(shè)計過程。

自動售賣機

自動售賣機的功能描述如下:

飲料單價 2 元,該售賣機只能接受 0.5 元、1 元的硬幣。考慮找零和出貨。投幣和出貨過程都是一次一次的進行,不會出現(xiàn)一次性投入多幣或一次性出貨多瓶飲料的現(xiàn)象。每一輪售賣機接受投幣、出貨、找零完成后,才能進入到新的自動售賣狀態(tài)。

該售賣機的工作狀態(tài)轉(zhuǎn)移圖如下所示,包含了輸入、輸出信號狀態(tài)。

其中,coin = 1 代表投入了 0.5 元硬幣,coin = 2 代表投入了 1 元硬幣。


狀態(tài)機設(shè)計:3 段式(推薦)

狀態(tài)機設(shè)計如下:

  1. 首先,根據(jù)狀態(tài)機的個數(shù)確定狀態(tài)機編碼。利用編碼給狀態(tài)寄存器賦值,代碼可讀性更好。
  2. 狀態(tài)機第一段,時序邏輯,非阻塞賦值,傳遞寄存器的狀態(tài)。
  3. 狀態(tài)機第二段,組合邏輯,阻塞賦值,根據(jù)當(dāng)前狀態(tài)和當(dāng)前輸入,確定下一個狀態(tài)機的狀態(tài)。
  4. 狀態(tài)機第三代,時序邏輯,非阻塞賦值,因為是 ?Mealy ?型狀態(tài)機,根據(jù)當(dāng)前狀態(tài)和當(dāng)前輸入,確定輸出信號。

// vending-machine
// 2 yuan for a bottle of drink
// only 2 coins supported: 5 jiao and 1 yuan
// finish the function of selling and changing

module  vending_machine_p3  (
    input           clk ,
    input           rstn ,
    input [1:0]     coin ,     //01 for 0.5 jiao, 10 for 1 yuan

    output [1:0]    change ,
    output          sell    //output the drink
    );

    //machine state decode
    parameter            IDLE   = 3'd0 ;
    parameter            GET05  = 3'd1 ;
    parameter            GET10  = 3'd2 ;
    parameter            GET15  = 3'd3 ;

    //machine variable
    reg [2:0]            st_next ;
    reg [2:0]            st_cur ;

    //(1) state transfer
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            st_cur      <= 'b0 ;
        end
        else begin
            st_cur      <= st_next ;
        end
    end

    //(2) state switch, using block assignment for combination-logic
    //all case items need to be displayed completely    
    always @(*) begin
        //st_next = st_cur ;//如果條件選項考慮不全,可以賦初值消除latch
        case(st_cur)
            IDLE:
                case (coin)
                    2'b01:     st_next = GET05 ;
                    2'b10:     st_next = GET10 ;
                    default:   st_next = IDLE ;
                endcase
            GET05:
                case (coin)
                    2'b01:     st_next = GET10 ;
                    2'b10:     st_next = GET15 ;
                    default:   st_next = GET05 ;
                endcase

            GET10:
                case (coin)
                    2'b01:     st_next = GET15 ;
                    2'b10:     st_next = IDLE ;
                    default:   st_next = GET10 ;
                endcase
            GET15:
                case (coin)
                    2'b01,2'b10:
                               st_next = IDLE ;
                    default:   st_next = GET15 ;
                endcase
            default:    st_next = IDLE ;
        endcase
    end

    //(3) output logic, using non-block assignment
    reg  [1:0]   change_r ;
    reg          sell_r ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            change_r       <= 2'b0 ;
            sell_r         <= 1'b0 ;
        end
        else if ((st_cur == GET15 && coin ==2'h1)
               || (st_cur == GET10 && coin ==2'd2)) begin
            change_r       <= 2'b0 ;
            sell_r         <= 1'b1 ;
        end
        else if (st_cur == GET15 && coin == 2'h2) begin
            change_r       <= 2'b1 ;
            sell_r         <= 1'b1 ;
        end
        else begin
            change_r       <= 2'b0 ;
            sell_r         <= 1'b0 ;
        end
    end
    assign       sell    = sell_r ;
    assign       change  = change_r ;

endmodule

testbench 設(shè)計如下。仿真中模擬了 4 種情景,分別是:

case1 對應(yīng)連續(xù)輸入 4 個 5 角硬幣;case2 對應(yīng) 1 元 - 5 角 - 1 元的投幣順序;case3 對應(yīng) 5 角 - 1 元 - 5 角的投幣順序;case4 對應(yīng)連續(xù) 3 個 5 角然后一個 1 元的投幣順序。

`timescale 1ns/1ps

module test ;
    reg          clk;
    reg          rstn ;
    reg [1:0]    coin ;
    wire [1:0]   change ;
    wire         sell ;

    //clock generating
    parameter    CYCLE_200MHz = 10 ; //
    always begin
        clk = 0 ; #(CYCLE_200MHz/2) ;
        clk = 1 ; #(CYCLE_200MHz/2) ;
    end

    //motivation generating
    reg [9:0]    buy_oper ; //store state of the buy operation
    initial begin
        buy_oper  = 'h0 ;
        coin      = 2'h0 ;
        rstn      = 1'b0 ;
        #8 rstn   = 1'b1 ;
        @(negedge clk) ;

        //case(1) 0.5 -> 0.5 -> 0.5 -> 0.5
        #16 ;
        buy_oper  = 10'b00_0101_0101 ;
        repeat(5) begin
            @(negedge clk) ;
            coin      = buy_oper[1:0] ;
            buy_oper  = buy_oper >> 2 ;
        end

        //case(2) 1 -> 0.5 -> 1, taking change
        #16 ;
        buy_oper  = 10'b00_0010_0110 ;
        repeat(5) begin
            @(negedge clk) ;
            coin      = buy_oper[1:0] ;
            buy_oper  = buy_oper >> 2 ;
        end

        //case(3) 0.5 -> 1 -> 0.5
        #16 ;
        buy_oper  = 10'b00_0001_1001 ;
        repeat(5) begin
            @(negedge clk) ;
            coin      = buy_oper[1:0] ;
            buy_oper  = buy_oper >> 2 ;
        end

        //case(4) 0.5 -> 0.5 -> 0.5 -> 1, taking change
        #16 ;
        buy_oper  = 10'b00_1001_0101 ;
        repeat(5) begin
            @(negedge clk) ;
            coin      = buy_oper[1:0] ;
            buy_oper  = buy_oper >> 2 ;
        end
    end

   //(1) mealy state with 3-stage
    vending_machine_p3    u_mealy_p3     (
        .clk              (clk),
        .rstn             (rstn),
        .coin             (coin),
        .change           (change),
        .sell             (sell)
        );

   //simulation finish
   always begin
      #100;
      if ($time >= 10000)  $finish ;
   end

endmodule // test

仿真結(jié)果如下:

由圖可知,代表出貨動作的信號 sell 都能在投幣完畢后正常的拉高,而代表找零動作的信號 change 也都能根據(jù)輸入的硬幣場景輸出正確的是否找零信號。


狀態(tài)機修改:2 段式

將 3 段式狀態(tài)機 2、3 段描述合并,其他部分保持不變,狀態(tài)機就變成了 2 段式描述。

修改部分如下:

//(2) state switch, and output logic
//all using block assignment for combination-logic
reg  [1:0]   change_r ;
reg          sell_r ;
always @(*) begin //all case items need to be displayed completely
    case(st_cur)
        IDLE: begin
            change_r     = 2'b0 ;
            sell_r       = 1'b0 ;
            case (coin)
                2'b01:     st_next = GET05 ;
                2'b10:     st_next = GET10 ;
                default:   st_next = IDLE ;
            endcase // case (coin)
        end
        GET05: begin
            change_r     = 2'b0 ;
            sell_r       = 1'b0 ;
            case (coin)
                2'b01:     st_next = GET10 ;
                2'b10:     st_next = GET15 ;
                default:   st_next = GET05 ;
            endcase // case (coin)
        end

        GET10:
            case (coin)
                2'b01:     begin
                    st_next      = GET15 ;
                    change_r     = 2'b0 ;
                    sell_r       = 1'b0 ;
                end
                2'b10:     begin
                    st_next      = IDLE ;
                    change_r     = 2'b0 ;
                    sell_r       = 1'b1 ;
                end
                default:   begin
                    st_next      = GET10 ;
                    change_r     = 2'b0 ;
                    sell_r       = 1'b0 ;
                end
            endcase // case (coin)

        GET15:
            case (coin)
                2'b01: begin
                    st_next     = IDLE ;
                    change_r    = 2'b0 ;
                    sell_r      = 1'b1 ;
                end
                2'b10:     begin
                    st_next     = IDLE ;
                    change_r    = 2'b1 ;
                    sell_r      = 1'b1 ;
                end
                default:   begin
                    st_next     = GET15 ;
                    change_r    = 2'b0 ;
                    sell_r      = 1'b0 ;
                end
            endcase
        default:  begin
            st_next     = IDLE ;
            change_r    = 2'b0 ;
            sell_r      = 1'b0 ;
        end

    endcase
end

將上述修改的新模塊例化到 3 段式的 testbench 中即可進行仿真,結(jié)果如下:

由圖可知,出貨信號 sell 和 找零信號 change 相對于 3 段式狀態(tài)機輸出提前了一個時鐘周期,這是因為輸出信號都是阻塞賦值導(dǎo)致的。

如圖中紅色圓圈部分,輸出信號都出現(xiàn)了干擾脈沖,這是因為輸入信號都是異步的,而且輸出信號是組合邏輯輸出,沒有時鐘驅(qū)動。

實際中,如果輸入信號都是與時鐘同步的,這種干擾脈沖是不會出現(xiàn)的。如果是異步輸入信號,首先應(yīng)當(dāng)對信號進行同步。


狀態(tài)機修改:1 段式(慎用)

將 3 段式狀態(tài)機 1、 2、3 段描述合并,狀態(tài)機就變成了 1 段式描述。

修改部分如下:

    //(1) using one state-variable do describe
    reg  [1:0]   change_r ;
    reg          sell_r ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            st_cur     <= 'b0 ;
            change_r   <= 2'b0 ;
            sell_r     <= 1'b0 ;
        end
        else begin
            case(st_cur)

            IDLE: begin
                change_r  <= 2'b0 ;
                sell_r    <= 1'b0 ;
                case (coin)
                    2'b01:     st_cur <= GET05 ;
                    2'b10:     st_cur <= GET10 ;
                endcase
            end
            GET05: begin
                case (coin)
                    2'b01:     st_cur <= GET10 ;
                    2'b10:     st_cur <= GET15 ;
                endcase
            end

            GET10:
                case (coin)
                    2'b01:     st_cur   <=  GET15 ;
                    2'b10:     begin
                        st_cur   <= IDLE ;
                        sell_r   <= 1'b1 ;
                    end
                endcase

            GET15:
                case (coin)
                    2'b01:     begin
                        st_cur   <= IDLE ;
                        sell_r   <= 1'b1 ;
                    end
                    2'b10:     begin
                        st_cur   <= IDLE ;
                        change_r <= 2'b1 ;
                        sell_r   <= 1'b1 ;
                    end
                endcase

            default:  begin
                  st_cur    <= IDLE ;
            end

            endcase // case (st_cur)
        end // else: !if(!rstn)
    end

將上述修改的新模塊例化到 3 段式的 testbench 中即可進行仿真,結(jié)果如下:

由圖可知,輸出信號與 3 段式狀態(tài)機完全一致。

1 段式狀態(tài)機的缺點就是許多種邏輯糅合在一起,不易后期的維護。當(dāng)狀態(tài)機和輸出信號較少時,可以嘗試此種描述方式。


狀態(tài)機修改:Moore 型

如果使用 ?Moore ?型狀態(tài)機描述售賣機的工作流程,那么還需要再增加 2 個狀態(tài)編碼,用以描述 ?Mealy ?狀態(tài)機輸出時的輸入信號和狀態(tài)機狀態(tài)。

3 段式 ?Moore ?型狀態(tài)機描述的自動售賣機 Verilog 代碼如下:

module  vending_machine_moore    (
    input           clk ,
    input           rstn ,
    input [1:0]     coin ,     //01 for 0.5 jiao, 10 for 1 yuan

    output [1:0]    change ,
    output          sell    //output the drink
    );

    //machine state decode
    parameter            IDLE   = 3'd0 ;
    parameter            GET05  = 3'd1 ;
    parameter            GET10  = 3'd2 ;
    parameter            GET15  = 3'd3 ;
    // new state for moore state-machine
    parameter            GET20  = 3'd4 ;
    parameter            GET25  = 3'd5 ;

    //machine variable
    reg [2:0]            st_next ;
    reg [2:0]            st_cur ;

    //(1) state transfer
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            st_cur      <= 'b0 ;
        end
        else begin
            st_cur      <= st_next ;
        end
    end

    //(2) state switch, using block assignment for combination-logic
    always @(*) begin //all case items need to be displayed completely
        case(st_cur)
            IDLE:
                case (coin)
                    2'b01:     st_next = GET05 ;
                    2'b10:     st_next = GET10 ;
                    default:   st_next = IDLE ;
                endcase
            GET05:
                case (coin)
                    2'b01:     st_next = GET10 ;
                    2'b10:     st_next = GET15 ;
                    default:   st_next = GET05 ;
                endcase

            GET10:
                case (coin)
                    2'b01:     st_next = GET15 ;
                    2'b10:     st_next = GET20 ;
                    default:   st_next = GET10 ;
                endcase
            GET15:
                case (coin)
                    2'b01:     st_next = GET20 ;
                    2'b10:     st_next = GET25 ;
                    default:   st_next = GET15 ;
                endcase
            GET20:         st_next = IDLE ;
            GET25:         st_next = IDLE ;
            default:       st_next = IDLE ;
        endcase // case (st_cur)
    end // always @ (*)

   // (3) output logic,
   // one cycle delayed when using non-block assignment
    reg  [1:0]   change_r ;
    reg          sell_r ;
    always @(posedge clk or negedge rstn) begin
        if (!rstn) begin
            change_r       <= 2'b0 ;
            sell_r         <= 1'b0 ;
        end
        else if (st_cur == GET20 ) begin
            sell_r         <= 1'b1 ;
        end
        else if (st_cur == GET25) begin
            change_r       <= 2'b1 ;
            sell_r         <= 1'b1 ;
        end
        else begin
            change_r       <= 2'b0 ;
            sell_r         <= 1'b0 ;
        end
    end
    assign       sell    = sell_r ;
    assign       change  = change_r ;

endmodule

將上述修改的 ?Moore ?狀態(tài)機例化到 3 段式的 testbench 中即可進行仿真,結(jié)果如下:

由圖可知,輸出信號與 ?Mealy ?型 3 段式狀態(tài)機相比延遲了一個時鐘周期,這是因為進入到新增加的編碼狀態(tài)機時需要一個時鐘周期的時延。此時,輸出再用非阻塞賦值就會導(dǎo)致最終的輸出信號延遲一個時鐘周期。這也屬于 ?Moore ?型狀態(tài)機的特點。


輸出信號賦值時,用阻塞賦值,則可以提前一個時鐘周期。

輸出邏輯修改如下:

    // (3.2) output logic, using block assignment
    reg  [1:0]   change_r ;
    reg          sell_r ;
    always @(*) begin
        change_r  = 'b0 ;
        sell_r    = 'b0 ; //not list all condition, initializing them
        if (st_cur == GET20 ) begin
            sell_r         = 1'b1 ;
        end
        else if (st_cur == GET25) begin
            change_r       = 2'b1 ;
            sell_r         = 1'b1 ;
        end
    end

輸出信號阻塞賦值的仿真結(jié)果如下:

由圖可知,輸出信號已經(jīng)和 3 段式 ?Mealy ?型狀態(tài)機一致。


點擊這里下載源碼


以上內(nèi)容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號