FPGA学习(二)——LED闪烁

一、几个常用的逻辑代码

1、组合逻辑

2、时序逻辑

二、LED闪烁实验

1、程序编写

2、仿真搭建

三、实验源码

1、方案1

2、方案2


一、几个常用的逻辑代码

1、组合逻辑

下面这段 Verilog 代码实现的是一个组合逻辑比较器,根据输入a和b的大小关系,实时决定输出Q:当a > b时,Q=1;当a <= b时,Q=0。

bash 复制代码
always @ (*)
begin
    if (a > b)
        Q = 1;
    else
    Q = 0;
end

等价于

bash 复制代码
assign Q = (a > b) ? 1'b1 : 1'b0;

或者

bash 复制代码
assign Q = (a > b);

下面这段代码中的always @ (a)表示只有当信号 a 发生变化时,才执行这个 always 块。但是块内判断条件if(a>b)同时读取了 a 和 b。由于 b 没有出现在敏感列表中,所以:a变化时,块会执行,重新计算Q;b变化时,块不会执行,Q不会更新。

bash 复制代码
always @ (a)
begin
    if(a>b)
        Q=1;
    else
        Q=0;
end

2、时序逻辑

这段代码是一个带时钟的 2 位二进制加 1 计数器,也就是模 4 计数器:

bash 复制代码
reg [1:0] q;

always @ (posedge clk)
begin
    q <= q + 1'b1;
end
bash 复制代码
reg [1:0] q;
always @ (posedge clk or negedge rst_n)
begin
if(!rst_n)
q <= 2'b00;
else
q <= q + 1'b1;
end

二、LED闪烁实验

1、程序编写

对于需要输出不同闪烁频率的LED来说,无非是需要确定两个内容,一个是需要计数的最大值,一个是达到复位时候的值,用这两个值来作为电平切换的条件。下面以实现1MHz、1kHz以及1Hz的时钟信号为例,创建新的工程命名为flash_ledm在flash_led_top中将上一篇文章中的代码复制过来,如下所示。

bash 复制代码
module flash_led_top(
input clk50m                                  ,
(* mark_debug = "true" *)output reg led1
    );
wire sys_clk50m;
BUFG U1(
    .O(sys_clk50m),
    .I(clk50m)
);

当计数到128时进行复位,也就是0~127,只需要8位(0~7).

第一个 always 块让 8 位计数器 rst_cnt 从 0 开始每个时钟加 1,计到RST_END=128后保持不变;第二个 always 块根据计数值产生低有效复位信号 reset_n ,在 rst_cnt 未到 128 时保持 reset_n = 0,当 rst_cnt == 128 时把 reset_n 拉高为 1,从而在系统上电后延时约 128 个时钟周期再释放复位,确保后续分频和 LED 逻辑在时钟稳定后开始工作;该电路依靠 FPGA 寄存器上电初值自启动,第一个计数块本身不带复位端。

bash 复制代码
reg reset_n;                            // 复位信号,低电平有效
reg [7:0] rst_cnt;                      // 8 位复位延时计数器

localparam [7:0] RST_END = 8'd128;      // 复位延时的计数值,计到 128 释放复位

//第一个 always 块:复位延时计数器
always @(posedge sys_clk50m) begin
    if (rst_cnt < RST_END)
        rst_cnt <= rst_cnt + 8'd1;
    else
        rst_cnt <= rst_cnt;
end

//第二个 always 块:产生复位信号
always @(posedge sys_clk50m) begin
    if (rst_cnt == RST_END)
        reset_n <= 1'b1;
    else
        reset_n <= 1'b0;
end

在50MHz基础上获取1MHz的时钟,由于

那么对于50MHz的波形来说,10ns是高电平,10ns是低电平。

对于1Mhz来说

那么对于1MHz的波形来说,500ns是高电平,500ns是低电平。

那么就有

