FPGA(四)——状态机

发布于:2025-04-05 ⋅ 阅读:(14) ⋅ 点赞:(0)

FPGA(四)——状态机

一、状态机编程思想

1、基本组成

要素 含义
状态 状态机在任意时刻都处于一个确定的状态,每个状态代表系统的一种工作模式或情况
输入 外部提供的信号,可能会影响当前状态到下一个状态的转移
输出 基于当前状态或状态转移产生的结果,可以是内部使用或输出到外部的信号
状态转移条件 决定从当前状态转移到哪个下一状态的条件

2、状态机类型

类型 特点
摩尔型状态机(Moore State Machine) 输出仅依赖于当前状态。 对于相同的输入,在不同的时钟周期内如果状态相同,则输出也相同。
米利型状态机(Mealy State Machine)

3、状态迁移图

在这里插入图片描述

(1)状态框:用方框表示状态,包括所谓的“现态”和“次态”;

(2)条件及迁移箭头:用箭头表示状态迁移的方向,并在该箭头上标注触发条件;

(3)节点圆圈:当多个箭头指向一个状态时,可以用节点符号(小圆圈)连接汇总;

(4)动作框:用椭圆框表示;

(5)附加条件判断框:用六角菱形框表示;

4、状态机设计步骤

步骤 目的
识别状态 明确需要哪些状态来描述系统的行为
定义输入输出 确定状态机的输入信号和输出信号
绘制状态图 用图形表示所有状态及它们之间如何根据输入信号进行转移
编码状态 为每个状态分配唯一的二进制编码
编写代码 使用硬件描述语言(如Verilog或VHDL)实现状态机
仿真验证 利用仿真工具验证状态机的功能是否正确

5、状态机的写法

在这里插入图片描述

在这里插入图片描述

6、实现注意事项

  • 同步 vs 异步:状态机可以是同步的(状态转移发生在时钟边沿)或者异步的(状态转移可以由非时钟信号触发)。大多数情况下,推荐使用同步状态机以避免亚稳态问题。
  • 复位机制:为了确保状态机能够可靠地启动并恢复到已知状态,通常会包含一个复位信号。
  • 编码选择:对于状态编码的选择(如二进制编码、独热码),会影响到资源使用效率和状态转移的速度。

二、LED流水灯仿真实验

led_flow.v:

module flow_led (
    input wire clk,          // 系统时钟输入(假设50MHz)
    input wire rst_n,        // 异步低电平复位信号(0复位,1工作)
    output reg [7:0] leds    // 8位LED输出,控制8个LED灯
);

// ==============================================
// 状态定义:使用独热码(one-hot)或二进制编码
// 这里使用3位二进制编码表示8个状态
// ==============================================
parameter S0 = 3'b000;  // 第1个LED亮
parameter S1 = 3'b001;  // 第2个LED亮
parameter S2 = 3'b010;  // 第3个LED亮
parameter S3 = 3'b011;  // 第4个LED亮
parameter S4 = 3'b100;  // 第5个LED亮
parameter S5 = 3'b101;  // 第6个LED亮
parameter S6 = 3'b110;  // 第7个LED亮
parameter S7 = 3'b111;  // 第8个LED亮

// ==============================================
// 内部信号定义
// ==============================================
reg [2:0] current_state;    // 当前状态寄存器
reg [2:0] next_state;       // 下一状态组合逻辑
reg [25:0] counter;         // 26位计数器,用于分频产生延时

