FPGA入门(四):时序逻辑计数器原理与 LED 闪烁实现

FPGA入门


文章目录

  • FPGA入门
  • 前言
  • [一、核心原理:从 D 触发器到时序计数器](#一、核心原理:从 D 触发器到时序计数器)
    • [1.1 时序逻辑的基石:D 触发器](#1.1 时序逻辑的基石:D 触发器)
    • [1.2 时序逻辑计数器:D 触发器的扩展应用](#1.2 时序逻辑计数器:D 触发器的扩展应用)
    • [1.3 从计数器到 LED 闪烁:逻辑闭环设计](#1.3 从计数器到 LED 闪烁:逻辑闭环设计)
  • [二、LED 闪烁项目代码解析](#二、LED 闪烁项目代码解析)
  • 三、仿真波形调试:从误差到精准的全过程
  • 总结

前言

哈喽大家好!在前几章我们学习了组合逻辑电路,这一章我们将正式进入时序逻辑电路的世界。时序逻辑是 FPGA 开发的核心,而计数器则是时序逻辑中最基础也最重要的单元之一。今天我们就从 D 触发器讲起,一步步实现一个基于计数器的 LED 闪烁项目,并通过仿真波形调试代码,彻底搞懂时序逻辑的工作原理。


一、核心原理:从 D 触发器到时序计数器

1.1 时序逻辑的基石:D 触发器

时序逻辑电路的核心是触发器,而 D 触发器 D Flip-Flop, DFF是最常用的触发器类型,它的功能是在时钟的控制下,将输入 D 的值传递到输出 Q。

  • 输入D:数据输入端
  • 输入CK:时钟输入端(三角形表示边沿触发,此处为上升沿触发)
  • 输出Q:数据输出端
    D 触发器的工作规则非常简单:
  • 只有在时钟上升沿到来时,输出Q才会更新为此时D的值;
  • 其他任何时候,不管D如何变化,Q都保持原来的值不变
    对应的时序波形图也能清晰体现这一特性:在时钟上升沿,Q的值会同步更新为D在上升沿时刻的值,其余时间保持不变。这就是时序逻辑 同步更新、状态保持 的核心思想

1.2 时序逻辑计数器:D 触发器的扩展应用

计数器本质上就是一个带加法器的 D 触发器,它的功能是每来一个时钟脉冲,计数值加 1,当计数值达到设定的最大值时,自动复位并触发一次翻转。

  • 计数器由加法器(+1)和 D 触发器组成;
  • D 触发器的输出cnt[3:0]反馈到加法器输入端,加法器的输出再接到 D 触发器的D端;
  • 每来一个时钟上升沿,D 触发器就会把加法器的结果(当前值 + 1)锁存到cnt中,实现计数功能。
    我们项目中的 25 位计数器就是这个结构的扩展:用 25 个 D 触发器组成 25 位寄存器,配合加法器实现0~24999999的循环计数。
    其实就相当于c语言中的
c 复制代码
// 全局变量a(对应FPGA的寄存器counter)
int a = 0;
void time(){
    a++;              // 每个周期计数+1
    if(a >= 10){      // 计数到10就触发动作
        a=0;          // 计数器清零
        led=!led;     // LED翻转
    }
}

1.3 从计数器到 LED 闪烁:逻辑闭环设计

LED 闪烁的核心逻辑是:让 LED 以固定频率翻转电平,比如 1 秒亮 1 秒灭,周期 2 秒。要实现这个功能,我们需要用计数器来 "计时":

  1. 开发板时钟通常为 50MHz,时钟周期为 20ns;
  2. 要实现 500ms 翻转一次 LED,需要计数的次数为:500ms / 20ns = 25,000,000次;
  3. 当计数器计数到24999999(即25,000,000-1)时,让 LED 翻转一次,同时计数器复位,重新开始计数。
    这样就形成了完整的逻辑闭环:计数器循环计数,每 500ms 触发一次 LED 翻转,实现 1 秒周期的闪烁效果。

二、LED 闪烁项目代码解析

项目包含两个文件:顶层模块led_twinkle.v和仿真测试文件led_twinkle_tb.v。

2.1顶层模块led_twinkle.v

我先给大家看实测结果:

  1. 代码写 counter == 25_000_00 → 仿真波形周期:50.000020ms(多了 20ns,误差 1 个时钟周期)
  2. 代码写 counter == 25_000_00-1 → 仿真波形周期:50.000000ms(精准无误差)
    根本原因:计数器从 0 开始计数!
  • 计数器初始值 = 0
  • 计 1 个数:0 → 1
  • 计 25000000 个数:需要从 0 数到 24999999(也就是25_000_00-1)
  • 如果数到25_000_00,就多计了 1 个时钟周期(20ns),导致计时不准!
c 复制代码
module led_twinkle(
    input        Clk,          // 系统时钟,50MHz
    input        Reset_n,      // 复位信号,低电平有效
    output reg   Led           // LED控制信号
);

// 定义25位计数器,最大计数到2^25-1=33554431,足够容纳25000000
reg [24:0] counter;

// 计数器逻辑:0~24999999循环计数
always @(posedge Clk or negedge Reset_n) begin
    if(!Reset_n) begin
        counter <= 25'd0;  // 复位时计数器清零
    end
    else if(counter == 25'd25_000_00 - 1) begin
        counter <= 25'd0;  // 计数到最大值时复位
    end
    else begin
        counter <= counter + 1'd1;  // 每个时钟周期加1
    end
end

// LED翻转逻辑:计数到最大值时翻转LED电平
always @(posedge Clk or negedge Reset_n) begin
    if(!Reset_n) begin
        Led <= 1'b0;       // 复位时LED熄灭
    end
    else if(counter == 25'd25_000_00 - 1) begin
        Led <= !Led;       // 计数到最大值时翻转LED
    end
    // 时序逻辑中,条件不满足时会自动保持原值,可省略else分支
end

endmodule

2.2 仿真测试文件led_twinkle_tb.v

c 复制代码
`timescale 1ns / 1ns  // 定义仿真时间单位和精度

module led_twinkle_tb();

reg  Clk;
reg  Reset_n;
wire Led;

// 实例化顶层模块
led_twinkle led_twinkle(
    .Clk(Clk),
    .Reset_n(Reset_n),
    .Led(Led)
);

// 生成50MHz时钟,周期20ns
initial Clk = 1'b1;
always #10 Clk = !Clk;

// 复位与仿真激励
initial begin
    Reset_n = 1'b0;  // 初始复位
    #201;            // 保持复位201ns,确保系统稳定
    Reset_n = 1'b1;  // 释放复位
    #2000000000;     // 仿真2秒,足够看到两次LED翻转
    $stop;           // 停止仿真
end

endmodule

关键代码解析:

  • 时钟生成:always #10 Clk = !Clk生成周期 20ns 的时钟,对应 50MHz 频率,与开发板时钟一致;
  • 复位激励:先保持复位 201ns,避免复位信号与时钟沿冲突,确保系统稳定后再释放复位;
  • 仿真时长:#2000000000即仿真 2 秒,足够观察 LED 两次翻转(亮→灭→亮),验证闪烁周期是否为 1 秒。

三、仿真波形调试:从误差到精准的全过程

我们通过仿真波形的实测数据,直观验证「减 1」的必要性。

错误代码:counter == 25_000_00(未减 1)

现象:LED 翻转周期 = 50.000020ms

原因:计数器从 0 数到 25000000,多计了 1 个时钟周期(20ns),计时偏移

结论:不满足精准闪烁需求

修正代码:counter == 25_000_00-1(减 1)

现象:LED 翻转周期 = 50.000000ms

原因:计数器从 0 数到 24999999,刚好 25000000 个时钟周期,无任何误差

结论:这就是通过波形调整代码的标准答案!


总结

通过这一章的学习,我们从 D 触发器的基础原理出发,理解了时序逻辑计数器的工作机制,并实现了 LED 闪烁项目。这个项目虽简单,但包含了时序逻辑开发的核心要点:

  1. 理解 D 触发器的边沿触发和状态保持特性;
  2. 掌握计数器的设计方法:位宽计算、最大值设置、复位逻辑;
  3. 学会通过仿真波形验证时序逻辑,排查问题;
  4. 养成良好的代码规范,如参数化、清晰的分支结构。
相关推荐
ALINX技术博客2 小时前
【黑金云课堂】FPGA技术教程FPGA基础:呼吸灯实验+RAM/ROM IP设计与验证
网络协议·fpga开发·fpga
搁浅小泽2 小时前
常用电子元器件
单片机·嵌入式硬件·可靠性工程师
皮卡蛋炒饭.2 小时前
传输层协议UDP
linux·网络协议·udp
zhaoshuzhaoshu2 小时前
嵌入式开发之时钟树解析-SMT32平台
嵌入式硬件
syagain_zsx2 小时前
Linux指令初识(实用篇)
linux·运维·服务器
三佛科技-187366133972 小时前
FT60E211-RB省成本,提效率!IO型8位单片机智能家居产品应用解析
单片机·嵌入式硬件·智能家居
王木风3 小时前
终端里的编程副驾:DeepSeek-TUI-项目深度拆解,实测与原理分析
linux·运维·人工智能·rust·node.js
槑槑紫3 小时前
windows系统装轻量版linux开发
linux·运维·服务器
哄娃睡觉3 小时前
STM32F407VET6 的串口分别对应了哪些引脚?
stm32