第22课: UART桥

UART TX+RX回环通信,1起始+8数据+1停止

🏆 双模块UART通信 ✅ Verilator仿真验证通过

📖 核心概念

💡 关键思路:本课的核心是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输出会拖慢仿真速度,验证通过后可以减少打印频率