也就是说想要生成1MHz这样的目标周期,就要计数50次50MHz这样的周期,那么只需要高电平计25次,低电平计25次,即高低电平各计数0~24,24只需要5位(0~4).

bash 复制代码
reg clk_1mhz;                 // 分频输出的 1MHz 时钟
reg [4:0] T_1M_cnt;           // 5 位计数器,范围 0~31
localparam T_1M_END = 5'd24;  // 翻转阈值:计到 24 时翻转 clk_1mhz

always @(posedge sys_clk50m or negedge reset_n) begin
    if (!reset_n)
        clk_1mhz<= 1'b0;
    else if (T_1M_cnt == T_1M_END) begin
        clk_1mhz<= 1'b1;
        T_1M_cnt <= 5'd0;
    end else begin
        clk_1mhz<= 1'b0;
        T_1M_cnt <= T_1M_cnt + 1'b1;
    end
end

在1MHz的基础上获得1kHz的时钟,数500次翻转即可,那么就是0~499,按照500来算的话最多9位(0~8)。

bash 复制代码
reg clk_1khz;                        // 分频输出:1 kHz 时钟
reg [8:0] T_1K_cnt;                  // 9 位计数器,范围 0~511
localparam T_1K_END = 9'd499;        // 翻转阈值:计到 499 时翻转输出

always@(posedge clk_1mhz or negedge reset_n)
begin
    if(!reset_n)
        begin
            T_1K_cnt <= 9'd0;
            clk_1khz <= 1'b0;
        end
    else if(T_1K_cnt == T_1K_END)
        begin
            clk_1khz <= ~clk_1khz;
            T_1K_cnt <= 9'd0;
        end
    else
        T_1K_cnt <= T_1K_cnt + 1'b1;
end

同理在1kHz的基础上获得1Hz的时钟,数500次翻转即可,那么就是0~499,按照500来算的话最多也是9位(0~8)。

cpp 复制代码
reg clk_1hz;
reg [8:0] T_1_cnt;
localparam T_1_END = 9'd499;
always@(posedge clk_1khz or negedge reset_n)
begin
	if(!reset_n)
		begin
			T_1_cnt <= 9'd0;
			clk_1hz <= 1'b0;
		end
	else if(T_1_cnt == T_1_END)
		begin
			clk_1hz <= ~clk_1hz;
			T_1_cnt <= 9'd0;
		end
	else
		T_1_cnt <= T_1_cnt + 1'b1;
end

2、仿真搭建

创建激励文件,如下图所示。

将文件命名为"tb_flash_led",点击"OK"。

点击"Finish",如下图所示。

在弹出的界面中点击"OK",如下图所示。

在弹出的界面中点击"Yes",如下图所示。

将所创建的激励文件置于顶层,这样就可以调用flash_led_top等下层激励文件。

在所创建的tb_flash_led.v中添加下面的文件。

bash 复制代码
module tb_flash_led(

    );
reg clk50m;
wire led1;
initial begin
    clk50m = 1'b0;
end
flash_led_top U1(
    .clk50m(clk50m)       ,
    .led1(led1)
);

always #10 clk50m = ~clk50m;
endmodule

点击"Run Simulation"启动仿真,如下图所示。

点击"U1"后可以在展开的Objects栏中将想要观察的信号拖入右侧

点击下图所示的按钮开启仿真。

可以看到1MHz的高电平时间为500ns,并且计数值为18(注意,是16进制的18),对应十进制就是24,也就是说0~24即计数25次发生翻转,与之前计算一致。

可以看到1kHz的高电平时间为500us,与之前计算一致。

同理可以看到1Hz的高电平时间为500ms,与理论值一致。

三、实验源码

1、方案1

约束文件flash_led.xdc与上一篇文章基本一致

bash 复制代码
set_property PACKAGE_PIN K21 [get_ports led1]
set_property IOSTANDARD LVCMOS33 [get_ports led1]