// ==============================================
// 第一段:状态寄存器(时序逻辑)
// 功能:在时钟上升沿或复位下降沿更新当前状态
// ==============================================
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        // 异步复位,回到初始状态
        current_state <= S0;
        counter <= 26'd0;
    end
    else begin
        // 当计数器达到50_000_000时(约0.2秒@50MHz)切换状态
        if (counter == 26'd50_000_000) begin  
            current_state <= next_state;  // 状态转移
            counter <= 26'd0;             // 计数器清零
        end
        else begin
            counter <= counter + 1;       // 计数器递增
        end
    end
end

// ==============================================
// 第二段:下一状态逻辑(组合逻辑)
// 功能:根据当前状态确定下一状态
// ==============================================
always @(*) begin
    case (current_state)
        S0: next_state = S1;  // 状态0→状态1
        S1: next_state = S2;  // 状态1→状态2
        S2: next_state = S3;  // 状态2→状态3
        S3: next_state = S4;  // 状态3→状态4
        S4: next_state = S5;  // 状态4→状态5
        S5: next_state = S6;  // 状态5→状态6
        S6: next_state = S7;  // 状态6→状态7
        S7: next_state = S0;  // 状态7→状态0(循环)
        default: next_state = S0;  // 默认回到初始状态
    endcase
end

// ==============================================
// 第三段:输出逻辑(时序逻辑)
// 功能:根据当前或下一状态产生输出信号
// 注意:这里使用时序逻辑输出可以避免毛刺
// ==============================================
always @(posedge clk or negedge rst_n) begin
    if (!rst_n) begin
        // 复位时点亮第一个LED
        leds <= 8'b0000_0001;
    end
    else if (counter == 26'd50_000_000) begin
        // 在状态切换时刻更新LED输出
        case (next_state)
            S0: leds <= 8'b0000_0001;  // LED0亮
            S1: leds <= 8'b0000_0010;  // LED1亮
            S2: leds <= 8'b0000_0100;  // LED2亮
            S3: leds <= 8'b0000_1000;  // LED3亮
            S4: leds <= 8'b0001_0000;  // LED4亮
            S5: leds <= 8'b0010_0000;  // LED5亮
            S6: leds <= 8'b0100_0000;  // LED6亮
            S7: leds <= 8'b1000_0000;  // LED7亮
            default: leds <= 8'b0000_0001;  // 默认LED0亮
        endcase
    end
    // 其他时候保持LED状态不变
end

endmodule

测试代码 tb_flow_led.v

`timescale 1ns / 1ps

module tb_flow_led();

// 输入信号
reg clk;          // 时钟信号
reg rst_n;        // 复位信号

// 输出信号
wire [7:0] leds;  // LED输出

// ==============================================
// 实例化被测模块
// 注意:模块名称改为flow_led以匹配设计文件
// ==============================================
flow_led uut (
    .clk(clk),
    .rst_n(rst_n),
    .leds(leds)
);

// ==============================================
// 时钟生成(50MHz)
// ==============================================
initial begin
    clk = 0;
    forever #10 clk = ~clk;  // 10ns半周期=20ns周期(50MHz)
end

// ==============================================
// 测试激励
// ==============================================
initial begin
    // 初始化信号
    rst_n = 0;  // 初始复位
    
    // 波形记录开始
    $dumpfile("tb_flow_led.vcd");
    $dumpvars(0, tb_flow_led);
    
    // 释放复位(100ns后)
    #100 rst_n = 1;
    
    $display("Simulation started at %0t ns", $time);
    
    // 观察LED流水效果
    // 每个状态持续1秒(50,000,000个周期)
    // 观察8个完整周期(8秒模拟时间)
    #8_000_000_000;  // 400ms观察时间(原代码有误,应为8秒=8*50,000,000*20ns)
    
    $display("Simulation finished at %0t ns", $time);
    $stop;
end

endmodule

三、实现效果

FPGA(三)

四、CPLD和FPGA芯片主要技术区别

特性 CPLD FPGA
架构 基于与或阵列(AND-OR array),适合组合逻辑 基于查找表(LUTs)和触发器,支持复杂逻辑
容量和规模 逻辑单元较少,适合简单逻辑设计 逻辑单元多,支持大规模、复杂系统级设计
非易失性 vs 易失性 非易失性(基于EEPROM或Flash),断电后程序保留 易失性(基于SRAM),需要外部配置存储器加载配置文件
功耗 静态功耗较低,适合低功耗场景 功耗较高,尤其是动态功耗
启动速度 启动速度快,无需加载配置文件 启动速度较慢,需从外部存储器加载配置
灵活性 灵活性较低,适合固定功能的设计 灵活性高,支持动态重配置和复杂算法实现
开发难度 开发相对简单,适合小规模逻辑设计 开发复杂度较高,但工具链支持强大
成本 小规模应用成本较低 大规模应用时单位逻辑成本较低,但整体成本较高
适用场合 - 胶合逻辑
- 接口转换
- 简单状态机
- 低功耗、快速启动的应用
- 图像处理
- 视频流媒体
- 硬件加速
- 原型设计
- 复杂算法实现

总结

  • CPLD:适合逻辑简单、对启动速度和功耗要求高的应用场景。
  • FPGA:适合需要高性能、大规模并行处理能力和复杂算法实现的应用场景。

五、hdlbitsFPGA——组合逻辑学习

1、创建一个D触发器

A D flip-flop 是一个存储 bit 并定期更新的电路,位于 clock signal的(通常)正边沿。

在这里插入图片描述

module top_module (
    input clk,    // 时钟输入,用于时序电路
    input d,      // 数据输入
    output reg q  // 数据输出(需要声明为 reg 类型)
);

// 使用时钟控制的 always 块
// 在每个 clk 的上升沿将 d 的值赋给 q
// 时钟控制的 always 块应使用非阻塞赋值(<=)
always @(posedge clk) begin
    q <= d;
end

endmodule

在这里插入图片描述

2、简单状态转换

以下是具有 1 个输入、1 个输出和 4 个状态的 Moore 状态机的状态转换表。使用以下状态编码:A=2’b00、B=2’b01、C=2’b10、D=2’b11。

in=0 in=1
一个 一个 B 0
B C B 0
C 一个 D 0
D C B 1
module top_module(
    input in,
    input [1:0] state,
    output [1:0] next_state,
    output out); //

    parameter A=2'b00, B=2'b01, C=2'b10, D=2'b11;

    // State transition logic: next_state = f(state, in)
    always @(*) begin
        case(state)
            A: next_state = in ? B : A;
            B: next_state = in ? B : C;
            C: next_state = in ? D : A;
            D: next_state = in ? B : C;
            default: next_state = A;
        endcase
    end

    // Output logic: out = f(state) for a Moore state machine
    assign out = (state == D);  // 仅在D状态输出1

endmodule

3、4位移位寄存器

构建一个 4 位移位寄存器 (右移位),具有 asynchronous reset、synchronous load 和 enable。

areset:将 shift 寄存器重置为零。
load:使用 data[3:0] 加载移位寄存器,而不是移位。
ena:向右移动(q[3] 变为零,q[0] 移出并消失)。
q:移位寄存器的内容。
如果 load 和 ena inputs都置位 (1),则 load input 具有更高的优先级

module top_module(
    input clk,
    input areset,  // 异步高电平复位(复位为0)
    input load,    // 同步加载
    input ena,     // 右移使能
    input [3:0] data, // 并行加载数据
    output reg [3:0] q // 移位寄存器输出
);

always @(posedge clk or posedge areset) begin
    if (areset) begin
        // 异步复位,优先级最高
        q <= 4'b0;
    end
    else if (load) begin
        // 同步加载,优先级次之
        q <= data;
    end
    else if (ena) begin
        // 右移操作
        q <= {1'b0, q[3:1]}; // q[3]变为0,其余位右移
    end
    // 如果没有使能信号,保持当前值
end

endmodule

在这里插入图片描述

4、计数器1-12

设计一个具有以下输入和输出的 1-12 计数器:

重置同步高电平有效复位,强制计数器为 1
使将 counter 设置为高位以运行
时钟正边沿触发时钟输入
**问[3:0]**计数器的输出
**c_enable、c_load、c_d[3:0]**控制信号进入提供的 4 位计数器,因此可以验证作是否正确。

module top_module (
    input clk,
    input reset,    // 同步高电平复位
    input enable,   // 计数使能
    output [3:0] Q, // 计数器输出(1-12)
    output c_enable, // 连接到4位计数器的使能
    output c_load,   // 连接到4位计数器的加载
    output [3:0] c_d // 连接到4位计数器的加载数据
);

    // 实例化4位计数器
    count4 the_counter (
        .clk(clk),
        .enable(c_enable),
        .load(c_load),
        .d(c_d),
        .Q(Q)
    );
    
    // 控制逻辑
    always @(*) begin
        if (reset) begin
            // 复位时强制计数器为1
            c_enable = 1'b0;
            c_load = 1'b1;
            c_d = 4'd1;
        end
        else if (enable) begin
            if (Q == 4'd12) begin
                // 达到12时重新加载1
                c_enable = 1'b0;
                c_load = 1'b1;
                c_d = 4'd1;
            end
            else begin
                // 正常计数
                c_enable = 1'b1;
                c_load = 1'b0;
                c_d = 4'd0; // 不使用
            end
        end
        else begin
            // 不使能时保持当前值
            c_enable = 1'b0;
            c_load = 1'b0;
            c_d = 4'd0; // 不使用
        end
    end

endmodule

在这里插入图片描述

5、边缘捕获寄存器

对于 32-bit 向量中的每个 bit,当 input 信号从一个 clock cycle 中的 1 变为下一个 clock cycle 的 0 时捕获。“Capture” 意味着输出将保持 1 ,直到 register 被重置(同步重置)。

每个输出位的行为类似于 SR 触发器: 输出位应在 1 到 0 转换发生后的周期内设置 (至 1)。当 reset 为高电平时,output bit 应在正 clock edge 重置(为 0)。如果上述两个事件同时发生,则 reset 优先。在下面示例波形的最后 4 个周期中,‘reset’ 事件比 ‘set’ 事件早一个周期发生,因此这里没有冲突。

module top_module (
    input clk,
    input reset,
    input [31:0] in,
    output [31:0] out
);

    // 存储上一个时钟周期的输入值
    reg [31:0] prev_in;
    
    always @(posedge clk) begin
        prev_in <= in;  // 记录上一个周期的输入值
        
        if (reset) begin
            out <= 32'b0;  // 同步复位,优先级最高
        end
        else begin
            // 检测每个bit的下降沿(1->0)
            out <= out | (prev_in & ~in);
        end
    end

endmodule

在这里插入图片描述