Skip to content

学习verilog状态机的的几种常见写法。

有限状态机是一种用来进行对象行为建模的工具,其作用主要是描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事件。有限状态机被用于描述电路的状态变化。

状态机概览

状态机可分为两类:

  • Mealy 状态机:

    状态机的输出和当前状态和输入相关

  • Moore状态机:

    状态机的输出只和当前状态相关。

状态机的4个要素现态、条件、动作、次态。其中“现态”和“条件”作为产生”次态“和”动作“的原因。

  • 现态:是指当前所处的状态
  • 条件:又称为“事件”。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移
  • 动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
  • 次态:条件满足后要迁往的新状态。次态一旦被激活,就会转变成现态。

在实现过程中需要注意的问题:

  1. 区分”状态“与”动作“:动作时改变后所做出的动作,一般动作执行结束后不会一致保持,但是状态会一直保持,直到下一个状态的改变。

状态机的描述方法分为3种:

  1. 一段式状态机

  2. 二段式状态机

  3. 三段式状态机

  4. 四段式状态机

一段式状态机

在描述比较简单的逻辑转换关系时可以直接使用一段式状态机,一段式状态机的特征是只有一个always的时序逻辑块,状态转移,以及输出的变化都在该always块中进行更改。一段式状态机在软件开发中使用的比较多的,但是在硬件描述中则不是非常推荐。

注意:

在声明状态常量时使用localparam关键字进行声明而不是 parameter,这样可以避免Vivado的综合工具报警告

verilog
module m1(
	input clk_i,
    input rst_n_i;
    output out_o
);
    reg[1:0] state;
    // 表示状态的常量声明,在实际工程中需要对状态名称进行更准确的描述。
    localparam [1:0] S0 = 2'b00;
    localparam [1:0] S1 = 2'b01;
    localparam [1:0] S2 = 2'b10;
    localparam [1:0] S3 = 2'b11;
    
    always @(posedge clk_i) begin
        if (!rst_i) begin
            state <= 2'd0;
            out_o <= 1'b0;
        end
        else begin
            // 使用 case 语句描述状态的变化以及输出的变换
            case (state)
                s0: begin
                    out_r <=1'b0;
					state <= S1;
                end
                S2: begin
                    out_r <=1'b1;
					state <= S3;
                end
                S3: begin
                    out_r <=1'b0;
					state <= S4;
                end
                S4: begin
                    out_r <=1'b1;
					state <= S3;
                end
            endcase
            
        end
    end
    
    assign out_o = out_r;
    
endmodule

二段式状态机

两段式状态机的特征是采用两个 always 模块实现状态机的功能,其中一个 always 采用同步时序逻辑描述状态转移,另一个 always 采用组合逻辑来判断状态条件转移。

verilog
module m1(
	input clk_i,
    input rst_n_i;
    output out_o
);
    reg[1:0] current_state;
    reg[1:0] next_state;
    // 表示状态的常量声明,在实际工程中需要对状态名称进行更准确的描述。
    localparam [1:0] S0 = 2'b00;
    localparam [1:0] S1 = 2'b01;
    localparam [1:0] S2 = 2'b10;
    localparam [1:0] S3 = 2'b11;
    
    // 描述状态的转换
    always @(posedge clk_i) begin
        if(!rst_n_i)
			Current_state<=0;
        else
			Current_state<=next_state;
    end
    
    // 描述下一个状态的转换以及输出的变换
    always @(*) begin
        case (state)
            s0: begin
                out_r =1'b0;
                next_state = S1;
            end
            S2: begin
                out_r =1'b1;
                next_state = S3;
            end
            S3: begin
                out_r =1'b0;
                next_state = S4;
            end
            S4: begin
                out_r =1'b1;
                next_state = S3;
            end
        endcase
    end
    
    assign out_o = out_r;
    
endmodule

三段式状态机

三段式状态机的特征是在第一个 always 模块采用同步时序逻辑方式描述状态转移,第二个 always 采用组合逻辑方式描述状态转移规律,第三个 always 采用组合逻辑方式描述电路的输出。这种状态机的组合逻辑与时序逻辑完全独立。

