Lesson 10

📡 UART发送

🏆 8N1帧发送正确
✅ Verilator仿真验证通过

📖 实验描述

实现UART发送器,按照8N1格式(8数据位、无校验、1停止位)串行发送数据。

UART是最基础且最广泛使用的串行通信协议。 8N1格式:1位起始位(0) + 8位数据位(LSB first) + 1位停止位(1),共10位/帧。 波特率:常用9600/115200bps。50MHz时钟下,115200bps的分频系数=50M/115200≈434。 发送流程:数据写入→生成START位→逐位发送D0-D7→生成STOP位→完成。 UART是异步通信:收发双方约定相同波特率,靠起始位同步。全球每年生产数十亿个UART接口芯片。

🧠 核心概念

📐 电路结构

电路与状态图
UART TX帧结构:

  空闲  起始   D0  D1  D2  D3  D4  D5  D6  D7  停止  空闲
  ──┐  ┌──┬──┬──┬──┬──┬──┬──┬──┬──┬──┐  ┌──────
    └──┘  │  │  │  │  │  │  │  │  │  └──┘
         0  b0 b1 b2 b3 b4 b5 b6 b7   1

  波特率115200: 每位持续434个50MHz时钟周期

📝 设计步骤

  1. 1计算波特率分频系数(50MHz/115200=434)
  2. 2设计发送状态机:IDLE→START→DATA→STOP→IDLE
  3. 3START位:TX拉低1个波特率周期
  4. 4DATA位:逐位发送D0-D7(LSB first)
  5. 5STOP位:TX拉高1个波特率周期
  6. 6空闲态TX保持高电平

💻 Verilog实现

uart_tx.svSystemVerilog · Verilator 5.020
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
// UART Transmitter - UART发送器
// 8N1格式, 可配置波特率
module uart_tx(
    input  wire       clk,       // 50MHz
    input  wire       rst,
    input  wire [7:0] tx_data,   // 发送数据
    input  wire       tx_start,  // 发送请求
    output reg        tx_done,   // 发送完成
    output reg        tx_line    // TX输出线
);

localparam CLK_FREQ = 32'd50_000_000;
localparam BAUD     = 32'd115200;
localparam DIV      = CLK_FREQ / BAUD;  // 434

typedef enum logic [1:0] {
    S_IDLE = 2'd0,
    S_START = 2'd1,
    S_DATA = 2'd2,
    S_STOP = 2'd3
} state_t;

state_t state;
reg [31:0] baud_cnt;
reg [2:0]  bit_idx;
reg [7:0]  shift_reg;

always @(posedge clk or posedge rst) begin
    if (rst) begin
        state     <= S_IDLE;
        tx_line   <= 1'b1;  // Idle high
        tx_done   <= 1'b0;
        baud_cnt  <= 32'd0;
        bit_idx   <= 3'd0;
        shift_reg <= 8'd0;
    end else begin
        tx_done <= 1'b0;

        case (state)
            S_IDLE: begin
                tx_line <= 1'b1;
                if (tx_start) begin
                    shift_reg <= tx_data;
                    baud_cnt  <= 32'd0;
                    state     <= S_START;
                    tx_line   <= 1'b0;  // Start bit
                end
            end
            S_START: begin
                tx_line <= 1'b0;
                if (baud_cnt >= DIV - 32'd1) begin
                    baud_cnt <= 32'd0;
                    bit_idx  <= 3'd0;
                    state    <= S_DATA;
                end else begin
                    baud_cnt <= baud_cnt + 32'd1;
                end
            end
            S_DATA: begin
                tx_line <= shift_reg[0];
                if (baud_cnt >= DIV - 32'd1) begin
                    baud_cnt <= 32'd0;
                    shift_reg <= {1'b0, shift_reg[7:1]};
                    if (bit_idx == 3'd7) begin
                        state <= S_STOP;
                    end else begin
                        bit_idx <= bit_idx + 3'd1;
                    end
                end else begin
                    baud_cnt <= baud_cnt + 32'd1;
                end
            end
            S_STOP: begin
                tx_line <= 1'b1;  // Stop bit
                if (baud_cnt >= DIV - 32'd1) begin
                    baud_cnt <= 32'd0;
                    tx_done  <= 1'b1;
                    state    <= S_IDLE;
                end else begin
                    baud_cnt <= baud_cnt + 32'd1;
                end
            end
            default: state <= S_IDLE;
        endcase
    end
end

endmodule

🔬 仿真说明

仿真环境与策略

测试:发送0x55(01010101)→TX线: 0_10101010_1(起始+LSB first+停止)。发送0xAA(10101010)→TX线: 0_01010101_1。验证波特率定时正确(每位434个时钟周期)。

✅ 验证结果

Verilator 5.020 仿真通过

本实验所有Verilog代码已通过Verilator编译验证,功能行为正确。测试用例覆盖核心功能路径,确保设计满足规格要求。

⚠️ 常见错误

🌍 真实世界

工业应用

UART是嵌入式系统最常用的调试接口。MCU的printf调试几乎都通过UART。RS-232/RS-485是UART的电气标准扩展。现代USB-UART桥接芯片(如CH340/FT232)让PC可通过USB与UART设备通信。

💡 扩展挑战

🚀 自己动手

# 编译并运行
verilator --cc uart_tx.sv --exe uart_tx_tb.cpp --build -j 0
./obj_dir/Vuart_tx

🎯 试一试

修改参数

尝试修改代码中的关键参数,观察仿真结果变化:

  • 调整位宽,观察不同数据范围
  • 修改初始值/种子,观察不同起始条件
  • 改变时钟分频,测试不同速度

💡 Verilator会在位宽不匹配时给出Warning,这是学习的好机会

添加功能

在现有基础上增加新功能:

  • 添加新的输入信号和控制逻辑
  • 增加状态或修改状态转移条件
  • 扩展输出,增加更多显示信息

🔧 增量开发:每次只改一个地方,验证通过后再改下一个

📋 Verilog速查

语法说明示例
reg [7:0]8位寄存器reg [7:0] data;
wire组合逻辑连线wire valid = cnt > 5;
always @(posedge clk)时序逻辑上升沿触发
always @(*)组合逻辑敏感列表自动推导
localparam局部常量localparam DIV = 50000000;
case多分支选择注意default分支
$display仿真打印不可综合,仅仿真用

📊 性能指标

资源估算(FPGA参考)

本设计在典型FPGA上的资源占用估算:LUT约20-120个,FF约30-100个,无BRAM/DSP依赖(特殊模块除外)。时钟频率可达50-100MHz+。Verilator仿真速度约5-10M周期/秒。

← 上一课 📚 目录 下一课 →