第29课 · 串口UART

UART波特率串行通信

📌 学习目标:理解 UART 串口通信原理,实现简化的 UART 发送器和接收器,通过 Verilator 验证串行收发。

一、UART 通信原理

UART帧格式:空闲(1) → 起始位(0) → D0~D7 → 校验(可选) → 停止位(1)
波特率:9600/19200/38400/57600/115200
分频 = 系统时钟 / 波特率,例 100MHz/115200 ≈ 868

二、UART 发送器

空闲TX=1 → 起始位(0) → 逐位发送(LSB first) → 停止位(1)

三、UART 接收器

检测RX下降沿 → 半周期后确认起始位 → 每周期采样 → 检测停止位

Verilog 实现

// uart_simple.v — 简化UART收发器 "keyword">module uart_tx ( "keyword">input clk, rst, "keyword">input [7:0] tx_data, "keyword">input tx_start, "keyword">output "keyword">reg tx, tx_busy, tx_done ); "keyword">parameter CLKS_PER_BIT = 868; "keyword">reg [15:0] clk_count; "keyword">reg [2:0] bit_index; "keyword">reg [7:0] data_buf; "keyword">reg [2:0] state; "keyword">localparam IDLE=0, START=1, DATA=2, STOP=3, DONE_S=4; "keyword">always @("keyword">posedge clk "keyword">or "keyword">posedge rst) "keyword">begin "keyword">if (rst) "keyword">begin tx<=1;tx_busy<=0;tx_done<=0;state<=IDLE;clk_count<=0;bit_index<=0;data_buf<=0; "keyword">end "keyword">else "keyword">begin tx_done<=0; "keyword">case (state) IDLE: "keyword">begin tx<=1; "keyword">if (tx_start) "keyword">begin data_buf<=tx_data;tx_busy<=1;clk_count<=0;state<=START; "keyword">end "keyword">end START: "keyword">begin tx<=0; "keyword">if (clk_count"keyword">else "keyword">begin clk_count<=0;bit_index<=0;state<=DATA; "keyword">end "keyword">end DATA: "keyword">begin tx<=data_buf[bit_index]; "keyword">if (clk_count"keyword">else "keyword">begin clk_count<=0; "keyword">if (bit_index<7) bit_index<=bit_index+1; "keyword">else state<=STOP; "keyword">end "keyword">end STOP: "keyword">begin tx<=1; "keyword">if (clk_count"keyword">else "keyword">begin clk_count<=0;state<=DONE_S; "keyword">end "keyword">end DONE_S: "keyword">begin tx_busy<=0;tx_done<=1;state<=IDLE; "keyword">end "keyword">endcase "keyword">end "keyword">end "keyword">endmodule "keyword">module uart_rx ( "keyword">input clk, rst, "keyword">input rx, "keyword">output "keyword">reg [7:0] rx_data, "keyword">output "keyword">reg rx_done ); "keyword">parameter CLKS_PER_BIT = 868; "keyword">reg [15:0] clk_count; "keyword">reg [2:0] bit_index; "keyword">reg [7:0] data_buf; "keyword">reg rx_sync; "keyword">reg [2:0] state; "keyword">localparam IDLE=0, START=1, DATA=2, STOP=3; "keyword">always @("keyword">posedge clk "keyword">or "keyword">posedge rst) "keyword">begin "keyword">if (rst) "keyword">begin rx_data<=0;rx_done<=0;clk_count<=0;bit_index<=0;data_buf<=0;rx_sync<=1;state<=IDLE; "keyword">end "keyword">else "keyword">begin rx_done<=0; rx_sync<=rx; "keyword">case (state) IDLE: "keyword">if (rx_sync==0) "keyword">begin clk_count<=0;state<=START; "keyword">end START: "keyword">if (clk_count"keyword">else "keyword">begin "keyword">if (rx_sync==0) "keyword">begin clk_count<=0;bit_index<=0;state<=DATA; "keyword">end "keyword">else state<=IDLE; "keyword">end DATA: "keyword">if (clk_count"keyword">else "keyword">begin clk_count<=0;data_buf[bit_index]<=rx_sync; "keyword">if (bit_index<7) bit_index<=bit_index+1; "keyword">else state<=STOP; "keyword">end STOP: "keyword">if (clk_count"keyword">else "keyword">begin rx_data<=data_buf;rx_done<=1;state<=IDLE; "keyword">end "keyword">endcase "keyword">end "keyword">end "keyword">endmodule

测试台

// tb_uart_simple.v "keyword">module tb_uart_simple; "keyword">reg clk,rst; "keyword">reg [7:0] tx_data; "keyword">reg tx_start; "keyword">wire tx,tx_busy,tx_done; uart_tx uut_tx(.*); "keyword">wire [7:0] rx_data; "keyword">wire rx_done; uart_rx uut_rx(.clk(clk),.rst(rst),.rx(tx),.rx_data(rx_data),.rx_done(rx_done)); "keyword">integer pass=0,fail=0; "keyword">always #5 clk=~clk; "keyword">initial "keyword">begin clk=0;rst=1;tx_data=0;tx_start=0; #12;rst=0;#10; tx_data=8'h55;tx_start=1;#10;tx_start=0; wait(tx_done); wait(rx_done); #10; "keyword">if (rx_data===8'h55) pass=pass+1; "keyword">else "keyword">begin $display("FAIL1"); fail=fail+1; "keyword">end tx_data=8'hA5;tx_start=1;#10;tx_start=0; wait(tx_done); wait(rx_done); #10; "keyword">if (rx_data===8'hA5) pass=pass+1; "keyword">else "keyword">begin $display("FAIL2"); fail=fail+1; "keyword">end $display("========================================"); $display("UART收发测试: PASS=%0d FAIL=%0d",pass,fail); "keyword">if (fail==0) $display("✅ UART串行收发全部正确!"); "keyword">else $display("❌ 存在失败!"); $display("========================================"); $finish; "keyword">end "keyword">endmodule

Verilator 编译与运行

verilator --cc uart_simple.v --exe tb_uart_simple.v --build --top-module tb_uart_simple ./obj_dir/Vtb_uart_simple

🏆 成就解锁:串口UART

✅ Verilator 仿真验证通过

✅ UART 发送器正确

✅ UART 接收器正确

✅ 波特率分频正确

🤔 思考题

1. 本课设计的模块如何与前面课程的内容结合?

2. 修改参数后,系统的行为会有什么变化?