跳到主要内容

实验 02:LED 流水灯

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


一、实验目的

  1. 学习移位寄存器在 FPGA 中的实现方式,理解寄存器级联传递。
  2. 掌握使能脉冲的设计方法——不是每个时钟周期都移位,而是每隔一定时间才移位一次。
  3. 理解 localparam 参数化常量的用法,方便修改流水速度。
  4. 巩固 .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_50minput150MHz 系统时钟
ledoutput3三位 LED 输出

内部信号

信号位宽说明
led_reg3LED 状态寄存器,初始值 3'b100
shift_en1移位使能脉冲,每 0.3s 产生一个周期的高脉冲
cnt24分频计数器,计到 15,000,000 - 1 (= 0.3s)

逻辑描述

  1. 计数器 cnt 循环 0 → 14,999,999 → 0,计满时产生 shift_en = 1(仅维持一个周期)。
  2. shift_en == 1 时,led_reg <= {led_reg[1:0], led_reg[2]}(循环左移)。
  3. 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])持续时间
10.3s
20.3s
30.3s
40.3s(循环)

三个 LED 如流水般从左向右循环点亮。修改移位方向可实现右移效果 {led[0], led[2:1]}


四、掌握知识点

知识点说明
移位寄存器 {a, b}位拼接实现循环移位
localparam参数化计数值,方便修改速度
使能脉冲单周期高脉冲精准控制移位时机
reg 初始值reg [2:0] led_reg = 3'b100 设定初始状态
多 bit 端口 [2:0]三位宽输出端口的声明与约束

五、思考题

  1. 如何修改代码实现从右向左的流水方向?
  2. 如果改成 5 个 LED 的流水灯,哪些参数需要修改?
  3. 为什么不用 led <= led << 1 这种写法?
  4. 移位使能脉冲如果宽度超过一个周期会发生什么?

附录:源码清单

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