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 尽量用三段式编写代码 容易懂 好维护

相关推荐
limengshi1383922 小时前
通信工程学习:什么是RIP路由信息协议
网络·网络协议·学习·智能路由器·信息与通信
xiaobuding_QAQ3 小时前
自用Proteus(8.15)常用元器件图示和功能介绍(持续更新...)
单片机·嵌入式硬件·学习·proteus
wei_shuo3 小时前
偏标记学习+图像分类(论文复现)
学习·分类·数据挖掘
Miqiuha4 小时前
lock_guard和unique_lock学习总结
java·数据库·学习
一 乐5 小时前
学籍管理平台|在线学籍管理平台系统|基于Springboot+VUE的在线学籍管理平台系统设计与实现(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·学习
加油,旭杏6 小时前
【中间件学习】fastCG介绍和使用
学习·nginx·fastcgi
limengshi1383926 小时前
通信工程学习:什么是TFTP简单文件传输协议
网络·网络协议·学习·信息与通信
GFCGUO7 小时前
ubuntu18.04运行OpenPCDet出现的问题
linux·python·学习·ubuntu·conda·pip
丝丝不是土豆丝8 小时前
学习 CSS 新的属性 conic-gradient 实现环形进度条
学习
S hh8 小时前
【Linux】进程地址空间
java·linux·运维·服务器·学习