create_clock -period 20.000 [get_ports clk50m]
set_property PACKAGE_PIN F22 [get_ports clk50m]
set_property IOSTANDARD LVCMOS33 [get_ports clk50m]

仿真文件tb_flash_led.v

bash 复制代码
module tb_flash_led(

    );
reg clk50m;
wire led1;
initial begin
    clk50m = 1'b0;
end
flash_led_top U1(
    .clk50m(clk50m)       ,
    .led1(led1)
);

always #10 clk50m = ~clk50m;
endmodule

flash_led_top.v,采用三级分频结构,50 MHz → 1 MHz → 1 kHz → 1 Hz 的分频过程;

bash 复制代码
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company: 
// Engineer: 
// 
// Create Date: 2026/11/05 20:43:50
// Design Name: 
// Module Name: flash_led_top
// Project Name: 
// Target Devices: 
// Tool Versions: 
// Description: 
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//////////////////////////////////////////////////////////////////////////////////


module flash_led_top(
input clk50m                                  ,
(* mark_debug = "true" *)output reg led1
    );
wire sys_clk50m;
BUFG U1(
    .O(sys_clk50m),
    .I(clk50m)
);

//=========================================================
// 上电复位延时释放电路
//=========================================================

reg reset_n;
reg [7:0] rst_cnt;
localparam [7:0] RST_END = 8'd128;

always @(posedge sys_clk50m) begin
    if (rst_cnt < RST_END)
        rst_cnt <= rst_cnt + 8'd1;
    else if(rst_cnt == RST_END)
        rst_cnt <= rst_cnt;
    else
        rst_cnt <= 8'd0;
end
always @(posedge sys_clk50m) begin
    if (rst_cnt == RST_END)
        reset_n <= 1'b1;
    else
        reset_n <= 1'b0;
end

//=========================================================
// 50MHz -> 1MHz
//=========================================================

reg clk_1mhz;
reg [4:0] T_1M_cnt;
localparam T_1M_END = 5'd24;
always@(posedge sys_clk50m or negedge reset_n) begin
	if(!reset_n) begin
			T_1M_cnt <= 5'd0;
			clk_1mhz <= 1'b0;
	end
	else if(T_1M_cnt == T_1M_END) begin
			clk_1mhz <= ~clk_1mhz;
			T_1M_cnt <= 5'd0;
	end
	else
		T_1M_cnt <= T_1M_cnt + 1'b1;
end

//=========================================================
// 1MHz -> 1kHz
//=========================================================

reg clk_1khz;
reg [8:0] T_1K_cnt;
localparam T_1K_END = 9'd499;
always@(posedge clk_1mhz or negedge reset_n) begin
	if(!reset_n) begin
			T_1K_cnt <= 9'd0;
			clk_1khz <= 1'b0;
	end
	else if(T_1K_cnt == T_1K_END) begin
			clk_1khz <= ~clk_1khz;
			T_1K_cnt <= 9'd0;
	end
	else
		T_1K_cnt <= T_1K_cnt + 1'b1;
end

//=========================================================
// 1kHz -> 1Hz
//=========================================================

reg clk_1hz;
reg [8:0] T_1_cnt;
localparam T_1_END = 9'd499;
always@(posedge clk_1khz or negedge reset_n) begin
	if(!reset_n) begin
			T_1_cnt <= 9'd0;
			clk_1hz <= 1'b0;
		end
	else if(T_1_cnt == T_1_END) begin
			clk_1hz <= ~clk_1hz;
			T_1_cnt <= 9'd0;
		end
	else
		T_1_cnt <= T_1_cnt + 1'b1;
end

//=========================================================
// LED 输出:用 1Hz 时钟驱动 LED 翻转
//=========================================================

always@(posedge clk_1hz or negedge reset_n)
begin
	if(!reset_n)
		led1 <= 1'b0;
	else
		led1 <= ~led1;
end
endmodule

2、方案2

