1. UART IP核设计概述
在FPGA开发中,UART通信模块是最基础也最常用的外设之一。这个开源的UART IP核采用纯Verilog实现,具有以下核心特性:
- 全参数化设计,支持波特率、数据位、停止位等灵活配置
- 独立的收发通道,最高支持115200bps通信速率
- 同步/异步FIFO可选配置,适配不同时钟域需求
- 完备的VCS仿真测试环境和Vivado上板验证工程
这个IP核的设计哲学是"够用就好"——没有复杂的流控协议,只实现最基础的异步串行通信功能,但确保了核心功能的稳定性和可移植性。实测表明,在Xilinx Artix-7和Altera Cyclone IV系列FPGA上都能稳定运行。
2. 核心模块实现解析
2.1 波特率生成器设计
波特率生成采用相位累加器方案,相比传统分频计数器有以下优势:
- 支持动态波特率调整
- 避免整数分频带来的累积误差
- 实现代码更加简洁
关键实现代码如下:
verilog复制parameter CLK_FREQ = 50_000_000; // 50MHz系统时钟
parameter BAUD_RATE = 115200;
localparam BAUD_STEP = (BAUD_RATE << 16) / CLK_FREQ;
reg [15:0] baud_accum = 0;
always @(posedge clk) begin
baud_accum <= baud_accum + BAUD_STEP;
end
wire baud_tick = baud_accum[15]; // 最高位翻转即为波特率时钟
注意:当系统时钟不是波特率的整数倍时,传统分频方案会产生累积误差。例如50MHz时钟生成115200波特率时,实际误差达2.3%,接近UART协议的容限上限(3%)。相位累加器方案可将误差降低到0.01%以下。
2.2 接收状态机实现
接收模块采用经典的三段式状态机设计:
- IDLE状态:检测起始位下降沿
- START状态:起始位确认
- DATA状态:数据位采样
- STOP状态:停止位校验
关键设计细节:
verilog复制always @(posedge clk) begin
case(rx_state)
IDLE: if(!rxd_sync) begin // 下降沿检测
baud_cnt <= CLK_DIVIDER/2; // 从半周期开始计数
rx_state <= START;
end
START: if(baud_cnt == 0) begin
if(!rxd_sync) begin // 二次确认起始位
bit_cnt <= 0;
rx_state <= DATA;
end else rx_state <= IDLE; // 毛刺过滤
end
DATA: if(baud_cnt == 0) begin
rx_shift[bit_cnt] <= rxd_sync; // 数据采样
if(bit_cnt == DATA_BITS-1)
rx_state <= STOP;
else
bit_cnt <= bit_cnt + 1;
end
STOP: if(baud_cnt == 0) begin
rx_done <= 1'b1;
rx_byte <= rx_shift;
rx_state <= IDLE;
end
endcase
end
经验分享:起始位检测采用"两次采样"机制能有效过滤毛刺干扰。第一次检测到下降沿后,在比特周期中点再次确认起始位状态,避免误触发。
2.3 发送模块设计
发送模块相对简单,主要注意以下几点:
- 发送使能信号需要至少保持一个波特率周期
- 发送完成标志应在停止位中间位置产生
- 支持发送FIFO可配置为同步或异步模式
发送状态机核心代码:
verilog复制always @(posedge clk) begin
if(tx_en && tx_state == IDLE) begin
tx_state <= START;
tx_shift <= {1'b1, tx_data, 1'b0}; // 拼接停止位和数据
bit_cnt <= 0;
end
case(tx_state)
START: begin
txd <= 1'b0; // 起始位
if(baud_tick) tx_state <= DATA;
end
DATA: if(baud_tick) begin
txd <= tx_shift[bit_cnt];
if(bit_cnt == DATA_BITS+1) // 数据位+停止位
tx_state <= IDLE;
else
bit_cnt <= bit_cnt + 1;
end
endcase
end
3. 仿真与验证环境搭建
3.1 VCS仿真测试平台
测试平台主要验证以下场景:
- 单字节收发功能
- 连续字节传输
- 波特率容错测试
- 帧错误检测
典型测试用例:
verilog复制initial begin
// 测试单字节收发
send_byte(8'h55);
check_byte(8'h55);
// 测试连续传输
for(int i=0; i<16; i++)
send_byte(i);
for(int i=0; i<16; i++)
check_byte(i);
// 波特率偏差测试
set_baud_error(+3%); // 设置+3%波特率偏差
send_byte(8'hAA);
check_byte(8'hAA);
end
3.2 Vivado上板验证
上板测试需要特别注意:
- 引脚约束必须匹配开发板的UART接口
- IO bank电压需与USB转串口芯片一致
- 建议先用示波器验证物理层信号
典型XDC约束示例:
tcl复制set_property PACKAGE_PIN F16 [get_ports txd]
set_property IOSTANDARD LVCMOS33 [get_ports {txd rxd}]
set_property DRIVE 8 [get_ports txd]
set_property SLEW SLOW [get_ports txd]
4. 常见问题与调试技巧
4.1 数据接收不完整
可能原因及解决方案:
- 波特率不匹配 → 用示波器测量实际比特宽度
- 停止位设置错误 → 确认串口助手与代码配置一致
- 信号抖动过大 → 添加施密特触发器或软件滤波
4.2 连续传输丢失数据
典型排查步骤:
- 检查FIFO深度是否足够
- 确认状态机能否及时返回IDLE
- 测试系统时钟是否稳定
4.3 上板通信失败
硬件检查清单:
- 测量TX/RX信号电压是否符合预期
- 检查接地是否良好
- 确认串口线是否支持全双工
- 尝试降低波特率测试
血泪教训:曾遇到因FTDI芯片驱动问题导致通信失败,现象与硬件故障极其相似。建议先在设备管理器中确认串口设备被正确识别,再排查其他问题。
5. 性能优化与扩展
5.1 动态波特率调整
通过修改相位累加器步长实现:
verilog复制reg [15:0] dynamic_step = BAUD_STEP;
always @(posedge clk) begin
if(baud_update) // 外部更新信号
dynamic_step <= new_step;
baud_accum <= baud_accum + dynamic_step;
end
5.2 硬件流控支持
可扩展RTS/CTS信号实现:
verilog复制// 接收端流控
assign rts = (fifo_count > FIFO_HIGH); // FIFO快满时拉高
// 发送端流控
always @(posedge clk) begin
if(!cts) tx_enable <= 1'b0; // CTS为高时暂停发送
else if(!tx_busy) tx_enable <= 1'b1;
end
5.3 多协议支持
通过参数化设计支持不同配置:
verilog复制module uart_core #(
parameter DATA_BITS = 8,
parameter STOP_BITS = 1,
parameter PARITY = "NONE" // "NONE","ODD","EVEN"
)(
// 端口定义
);
// 根据参数生成不同的校验逻辑
generate
if(PARITY != "NONE") begin
// 校验位生成与检查逻辑
end
endgenerate
endmodule
这个UART IP核虽然功能简单,但通过模块化设计和完备的验证环境,可以稳定地集成到各种FPGA项目中。在实际使用中,建议根据具体需求调整FIFO深度、添加流控协议或扩展多串口支持。