Quick Start:最短路径跑通一个FPGA工程
- 步骤一:安装Vivado 2024.2或更高版本(Lite/WebPACK版免费),确保系统为Windows 10/11 64位或Ubuntu 20.04+。
- 步骤二:下载Xilinx Artix-7(XC7A35T)板卡支持包,或使用Nexys A7-35T开发板。
- 步骤三:新建Vivado工程,选择目标器件xc7a35tcsg324-1。
- 步骤四:创建顶层Verilog文件(top.v),编写一个4位计数器模块。
- 步骤五:添加约束文件(top.xdc),绑定板载100 MHz时钟到引脚,复位按键到GPIO。
- 步骤六:运行综合(Synthesis)与实现(Implementation),生成比特流(bitstream)。
- 步骤七:连接开发板,下载比特流,观察LED以约0.5 Hz频率闪烁(预期现象)。
前置条件与环境
| 项目/推荐值 | 说明 | 替代方案 |
|---|---|---|
| 器件/板卡 | Xilinx Artix-7 (XC7A35T) 或 Zynq-7000 | Altera Cyclone V (Intel Quartus);Lattice iCE40 |
| EDA版本 | Vivado 2024.2 (WebPACK) | Quartus Prime 23.1 Lite;ISE 14.7(仅限7系列) |
| 仿真器 | Vivado Simulator (xsim) 或 ModelSim SE-64 2024.1 | Verilator 5.0+(仅仿真,不支持综合) |
| 时钟/复位 | 板载100 MHz差分/单端时钟;低电平有效复位 | 外部晶振+RC复位电路 |
| 接口依赖 | USB-JTAG(Digilent Adept) | Platform Cable USB II |
| 约束文件 | XDC格式,包含时钟周期、引脚分配、时序例外 | SDC(Quartus);LDC(Lattice) |
目标与验收标准
- 功能点:实现一个UART收发器(波特率115200)、一个SPI主控制器、一个PWM发生器,以及一个AXI4-Lite从接口。
- 性能指标:系统时钟100 MHz时,UART无丢帧(误码率<1e-9);SPI时钟最高50 MHz;PWM分辨率8位,频率可调。
- 资源占用:LUT < 2000,FF < 1500,BRAM < 4块(以Artix-7为参考)。
- Fmax:综合后时序报告显示所有路径建立时间裕量 > 0 ns,保持时间裕量 > 0 ns。
- 验收方式:仿真波形验证各模块功能;上板后通过串口助手发送数据并回显,SPI驱动外部ADC读取数值,PWM驱动LED呼吸效果。
实施步骤
工程结构
- 创建顶层模块top.v,例化所有子模块。
- 子模块分目录存放:src/uart、src/spi、src/pwm、src/axi_lite。
- 仿真文件单独放在sim/目录,与RTL隔离。
- 约束文件约束时钟、复位、UART TX/RX、SPI CS/SCLK/MOSI/MISO、PWM输出引脚。
关键模块:UART收发器
module uart_tx (
input wire clk, // 100 MHz 系统时钟
input wire rst_n, // 低电平复位
input wire start, // 发送启动信号
input wire [7:0] data_in, // 待发送数据
output reg tx, // 串行输出
output reg busy // 忙标志
);
parameter BAUD_DIV = 868; // 100MHz / 115200 ≈ 868
reg [9:0] cnt; // 波特率计数器
reg [3:0] bit_cnt; // 位计数器 (起始位+8数据位+停止位)
reg [9:0] shift; // 移位寄存器
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tx <= 1'b1;
busy <= 1'b0;
cnt <= 0;
bit_cnt <= 0;
shift <= 0;
end else begin
if (start && !busy) begin
busy <= 1'b1;
shift <= {1'b1, data_in, 1'b0}; // 停止位+数据+起始位
bit_cnt <= 4'd10;
cnt <= 0;
end else if (busy) begin
if (cnt == BAUD_DIV-1) begin
cnt <= 0;
tx <= shift[0];
shift <= {1'b1, shift[9:1]};
if (bit_cnt == 4'd1)
busy <= 1'b0;
else
bit_cnt <= bit_cnt - 1;
end else begin
cnt <= cnt + 1;
end
end
end
end
endmodule逐行说明
- 第1行:模块声明,定义输入时钟clk、复位rst_n、启动信号start、8位数据data_in;输出串行tx和忙标志busy。
- 第8行:参数BAUD_DIV = 868,由100 MHz除以115200波特率计算得到,用于产生每位的时间间隔。
- 第9-11行:内部寄存器cnt用于波特率计数,bit_cnt记录当前发送的位数(起始位+8数据位+停止位共10位),shift为移位寄存器,存储待发送的帧。
- 第13行:always块对时钟上升沿和复位下降沿敏感,实现同步复位。
- 第14-18行:复位时,tx拉高(空闲高电平),busy清零,计数器归零。
- 第19-24行:检测到start且busy为低时,启动发送:busy置1,shift装入{停止位1, data_in, 起始位0},bit_cnt设为10,cnt清零。
- 第25-36行:发送过程中,每当cnt计数到BAUD_DIV-1时,输出shift最低位到tx,右移shift,bit_cnt减1;当bit_cnt减到1时(即最后一位停止位已发送),busy清零。
关键模块:SPI主控制器
module spi_master (
input wire clk, // 100 MHz
input wire rst_n,
input wire start,
input wire [7:0] data_in,
output reg [7:0] data_out,
output reg sclk,
output reg mosi,
input wire miso,
output reg cs_n,
output reg busy
);
parameter CLK_DIV = 2; // 100MHz / (2*2) = 25MHz SPI时钟
reg [1:0] cnt;
reg [2:0] bit_cnt;
reg [7:0] shift_in, shift_out;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cs_n <= 1'b1;
sclk <= 1'b0;
mosi <= 1'b0;
busy <= 1'b0;
cnt <= 0;
bit_cnt <= 0;
end else if (start && !busy) begin
cs_n <= 1'b0; // 拉低片选
busy <= 1'b1;
shift_out <= data_in;
bit_cnt <= 3'd7;
cnt <= 0;
end else if (busy) begin
if (cnt == CLK_DIV-1) begin
cnt <= 0;
sclk <= ~sclk; // 翻转时钟
if (sclk) begin // 下降沿采样
shift_in <= {shift_in[6:0], miso};
end else begin // 上升沿输出
mosi <= shift_out[7];
shift_out <= {shift_out[6:0], 1'b0};
if (bit_cnt == 0) begin
busy <= 1'b0;
cs_n <= 1'b1;
data_out <= shift_in;
end else
bit_cnt <= bit_cnt - 1;
end
end else
cnt <= cnt + 1;
end
end
endmodule逐行说明
- 第1行:模块声明,包含时钟、复位、启动、输入输出数据、SPI四线接口(sclk, mosi, miso, cs_n)及忙标志。
- 第12行:参数CLK_DIV=2,SPI时钟频率 = 100MHz / (2*2) = 25MHz,符合典型SPI从设备要求。
- 第13-15行:内部寄存器cnt用于分频,bit_cnt记录剩余位数,shift_in和shift_out分别用于接收和发送移位。
- 第17-24行:复位时,cs_n拉高(无效),sclk低,mosi低,busy清零。
- 第25-29行:检测到start且busy为低时,拉低cs_n,busy置1,加载数据到shift_out,bit_cnt设为7(8位数据,从MSB开始),cnt清零。
- 第30-47行:发送过程中,每CLK_DIV个时钟周期翻转sclk一次。在sclk下降沿(原sclk为高)采样miso到shift_in;在sclk上升沿(原sclk为低)将shift_out最高位输出到mosi,并左移。当bit_cnt减到0时,传输完成,busy清零,cs_n拉高,data_out更新为接收到的数据。
关键模块:PWM发生器
module pwm_gen (
input wire clk,
input wire rst_n,
input wire [7:0] duty, // 占空比 0-255
input wire [15:0] period, // 周期计数 (决定频率)
output reg pwm_out
);
reg [15:0] cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 0;
pwm_out <= 1'b0;
end else begin
if (cnt >= period) begin
cnt <= 0;
pwm_out <= 1'b1;
end else begin
cnt <= cnt + 1;
if (cnt == duty)
pwm_out <= 1'b0;
end
end
end
endmodule逐行说明
- 第1行:模块声明,输入8位占空比duty和16位周期period,输出pwm_out。
- 第8行:16位计数器cnt,用于跟踪当前周期位置。
- 第10-12行:复位时cnt清零,pwm_out输出低。
- 第13-22行:每个时钟周期cnt递增。当cnt达到period时,周期结束,cnt归零,pwm_out置高(开始新周期)。当cnt等于duty时,pwm_out置低(占空比结束)。这样产生一个周期为(period+1)个时钟周期、高电平持续(duty+1)个时钟周期的PWM波形。
关键模块:AXI4-Lite从接口
module axi_lite_slave (
input wire clk,
input wire rst_n,
// 写地址通道
input wire [31:0] awaddr,
input wire awvalid,
output reg awready,
// 写数据通道
input wire [31:0] wdata,
input wire wvalid,
output reg wready,
// 写响应通道
output reg [1:0] bresp,
output reg bvalid,
input wire bready,
// 读地址通道
input wire [31:0] araddr,
input wire arvalid,
output reg arready,
// 读数据通道
output reg [31:0] rdata,
output reg [1:0] rresp,
output reg rvalid,
input wire rready
);
// 内部寄存器示例
reg [31:0] reg0, reg1;
// 写事务处理
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
awready <= 1'b0;
wready <= 1'b0;
bvalid <= 1'b0;
bresp <= 2'b00;
reg0 <= 0;
reg1 <= 0;
end else begin
// 地址与数据同时有效时,握手并写入
if (awvalid && wvalid && !bvalid) begin
awready <= 1'b1;
wready <= 1'b1;
case (awaddr[3:2]) // 按字对齐
2'b00: reg0 <= wdata;
2'b01: reg1 <= wdata;
default: ;
endcase
bresp <= 2'b00; // OKAY
bvalid <= 1'b1;
end else if (bvalid && bready) begin
bvalid <= 1'b0;
awready <= 1'b0;
wready <= 1'b0;
end
end
end
// 读事务处理
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
arready <= 1'b0;
rvalid <= 1'b0;
rresp <= 2'b00;
rdata <= 0;
end else begin
if (arvalid && !rvalid) begin
arready <= 1'b1;
case (araddr[3:2])
2'b00: rdata <= reg0;
2'b01: rdata <= reg1;
default: rdata <= 0;
endcase
rresp <= 2'b00;
rvalid <= 1'b1;
end else if (rvalid && rready) begin
rvalid <= 1'b0;
arready <= 1'b0;
end
end
end
endmodule逐行说明
- 第1-22行:模块声明,定义AXI4-Lite五个独立通道(写地址、写数据、写响应、读地址、读数据),每个通道包含valid/ready握手信号。
- 第24-25行:内部寄存器reg0和reg1,作为从设备存储空间示例。
- 第27-47行:写事务处理。复位时所有握手信号清零。当awvalid和wvalid同时有效且bvalid为低时,握手成功(awready/wready置1),根据地址低两位(字对齐)写入对应寄存器,bresp返回OKAY,bvalid置1。当bvalid和bready同时有效时,写响应完成,清除握手信号。
- 第49-69行:读事务处理。复位时清零。当arvalid有效且rvalid为低时,握手成功(arready置1),根据地址读取对应寄存器到rdata,rresp返回OKAY,rvalid置1。当rvalid和rready同时有效时,读完成,清除握手信号。
时序/CDC与约束
- 所有模块使用同一时钟域(100 MHz),避免跨时钟域问题。若引入异步复位,使用同步释放电路。
- 约束文件示例:
create_clock -period 10.000 -name sys_clk [get_ports clk];set_input_delay -clock sys_clk 2.0 [get_ports miso];set_output_delay -clock sys_clk 2.0 [get_ports {mosi sclk cs_n}]。 - 常见坑:未约束异步复位会导致保持时间违规;SPI sclk输出若不约束,综合工具可能插入缓冲器使时钟偏移过大。
验证
- 编写testbench,例化所有模块,提供时钟和复位激励。
- UART测试:发送0x55(交替位),观察tx波形应产生起始位0、8位数据(01010101)、停止位1。
- SPI测试:发送0xA5,观察sclk、mosi、miso(回环连接)波形,验证数据正确。
- PWM测试:设置duty=128,period=1000,验证pwm_out占空比约50%。
- AXI测试:通过写reg0再读回,验证数据一致性。
上板验证
- 连接开发板,使用Vivado Hardware Manager下载比特流。
- UART:用串口助手(如Putty)打开对应COM口,发送字符应回显。
- SPI:连接SPI ADC(如MCP3008),读取电压值并通过UART打印。
- PWM:连接LED,观察呼吸效果。
- 常见坑:引脚约束错误导致无输出;时钟频率过高导致SPI通信失败;UART波特率偏差过大(需用示波器测量实际波特率)。
原理与设计说明
FPGA设计本质是“用硬件描述语言描述数字电路”,与软件编程有本质区别。关键trade-off包括:
- 资源 vs Fmax:组合逻辑层级越多,路径延迟越大,Fmax越低。例如UART的波特率计数器若用32位加法器,路径延迟可能超过10 ns,导致100 MHz时序违例。解决方案是流水线(pipeline)或使用专用进位链。
- 吞吐 vs 延迟:SPI全双工模式可同时收发,但需要额外寄存器缓冲。若追求低延迟,可减少缓冲深度;若追求高吞吐,可增加FIFO。
- 易用性 vs 可移植性:使用Xilinx原语(如BUFG、MMCM)可优化时序,但移植到Altera需重写。建议用标准Verilog描述逻辑,仅在必要时使用原语。
AXI4-Lite协议的核心是valid-ready握手:主设备置valid,从设备置ready,当两者同时为高时传输发生。这种机制允许主从设备在不同速率下工作,且易于流水线化。在实现中,注意避免死锁:若从设备在未准备好时一直拉低ready,主设备可能无限等待。
验证与结果
| 指标 | 测量值(示例) | 测量条件 |
|---|---|---|
| Fmax (UART) | 200 MHz | Vivado 2024.2, Artix-7 -1速度等级 |
| Fmax (SPI) | 150 MHz | 同上,SPI时钟输出约束为50 MHz |
Fmax (PWM标签:如需转载,请注明出处:https://z.shaonianxue.cn/41127.html ![]() ![]() ![]() ![]() FPGA时序收敛技巧:时钟偏斜与建立时间优化![]() FPGA项目实战:基于Verilog的SPI通信协议实现![]() FPGA实现HDMI 2.0视频接口:TMDS编码与显示控制器设计加载中… |