verilog
module m1(
	input clk_i,
    input rst_n_i;
    output out_o
);
    reg[1:0] current_state;
    reg[1:0] next_state;
    // 表示状态的常量声明,在实际工程中需要对状态名称进行更准确的描述。
    localparam [1:0] S0 = 2'b00;
    localparam [1:0] S1 = 2'b01;
    localparam [1:0] S2 = 2'b10;
    localparam [1:0] S3 = 2'b11;
    
    // 改变当前电路的状态, 这一段一般不用变化
    always @(posedge clk_i) begin
        if(!rst_n_i)
			Current_state<=0;
        else
			Current_state<=next_state;
    end
    
    // 描述状态的转换
    always @(*) begin
        case (Current_state)
            s0: begin
                next_state = S1;
            end
            S2: begin
                next_state = S3;
            end
            S3: begin
                next_state = S4;
            end
            S4: begin
                next_state = S3;
            end
            default : next_state = S0;
        endcase
    end
    // 描述电路输出
    always @(*) begin 
        case (Current_state)
            S0, S1, S2: out_r = 1'b1;
            S3, S4: out_r = 1'b0;
            default: out_r=out_r;
        endcase
    end
    assign out_o = out_r;
    
endmodule

四段式状态机

四段式状态机是在三段式状态机的基础上进行的修改,可以更加清晰地分离状态变化以及输出,并且可以避免写出 state_c <= state_c之类的代码,避免生成锁存器,它包含四个部分:

状态信息的声明:

verilog
localparam IDLE = 4'd0;
localparam S1 = 4'd1;
localparam S2 = 4'd2;
localparam S3 = 4'd3;

时序逻辑部分,该部分的内容在所有代码中都不需要改变,格式化地描述现态和次态的转换。

verilog
reg[3:0] state_c; // 当前的状态寄存器
reg[3:0] state_n; // 下一个状态

always @(posedge clk_sys or negedge rst_n) begin
    if (!rst_n)
        state_c <= IDLE;
    else
        state_c <= state_n;
end

组合逻辑部分,该部分代码描述状态转移的条件:

verilog
always @(*) begin
    case (state_c)
        IDLE: begin
            if (c_idle_s1) 
                state_n = S1;
            else
                state_n = state_c; // 避免生成锁存器
        end
        S1: begin
            if (c_s1_s2) 
                state_n = S2;
            else
                state_n = state_c;
        end
        S2: begin
            if (c_s2_s3) 
                state_n = S3;
            else
                state_n = state_c;
        end
        IDLE: begin
            if (c_s3_idle) 
                state_n = IDLE;
            else
                state_n = state_c;
        end
    endcase
    
end

组合逻辑部分,定义状态转移的信号。注意条件命名规范: c_现态_次态

verilog
assign c_idle_d1 = state_c == IDLE && 转移条件;
assign c_s1_s2 = state_c == S1 && 转移条件;
assign c_s2_s3 = state_c == S2 && 转移条件;
assign c_s3_idle = state_c == S3 && 转移条件;

设计输出信号,此部分不限于使用时序逻辑还是组合逻辑进行描述,只需要清晰就可以了。

一般一个 always 语句只控制一个信号,有多少个输出信号就有多少个always 块

verilog
always @(posedge clk_sys or negedge rst_n) begin
    if (!rst_n)
        out1 <= 1'b0;
    else begin
        case (state_c)
            IDLE, S1, S2: out1 <= 1'b0;
           	S3 : out1 <= 1'b1;
            default : out1 <= 1'b0;
        endcase
    end
    
end

使用组合逻辑的分支判断的时候,每个分支只能造成一个变量发生改变,且保证每个分支中都在改变这个变量,否则,就会综合出锁存器电路。

其他

除了可以使用 localparam 定义参数外还可以使用 typedef定义状态的名称

verilog
// 状态定义
typedef enum {
    STATE_IDLE,
    STATE_A,
    STATE_B
} state_t;

最新更新: