跳到主要内容

实验 02:LED 流水灯

🛒 开发板: EK301 GW5A FPGA 开发板 芯片: GW5A-LV25UG324C1/I0 (Arora V 系列)


一、实验目的

  1. 掌握时钟分频技术——将 50MHz 高速时钟降为 1Hz 肉眼可见的低频信号。
  2. 理解计数器设计,学会使用 localparam 参数化定义分频系数。
  3. 掌握 LED 驱动方式——SS8050 开集驱动、高电平点亮。
  4. 学习 CST 引脚约束的完整写法:IO_LOC + IO_PORT + IO_TYPE

二、实验过程

2.1 硬件连接

信号方向FPGA 引脚BankIO 标准说明
CLK_G输入T9Bank 4LVCMOS3350MHz 有源晶振
LED1输出G9Bank 1LVCMOS33蓝色LED,SS8050驱动,高电平点亮
LED2输出E7Bank 0LVCMOS33同上
LED3输出E6Bank 0LVCMOS33同上
LED4输出A5Bank 0LVCMOS33同上
LED5输出A4Bank 0LVCMOS33同上
LED6输出B4Bank 0LVCMOS33同上
LED7输出A3Bank 0LVCMOS33同上
LED8输出C5Bank 0LVCMOS33同上

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

端口列表

端口方向位宽说明
clkinput150MHz 系统时钟
ledoutput[7:0]8 路 LED 输出 (高电平点亮)

内部寄存器和参数

信号位宽说明
cnt[25:0]基准计数器,0 → 49,999,999 → 0 循环
tick11Hz 单周期脉冲(仅持续 1 个 clk = 20ns)

参数定义

参数计算公式
MAX_1HZ50,000,000 / 1Hz - 149,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