实验 02:LED 流水灯
🛒 开发板: EK301 GW5A FPGA 开发板 芯片: GW5A-LV25UG324C1/I0 (Arora V 系列)
一、实验目的
- 掌握时钟分频技术——将 50MHz 高速时钟降为 1Hz 肉眼可见的低频信号。
- 理解计数器设计,学会使用
localparam参数化定义分频系数。 - 掌握 LED 驱动方式——SS8050 开集驱动、高电平点亮。
- 学习 CST 引脚约束的完整写法:
IO_LOC+IO_PORT+IO_TYPE。
二、实验过程
2.1 硬件连接
| 信号 | 方向 | FPGA 引脚 | Bank | IO 标准 | 说明 |
|---|---|---|---|---|---|
| CLK_G | 输入 | T9 | Bank 4 | LVCMOS33 | 50MHz 有源晶振 |
| LED1 | 输出 | G9 | Bank 1 | LVCMOS33 | 蓝色LED,SS8050驱动,高电平点亮 |
| LED2 | 输出 | E7 | Bank 0 | LVCMOS33 | 同上 |
| LED3 | 输出 | E6 | Bank 0 | LVCMOS33 | 同上 |
| LED4 | 输出 | A5 | Bank 0 | LVCMOS33 | 同上 |
| LED5 | 输出 | A4 | Bank 0 | LVCMOS33 | 同上 |
| LED6 | 输出 | B4 | Bank 0 | LVCMOS33 | 同上 |
| LED7 | 输出 | A3 | Bank 0 | LVCMOS33 | 同上 |
| LED8 | 输出 | C5 | Bank 0 | LVCMOS33 | 同上 |
IO 电平标准:所有 GPIO 统一使用 3.3V LVCMOS33。LED 输出加
DRIVE=8提供足够驱动电流。
2.2 设计思路
核心问题:50MHz 时钟每 20ns 一个周期,人眼无法分辨。需要把 50MHz "降速"到约 1Hz。
解决思路——计数器分频 + 单周期脉冲:
50MHz 时钟 (周期=20ns)
↓ 每个上升沿 cnt + 1
↓ cnt == 50_000_000 - 1 ?
├─ 是 → 产生 tick 脉冲,cnt 清零
└─ 否 → 继续计数
↓ tick 驱动 LED 移位
LED 每秒移动一步
为什么用 tick 单周期脉冲而不是 clk_div 门控时钟?
❌ 门控时钟 (不推荐):
always @(posedge clk_div) led <= ...; // clk_div 作为时钟
问题: clk_div 不是全局时钟,可能有毛刺和时钟偏移
✅ 单周期脉冲 (推荐):
always @(posedge clk) if(tick) led <= ...; // 统一用 clk 触发
优势: 所有逻辑在同一个时钟域,时序收敛简单
2.3 模块设计
模块名:led_blink
端口列表:
| 端口 | 方向 | 位宽 | 说明 |
|---|---|---|---|
clk | input | 1 | 50MHz 系统时钟 |
led | output | [7:0] | 8 路 LED 输出 (高电平点亮) |
内部寄存器和参数:
| 信号 | 位宽 | 说明 |
|---|---|---|
cnt | [25:0] | 基准计数器,0 → 49,999,999 → 0 循环 |
tick | 1 | 1Hz 单周期脉冲(仅持续 1 个 clk = 20ns) |
参数定义:
| 参数 | 计算公式 | 值 |
|---|---|---|
MAX_1HZ | 50,000,000 / 1Hz - 1 | 49,999,999 |
2.4 CST 约束文件
// ---- 系统时钟 (50MHz 有源晶振) ----
IO_LOC "clk" T9;
IO_PORT "clk" IO_TYPE=LVCMOS33 PULL_MODE=NONE;
// ---- 8路蓝色LED (SS8050驱动, 高电平点亮) ----
IO_LOC "led[0]" G9;
IO_PORT "led[0]" IO_TYPE=LVCMOS33 PULL_MODE=NONE DRIVE=8;
// ... led[1]~led[7] 同理
⚠️ CST 关键规则:
- 总线端口使用
led[0]、led[1]等方括号表示法。- GW5A 的
IO_LOC使用裸引脚号(如T9),不要加IO前缀。- LED 输出设置
DRIVE=8(最大驱动电流)。
2.5 TCL 构建
# 在 git-bash 中执行
"C:/Gowin/Gowin_V1.9.9_x64/IDE/bin/gw_sh.exe" build.tcl
构建脚本 build.tcl 的关键配置:
create_project -name led_blink -dir "..." -pn GW5A-LV25UG324C1/I0 -device_version A -force
三、代码逐段讲解
3.1 时钟分频(tick 生成)
localparam MAX_1HZ = 50_000_000 - 1;
always @(posedge clk) begin
if (cnt == MAX_1HZ) begin
cnt <= 0; // 到达最大值,归零
tick <= 1; // 产生单周期脉冲
end else begin
cnt <= cnt + 1; // 自增
tick <= 0; // 其余时间 tick 保持 0
end
end
执行过程(以 MAX=4 的简化版为例):
时钟: ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑ ↑
cnt: 0 1 2 3 4 0 1 2 3 4
tick: 0 0 0 0 1 0 0 0 0 1
↑ ↑
cnt==MAX cnt==MAX
- 50MHz 时钟下,
MAX_1HZ = 49,999,999,计满需要 50M 个周期 = 1 秒。 tick每个周期高电平仅持续 20ns。
3.2 流水灯逻辑
initial led = 8'b0000_0001; // 初始仅LED1亮
always @(posedge clk) begin
if (tick)
led <= {led[6:0], led[7]}; // 右移循环
end
{led[6:0], led[7]}实现循环右移:低 7 位左移一位,最高位移到最低位。- Gowin 综合器支持
initial块为寄存器赋上电初值。
流水灯示意:
时刻 0s: 0000_0001 (LED1 亮)
时刻 1s: 0000_0010 (LED2 亮)
时刻 2s: 0000_0100 (LED3 亮)
...
时刻 7s: 1000_0000 (LED8 亮)
时刻 8s: 0000_0001 (LED1 亮, 回到起点)
四、实验现象
| 现象 | 说明 |
|---|---|
| 单个亮点从左往右移动 | 每秒移动一步,8 秒完成一个循环 |
| 上电立即亮 | LED1 首先点亮(initial 初值),非全灭状态 |
| 亮度均匀 | 每个 LED 导通时间相同 |
验证要点:
- ✅ LED 移动速度约为每秒一步(1Hz),肉眼可清晰分辨。
- ✅ 上电后 LED1 立即亮起。
- ✅ 8 个 LED 依次循环,无断续。
五、掌握知识点
| 知识点 | 说明 | 在本实验中的体现 |
|---|---|---|
| 时钟分频 | 计数器溢出法实现低频信号 | cnt 每 50M 周期产生一个脉冲 |
| 单周期脉冲 | tick 只持续 1 个时钟周期 | tick 作为 LED 更新使能 |
| 参数化设计 | localparam 便于修改分频系数 | MAX_1HZ 统一管理分频比 |
| 移位寄存器 | 循环移位实现流水灯 | {led[6:0], led[7]} 右移拼接 |
| IO_TYPE | 指定 IO 电平标准 | LVCMOS33 = 3.3V CMOS |
| DRIVE | 设置 IO 驱动电流 | DRIVE=8 = 最大驱动能力 |
| 上电初值 | initial 块设置寄存器初始值 | led = 8'b0000_0001 |
附录:源码
top.v
// =============================================================================
// EK301 实验02: LED流水灯
// 芯片: GW5A-LV25UG324C1/I0 | 时钟: 50MHz (T9)
// 功能: 8路LED右移循环流水灯, 1秒移动一步
// =============================================================================
module led_blink (
input wire clk, // 50MHz 系统时钟 (T9)
output reg [7:0] led // 8路蓝色LED (高电平点亮)
);
// ---- 1Hz tick 生成 ----
localparam MAX_1HZ = 50_000_000 - 1;
reg [25:0] cnt;
reg tick;
always @(posedge clk) begin
if (cnt == MAX_1HZ) begin
cnt <= 0;
tick <= 1;
end else begin
cnt <= cnt + 1;
tick <= 0;
end
end
// ---- LED 右移流水灯 ----
initial led = 8'b0000_0001; // 初始仅LED1亮
always @(posedge clk) begin
if (tick)
led <= {led[6:0], led[7]}; // LED1→LED2→...→LED8→LED1 循环
end
endmodule