一、几个常用的逻辑代码
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