Quick Start
- 步骤一:下载并安装 Vivado 2023.2(或更高版本),选择 Artix-7 系列器件(如 XC7A35T)。
- 步骤二:创建新工程,选择目标板卡(如 Nexys A7-50T 或自定义板卡),添加约束文件(.xdc)。
- 步骤三:编写 LED 流水灯顶层模块(led_flow.v),实现 8 个 LED 以 1 Hz 频率循环点亮。
- 步骤四:编写 UART 发送模块(uart_tx.v),波特率 115200,数据位 8,无校验位,1 停止位。
- 步骤五:编写 UART 接收模块(uart_rx.v),与发送模块对称,支持回环测试。
- 步骤六:编写顶层模块(top.v),实例化 LED 流水灯和 UART 模块,连接时钟(50 MHz)、复位(低有效)和串口引脚。
- 步骤七:运行综合(Synthesis)和实现(Implementation),检查时序是否满足(WNS ≥ 0)。
- 步骤八:生成比特流,下载到板卡。观察 LED 流水灯效果;用串口助手发送数据,验证回环功能。
- 步骤九:验收点:LED 按顺序循环点亮,周期 1 秒;串口发送“Hello”回显相同字符串。
前置条件与环境
| 项目/推荐值 | 说明 | 替代方案 |
|---|---|---|
| 器件/板卡 | Xilinx Artix-7 XC7A35T-1CPG236C | 其他 7 系列(如 Spartan-7)或 Intel Cyclone IV |
| EDA 版本 | Vivado 2023.2 | Vivado 2022.2 或更高 |
| 仿真器 | Vivado Simulator(xsim) | ModelSim、QuestaSim |
| 时钟/复位 | 50 MHz 单端时钟,低有效异步复位 | 100 MHz 时钟需调整分频系数 |
| 接口依赖 | UART 引脚:TX(J2-3)、RX(J2-4) | 按板卡原理图修改约束 |
| 约束文件 | top.xdc(时钟周期 20 ns,引脚分配) | 使用 Vivado 自动引脚分配 |
| 串口工具 | 串口助手(波特率 115200,8N1) | PuTTY、minicom |
| 开发语言 | Verilog-2001 | VHDL(需重写模块) |
目标与验收标准
- 功能点:8 个 LED 以 1 Hz 频率循环流水;UART 回环(发送任意字节,接收相同字节)。
- 性能指标:UART 波特率 115200 bps,误码率 < 1e-6(在 50 MHz 时钟下)。
- 资源占用:LUT ≤ 200,FF ≤ 150(示例值,以实际综合报告为准)。
- Fmax:≥ 100 MHz(实际时钟 50 MHz,留有余量)。
- 验收方式:上板后 LED 流水;串口助手发送 0x55,收到 0x55;发送 0xAA,收到 0xAA。
实施步骤
工程结构
project/
├── src/
│ ├── led_flow.v # LED 流水灯模块
│ ├── uart_tx.v # UART 发送模块
│ ├── uart_rx.v # UART 接收模块
│ ├── top.v # 顶层模块
├── constr/
│ └── top.xdc # 约束文件
├── sim/
│ └── tb_top.v # 测试平台
└── vivado_project.xpr # Vivado 工程文件逐行说明
- 第 1 行:项目根目录,包含所有源文件。
- 第 2 行:src/ 目录存放 RTL 源文件。
- 第 3–6 行:各模块文件,每个文件对应一个 module。
- 第 7 行:constraints 目录存放约束文件。
- 第 8 行:top.xdc 包含时钟周期定义和引脚分配。
- 第 9 行:sim/ 目录存放仿真文件。
- 第 10 行:tb_top.v 用于功能仿真。
- 第 11 行:Vivado 工程文件,双击打开。
关键模块:LED 流水灯
module led_flow (
input wire clk,
input wire rst_n,
output reg [7:0] led
);
reg [25:0] cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 26'd0;
led <= 8'b0000_0001;
end else if (cnt == 26'd49_999_999) begin
cnt <= 26'd0;
led <= {led[6:0], led[7]};
end else begin
cnt <= cnt + 1'b1;
end
end
endmodule逐行说明
- 第 1 行:模块声明,端口包括时钟、复位和 8 位 LED 输出。
- 第 2–4 行:输入 clk(50 MHz),输入 rst_n(低有效复位),输出 led(寄存器类型)。
- 第 5 行:声明 26 位计数器 cnt,最大计数值 50,000,000(1 秒)。
- 第 6 行:时序逻辑,时钟上升沿或复位下降沿触发。
- 第 7–8 行:复位时 cnt 清零,led 初始化为 0x01(最右 LED 亮)。
- 第 9–10 行:当 cnt 达到 49,999,999(50 MHz 时钟下 1 秒),cnt 归零。
- 第 11 行:循环左移 led,{led[6:0], led[7]} 实现流水效果。
- 第 12–13 行:否则 cnt 递增。
关键模块:UART 发送
module uart_tx (
input wire clk,
input wire rst_n,
input wire tx_start,
input wire [7:0] tx_data,
output reg tx,
output reg tx_busy
);
localparam BAUD_CNT = 434; // 50MHz / 115200 ≈ 434
reg [8:0] baud_cnt;
reg [3:0] bit_idx;
reg [9:0] shift_reg;
reg sending;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tx <= 1'b1;
tx_busy <= 1'b0;
baud_cnt <= 9'd0;
bit_idx <= 4'd0;
shift_reg <= 10'd0;
sending <= 1'b0;
end else begin
if (!sending && tx_start) begin
shift_reg <= {1'b1, tx_data, 1'b0}; // stop, data, start
bit_idx <= 4'd0;
baud_cnt <= 9'd0;
sending <= 1'b1;
tx_busy <= 1'b1;
end else if (sending) begin
if (baud_cnt == BAUD_CNT-1) begin
baud_cnt <= 9'd0;
tx <= shift_reg[0];
shift_reg <= {1'b1, shift_reg[9:1]};
if (bit_idx == 4'd9) begin
sending <= 1'b0;
tx_busy <= 1'b0;
end else begin
bit_idx <= bit_idx + 1'b1;
end
end else begin
baud_cnt <= baud_cnt + 1'b1;
end
end
end
end
endmodule逐行说明
- 第 1 行:模块声明,端口包括时钟、复位、启动信号、数据输入、串行输出和忙标志。
- 第 2–7 行:输入输出定义,tx 为串行输出,tx_busy 指示发送忙。
- 第 8 行:本地参数 BAUD_CNT = 434,由 50 MHz / 115200 计算得到(取整)。
- 第 9–12 行:内部寄存器:波特率计数器、位索引、移位寄存器、发送状态。
- 第 13 行:时序逻辑,时钟上升沿或复位下降沿触发。
- 第 14–20 行:复位时 tx 拉高(空闲高),tx_busy 清零,其他寄存器清零。
- 第 21–22 行:当不在发送且收到启动信号时,加载数据到移位寄存器(格式:停止位1 + 数据 + 起始位0)。
- 第 23–25 行:位索引清零,波特计数器清零,设置发送状态和忙标志。
- 第 26 行:发送状态下,检查波特计数器是否达到阈值。
- 第 27–28 行:达到阈值时,波特计数器归零,输出当前最低位(shift_reg[0])。
- 第 29 行:移位寄存器右移一位,高位补 1(空闲位)。
- 第 30–31 行:如果已发送完 10 位(起始+8数据+停止),结束发送。
- 第 32–33 行:否则位索引加 1。
- 第 34–36 行:未达到阈值时,波特计数器递增。
关键模块:UART 接收
module uart_rx (
input wire clk,
input wire rst_n,
input wire rx,
output reg rx_done,
output reg [7:0] rx_data
);
localparam BAUD_CNT = 434;
localparam HALF_CNT = 217; // 半周期采样
reg [8:0] baud_cnt;
reg [3:0] bit_idx;
reg [7:0] data_reg;
reg receiving;
reg rx_sync;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
rx_done <= 1'b0;
rx_data <= 8'd0;
baud_cnt <= 9'd0;
bit_idx <= 4'd0;
data_reg <= 8'd0;
receiving <= 1'b0;
rx_sync <= 1'b1;
end else begin
rx_sync <= rx; // 同步打拍
if (!receiving && !rx_sync) begin
receiving <= 1'b1;
baud_cnt <= 9'd0;
bit_idx <= 4'd0;
end else if (receiving) begin
if (baud_cnt == BAUD_CNT-1) begin
baud_cnt <= 9'd0;
if (bit_idx == 4'd0) begin
// 起始位,忽略
end else if (bit_idx <= 4'd8) begin
data_reg[bit_idx-1] <= rx_sync;
end else if (bit_idx == 4'd9) begin
// 停止位,输出数据
rx_data <= data_reg;
rx_done <= 1'b1;
receiving <= 1'b0;
end
bit_idx <= bit_idx + 1'b1;
end else begin
baud_cnt <= baud_cnt + 1'b1;
end
end
end
end
endmodule逐行说明
- 第 1 行:模块声明,端口包括时钟、复位、串行输入、接收完成标志和数据输出。
- 第 2–6 行:输入输出定义,rx 为串行输入,rx_done 指示接收完成。
- 第 7 行:本地参数 BAUD_CNT = 434,与发送模块一致。
- 第 8 行:HALF_CNT = 217,用于起始位中间采样(此处简化,实际使用半周期对齐)。
- 第 9–13 行:内部寄存器:波特计数器、位索引、数据寄存器、接收状态、同步打拍。
- 第 14 行:时序逻辑。
- 第 15–22 行:复位时所有寄存器清零,rx_sync 拉高(空闲高)。
- 第 23 行:同步打拍 rx 信号,消除亚稳态。
- 第 24–25 行:检测到起始位(rx_sync 下降沿),开始接收。
- 第 26–27 行:设置接收状态,重置计数器和位索引。
- 第 28 行:接收状态下,检查波特计数器。
- 第 29–30 行:达到阈值时,计数器归零。
- 第 31–32 行:位索引为 0 时,忽略起始位。
- 第 33–34 行:位索引 1–8 时,采样数据位并存入 data_reg。
- 第 35–38 行:位索引为 9 时,停止位,输出数据并置 rx_done 信号。
- 第 39 行:位索引递增。
- 第 40–42 行:未达到阈值时,计数器递增。
顶层模块与约束
module top (
input wire clk,
input wire rst_n,
output wire [7:0] led,
input wire rx,
output wire tx
);
wire tx_busy;
wire rx_done;
wire [7:0] rx_data;
reg [7:0] tx_data;
reg tx_start;
// LED 流水灯实例
led_flow u_led (
.clk (clk),
.rst_n (rst_n),
.led (led)
);
// UART 接收实例
uart_rx u_rx (
.clk (clk),
.rst_n (rst_n),
.rx (rx),
.rx_done (rx_done),
.rx_data (rx_data)
);
// UART 发送实例(回环)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tx_data <= 8'd0;
tx_start <= 1'b0;
end else if (rx_done && !tx_busy) begin
tx_data <= rx_data;
tx_start <= 1'b1;
end else begin
tx_start <= 1'b0;
end
end
uart_tx u_tx (
.clk (clk),
.rst_n (rst_n),
.tx_start (tx_start),
.tx_data (tx_data),
.tx (tx),
.tx_busy (tx_busy)
);
endmodule逐行说明
- 第 1 行:顶层模块声明,端口包括时钟、复位、LED、UART 引脚。
- 第 2–6 行:输入输出定义。
- 第 7–10 行:内部连线声明,连接子模块。
- 第 11–12 行:寄存器用于回环控制。
- 第 13–17 行:实例化 LED 流水灯模块。
- 第 18–24 行:实例化 UART 接收模块。
- 第 25–33 行:回环逻辑:收到数据后,若发送空闲,则启动发送。
- 第 34–41 行:实例化 UART 发送模块。
约束文件(top.xdc)
# 时钟约束
create_clock -period 20.000 [get_ports clk]
# LED 引脚(示例,按板卡修改)
set_property PACKAGE_PIN U12 [get_ports {led[0]}]
set_property PACKAGE_PIN U11 [get_ports {led[1]}]
# ... 其他 LED 引脚类似
# UART 引脚
set_property PACKAGE_PIN J2 [get_ports rx]
set_property PACKAGE_PIN J3 [get_ports tx]
# 电平标准
set_property IOSTANDARD LVCMOS33 [get_ports *]逐行说明
- 第 1 行:定义时钟周期 20 ns(50 MHz)。
- 第 2 行:LED 引脚分配示例,按实际板卡修改。
- 第 3–4 行:更多 LED 引脚。
- 第 5 行:UART 接收引脚。
- 第 6 行:UART 发送引脚。
- 第 7 行:设置 I/O 电平标准为 3.3V LVCMOS。
常见坑与排查
- 坑 1:波特率计算错误。排查:检查 BAUD_CNT 是否等于 50,000,000 / 115,200 ≈ 434。若使用 100 MHz 时钟,BAUD_CNT = 868。
- 坑 2:复位极性错误。排查:确认 rst_n 为低有效,且板卡复位按键输出低电平。
- 坑 3:UART 引脚 TX/RX 接反。排查:交换约束文件中的引脚分配。
- 坑 4:LED 不亮或全亮。排查:检查 led 输出是否被驱动;用仿真验证 led_flow 模块。
- 坑 5:串口无回显。排查:用示波器或逻辑分析仪观察 TX 引脚波形;确认串口助手波特率、数据位、停止位设置一致。
原理与设计说明
LED 流水灯的核心是分频计数器。50 MHz 时钟周期 20 ns,要得到 1 Hz 周期(1 秒),需要计数 50,000,000 个时钟周期。计数器宽度为 26 位(2^26 = 67,108,864,足够覆盖)。流水效果通过循环左移实现,每次溢出时 led 值左移一位,最低位补最高位。
UART 通信的关键是波特率生成和位采样。发送模块使用一个计数器在 50 MHz 时钟下产生 115200 Hz 的使能信号(每 434 个时钟周期产生一个脉冲)。数据以 10 位帧格式发送:1 位起始位(低电平)、8 位数据位(LSB 先发)、1 位停止位(高电平)。接收模块同样使用计数器,但为了正确采样,通常需要在数据位中间采样。本设计简化了采样逻辑,直接在每个波特率周期末尾采样,对于 115200 波特率在 50 MHz 时钟下仍能可靠工作(采样窗口误差 < 1%)。
回环逻辑在顶层模块中实现:当 uart_rx 检测到完整一帧数据时,rx_done 脉冲触发,将 rx_data 赋值给 tx_data,并启动 tx_start。发送模块在 tx_busy 为低时接收新数据,完成后自动置位 tx_busy。这种握手方式避免了数据冲突。
Trade-off 分析:本设计采用计数器分频而非 PLL,优点是简单、可移植,缺点是占用更多逻辑资源。对于 50 MHz 时钟和 115200 波特率,计数器宽度为 9 位(2^9=512),资源开销很小。如果使用 PLL 生成精确时钟,可提高波特率精度(如 921600),但增加了时钟域约束复杂度。
验证与结果
| 验证项 | 预期结果 | 实际结果(示例) | 测量条件 |
|---|---|---|---|
| LED 流水频率 | 1 Hz | 1.002 Hz(误差 < 0.2%) | 示波器测量 led[0] 波形 |
UART 发送标签:如需转载,请注明出处:https://z.shaonianxue.cn/43455.html ![]() ![]() ![]() ![]() FPGA仿真效率提升指南:基于SystemVerilog的可重用测试平台设计与实现![]() FPGA图像处理实战:基于Vivado HLS的实时边缘检测系统设计![]() FPGA软核处理器与商用嵌入式MPU选型指南:灵活性与性能的权衡实践加载中… |



