实验 01:LED 闪烁
🛒 开发板购买: EK303 GW1N FPGA核心板
一、实验目的
- 跑通 GW1N FPGA 开发全流程:创建工程 → 编写 Verilog → 引脚约束 → 综合 → 布局布线 → 下载。
- 理解 FPGA 的时钟概念——时钟是数字电路的"心跳",所有同步逻辑在时钟上升沿触发。
- 学会用计数器实现分频:将高速时钟(50MHz)降为肉眼可见的低频信号(1Hz)驱动 LED。
- 建立 Verilog 模块的基本认知:端口声明、时序逻辑
always块、reg类型信号。
二、实验过程
2.1 硬件连接
| 信号 | 方向 | FPGA 引脚 | 说明 |
|---|---|---|---|
| 系统时钟 | 输入 | Pin 98 | 50MHz 有源晶振,接 GCLKT_2 全局时钟引脚 |
| LED1 | 输出 | Pin 8 | 高电平点亮,串 220Ω 限流电阻 |
| LED2 | 输出 | Pin 9 | 同上 |
| LED3 | 输出 | Pin 10 | 同上 |
IO 电平标准统一为 3.3V LVCMOS33。
2.2 设计思路
核心问题:50MHz 时钟变化太快(每 20ns 一个周期),人眼无法分辨。需要把 50MHz "降速"到 1Hz 左右。
解决思路——计数器分频:
50MHz 时钟 (20ns/周期)
↓
每个上升沿计数器 +1
↓
当计数器计到 N 时:清零计数器,同时翻转 LED
↓
LED 以 (50MHz / 2N) 的频率闪烁
三路不同频率(共用同一个计数器基准):
| LED | 目标频率 | 半周期 | 翻转条件 |
|---|---|---|---|
| LED1 | 1.0 Hz | 500ms | 计数器 == 25,000,000 - 1 |
| LED2 | 0.5 Hz | 1000ms | 计数器 == 50,000,000 - 1 |
| LED3 | 0.4 Hz | 1250ms | 计数器 == 62,500,000 - 1 |
计数器位宽选择:需要覆盖最大计数值 62,500,000,26 位可表示 0~67,108,863,满足需求。
2.3 Verilog 模块设计
模块名:led_blink
端口列表:
| 端口 | 方向 | 位宽 | 说明 |
|---|---|---|---|
clk_50m | input | 1 | 50MHz 系统时钟 |
led1 | output | 1 | LED1 输出 (1.0Hz) |
led2 | output | 1 | LED2 输出 (0.5Hz) |
led3 | output | 1 | LED3 输出 (0.4Hz) |
内部寄存器:
| 信号 | 位宽 | 说明 |
|---|---|---|
counter | 26 | 基准计数器,0 → 62,499,999 → 0 循环 |
逻辑描述:
- 每个
clk_50m上升沿,counter自增 1;达到 62,499,999 时归零。 - 当
counter等于对应阈值时,翻转对应的 LED 输出(led <= ~led)。 - LED1 有两个翻转点(半周期和全周期各一次),LED2/LED3 各只有一个翻转点(利用 counter 归零作为第二次翻转)。
2.4 引脚约束
在 .cst 文件中完成信号到物理引脚的映射:
| 信号 | 约束内容 |
|---|---|
clk_50m | IO_LOC "clk_50m" 98; · IO_TYPE=LVCMOS33 · PULL_MODE=NONE |
led1 | IO_LOC "led1" 8; · IO_TYPE=LVCMOS33 · DRIVE=8 |
led2 | IO_LOC "led2" 9; · IO_TYPE=LVCMOS33 · DRIVE=8 |
led3 | IO_LOC "led3" 10; · IO_TYPE=LVCMOS33 · DRIVE=8 |
2.5 编译与下载
- 在 Gowin IDE 中创建工程,选择器件
GW1N-LV9LQ144C6/I5。 - 添加 Verilog 源文件和 .cst 约束文件。
- 设置顶层模块为
led_blink。 - 依次运行综合 (Synthesis) 和布局布线 (Place & Route)。
- 生成比特流文件 (
.fs)。 - 用 Gowin Programmer 连接下载器,将比特流烧录到 FPGA。
三、实验现象
| 观察对象 | 现象 |
|---|---|
| LED1 (Pin 8) | 以 1Hz 频率闪烁:亮 0.5 秒 → 灭 0.5 秒 → 循环。肉眼清晰可见亮灭交替。 |
| LED2 (Pin 9) | 以 0.5Hz 频率闪烁:亮 1 秒 → 灭 1 秒 → 循环。闪烁速度约为 LED1 的一半。 |
| LED3 (Pin 10) | 以 0.4Hz 频率闪烁:亮 1.25 秒 → 灭 1.25 秒 → 循环。三个 LED 中闪烁最慢。 |
| 三者同观 | 三个 LED 以不同节奏闪烁,可直观感受频率差异。LED1 最快,LED3 最慢。 |
时间轴示意 (秒): 0 0.5 1.0 1.5 2.0 2.5
LED1 (1.0Hz): ████ ░░░░ ████ ░░░░ ████ ░░░░
LED2 (0.5Hz): ████████ ░░░░░░░░ ████████ ░░░░
LED3 (0.4Hz): ████████████ ░░░░░░░░░░░░ ████
✅ 验证标准:三个 LED 按照各自频率稳定闪烁,无抖动、无停顿、频率比例符合预期。
四、掌握知识点
| 知识点 | 在本实验中的体现 | 掌握程度 |
|---|---|---|
module 结构 | 定义 led_blink 模块,声明输入输出端口 | 理解 |
always @(posedge clk) | 所有计数器更新和 LED 翻转都在时钟上升沿触发 | 掌握 |
非阻塞赋值 <= | counter <= counter + 1 与 led <= ~led 在同一时钟沿并行生效 | 掌握 |
| 计数器分频原理 | 50MHz ÷ 50M ÷ 2 = 0.5Hz 半周期,通过计数值控制翻转时机 | 掌握 |
reg vs wire | counter 和 led 在 always 块中赋值,必须声明为 reg | 理解 |
| 位宽计算 | 根据最大计数值 62.5M 确定需要 26 位 (2^26 > 62.5M) | 掌握 |
物理约束 .cst | IO_LOC 将逻辑信号映射到物理引脚,IO_PORT 设置电气属性 | 掌握 |
| FPGA 开发流程 | 建工程 → 写代码 → 约束 → 综合 → P&R → 下载 | 掌握 |
| Gowin 工具链 | create_project、run syn、run pnr、Programmer 下载 | 了解 |
五、思考题
- 如果要把 LED1 改成 2Hz,计数值应该改为多少?
- 如果系统时钟是 27MHz 而不是 50MHz,半周期计数值如何计算?
- 为什么
.cst中引脚写98而不是IO98? - 非阻塞赋值
<=和阻塞赋值=有什么区别?(可提前翻阅实验 03) - 三个 LED 能不能各用一个独立的计数器?这样做有什么优缺点?
📝 本实验是 FPGA 开发的"Hello World",后续所有实验都建立在本实验的工程框架之上。
附录:源码清单
top.v
// =============================================================================
// led_blink.v - 三路 LED 闪烁顶层模块
// 芯片: GW1N-LV9LQ144C6/I5
// 时钟: 50MHz (Pin 98)
// LED1: Pin 8 → 1.0Hz (亮灭各500ms)
// LED2: Pin 9 → 0.5Hz (亮灭各1000ms)
// LED3: Pin 10 → 0.4Hz (亮灭各1250ms)
// =============================================================================
module led_blink (
input wire clk_50m, // 50MHz 系统时钟输入
output reg led1, // LED1: 1Hz → Pin 8
output reg led2, // LED2: 0.5Hz → Pin 9
output reg led3 // LED3: 0.4Hz → Pin 10
);
// =========================================================================
// 26位计数器: 2^26 = 67,108,864 > 62,500,000 (LED3半周期)
// 基准计数: 0 ~ 62,499,999 为一个完整循环
// =========================================================================
reg [25:0] counter;
always @(posedge clk_50m) begin
if (counter == 26'd62_499_999) begin
counter <= 26'd0;
end else begin
counter <= counter + 1'b1;
end
end
// =========================================================================
// LED1: 1.0Hz = 亮灭各 500ms
// 翻转点: 25,000,000 - 1 = 24,999,999 (半周期)
// 再翻转: 50,000,000 - 1 = 49,999,999 (全周期归位时已翻转)
// 简化: 每 25,000,000 周期翻转一次
// =========================================================================
always @(posedge clk_50m) begin
if (counter == 26'd24_999_999 || counter == 26'd49_999_999) begin
led1 <= ~led1;
end
end
// =========================================================================
// LED2: 0.5Hz = 亮灭各 1000ms
// 翻转点: 50,000,000 - 1 = 49,999,999 (半周期)
// =========================================================================
always @(posedge clk_50m) begin
if (counter == 26'd49_999_999) begin
led2 <= ~led2;
end
end
// =========================================================================
// LED3: 0.4Hz = 亮灭各 1250ms
// 翻转点: 62,500,000 - 1 = 62,499,999 (半周期)
// =========================================================================
always @(posedge clk_50m) begin
if (counter == 26'd62_499_999) begin
led3 <= ~led3;
end
end
endmodule