Quick Start
本指南面向FPGA初学者,以Xilinx Vivado工具链和Artix-7开发板为载体,通过一个4位二进制计数器项目,完整演示从Verilog编码、仿真验证到时序收敛的入门流程。读者可在2小时内完成第一个可运行的FPGA设计。
前置条件
- 硬件:Xilinx Artix-7开发板(如Basys 3或Nexys A7),板载100MHz晶振、4个以上LED、至少1个按钮。
- 软件:Vivado 2025.2或更高版本(HL WebPACK版即可),安装时勾选“Vivado”和“Vivado Simulator”。
- 基础:了解数字电路基本概念(寄存器、时钟、复位),熟悉Verilog语法基础(模块、always块、赋值)。
目标与验收标准
- 目标1:编写并仿真一个4位二进制计数器,在100MHz时钟下每2^26个周期(约671ms)翻转一次最高位。
- 目标2:通过综合、实现、生成比特流并下载到开发板,观察LED以约0.5Hz频率闪烁。
- 验收标准:LED实际闪烁周期与仿真结果一致(误差<5%),且时序分析报告无建立时间或保持时间违例。
实施步骤
步骤1:安装Vivado并创建工程
从Xilinx官网下载Vivado 2025.2 HL WebPACK安装程序,运行安装向导,选择“Vivado HL WebPACK”产品。安装完成后启动Vivado,点击“Create Project”,选择“RTL Project”,目标器件选择Xilinx Artix-7 XC7A35T(与Basys 3或Nexys A7匹配)。工程名称建议使用“counter_demo”。
步骤2:编写顶层模块
在工程中添加一个新的Verilog源文件,命名为“counter_top.v”,输入以下代码:
module counter_top (
input wire clk, // 100MHz 系统时钟
input wire rst_n, // 低电平有效异步复位
input wire en, // 使能信号
output wire [3:0] led // 4位LED输出
);
// 内部计数器值
wire [3:0] cnt;
// 实例化计数器模块
counter_4bit u_counter (
.clk (clk),
.rst_n (rst_n),
.en (en),
.count (cnt)
);
assign led = ~cnt; // 低电平驱动LED(共阳接法)
endmodule逐行说明
- 第1行:模块声明,端口列表包含clk、rst_n、en、led。clk是输入,rst_n是低电平有效复位,en是使能,led是输出。
- 第2行:clk为100MHz系统时钟,来自板载振荡器。
- 第3行:rst_n低电平有效,意味着当rst_n=0时复位生效。
- 第4行:en使能信号,高电平有效,控制计数器是否递增。
- 第5行:led是4位输出,连接到板载LED。
- 第9行:内部连线cnt,用于传递计数器值。
- 第12-17行:实例化计数器模块counter_4bit,端口按名称连接。
- 第19行:将计数器值取反后赋值给led。这是因为许多开发板LED是低电平点亮(共阳接法),取反后使逻辑1对应LED亮。若板卡为共阴,则直接assign led = cnt。
步骤3:编写计数器模块
添加另一个Verilog源文件“counter_4bit.v”,输入以下代码:
module counter_4bit (
input wire clk,
input wire rst_n,
input wire en,
output reg [3:0] count
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
count <= 4'd0;
else if (en)
count <= count + 1'b1;
else
count <= count;
end
endmodule逐行说明
- 第1行:模块声明,端口与顶层一致,但count是reg类型(在always块中被赋值)。
- 第2-4行:输入端口声明。
- 第5行:输出count声明为reg,因为它在always块中被赋值。
- 第7行:always块敏感列表为posedge clk和negedge rst_n,表示时钟上升沿触发,复位下降沿触发(异步复位)。
- 第8行:if (!rst_n)判断复位条件,当rst_n=0时执行复位。
- 第9行:复位时count赋值为4位二进制0(4'd0)。
- 第10行:else if (en)判断使能有效。
- 第11行:使能有效时,count递增1。注意:这里使用非阻塞赋值(<=),符合时序逻辑设计规范,避免仿真竞争。
- 第12行:else分支保持count不变,防止综合出锁存器。
步骤4:添加约束文件
在工程中添加约束文件(.xdc),命名为“basys3.xdc”(以Basys 3为例),输入以下内容:
# 100MHz时钟引脚
set_property PACKAGE_PIN W5 [get_ports clk]
set_property IOSTANDARD LVCMOS33 [get_ports clk]
# 复位按钮(低电平有效)
set_property PACKAGE_PIN U18 [get_ports rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports rst_n]
# 使能开关(高电平有效)
set_property PACKAGE_PIN J15 [get_ports en]
set_property IOSTANDARD LVCMOS33 [get_ports en]
# LED输出(共阳接法)
set_property PACKAGE_PIN M14 [get_ports {led[0]}]
set_property PACKAGE_PIN M15 [get_ports {led[1]}]
set_property PACKAGE_PIN G14 [get_ports {led[2]}]
set_property PACKAGE_PIN D18 [get_ports {led[3]}]
set_property IOSTANDARD LVCMOS33 [get_ports {led[*]}]注意:引脚编号需根据实际板卡原理图调整。Basys 3的时钟引脚为W5,复位按钮为U18(按下为低电平),使能开关为J15(拨上为高电平),LED为M14、M15、G14、D18(共阳,低电平点亮)。
步骤5:运行综合与实现
在Vivado的Flow Navigator中,依次点击“Run Synthesis”和“Run Implementation”。综合完成后,检查Messages窗口,确保无严重警告(如Latch inferred)或错误。实现完成后,打开“Report Timing Summary”,确认建立时间(Setup Slack)和保持时间(Hold Slack)均为正值。
步骤6:生成比特流并下载
点击“Generate Bitstream”,等待完成后,连接开发板并上电。点击“Open Hardware Manager”,自动检测目标器件,选择“Program Device”并加载生成的.bit文件。下载成功后,观察LED:拨动使能开关(en)至高电平,按下复位按钮(rst_n)再松开,LED应开始以约0.5Hz频率闪烁(最高位对应LED[3])。
步骤7:编写仿真Testbench
在工程中添加仿真源文件“tb_counter_top.v”,输入以下代码:
`timescale 1ns / 1ps
module tb_counter_top;
reg clk;
reg rst_n;
reg en;
wire [3:0] led;
// 实例化顶层模块
counter_top uut (
.clk (clk),
.rst_n (rst_n),
.en (en),
.led (led)
);
// 生成100MHz时钟(周期10ns)
always #5 clk = ~clk;
initial begin
// 初始化信号
clk = 0;
rst_n = 0;
en = 0;
// 释放复位
#100 rst_n = 1;
// 使能计数器
#10 en = 1;
// 仿真足够时间观察翻转(2^26个周期 ≈ 671ms)
// 为加速仿真,可减少计数深度,此处仅验证功能
#1000;
// 停止仿真
$stop;
end
endmodule逐行说明
- 第1行:`timescale指令设置仿真时间单位1ns,精度1ps。
- 第3行:模块声明,无端口列表。
- 第5-8行:声明激励信号(reg类型)和观测信号(wire类型)。
- 第11-16行:实例化顶层模块,端口连接。
- 第19行:always块每5ns翻转clk,生成周期10ns(100MHz)时钟。
- 第21-35行:initial块定义仿真序列。
- 第23-25行:初始化所有信号为0。
- 第28行:100ns后释放复位(rst_n=1)。
- 第31行:再10ns后使能计数器(en=1)。
- 第34行:仿真1000ns(实际计数器深度为4位,每16个时钟翻转一次,此处仅验证基本功能)。
- 第37行:$stop停止仿真。
在Vivado中设置仿真运行时间至少1ms(对应实际671ms的缩放),观察led[3]波形是否每671ms翻转一次。若仿真时间不足,可修改计数器深度为26位(在顶层模块中扩展)以验证完整周期。
步骤8:验证结果
下载后,用示波器或逻辑分析仪测量LED[3]引脚,其翻转周期应为671ms ± 5%。若偏差过大,检查时钟频率是否准确(板载晶振误差通常<50ppm)。仿真波形中,计数器值每16个时钟周期(160ns)变化一次,led[3]每8次计数翻转一次(即每1280ns翻转一次,在4位深度下)。实际设计需将计数器扩展至26位以实现0.5Hz闪烁。
排障指南
- LED不亮:检查约束文件引脚分配是否正确,确认板卡供电正常,复位按钮是否被按下(低电平有效)。
- 综合报错“Latch inferred”:检查always块中是否所有分支都赋值了count,确保else分支存在。
- 时序违例(Setup/Hold Slack为负):降低时钟频率或优化代码(如减少组合逻辑级数),或添加流水线寄存器。
- 仿真结果与硬件不一致:检查testbench中的时间尺度(`timescale),确认复位和使能时序与硬件一致。
扩展练习
- 将计数器扩展至26位,实现精确的0.5Hz闪烁(2^26 / 100MHz ≈ 0.671s)。
- 添加按键消抖模块,使复位更可靠。
- 使用PLL(MMCM)生成不同频率时钟,观察LED闪烁频率变化。
- 实现可调占空比的PWM输出,驱动LED亮度渐变。
参考资源
- Xilinx Vivado Design Suite User Guide (UG910)
- Basys 3 Reference Manual (Digilent)
- Verilog HDL: A Guide to Digital Design and Synthesis (Samir Palnitkar)
附录:时序收敛原理与风险边界
原因与机制:时序收敛的本质是确保每个寄存器的数据路径延迟满足建立时间和保持时间要求。建立时间要求数据在时钟沿到来前稳定,保持时间要求数据在时钟沿后保持稳定。在100MHz时钟下,周期为10ns,减去时钟偏斜(skew)和寄存器内部延迟后,可用路径延迟约8-9ns。若组合逻辑级数过多(如超过10级LUT),路径延迟可能超过周期,导致建立时间违例。
落地路径:在Vivado中,通过Report Timing Summary查看关键路径(Critical Path),分析其延迟组成。优化方法包括:1)减少组合逻辑级数(如将大计数器拆分为多个小计数器级联);2)使用流水线寄存器打断长路径;3)调整综合策略(如启用retiming);4)使用更快的时序模型(如Speed Grade -2)。
风险边界:初学者常见误区是忽视约束文件中的时钟定义(create_clock),导致Vivado无法正确分析时序。若未定义主时钟,时序分析将跳过该路径,但实际硬件中仍可能因时序违例而功能异常。此外,异步复位信号的同步化处理(使用两级寄存器)是必须的,否则可能导致亚稳态传播。



