实验 02:LED 流水灯
🛒 开发板购买: EK303 GW1N FPGA核心板
一、实验目的
- 学习移位寄存器在 FPGA 中的实现方式,理解寄存器级联传递。
- 掌握使能脉冲的设计方法——不是每个时钟周期都移位,而是每隔一定时间才移位一次。
- 理解
localparam参数化常量的用法,方便修改流水速度。 - 巩固
.cst约束文件的多引脚编写。
二、实验过程
2.1 硬件连接
同实验 01,使用 Pin 8 / 9 / 10 三个 LED。
2.2 设计思路
核心问题:如何让三个 LED 依次逐个点亮,形成"灯光追逐"效果?
解决思路——移位寄存器 + 定时使能:
50MHz 时钟
↓
计数器产生移位使能脉冲 (每 0.3 秒产生一个周期宽度的脉冲)
↓
移位使能脉冲到来时,将 LED 寄存器循环左移一位
↓
三个 LED 呈现出依次点亮的流水效果
移位逻辑:led <= {led[1:0], led[2]} — 低两位左移,高位补到最低位(循环左移)。
初始值:3'b100 — 最左侧 LED 先亮。
2.3 Verilog 模块设计
模块名:led_shift
端口列表:
| 端口 | 方向 | 位宽 | 说明 |
|---|---|---|---|
clk_50m | input | 1 | 50MHz 系统时钟 |
led | output | 3 | 三位 LED 输出 |
内部信号:
| 信号 | 位宽 | 说明 |
|---|---|---|
led_reg | 3 | LED 状态寄存器,初始值 3'b100 |
shift_en | 1 | 移位使能脉冲,每 0.3s 产生一个周期的高脉冲 |
cnt | 24 | 分频计数器,计到 15,000,000 - 1 (= 0.3s) |
逻辑描述:
- 计数器
cnt循环 0 → 14,999,999 → 0,计满时产生shift_en = 1(仅维持一个周期)。 - 当
shift_en == 1时,led_reg <= {led_reg[1:0], led_reg[2]}(循环左移)。 led直接由led_reg驱动。
2.4 引脚约束
| 信号 | 约束 |
|---|---|
led[0] | IO_LOC ... 8; IO_TYPE=LVCMOS33 |
led[1] | IO_LOC ... 9; IO_TYPE=LVCMOS33 |
led[2] | IO_LOC ... 10; IO_TYPE=LVCMOS33 |
三、实验现象
| 帧 | LED1 (led[2]) | LED2 (led[1]) | LED3 (led[0]) | 持续时间 |
|---|---|---|---|---|
| 1 | ● | ○ | ○ | 0.3s |
| 2 | ○ | ● | ○ | 0.3s |
| 3 | ○ | ○ | ● | 0.3s |
| 4 | ● | ○ | ○ | 0.3s(循环) |
三个 LED 如流水般从左向右循环点亮。修改移位方向可实现右移效果
{led[0], led[2:1]}。
四、掌握知识点
| 知识点 | 说明 |
|---|---|
移位寄存器 {a, b} | 位拼接实现循环移位 |
localparam | 参数化计数值,方便修改速度 |
| 使能脉冲 | 单周期高脉冲精准控制移位时机 |
reg 初始值 | reg [2:0] led_reg = 3'b100 设定初始状态 |
多 bit 端口 [2:0] | 三位宽输出端口的声明与约束 |
五、思考题
- 如何修改代码实现从右向左的流水方向?
- 如果改成 5 个 LED 的流水灯,哪些参数需要修改?
- 为什么不用
led <= led << 1这种写法? - 移位使能脉冲如果宽度超过一个周期会发生什么?
附录:源码清单
top.v
// =============================================================================
// top.v - LED流水灯 (LED Shift)
// 芯片: GW1N-LV9LQ144C6/I5
// 时钟: 50MHz (Pin 98)
// LED1: Pin 8, LED2: Pin 9, LED3: Pin 10
// 功能: 3个LED循环移位,每0.3秒移动一位,形成"流水追逐"效果
// =============================================================================
module led_shift (
input wire clk_50m, // 50MHz 系统时钟
output reg [2:0] led // 3位LED输出 (led[2]=LED1, led[1]=LED2, led[0]=LED3)
);
// =========================================================================
// 参数定义
// 0.3秒 = 50,000,000 × 0.3 = 15,000,000 个时钟周期
// 计数值 = 15,000,000 - 1 = 14,999,999
// 24位计数器: 2^24 = 16,777,216 > 15,000,000
// =========================================================================
localparam SHIFT_MAX = 24'd14_999_999;
// =========================================================================
// 内部信号
// =========================================================================
reg [23:0] cnt; // 分频计数器 (0 ~ 14,999,999)
wire shift_en; // 移位使能脉冲 (单周期高电平)
// =========================================================================
// 移位使能信号: 组合逻辑,当计数器计满时产生一个周期的高脉冲
// =========================================================================
assign shift_en = (cnt == SHIFT_MAX);
// =========================================================================
// 分频计数器: 0 → 14,999,999 → 0 循环
// 50MHz / 15,000,000 ≈ 3.33Hz, 即每0.3秒产生一次 shift_en
// =========================================================================
always @(posedge clk_50m) begin
if (cnt == SHIFT_MAX)
cnt <= 24'd0;
else
cnt <= cnt + 1'b1;
end
// =========================================================================
// LED 移位寄存器 (初始值 3'b100, 即最左侧LED先亮)
// 循环左移: {led[1:0], led[2]} 将高位移到最低位
//
// 状态变化:
// 3'b100 → 3'b010 → 3'b001 → 3'b100 → ...
// LED1亮 LED2亮 LED3亮 LED1亮
//
// Gowin 综合器支持 reg 声明时的初始值设定
// =========================================================================
always @(posedge clk_50m) begin
if (shift_en)
led <= {led[1:0], led[2]};
end
// =========================================================================
// 上电初始值 (Gowin 支持 initial 块用于设定寄存器上电值)
// =========================================================================
initial begin
led = 3'b100;
end
endmodule