【FPGA开发】利用状态机思想点亮流水灯/初学hdlbitsFPGA教程网站

一、状态机思想介绍

状态机是一种用于描述系统行为的形式化模型,它将系统抽象为有限的状态 ,并通过状态转移 来响应外部输入或事件。其核心思想是:系统在任何时刻只处于一个确定的状态,且在不同状态之间按规则切换。状态机是处理明确、离散状态转换问题的利器,适合任何需要结构化行为模型的场景。

1.1 状态机核心概念

(1)状态(State)

表示系统所处的某种模式或条件。例如:

  • 红绿灯的「红灯」「绿灯」「黄灯」。

  • 门的「打开」「关闭」「锁定」。

(2)事件(Event)

触发状态转移的输入或动作。例如:

  • 按下按钮(事件)使门从「锁定」转移到「打开」。

  • 定时器超时(事件)使红绿灯从「绿灯」转移到「黄灯」。

(3)转移(Transition)

定义状态如何因事件而改变。通常表示为:

  • 当前状态 + 事件 → 执行动作 + 下一状态。

(4)动作(Action)

在状态转移时执行的操作(可选)。例如:

  • 进入「红灯」状态时启动倒计时。

1.2 状态机类型

(1)有限状态机(FSM)

  • 状态数量有限。

  • 分为两类:

  • Moore型:输出仅依赖当前状态。

  • Mealy型:输出依赖当前状态和输入事件。

(2)分层状态机(HFSM)

  • 状态可以嵌套子状态,简化复杂逻辑。

  • 例如:汽车「行驶」状态包含「加速」「巡航」等子状态。

(3)并发状态机

  • 多个状态机并行运行,独立处理不同事件。

1.3 状态机应用场景

(1)嵌入式系统:电梯控制、交通灯管理。

(2)游戏开发:NPC行为逻辑(如「巡逻→攻击→逃跑」)。

(3)网络协议:TCP连接状态(建立、传输、关闭)。

(4)用户界面:页面跳转逻辑(如登录流程)。

以红绿灯来进行简单示例:

复制代码
状态:红灯、黄灯、绿灯
事件:定时器超时

转移规则:
1. 红灯 →(超时)→ 绿灯
2. 绿灯 →(超时)→ 黄灯
3. 黄灯 →(超时)→ 红灯

1.4 状态机的优点

(1)逻辑清晰:将复杂行为分解为离散状态。

(2)易于扩展:新增状态或事件不影响其他部分。

(3)可维护性:状态隔离,便于调试。

二、利用状态机思想点亮流水灯

2.1 设计思路

定义4个状态(S0, S1, S2, S3),每个状态对应点亮一个LED。定时器每0.5秒切换一次状态(假设FPGA时钟为50MHz,定时器计数25,000,000次),然后设置异步复位,初始状态为S0。

2.2 代码演示

verilog流水灯代码:

sql 复制代码
module LED(
    input wire clk,          // 50MHz时钟
    input wire reset_n,      // 异步低电平复位
    output reg [3:0] leds    // 4个LED(低电平点亮)
);

// 状态定义
localparam S0 = 2'b00;
localparam S1 = 2'b01;
localparam S2 = 2'b10;
localparam S3 = 2'b11;

reg [1:0] current_state;    // 当前状态
reg [1:0] next_state;       // 下一状态
reg [24:0] counter;         // 定时器计数器(25MHz计数)

// 状态转移逻辑
always @(posedge clk or negedge reset_n) begin
    if (!reset_n) begin
        current_state <= S0;
        counter <= 0;
    end else begin
        // 定时器计数
        if (counter >= 25_000_000 - 1) begin
            counter <= 0;
            current_state <= next_state;
        end else begin
            counter <= counter + 1;
        end
    end
end

// 下一状态逻辑
always @(*) begin
    case (current_state)
        S0: next_state = S1;
        S1: next_state = S2;
        S2: next_state = S3;
        S3: next_state = S0;
        default: next_state = S0;
    endcase
end

// 输出逻辑(Moore型:输出仅依赖状态)
always @(*) begin
    case (current_state)
        S0: leds = 4'b1110;  // LED0亮
        S1: leds = 4'b1101;  // LED1亮
        S2: leds = 4'b1011;  // LED2亮
        S3: leds = 4'b0111;  // LED3亮
        default: leds = 4'b1111;
    endcase
end

endmodule

测试代码:

