4. UART串口发送模块
任务目标
实现一个可配置波特率的UART发送模块,将8位并行数据按标准UART帧格式(1起始位 + 8数据位 + 1停止位,无校验)串行发出,并通过Testbench仿真验证发送波形与时序正确性。
实现方案
采用状态机方式实现发送逻辑:IDLE → START → DATA[7:0] → STOP。波特率通过参数 CLK_FREQ 与 BAUD_RATE 计算分频计数阈值,发送时钟基于系统时钟分频产生。核心代码包含发送模块与对应Testbench,可直接在Vivado/ModelSim中运行仿真。
关键完整代码
// uart_tx.v
module uart_tx #(
parameter CLK_FREQ = 50_000_000, // 系统时钟频率(Hz)
parameter BAUD_RATE = 115200 // 目标波特率
)(
input wire clk,
input wire rst_n,
input wire [7:0] tx_data, // 待发送8位数据
input wire tx_start, // 发送启动脉冲(高有效1周期)
output reg tx, // 串行输出
output reg tx_busy // 忙标志(1=发送中)
);
// 波特率分频计数阈值
localparam BAUD_CNT_MAX = CLK_FREQ / BAUD_RATE - 1;
localparam BIT_CNT_MAX = 9; // 起始位+8数据位+停止位 = 10位,索引0~9
reg [15:0] baud_cnt;
reg [3:0] bit_cnt;
reg baud_pulse; // 波特率时钟脉冲
reg [7:0] shift_reg;
// 状态编码
localparam IDLE = 2'd0, START = 2'd1, DATA = 2'd2, STOP = 2'd3;
reg [1:0] state, next_state;
// 波特率脉冲生成
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
baud_cnt <= 0;
baud_pulse <= 0;
end else if (state == IDLE) begin
baud_cnt <= 0;
baud_pulse <= 0;
end else begin
if (baud_cnt == BAUD_CNT_MAX) begin
baud_cnt <= 0;
baud_pulse <= 1;
end else begin
baud_cnt <= baud_cnt + 1;
baud_pulse <= 0;
end
end
end
// 状态转移
always @(posedge clk or negedge rst_n) begin
if (!rst_n)
state <= IDLE;
else
state <= next_state;
end
// 次态逻辑
always @(*) begin
next_state = state;
case (state)
IDLE: if (tx_start) next_state = START;
START: if (baud_pulse) next_state = DATA;
DATA: if (baud_pulse && (bit_cnt == 4'd8)) next_state = STOP;
STOP: if (baud_pulse) next_state = IDLE;
endcase
end
// 发送过程控制
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
tx <= 1'b1;
tx_busy <= 1'b0;
bit_cnt <= 0;
shift_reg <= 0;
end else begin
case (state)
IDLE: begin
tx <= 1'b1;
tx_busy <= 1'b0;
bit_cnt <= 0;
if (tx_start) begin
shift_reg <= tx_data;
tx_busy <= 1'b1;
end
end
START: begin
tx <= 1'b0; // 起始位
if (baud_pulse) bit_cnt <= 0;
end
DATA: begin
if (baud_pulse) begin
tx <= shift_reg[0]; // LSB先发
shift_reg <= {1'b0, shift_reg[7:1]};
bit_cnt <= bit_cnt + 1;
end
end
STOP: begin
tx <= 1'b1; // 停止位
if (baud_pulse) tx_busy <= 1'b0;
end
endcase
end
end
endmodule
仿真验证Testbench
// tb_uart_tx.v
`timescale 1ns/1ps
module tb_uart_tx;
reg clk = 0;
reg rst_n = 0;
reg [7:0] tx_data;
reg tx_start = 0;
wire tx, tx_busy;
// 实例化待测模块:50MHz时钟,波特率115200
uart_tx #(
.CLK_FREQ(50_000_000),
.BAUD_RATE(115200)
) dut (
.clk(clk), .rst_n(rst_n),
.tx_data(tx_data), .tx_start(tx_start),
.tx(tx), .tx_busy(tx_busy)
);
// 10ns周期时钟(50MHz)
always #10 clk = ~clk;
// 测试序列
initial begin
// 复位
rst_n = 0;
tx_data = 8'hA5; // 发送数据0xA5 = 1010_0101
repeat(5) @(posedge clk);
rst_n = 1;
// 发送第一个字节
@(posedge clk);
tx_start = 1;
@(posedge clk);
tx_start = 0;
// 等待发送完成
wait (!tx_busy);
#100;
// 发送第二个字节 0x3C
tx_data = 8'h3C;
@(posedge clk);
tx_start = 1;
@(posedge clk);
tx_start = 0;
wait (!tx_busy);
#200;
$finish;
end
// 波形转储
initial begin
$dumpfile("uart_tx.vcd");
$dumpvars(0, tb_uart_tx);
end
endmodule
运行与验证方法
- 仿真环境:Vivado/ModelSim/Verilator均可,需支持Verilog-2001。
- 编译运行:将
uart_tx.v与tb_uart_tx.v加入工程,运行行为仿真。 - 观察波形:查看
tx、tx_busy、state信号。 - 通过判定标准:
- 复位释放后,
tx保持高电平(空闲态); - 拉高
tx_start后,tx_busy变为高电平,tx先输出1位低电平起始位; - 随后依次输出8位数据(LSB在前):发送0xA5时,数据位应为
1,0,1,0,0,1,0,1(对应A5的bit0~bit7); - 最后输出1位高电平停止位,
tx_busy拉低,完成一帧发送; - 每比特持续时间约为
1/115200 ≈ 8.68μs(50MHz时钟下约434个时钟周期)。
- 复位释放后,
常见问题
- 起始位/停止位不完整:检查状态机中
baud_pulse是否在进入新状态时被正确清零,避免额外跳变。 - 数据位顺序错误:确认发送顺序为LSB先行,即先发送
tx_data[0]。 - 波特率偏差:若系统时钟不是整数倍分频,实际波特率会有微小误差,但通常低于2%即可正常通信。