1. FPGA串口通信模块设计概述
串口通信作为FPGA开发中最基础却又最实用的功能之一,几乎在每个项目中都会用到。不同于单片机内置的硬件UART,FPGA需要完全通过Verilog实现串口的时序控制和数据处理,这对理解通信协议底层原理非常有帮助。本次设计的核心目标是打造一个兼容Xilinx和Altera两大FPGA平台的通用串口通信模块,波特率支持115200bps,包含完整的收发功能。
在实际工程中,串口模块通常需要解决三个关键问题:精确的波特率生成、稳定的数据采样以及跨平台兼容性。我们的设计采用标准UART协议框架,包含起始位、8位数据位和1位停止位,无校验位。这种配置能够满足大多数应用场景,同时保持足够的兼容性。模块采用同步设计思想,所有状态转换都在系统时钟上升沿触发,确保时序稳定性。
注意:虽然Verilog支持异步设计,但在实际工程中强烈建议采用同步设计方法,可以避免绝大多数棘手的时序问题。
2. 波特率生成器实现细节
2.1 分频系数计算原理
波特率生成的核心在于精确的时钟分频。以常见的50MHz系统时钟为例,要产生115200bps的波特率,每个比特位需要维持的时钟周期数为:
code复制分频系数 = 系统时钟频率 / 波特率
= 50,000,000 / 115,200
≈ 434
在Verilog中实现如下:
verilog复制localparam CLK_FREQ = 50_000_000; // 50MHz系统时钟
localparam BAUD_RATE = 115200; // 目标波特率
localparam BAUD_COUNT = CLK_FREQ / BAUD_RATE; // 分频系数
reg [15:0] baud_counter; // 波特率计数器
reg baud_tick; // 波特率时钟使能信号
always @(posedge clk) begin
if(baud_counter == BAUD_COUNT-1) begin
baud_tick <= 1'b1;
baud_counter <= 0;
end
else begin
baud_tick <= 1'b0;
baud_counter <= baud_counter + 1;
end
end
2.2 平台特定调整技巧
在实际测试中发现,不同FPGA平台对分频系数的处理存在细微差异:
- Xilinx平台:直接使用计算得到的分频系数即可
- Altera平台:某些型号需要将分频系数减1才能获得准确的波特率
这种差异源于不同厂商的时钟管理架构设计。建议在代码中添加平台选择宏定义:
verilog复制`define USE_ALTERA 1 // 使用Altera平台时设为1,Xilinx设为0
localparam BAUD_ADJ = `USE_ALTERA ? BAUD_COUNT-1 : BAUD_COUNT;
实测经验:在Cyclone IV E平台上,直接使用434分频会导致实际波特率约为114942bps(误差0.22%),而使用433分频则得到准确的115207bps(误差0.006%)。
3. 发送模块设计与实现
3.1 状态机设计
发送模块采用有限状态机(FSM)实现,包含四个状态:
- IDLE:等待发送启动信号
- START:发送起始位(0)
- DATA:依次发送8位数据
- STOP:发送停止位(1)
状态转移图如下:
code复制[IDLE] -> (tx_start) -> [START] -> (baud_tick) -> [DATA]
^ |
| v
\---------------------------------- [STOP]
Verilog实现代码:
verilog复制reg [3:0] tx_state;
reg [7:0] tx_data;
reg [2:0] bit_count;
reg tx_reg;
always @(posedge clk) begin
case(tx_state)
0: begin // IDLE
if(tx_start) begin
tx_reg <= 1'b0; // 起始位
tx_state <= 1;
bit_count <= 0;
end
end
1: begin // START
if(baud_tick) begin
tx_reg <= tx_data[bit_count]; // 数据位
bit_count <= bit_count + 1;
tx_state <= (bit_count == 7) ? 2 : 1;
end
end
2: begin // STOP
if(baud_tick) begin
tx_reg <= 1'b1; // 停止位
tx_state <= 3;
end
end
3: begin // DONE
tx_done <= 1'b1;
tx_state <= 0;
end
endcase
end
3.2 关键设计考量
- 采样点选择:在波特率时钟使能信号(baud_tick)的上升沿进行状态转移和数据切换,确保信号稳定
- 位序处理:数据从低位(LSB)开始发送,符合UART标准
- 状态编码:虽然可以使用参数定义状态名,但在简单状态机中直接使用数字编码可以使代码更紧凑
常见错误:忘记在发送停止位后返回IDLE状态,导致模块无法连续发送多个字节。务必在状态3中明确设置tx_state <= 0。
4. 接收模块设计与抗干扰处理
4.1 三级采样抗抖动设计
接收模块面临的主要挑战是信号抖动和噪声干扰。我们采用三次采样多数表决的方案来提高可靠性:
verilog复制reg [2:0] sample_counter;
reg [1:0] sample_buffer;
always @(posedge clk) begin
if(rx_sync) begin // 消抖后的输入信号
case(rx_state)
0: begin // 检测起始位
if(!rx_sync) begin
sample_counter <= 0;
rx_state <= 1;
end
end
1: begin // 数据采样
if(baud_tick) begin
sample_counter <= sample_counter + 1;
if(sample_counter == 3) begin
// 取三次采样的多数值
rx_buffer[bit_count] <=
(sample_buffer[0] & sample_buffer[1]) |
(sample_buffer[0] & sample_buffer[2]) |
(sample_buffer[1] & sample_buffer[2]);
bit_count <= bit_count + 1;
end
if(bit_count == 8) rx_state <= 2;
end
end
2: begin // 校验停止位
if(rx_sync) rx_valid <= 1'b1;
rx_state <= 0;
end
endcase
end
end
4.2 平台相关延迟补偿
不同FPGA平台的IO延迟特性不同,需要针对性调整:
- Xilinx平台:通常不需要特殊处理
- Altera平台:某些型号需要在采样点前加入1-2个时钟周期的延迟
可以通过宏定义实现平台自适应:
verilog复制`define ALTERA_IO_DELAY 2 // Altera平台设为2,Xilinx设为0
localparam SAMPLE_POINT = 3 + `ALTERA_IO_DELAY;
5. 跨平台兼容性实现
5.1 复位信号处理
Xilinx和Altera平台在复位信号设计上存在差异:
| 特性 | Xilinx | Altera |
|---|---|---|
| 复位极性 | 通常高有效 | 通常低有效 |
| 全局复位 | GSR | CLRN |
通过宏定义实现自适应复位:
verilog复制`define ALTERA_RESET 1 // Altera平台设为1,Xilinx设为0
reg uart_rst;
always @(posedge clk) begin
uart_rst <= `ALTERA_RESET ? ~rst_n : rst_p;
end
5.2 时钟管理差异
两大平台在时钟处理上的主要区别:
- Xilinx:推荐使用BUFG驱动全局时钟
- Altera:使用全局时钟网络自动分配
建议在顶层模块中添加平台相关时钟缓冲:
verilog复制`ifdef USE_ALTERA
wire clk_uart = clk; // Altera直接使用
`else
BUFG u_bufg(.I(clk), .O(clk_uart)); // Xilinx添加时钟缓冲
`endif
6. 上板测试与调试技巧
6.1 回环测试方案
最简单的验证方法是实现数据回环:
verilog复制always @(posedge clk) begin
if(rx_valid) begin
tx_data <= rx_buffer;
tx_start <= 1'b1;
end
else begin
tx_start <= 1'b0;
end
end
6.2 常见问题排查
-
无数据收发:
- 检查波特率设置是否匹配
- 确认TX/RX线是否交叉连接
- 验证电平转换电路是否工作
-
数据错误:
- 用示波器检查信号质量
- 调整采样点位置
- 检查接地是否良好
-
间歇性通信失败:
- 增加电源去耦电容
- 缩短连接线长度
- 添加适当的终端电阻
重要安全提示:连接FPGA与PC时务必确保双方电压匹配(3.3V或5V),必要时使用电平转换模块,否则可能损坏设备。
7. 性能优化与扩展
7.1 FIFO缓冲实现
为提高实用性,可以添加FIFO缓冲:
verilog复制// 发送FIFO
reg [7:0] tx_fifo [0:15];
reg [3:0] tx_wr_ptr, tx_rd_ptr;
// 接收FIFO
reg [7:0] rx_fifo [0:15];
reg [3:0] rx_wr_ptr, rx_rd_ptr;
7.2 多波特率支持
通过参数化设计支持多种波特率:
verilog复制parameter BAUD_RATE = 115200;
localparam BAUD_9600 = CLK_FREQ / 9600;
localparam BAUD_115200 = CLK_FREQ / 115200;
7.3 硬件流控扩展
添加RTS/CTS流控信号:
verilog复制input cts_n; // 清除发送(低有效)
output rts_n; // 请求发送(低有效)
assign rts_n = (rx_wr_ptr - rx_rd_ptr) > 12 ? 0 : 1;
8. 工程实践建议
-
代码组织:
- 将串口模块独立为单独文件(uart.v)
- 定义清晰的模块接口
- 添加详细的注释头
-
版本控制:
- 使用Git管理代码
- 对Xilinx和Altera版本打不同tag
- 维护变更日志
-
文档规范:
- 编写README说明使用方法和参数配置
- 记录已知问题和解决方案
- 提供测试用例
-
持续集成:
- 建立自动化测试环境
- 对主要功能进行仿真验证
- 定期上板实测
在实际项目中,这个串口模块已经成功应用于多个产品,包括工业控制器和数据采集系统。一个特别有用的技巧是在发送和接收模块中添加调试计数器,可以实时监控通信质量:
verilog复制reg [31:0] tx_byte_cnt, rx_byte_cnt;
always @(posedge clk) begin
if(tx_done) tx_byte_cnt <= tx_byte_cnt + 1;
if(rx_valid) rx_byte_cnt <= rx_byte_cnt + 1;
end
