跳到主要内容

实验 01:LED 闪烁

🛒 开发板购买: EK303 GW1N FPGA核心板


一、实验目的

  1. 跑通 GW1N FPGA 开发全流程:创建工程 → 编写 Verilog → 引脚约束 → 综合 → 布局布线 → 下载。
  2. 理解 FPGA 的时钟概念——时钟是数字电路的"心跳",所有同步逻辑在时钟上升沿触发。
  3. 学会用计数器实现分频:将高速时钟(50MHz)降为肉眼可见的低频信号(1Hz)驱动 LED。
  4. 建立 Verilog 模块的基本认知:端口声明、时序逻辑 always 块、reg 类型信号。

二、实验过程

2.1 硬件连接

信号方向FPGA 引脚说明
系统时钟输入Pin 9850MHz 有源晶振,接 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目标频率半周期翻转条件
LED11.0 Hz500ms计数器 == 25,000,000 - 1
LED20.5 Hz1000ms计数器 == 50,000,000 - 1
LED30.4 Hz1250ms计数器 == 62,500,000 - 1

计数器位宽选择:需要覆盖最大计数值 62,500,000,26 位可表示 0~67,108,863,满足需求。

2.3 Verilog 模块设计

模块名led_blink

端口列表

端口方向位宽说明
clk_50minput150MHz 系统时钟
led1output1LED1 输出 (1.0Hz)
led2output1LED2 输出 (0.5Hz)
led3output1LED3 输出 (0.4Hz)

内部寄存器

信号位宽说明
counter26基准计数器,0 → 62,499,999 → 0 循环

逻辑描述

  1. 每个 clk_50m 上升沿,counter 自增 1;达到 62,499,999 时归零。
  2. counter 等于对应阈值时,翻转对应的 LED 输出(led <= ~led)。
  3. LED1 有两个翻转点(半周期和全周期各一次),LED2/LED3 各只有一个翻转点(利用 counter 归零作为第二次翻转)。

2.4 引脚约束

.cst 文件中完成信号到物理引脚的映射:

信号约束内容
clk_50mIO_LOC "clk_50m" 98; · IO_TYPE=LVCMOS33 · PULL_MODE=NONE
led1IO_LOC "led1" 8; · IO_TYPE=LVCMOS33 · DRIVE=8
led2IO_LOC "led2" 9; · IO_TYPE=LVCMOS33 · DRIVE=8
led3IO_LOC "led3" 10; · IO_TYPE=LVCMOS33 · DRIVE=8

2.5 编译与下载

  1. 在 Gowin IDE 中创建工程,选择器件 GW1N-LV9LQ144C6/I5
  2. 添加 Verilog 源文件和 .cst 约束文件。
  3. 设置顶层模块为 led_blink
  4. 依次运行综合 (Synthesis) 和布局布线 (Place & Route)。
  5. 生成比特流文件 (.fs)。
  6. 用 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 + 1led <= ~led 在同一时钟沿并行生效掌握
计数器分频原理50MHz ÷ 50M ÷ 2 = 0.5Hz 半周期,通过计数值控制翻转时机掌握
reg vs wirecounterledalways 块中赋值,必须声明为 reg理解
位宽计算根据最大计数值 62.5M 确定需要 26 位 (2^26 > 62.5M)掌握
物理约束 .cstIO_LOC 将逻辑信号映射到物理引脚,IO_PORT 设置电气属性掌握
FPGA 开发流程建工程 → 写代码 → 约束 → 综合 → P&R → 下载掌握
Gowin 工具链create_projectrun synrun pnr、Programmer 下载了解

五、思考题

  1. 如果要把 LED1 改成 2Hz,计数值应该改为多少?
  2. 如果系统时钟是 27MHz 而不是 50MHz,半周期计数值如何计算?
  3. 为什么 .cst 中引脚写 98 而不是 IO98
  4. 非阻塞赋值 <= 和阻塞赋值 = 有什么区别?(可提前翻阅实验 03)
  5. 三个 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