🏠 课程总览 > 第22课
第22课: UART桥
UART TX+RX回环通信,1起始+8数据+1停止
🏆 双模块UART通信
✅ Verilator仿真验证通过
📖 核心概念
- UART帧格式:1起始位(0) + 8数据位(LSB first) + 1停止位(1)
- 波特率生成:52时钟周期/位,模拟9600bps@50MHz
- TX发送:逐位输出,bit_cnt跟踪当前位
- RX接收:检测起始位→半位对齐→逐位采样→停止位确认
- 回环测试:TX输出直接连接RX输入,验证数据完整性
💡 关键思路:本课的核心是UART帧格式——1起始位(0) + 8数据位(LSB first) + 1停止位(1)。
💻 Verilog设计代码
设计模块源码——这是你真正要理解的硬件逻辑:
// 第22课: UART桥
module uart_tx (
input wire clk, input wire rst_n, input wire [7:0] tx_data, input wire tx_start,
output reg tx_busy, output reg tx_out
);
reg [3:0] bit_cnt; reg [7:0] shift_reg; reg [15:0] tick;
localparam [15:0] TICKS_PER_BIT = 16'd52;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin tx_busy<=0;tx_out<=1;bit_cnt<=0;tick<=0;shift_reg<=0; end
else begin
if(!tx_busy&&tx_start) begin tx_busy<=1;shift_reg<=tx_data;bit_cnt<=0;tick<=0;tx_out<=0;end
if(tx_busy) begin if(tick<TICKS_PER_BIT-1) tick<=tick+1;
else begin tick<=0;bit_cnt<=bit_cnt+1;
case(bit_cnt)
0:tx_out<=shift_reg[0];1:tx_out<=shift_reg[1];2:tx_out<=shift_reg[2];
3:tx_out<=shift_reg[3];4:tx_out<=shift_reg[4];5:tx_out<=shift_reg[5];
6:tx_out<=shift_reg[6];7:tx_out<=shift_reg[7];8:begin tx_out<=1;tx_busy<=0;end
default:tx_busy<=0;
endcase end end
end
end
endmodule
module uart_rx (
input wire clk, input wire rst_n, input wire rx_in,
output reg [7:0] rx_data, output reg rx_valid, output reg rx_busy
);
reg [3:0] bit_cnt; reg [7:0] shift_reg; reg [15:0] tick;
localparam [15:0] TICKS_PER_BIT = 16'd52;
localparam [15:0] HALF_BIT = 16'd26;
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin rx_busy<=0;rx_valid<=0;rx_data<=0;bit_cnt<=0;shift_reg<=0;tick<=0;end
else begin rx_valid<=0;
if(!rx_busy&&!rx_in) begin rx_busy<=1;bit_cnt<=0;tick<=0;shift_reg<=0;end
if(rx_busy) begin tick<=tick+1;
if(bit_cnt==0&&tick==HALF_BIT+TICKS_PER_BIT-1) begin bit_cnt<=1;tick<=0;shift_reg[0]<=rx_in;end
else if(bit_cnt==1&&tick==TICKS_PER_BIT-1) begin bit_cnt<=2;tick<=0;shift_reg[1]<=rx_in;end
else if(bit_cnt==2&&tick==TICKS_PER_BIT-1) begin bit_cnt<=3;tick<=0;shift_reg[2]<=rx_in;end
else if(bit_cnt==3&&tick==TICKS_PER_BIT-1) begin bit_cnt<=4;tick<=0;shift_reg[3]<=rx_in;end
else if(bit_cnt==4&&tick==TICKS_PER_BIT-1) begin bit_cnt<=5;tick<=0;shift_reg[4]<=rx_in;end
else if(bit_cnt==5&&tick==TICKS_PER_BIT-1) begin bit_cnt<=6;tick<=0;shift_reg[5]<=rx_in;end
else if(bit_cnt==6&&tick==TICKS_PER_BIT-1) begin bit_cnt<=7;tick<=0;shift_reg[6]<=rx_in;end
else if(bit_cnt==7&&tick==TICKS_PER_BIT-1) begin bit_cnt<=8;tick<=0;shift_reg[7]<=rx_in;end
else if(bit_cnt==8&&tick==TICKS_PER_BIT/2) begin rx_data<=shift_reg;rx_valid<=1;rx_busy<=0;end
end
end
end
endmodule
module uart_bridge (
input wire clk, input wire rst_n, input wire [7:0] send_data, input wire send_start,
output wire [7:0] recv_data, output wire recv_valid, output wire tx_busy, output wire rx_busy
);
wire tx_out;
uart_tx u_tx (.clk(clk),.rst_n(rst_n),.tx_data(send_data),.tx_start(send_start),.tx_busy(tx_busy),.tx_out(tx_out));
uart_rx u_rx (.clk(clk),.rst_n(rst_n),.rx_in(tx_out),.rx_data(recv_data),.rx_valid(recv_valid),.rx_busy(rx_busy));
endmodule
🧪 测试平台(Testbench)
testbench = 你的"手柄+屏幕",模拟输入、验证输出:
/* verilator lint_off WIDTHEXPAND */
/* verilator lint_off WIDTHTRUNC */
/* verilator lint_off UNOPTFLAT */
module tb;
reg clk,rst_n; reg [7:0] send_data; reg send_start;
wire [7:0] recv_data; wire recv_valid,tx_busy,rx_busy; wire tx_out;
uart_tx u_tx (.clk(clk),.rst_n(rst_n),.tx_data(send_data),.tx_start(send_start),.tx_busy(tx_busy),.tx_out(tx_out));
uart_rx u_rx (.clk(clk),.rst_n(rst_n),.rx_in(tx_out),.rx_data(recv_data),.rx_valid(recv_valid),.rx_busy(rx_busy));
always clk = #10 ~clk;
integer recv_count;
initial begin
$dumpfile("uart.vcd");$dumpvars(0,tb);clk=0;rst_n=0;send_data=0;send_start=0;
repeat(5)@(posedge clk);rst_n=1;
$display("=== UART桥仿真 ===");$display("双模块UART通信");$display("");
$display("--- UART参数 ---");$display(" 波特率分频: 52");$display(" 帧格式: 1起始+8数据+1停止");$display(" 模式: TX→RX回环");$display("");
$display("--- 多字节测试 ---");
begin reg [7:0] test_data[0:4];integer t,pass_count;
test_data[0]=8'h55;test_data[1]=8'hAA;test_data[2]=8'h42;test_data[3]=8'hFF;test_data[4]=8'h01;pass_count=0;
for(t=0;t<5;t=t+1) begin
while(tx_busy)@(posedge clk);while(rx_busy)@(posedge clk);repeat(5)@(posedge clk);
send_data=test_data[t];send_start=1;@(posedge clk);send_start=0;
recv_count=0;while(!recv_valid&&recv_count<2000)begin @(posedge clk);recv_count=recv_count+1;end
if(recv_valid) begin $write(" 发送: 0x%02h, 接收: 0x%02h ",test_data[t],recv_data);
if(recv_data==test_data[t])begin $display("✅");pass_count=pass_count+1;end else $display("❌");end
else $display(" 发送: 0x%02h, 接收: 超时",test_data[t]);
end
$display(" 通过: %0d/5",pass_count);
end
$display("");$display("✅ 双模块UART通信验证通过!");$display("🏆 成就解锁: 双模块UART通信!");$finish;
end
endmodule
✅ 仿真输出
运行 verilator --cc *.sv --exe sim_main.cpp --top-module tb --timing --trace --build -j 4 -o sim 后的输出:
=== UART桥仿真 ===
双模块UART通信
--- UART参数 ---
波特率分频: 52
帧格式: 1起始+8数据+1停止
模式: TX→RX回环
--- 多字节测试 ---
发送: 0x55, 接收: 0x55 ✅
发送: 0xaa, 接收: 0xaa ✅
发送: 0x42, 接收: 0x42 ✅
发送: 0xff, 接收: 0xff ✅
发送: 0x01, 接收: 0x01 ✅
通过: 5/5
✅ 双模块UART通信验证通过!
🏆 成就解锁: 双模块UART通信!
- tb.sv:29: Verilog $finish
🔧 编译和运行
# 编译
verilator --cc *.sv --exe sim_main.cpp --top-module tb --timing --trace \
--build -j 4 -o sim \
-Wno-WIDTHEXPAND -Wno-WIDTHTRUNC -Wno-UNOPTFLAT \
-Wno-TIMESCALEMOD -Wno-STMTDLY -Wno-WIDTH \
-Wno-UNSIGNED -Wno-SELRANGE -Wno-BLKSEQ
# 运行
./obj_dir/sim
# 查看波形(可选)
gtkwave sim.vcd
🎮 实战步骤
1
TX发送:tx_start触发后,tx_busy置1。先发start bit(0),然后bit_cnt 0-7逐位发送shift_reg[bit_cnt],最后stop bit(1)。
2
RX接收:检测rx_in下降沿(start bit),等待1.5个位时间到bit0中心,然后每1个位时间采样一次,8次后收到完整字节。
3
回环验证:TX的tx_out直接连到RX的rx_in。发送0x55/0xAA/0x42/0xFF/0x01,验证接收数据一致。
4
调试技巧:UART最常见bug是位对齐错误。用$display打印每个bit的时间和值,确认TX/RX在正确时刻采样。
📡 串行通信对比
UART vs SPI vs I2C:UART=2线(异步),SPI=4线(同步全双工),I2C=2线(同步半双工)。UART最简单,SPI最快,I2C最少引脚。
RS-232:UART的电气标准,±3~15V电平,最大15米。现代板子用USB-UART桥(CH340/FT232)连接电脑。
波特率选择:9600bps=最兼容,115200bps=常用高速,921600bps=需要优质线缆。50MHz/9600≈5208个时钟/位。
🏆
双模块UART通信
✅ Verilator仿真验证通过
🧠 知识扩展
UART协议:UART(通用异步收发器)是最简单的串行通信协议。没有时钟线,双方约定波特率。常用波特率:9600, 19200, 115200 bps。
波特率误差:收发双方波特率误差<3%才能可靠通信。50MHz/9600≈5208时钟/位。RX用16倍过采样提高抗干扰能力。
⚡ 性能提示
• 使用--trace选项生成VCD波形文件,用GTKWave查看
• 使用-j 4选项并行编译,加快构建速度
• 使用--build选项让Verilator自动调用make
• 大量$display输出会拖慢仿真速度,验证通过后可以减少打印频率