sql 复制代码
`timescale 1ns / 1ps

module LED_tb;

// 输入信号
reg clk;
reg reset_n;

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

// 实例化被测模块
LED uut (
    .clk(clk),
    .reset_n(reset_n),
    .leds(leds)
);

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

// 复位和测试逻辑
initial begin
    reset_n = 0;  // 初始复位
    #100 reset_n = 1;  // 释放复位

    // 仿真运行足够长时间(观察4个状态切换)
    #200_000_000;  // 200ms(实际需仿真更长时间)
    $stop;
end

// 监控输出
initial begin
    $monitor("Time = %t, State = %b, LEDs = %b", $time, uut.current_state, leds);
end

endmodule

2.3 modesim仿真测试

2.3.1 仿真环境调试

首先要指定仿真工具,找到Tools→Options→EDA Tool Options

点击Modesim Altera,确保路径指向安装目录(如C:\intelFPGA\modelsim_ase\win32aloem)

设置仿真参数,点击Assignments→Settings

按下图依次进行修改

创建测试文件 LED_tb.v,将其放在工程根目录下(步骤和创建LED.v一致)

然后添加Testbench文件,点击Test Benches→New

按照下图步骤进行添加,注意Top level module in test bench中必须与Testbench模块名一致

然后点击LED_tb.v文件,Open然后Add即可关联测试文件到Quartus工程

2.3.2 编译启动仿真

点击蓝色三角符号开始编译,编译成功后,Quartus会自动调用ModelSim并加载仿真文件。如果未自动启动,点击 Tools → Run Simulation Tool → RTL Simulation(功能仿真),ModelSim将打开并加载Testbench和设计文件。

(正在运行中)

生成波形图,可以看到信号变化情况(F可以观察全局波形,I可以放大细节区域)

具体观察变化情况:

如上图,可以发现四个信号的变化情况:

  • 时钟(clk):确认频率为50MHz(周期20ns)。

  • 复位(reset_n):前100ns为低电平,之后拉高。

  • 状态(current_state):每0.5秒(25,000,000个时钟周期)切换一次。

  • LED输出(leds):按1110→1101→1011→0111循环变化。

烧录结果:

状态机思想点亮流水灯

三、CPLD与FPGA芯片探索

3.1 CPLD芯片介绍

CPLD (Complex Programmable Logic Device,复杂可编程逻辑器件)是一种基于 可编程与或阵列(PAL/GAL结构) 的半定制集成电路,主要用于实现中小规模的数字逻辑功能。它介于简单PLD(如PAL、GAL)和FPGA之间,适合低复杂度、高确定性的控制场景。

3.1.1 CPLD的核心特点
特性 说明
架构 基于 乘积项(Product-Term)逻辑,由可编程与阵列和固定或阵列组成。逻辑块通过全局互联连接,延迟固定且可预测。
逻辑容量 较小(通常等效 几十到几千个逻辑门),适合简单控制逻辑。
非易失性 配置信息存储在 Flash或EEPROM 中,上电即运行,无需外部配置芯片。
低功耗 静态功耗极低,适合电池供电设备。
高可靠性 抗干扰能力强,适用于工业环境。
快速响应 信号传输延迟固定(纳秒级),适合实时控制。
3.1.2 CPLD的典型应用

(1) 胶合逻辑(Glue Logic)

  • 替代传统的 74系列逻辑芯片 ,实现电平转换、信号整形、总线仲裁等。
    示例:

    • 连接不同电压的MCU和传感器(如3.3V ↔ 5V转换)。

    • 扩展I/O口(如用CPLD实现多路复用)。

(2) 状态机与控制逻辑

  • 实现 确定性高的控制逻辑 ,如电机驱动、电源管理、按键扫描等。
    示例:

    • 工业PLC的IO控制模块。

    • 智能家居设备的按键防抖逻辑。

(3) 接口协议转换

  • 桥接不同通信协议(如UART转SPI、I2C主从切换)。
    示例:

    • 将并行总线转换为串行LVDS信号。

(4) 低功耗设备

  • 因 上电即运行 且 静态功耗低 ,常用于便携设备。
    示例:

    • 智能手表的电源管理。

    • 无线传感器网络的唤醒控制。

3.2 CPLD与FPGA芯片区别与适用场景分析

CPLD和FPGA(现场可编程门阵列)是两种主流的可编程逻辑器件,尽管功能相似,但在架构、性能和应用场景上存在显著差异。

3.2.1 主要技术区别
特性 CPLD FPGA
架构 基于 乘积项(PAL/GAL结构),逻辑块通过全局互联连接 基于 查找表(LUT)+ 寄存器,逻辑单元阵列通过可编程互联矩阵连接
逻辑容量 较小(通常几百到几千逻辑门) 较大(数万到数百万逻辑门,支持复杂设计)
时序特性 固定延迟(确定性高),适合同步逻辑 延迟可变(依赖布线),需时序约束优化
存储资源 少量嵌入式存储(如触发器) 丰富(Block RAM、分布式RAM)
DSP/算术能力 弱(依赖逻辑单元实现) 强(内置DSP硬核、乘法器)
功耗 静态功耗低,适合低功耗设计 动态功耗高,需优化设计
配置方式 非易失性(EEPROM/Flash,上电即运行) 易失性(SRAM,需外挂配置芯片)
灵活性 逻辑固定,适合简单控制逻辑 高度灵活,支持动态重构
3.2.2 两者适用场景

1. CPLD的典型应用

  • 简单控制逻辑

    地址译码、总线接口、状态机控制(如PCI接口协议转换)。

  • 实时性要求高的场景

    工业控制(PLC)、电机驱动(因固定延迟,响应时间可预测)。

  • 低功耗与即时启动

    便携设备、电源管理(上电无需配置,静态功耗低)。

  • 胶合逻辑(Glue Logic)

    连接不同标准的芯片(如电平转换、信号整形)。

示例:键盘扫描电路、电源时序控制、老式硬盘接口桥接

2. FPGA的典型应用

  • 高性能并行处理

    数字信号处理(DSP)、图像处理(如摄像头ISP流水线)。

  • 复杂算法加速

    机器学习推理、加密解密(利用DSP硬核和并行计算)。

  • 可重构计算

    通信协议栈(5G PHY层)、软件定义无线电(SDR)。

  • 原型验证与ASIC替代

    芯片流片前的功能验证(如SoC原型开发)。

示例:自动驾驶的传感器融合、高速数据采集卡(ADC/DAC接口)、区块链矿机

3.2.3 选择建议
场景 推荐器件 理由
需要即时启动、低功耗控制 CPLD 非易失性,静态功耗低
简单逻辑整合(替换多颗74芯片) CPLD 成本低,开发周期短
高速数据流处理(>100MHz) FPGA 并行架构,支持流水线优化
需要动态重构功能 FPGA 可运行时重新配置逻辑
复杂算法(FFT、卷积) FPGA 内置DSP硬核,计算效率高
3.2.4 关键总结

CPLD:

优势:确定性延迟、低功耗、即时启动。

局限:逻辑规模小,缺乏硬核IP。

FPGA:

优势:高灵活性、大容量、支持复杂算法。

局限:功耗高,需外挂配置芯片,成本较高。

而且随着工艺进步,FPGA逐渐集成CPLD的特性(如Intel MAX 10系列融合Flash配置功能),而CPLD市场被低端FPGA挤压,但在特定场景仍不可替代。

四、hdlbits网站练习组合逻辑题目

4.1 2选1多路复用器(MUX)

1位宽 2选1多路复用器(MUX)是数字电路中最基础的逻辑单元之一,其核心功能是 根据选择信号(sel)从两个输入(a和b)中选择一个输出。它的作用可以类比为一个"数据开关"

问题描述:

解决方案:

复制代码
module top_module(
    input a,
    input b,
    input sel,
    output out
);
    assign out = sel ? b : a;  // 三元运算符实现
endmodule

说明: sel ?b:a;是条件运算符,等价于 if - else 的逻辑,这是最简洁的组合逻辑实现方式。

用于测试用例的时序图:Sel 在 a 和 b 之间进行选择

4.2 256:1 多路复用器

是2对1多路复用器的升级版

问题描述:

解决方案:

复制代码
module top_module (
    input [255:0] in,
    input [7:0] sel,
    output out
);
    assign out = in[sel];  // 直接使用 sel 作为索引
endmodule

说明:in[sel] 表示从 in 向量中选择第 sel 位的值,这是最简洁、高效的方式,综合工具会自动优化为多路复用逻辑。

4.3 半加器设计

半加器是数字电路中最基础的算术单元,其核心逻辑是:

  • 和(sum)= a ^ b( 由 1 个异或门 实现**)**

  • 进位(cout)=a & b( 由 1 个与门 实现**)**

真值表:

a b sum cout
0 0 0 0
0 1 1 0
1 0 1 0
1 1 0 1

问题描述:

解决方案:

复制代码
module top_module (
    input a, b,
    output sum, cout
);
    assign sum = a ^ b;   // 异或:本位和
    assign cout = a & b;  // 与:进位
endmodule

掌握半加器后,可以进一步学习 全加器(Full Adder) 和 多位加法器(Ripple Carry Adder)。

4.4 全加器设计

全加器将两个1位二进制数和一个进位输入相加,产生和(sum)和进位输出(carry out)。

问题描述:

解决方案:

复制代码
module top_module( 
    input a, b, cin,
    output cout, sum );
    
    // 全加器实现
    assign sum = a ^ b ^ cin;               // 和的计算
    assign cout = (a & b) | (a & cin) | (b & cin);  // 进位输出的计算
    
endmodule

说明:

  1. 输入端口:

    a: 第一个1位加数 b: 第二个1位加数 cin: 进位输入

  2. 输出端口:

    sum: 和输出 (a + b + cin 的结果) cout: 进位输出

  3. 逻辑实现:

    sum 使用异或(XOR)运算计算,因为当奇数个输入为1时结果为1 cout 使用或(OR)运算组合三种可能的进位情况:a和b都为1、a和cin都为1、b和cin都为1

测试用例的时序图:

4.5 4位BCD加法器

这个加法器可以处理两个4位BCD数字(每个数字0-9,共16位输入)和一个进位输入,产生4位BCD和和进位输出。

问题描述:

解决方案:

复制代码
module top_module( 
    input [15:0] a, b,
    input cin,
    output cout,
    output [15:0] sum );
    
    // 定义中间进位信号
    wire c1, c2, c3;
    
    // 实例化4个bcd_fadd模块,形成波纹进位链
    bcd_fadd add0 (
        .a(a[3:0]),
        .b(b[3:0]),
        .cin(cin),
        .cout(c1),
        .sum(sum[3:0])
    );
    
    bcd_fadd add1 (
        .a(a[7:4]),
        .b(b[7:4]),
        .cin(c1),
        .cout(c2),
        .sum(sum[7:4])
    );
    
    bcd_fadd add2 (
        .a(a[11:8]),
        .b(b[11:8]),
        .cin(c2),
        .cout(c3),
        .sum(sum[11:8])
    );
    
    bcd_fadd add3 (
        .a(a[15:12]),
        .b(b[15:12]),
        .cin(c3),
        .cout(cout),
        .sum(sum[15:12])
    );

endmodule

说明(工作原理):

·最低位BCD加法器(add0)接收cin作为进位输入

·每个加法器的进位输出连接到下一个高位加法器的进位输入

·最高位BCD加法器(add3)的进位输出作为整个模块的cout

·每个加法器产生的4位和组合成最终的16位sum输出

测试用例的时序图:

4.6 100位二进制加法器

使用波纹进位(Ripple Carry)结构,该加法器将两个100位二进制数和一个进位输入相加,产生100位和和一个进位输出。

问题描述:

解决方案:

复制代码
module top_module( 
    input [99:0] a, b,
    input cin,
    output cout,
    output [99:0] sum );
    
    // 定义中间进位信号
    wire [100:0] carry;
    
    // 初始进位设为输入进位
    assign carry[0] = cin;
    
    // 生成100个全加器实例
    genvar i;
    generate
        for (i = 0; i < 100; i = i + 1) begin : adder_chain
            // 每个全加器实例
            full_adder fa (
                .a(a[i]),
                .b(b[i]),
                .cin(carry[i]),
                .cout(carry[i+1]),
                .sum(sum[i])
            );
        end
    endgenerate
    
    // 最终进位输出
    assign cout = carry[100];
    
endmodule

// 全加器模块定义
module full_adder(
    input a, b, cin,
    output cout, sum
);
    assign sum = a ^ b ^ cin;
    assign cout = (a & b) | (a & cin) | (b & cin);
endmodule

说明:

  • 使用generate语句创建100个全加器实例的链式结构

  • 定义101位宽的进位信号carry(包含初始进位和各级进位)

  • 每个全加器处理一位加法运算

  • 进位信号从低位向高位传播(波纹进位)

相关推荐
hahaha60169 小时前
3NRD降噪算法FPGA实现8并行度设计
fpga开发
松小白song10 小时前
一次“载电流显示为 0“的排查经历:负值钳位引发的踩坑
fpga开发
yingheceping13 小时前
复旦微FPGA开发板厂商TOP5甄选榜:2026选型参考
fpga开发·fpga
NamoAmitabha12815 小时前
PL 端的 GPIO IP(AXI GPIO)---PS控制----deepseek
fpga开发·zynq--ps
hahaha601618 小时前
3D降噪算法YUV降噪FPGA实现
fpga开发
Hello-FPGA2 天前
基于RFSOC FPGA ZCU216 的CoaXPress 采集demo
fpga开发
9527华安2 天前
FPGA基于ADV7182解码PAL视频,Video Processing Subsystem去隔行,提供6套工程源码和技术支持
fpga开发·音视频·pal·adv7182
hahaha60162 天前
数字图像时域滤波或者3D降噪算法FPGA实现
算法·计算机视觉·fpga开发
GateWorld2 天前
Xilinx 7系列FPGA时钟网络详解
fpga开发·时钟网络·xilinx 7系列
szxinmai主板定制专家2 天前
X86 工控机 vs RK3588 异构主控:AMR 控制器 BOM、散热、长期运维对比
运维·stm32·单片机·嵌入式硬件·fpga开发·am62x