把整个设计统一在sys_clk50m时钟域内,用计数器和使能信号控制 LED。全设计只有一个时钟 sys_clk50m,所有寄存器都在同一时钟域,时序分析简单;不再产生clk_1mhz、clk_1khz、clk_1hz这些逻辑时钟,避免占用普通布线资源、时钟偏斜和毛刺问题;en_1hz是单周期脉冲使能,LED 每秒翻转一次,闪烁周期 2 秒;直接用一次分频到 1 Hz,计数上限为 50,000,000- 1 =49,999,999,需要 26 位计数器(2²⁶ >50,000,000)。

bash 复制代码
module flash_led_top(
    input  clk50m,
    output reg led1
);

    wire sys_clk50m;

    BUFG U1(
        .O(sys_clk50m),
        .I(clk50m)
    );

    //=========================================================
    // 上电复位延时释放
    //=========================================================
    reg reset_n;
    reg [7:0] rst_cnt;
    localparam RST_END = 8'd128;

    always@(posedge sys_clk50m)
    begin
        if(rst_cnt < RST_END)
            rst_cnt <= rst_cnt + 1'b1;
        else
            rst_cnt <= rst_cnt;
    end

    always@(posedge sys_clk50m)
    begin
        if(rst_cnt == RST_END)
            reset_n <= 1'b1;
        else
            reset_n <= 1'b0;
    end

    //=========================================================
    // 50MHz 分频到 1Hz 的使能信号
    // 50MHz / 1Hz = 50_000_000 个时钟周期翻转一次
    //=========================================================
    reg [25:0] cnt_1hz;
    reg        en_1hz;
    localparam CNT_1HZ_END = 26'd49_999_999;

    always@(posedge sys_clk50m or negedge reset_n)
    begin
        if(!reset_n)
            begin
                cnt_1hz <= 26'd0;
                en_1hz  <= 1'b0;
            end
        else if(cnt_1hz == CNT_1HZ_END)
            begin
                cnt_1hz <= 26'd0;
                en_1hz  <= 1'b1;
            end
        else
            begin
                cnt_1hz <= cnt_1hz + 1'b1;
                en_1hz  <= 1'b0;
            end
    end

    //=========================================================
    // LED 翻转:每 1 秒翻转一次
    //=========================================================
    always@(posedge sys_clk50m or negedge reset_n)
    begin
        if(!reset_n)
            led1 <= 1'b0;
        else if(en_1hz)
            led1 <= ~led1;
    end

endmodule
相关推荐
9527华安18 小时前
FPGA纯verilog实现DP视频接口,支持2K60帧分辨率,提供3套工程源码和技术支持
fpga开发·音视频·dp
ChaseVLSI1 天前
FPGA与ASIC的区别详解:内部结构、开发成本与适用场景,项目选型到底该选哪个
嵌入式硬件·fpga开发·硬件架构·硬件工程
9527华安2 天前
FPGA实现ARINC818总线协议,基于GT Transceivers Wizard高速收发器,提供7套工程源码和技术支持
fpga开发·transceivers·arinc818
小蘇的FPGA2 天前
fpga-wide-data-crc32
fpga开发
落雨无风3 天前
fpga板级调试常见问题
fpga开发
码农小韩3 天前
FPGA学习(一)——LED点灯与开发流程
学习·fpga开发·入门·环境配置·仿真·点灯
FPGA小迷弟3 天前
FPGA 与千兆以太网:各类物理接口的形式、区别、使用方法与应用场景详解
tcp/ip·macos·fpga开发·udp·fpga
NamoAmitabha1283 天前
Zynq 的 AXI GPIO,是“ARM 通过一条国际标准高速公路(AXI 总线),去遥控 FPGA 里的一个寄存器”。---deepseek
fpga开发·zynq--ps
NamoAmitabha1283 天前
ARM 不懂寄存器,它只懂内存地址---deepseek
fpga开发·zynq--ps