FPGA Verilog编写状态机学习

1 二段式状态机

1.1 简介:

二段式状态机(Two-phase state machine)是一种常见的状态机实现方式,它将状态机的执行过程分为两个阶段:第一段是组合逻辑,用于确定下一个状态;第二段是时序逻辑,用于寄存状态。

1.2 实现步骤:

定义状态编码: 首先,需要定义状态机的所有可能状态,并为每个状态分配一个唯一的编码。
创建状态寄存器: 需要一个寄存器来存储当前的状态和下一个状态。
编写状态转移逻辑: 这是状态机的第一段,通常是一个组合逻辑块,它根据当前状态和输入信号计算下一个状态。
**编写状态输出:**这是状态机的第二段,通常是一个时序逻辑块,它根据当前状态用always块中的非阻塞赋值输出信号。

1.3 例子

复制代码
module fsm (
    input clk,                // 时钟信号
    input rst_n,              // 异步复位信号,低电平有效
    input condition,          // 控制状态转移的条件信号
    output reg action         // 执行的动作
);

// 定义状态参数
parameter S0 = 2'b00;        // 状态0
parameter S1 = 2'b01;        // 状态1
parameter S2 = 2'b10;        // 状态2

// 寄存当前状态和下一个状态
reg [1:0] current_state, next_state;

// 组合逻辑部分,根据当前状态和输入条件计算下一个状态
always @(*) begin
    next_state = current_state; // 默认保持当前状态
    case (current_state)
        S0: if (condition) next_state = S1;
        S1: if (condition) next_state = S2;
        S2: if (!condition) next_state = S0;
        default: next_state = S0;
    endcase
end

// 时序逻辑部分,在每个时钟边沿根据下一个状态更新当前状态
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        current_state <= S0;  // 异步复位,状态机回到初始状态
        action <= 1'b0;       // 复位动作输出
    end else begin
        current_state <= next_state; // 更新状态
        // 根据当前状态设置动作输出
        case (current_state)
            S0: action <= 1'b0;
            S1: action <= 1'b1;
            S2: action <= 1'bx; // 例如,不确定状态下的输出
            default: action <= 1'bx;
        endcase
    end
end

endmodule

在这个例子中,状态机的状态转移和动作输出都是在组合逻辑和时序逻辑中分别定义的。current_state 和 next_state 是寄存器,用于存储当前状态和下一个状态。condition 是一个输入信号,用于控制状态转移的条件。action 是一个输出信号,表示状态机在当前状态下应该执行的动作。

在组合逻辑部分,使用一个总是块(always @(*))来计算下一个状态。在这个块中,我们使用一个 case 语句来根据当前状态和输入条件来确定下一个状态。

在时序逻辑部分,使用另一个总是块(always @(posedge clk or negedge rst_n))来在每个时钟边沿更新当前状态。在这个块中,首先检查复位信号,如果复位为低,则将状态机重置为初始状态。否则,将 current_state 更新为 next_state。然后使用另一个 case 语句来根据当前状态设置动作输出。

2 三段式状态机

2.1 简介

三段式状态机(Three-phase state machine)是一种更加模块化和可维护的状态机实现方式。它将状态机的执行过程分为三个阶段:
时序逻辑阶段: 在这个阶段,当前状态被寄存,以便在下一个时钟周期中使用。
组合逻辑阶段: 在这个阶段,基于当前状态和输入条件,计算出下一个状态和输出。
**输出逻辑阶段:**在这个阶段,将计算出的输出赋值给输出信号。

2.2 实现步骤

定义状态编码: 首先需要定义状态编码。一般是通过参数或常量来完成的。例如,你可以定义一个3位的参数来表示不同的状态。
声明状态和输出寄存器 :接下来,需要声明用于存储当前状态和下一个状态的寄存器,以及任何输出信号。
时序逻辑部分: 在这一部分,使用时序逻辑来在每个时钟边沿更新当前状态。这通常涉及到将下一个状态寄存器的值赋给当前状态寄存器。
组合逻辑部分 :在这一部分,使用组合逻辑来根据当前状态和输入条件计算下一个状态和输出。这通常是通过一个总是块(always @(*))和一个 case 语句来完成的。
**输出逻辑部分:**在这一部分,使用时序逻辑来更新输出信号。这通常涉及到将计算出的输出赋值给输出信号。

2.3 例子

复制代码
module fsm (
    input clk,                // 时钟信号
    input rst_n,              // 异步复位信号,低电平有效
    input condition,          // 控制状态转移的条件信号
    output reg action         // 执行的动作
);

// 定义状态参数
parameter S0 = 3'b000;       // 状态0
parameter S1 = 3'b001;       // 状态1
parameter S2 = 3'b010;       // 状态2
parameter S3 = 3'b011;       // 状态3
parameter S4 = 3'b100;       // 状态4

// 寄存当前状态和下一个状态
reg [2:0] current_state, next_state;

// 时序逻辑部分,在每个时钟边沿寄存当前状态
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        current_state <= S0;  // 异步复位,状态机回到初始状态
    end else begin
        current_state <= next_state; // 更新状态
    end
end

// 组合逻辑部分,根据当前状态和输入条件计算下一个状态和输出
always @(*) begin
    next_state = current_state; // 默认保持当前状态
    action = 1'b0;              // 默认输出为0
    case (current_state)
        S0: if (condition) begin
                next_state = S1;
                action = 1'b1;
            end
        S1: if (condition) begin
                next_state = S2;
                action = 1'b0;
            end else begin
                next_state = S0;
                action = 1'b0;
            end
        S2: if (condition) begin
                next_state = S3;
                action = 1'b1;
            end
        S3: if (condition) begin
                next_state = S4;
                action = 1'b0;
            end else begin
                next_state = S0;
                action = 1'b0;
            end
        S4: if (!condition) begin
                next_state = S0;
                action = 1'b0;
            end
        default: begin
                next_state = S0;
                action = 1'b0;
            end
    endcase
end

// 输出逻辑部分,将计算出的输出赋值给输出信号
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        action <= 1'b0;       // 异步复位,输出为0
    end else begin
        action <= action;     // 在这里,输出逻辑可以更复杂,例如使用额外的组合逻辑
    end
end

endmodule

在这个例子中,定义了一个有三个状态的状态机,每个状态都可以根据输入条件 condition 转移到另一个状态,并且根据当前状态设置输出 action。

在时序逻辑部分,使用一个总是块(always @(posedge clk or negedge rst_n))来在每个时钟边沿更新当前状态。如果复位信号为低,则状态机被重置为初始状态 S0。

在组合逻辑部分,使用另一个总是块(always @(*))来计算下一个状态和输出。在这个块中,使用一个 case 语句来根据当前状态和输入条件来确定下一个状态和输出。

在输出逻辑部分,再次使用时序逻辑来更新输出信号 action。

3 尽量用三段式编写代码 容易懂 好维护

相关推荐
西岸行者3 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
ZPC82103 天前
docker 镜像备份
人工智能·算法·fpga开发·机器人
ZPC82103 天前
docker 使用GUI ROS2
人工智能·算法·fpga开发·机器人
悠哉悠哉愿意3 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码3 天前
嵌入式学习路线
学习
毛小茛3 天前
计算机系统概论——校验码
学习
babe小鑫3 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms3 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下3 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。3 天前
2026.2.25监控学习
学习