波特率生成 + 帧发送/接收 + FIFO缓冲 — FPGA与PC对话!
🏆 成就:串口达人 ✅ Verilator验证通过
UART(Universal Asynchronous Receiver/Transmitter)是最经典的串口协议——无需时钟线,双方约定波特率,异步收发。
// UART发送器 - 可配置波特率
module uart_tx #(
parameter CLK_FREQ = 50000000,
parameter BAUD_RATE = 115200
)(
input wire clk,
input wire rst,
input wire [7:0] tx_data,
input wire tx_start,
output reg tx_busy,
output reg tx_line
);
localparam DIV = CLK_FREQ / BAUD_RATE;
reg [15:0] baud_cnt;
reg [3:0] bit_idx;
reg [9:0] shift_reg; // start+8data+stop
always @(posedge clk) begin
if (rst) begin
tx_line <= 1; tx_busy <= 0;
baud_cnt <= 0; bit_idx <= 0;
end else if (tx_start && !tx_busy) begin
shift_reg <= {1'b1, tx_data, 1'b0}; // stop+data+start
bit_idx <= 0; baud_cnt <= 0;
tx_busy <= 1;
end else if (tx_busy) begin
if (baud_cnt >= DIV - 1) begin
baud_cnt <= 0;
tx_line <= shift_reg[0];
shift_reg <= {1'b1, shift_reg[9:1]};
if (bit_idx >= 9) tx_busy <= 0;
else bit_idx <= bit_idx + 1;
end else begin
baud_cnt <= baud_cnt + 1;
end
end
end
endmodule
// UART接收器
module uart_rx #(
parameter CLK_FREQ = 50000000,
parameter BAUD_RATE = 115200
)(
input wire clk,
input wire rst,
input wire rx_line,
output reg [7:0] rx_data,
output reg rx_valid,
output reg rx_error
);
localparam DIV = CLK_FREQ / BAUD_RATE;
localparam HALF = DIV / 2;
reg [15:0] baud_cnt;
reg [3:0] bit_idx;
reg [9:0] shift_reg;
reg rx_sync;
always @(posedge clk) begin
rx_sync <= rx_line; // 同步
end
always @(posedge clk) begin
if (rst) begin
rx_data <= 0; rx_valid <= 0; rx_error <= 0;
baud_cnt <= 0; bit_idx <= 0;
end else begin
rx_valid <= 0;
if (bit_idx == 0) begin
// 等待起始位
if (!rx_sync) begin
bit_idx <= 1; baud_cnt <= HALF;
end
end else begin
if (baud_cnt >= DIV - 1) begin
baud_cnt <= 0;
shift_reg <= {rx_sync, shift_reg[9:1]};
if (bit_idx >= 9) begin
bit_idx <= 0;
if (shift_reg[9] && !shift_reg[0]) begin
rx_data <= shift_reg[8:1];
rx_valid <= 1;
end else rx_error <= 1;
end else bit_idx <= bit_idx + 1;
end else baud_cnt <= baud_cnt + 1;
end
end
end
endmodule// 同步FIFO - UART缓冲
module sync_fifo #(
parameter DEPTH = 16,
parameter WIDTH = 8
)(
input wire clk,
input wire rst,
input wire [WIDTH-1:0] din,
input wire wr_en,
input wire rd_en,
output wire [WIDTH-1:0] dout,
output wire full,
output wire empty
);
localparam ADDR_W = $clog2(DEPTH);
reg [WIDTH-1:0] mem [0:DEPTH-1];
reg [ADDR_W:0] wr_ptr, rd_ptr;
assign full = (wr_ptr[ADDR_W] != rd_ptr[ADDR_W]) &&
(wr_ptr[ADDR_W-1:0] == rd_ptr[ADDR_W-1:0]);
assign empty = (wr_ptr == rd_ptr);
assign dout = mem[rd_ptr[ADDR_W-1:0]];
always @(posedge clk) begin
if (rst) begin
wr_ptr <= 0; rd_ptr <= 0;
end else begin
if (wr_en && !full) begin
mem[wr_ptr[ADDR_W-1:0]] <= din;
wr_ptr <= wr_ptr + 1;
end
if (rd_en && !empty)
rd_ptr <= rd_ptr + 1;
end
end
endmodulemodule uart_tb;
logic clk=0, rst=1;
logic [7:0] tx_data;
logic tx_start, tx_busy;
logic tx_line;
logic [7:0] rx_data;
logic rx_valid, rx_error;
logic rx_line;
uart_tx #(.CLK_FREQ(1000), .BAUD_RATE(100)) u_tx(.*);
assign rx_line = tx_line;
uart_rx #(.CLK_FREQ(1000), .BAUD_RATE(100)) u_rx(.*);
always #5 clk = ~clk;
initial begin
rst=1; #50; rst=0;
$display("--- UART回环测试 ---");
tx_data=8'h55; tx_start=1; #10; tx_start=0;
wait(rx_valid); #10;
$display(" 发送0x55, 接收0x%h %s", rx_data,
rx_data==8'h55?"✓":"FAIL");
tx_data=8'hA3; tx_start=1; #10; tx_start=0;
wait(rx_valid); #10;
$display(" 发送0xA3, 接收0x%h %s", rx_data,
rx_data==8'hA3?"✓":"FAIL");
$display("UART回环测试完成 ✓");
#100; $finish;
end
endmodule| 波特率 | 分频系数(50M) | 数据速率 | 典型用途 |
|---|---|---|---|
| 9600 | 5208 | 960B/s | 老式GPS |
| 19200 | 2604 | 1.9KB/s | 工业设备 |
| 115200 | 434 | 11.5KB/s | 调试串口 |
| 921600 | 54 | 92KB/s | 高速下载 |
💡 回环测试:把TX输出直接连到RX输入,自己发自己收。这是调试UART最有效的方法!确保波特率一致、帧格式正确、数据无丢失。
练习1:实现UART回环:PC发送→FPGA→PC接收
练习2:添加FIFO缓冲,测试不同速率下的数据流
练习3:实现命令解析:接收ASCII命令控制LED
练习4:设计UART多字节协议:帧头+长度+数据+校验
练习5:用UART传输VGA精灵坐标数据
步骤1:verilator --lint-only uart_tx.v
步骤2:verilator --lint-only sync_fifo.v
步骤3:verilator --binary -j 0 uart_tx.v sync_fifo.v uart_tb.sv
步骤4:./obj_dir/Vuart_tb
1. DMA传输:UART+FIFO自动搬运数据到BRAM,CPU零开销
2. 多机通信:9位数据模式,第9位=地址标志,实现RS-485多节点
3. 自动波特率检测:测量起始位宽度,自动适配对方波特率
4. 硬件流控:RTS/CTS信号,FIFO快满时通知对方暂停
5. 校验纠错:CRC8/CRC16附加在数据帧尾,接收端校验
示波器法:测量TX引脚的位宽度,反算波特率。115200→8.68μs/位
回环法:TX→RX短接,发送已知数据验证接收
逻辑分析仪:用第31课的LA捕获UART波形,自动解码
LED指示:RX有效时LED闪烁,最直观的调试手段
UART的关键设计挑战是波特率容差。接收端用中间采样法——在每个位的中间位置采样。如果波特率偏差>3%,可能采样到错误位。
容差计算:允许偏差 = 0.5/10 = 5%(8N1格式下)
中间采样:检测到起始位后,等待半位时间到中间,然后每位等1位时间
多次采样:每位采3次(1/4、1/2、3/4位置),多数表决,更抗干扰
FIFO深度:115200bps=11.5KB/s,FPGA处理延迟1ms→需≥12字节FIFO
| 参数 | 8N1(最常用) | 8E1 | 8O1 | 7E1 |
|---|---|---|---|---|
| 数据位 | 8 | 8 | 8 | 7 |
| 校验位 | 无 | 偶校验 | 奇校验 | 偶校验 |
| 停止位 | 1 | 1 | 1 | 1 |
| 总位数 | 10 | 11 | 11 | 10 |
| 效率 | 80% | 72.7% | 72.7% | 70% |
1. lint:verilator --lint-only uart_tx.v sync_fifo.v
2. 编译:verilator --binary -j 0 --trace uart_tx.v sync_fifo.v uart_tb.sv
3. 运行:./obj_dir/Vuart_tb
4. 波形:gtkwave uart_tb.vcd