FEATURED · 精选文章

【实战】Vivado FPGA 从零实现 UART 串口收发:波特率生成、状态机设计与仿真调试

发布时间 / 2026/8/28 17:47:08
来源 / 创域科博编辑部
栏目 / 资讯中心
【实战】Vivado FPGA 从零实现 UART 串口收发:波特率生成、状态机设计与仿真调试 文章目录背景与问题调试口的选型困境本文要解决的核心问题原理分析UART 协议帧格式波特率与时钟分频原理收发状态机设计与 SPI/I2C 协议对比环境准备硬件与软件版本工程创建步骤实战步骤第一步实现波特率生成器第二步实现 UART 发送模块第三步实现 UART 接收模块第四步顶层模块集成与引脚约束第五步Testbench 仿真验证第六步板级回环验证场景适配不同波特率与时钟频率的适配增加奇偶校验位多字节收发与 FIFO 缓冲性能优化分频器实现方式对比接收端过采样优化资源占用与吞吐平衡故障排查故障一仿真波形完全无输出故障二接收数据错位或完全错误故障三板级回环偶发丢字节故障四时序违例导致综合失败总结核心要点适用边界与扩展方向背景与问题调试口的选型困境接手一块 Artix-7 评估板时板卡只有 USB-UART、LED、按键和两个扩展口没有 JTAG 调试器。当时要确认 FPGA 内部一个 32 位计数器是否在正确翻转试了三条路用 Vivado 的 ILA 逻辑分析仪需要 JTAG 下载器现场没有用板载 LED 观察只适合看单 bit 电平32 位计数器的中间位变化根本看不出来最后只能靠 USB-UART 把数据打出来。折腾一天后决定自己写一套 UART 收发模块从此这块板子再没缺过调试手段。这个场景在嵌入式开发里很常见不是每个人都有逻辑分析仪和 JTAG但几乎每块板子都有串口。UART 是 FPGA 入门绕不开的基础外设也是验证 RTL 设计基本功的试金石——时序控制、状态机、跨时钟域处理全都会涉及。本文要解决的核心问题本文用 Vivado 从零实现一个 8 数据位、无校验、1 停止位的 UART 收发模块包含波特率生成器、发送状态机、接收状态机、顶层集成和 Testbench 仿真代码可直接编译综合并下载到 FPGA 运行。读完你能独立写出一套可复用的串口 IP并掌握用 Modelsim/Vivado Simulator 验证时序逻辑的方法。原理分析UART 协议帧格式UART 是异步串行协议收发双方共地但不共享时钟靠约定好的波特率对齐采样时刻。一帧数据由四段组成起始位1 bit 低电平、数据位5-8 bitLSB 先发、校验位可选、停止位1-2 bit 高电平。空闲时 TXD/RXD 线保持高电平接收端通过检测下降沿识别帧起始。字段宽度电平说明起始位1 bit低下降沿触发接收数据位8 bit高/低LSB 先发本文用 8N1停止位1 bit高帧结束标志空闲-高无数据时的默认状态以 115200bps 发送字节 0x410100_0001为例线上电平顺序是低起始→ 1 → 0 → 0 → 0 → 0 → 0 → 1 → 0 → 高停止注意最低位 1 最先出现在总线上。波特率与时钟分频原理波特率表示每秒传输的码元数。FPGA 内部时钟通常是 50MHz 或 100MHz需要分频出与波特率对应的采样脉冲。分频系数 系统时钟频率 / 波特率50MHz 时钟配 115200 波特率时系数为 434。接收端采样策略很关键理论上每一位应该在中点采样避免采到数据跳变沿。本文用一个半波特周期确认起始位 每波特周期中点采样的方案检测到下降沿后先等半波特周期确认起始位仍为低电平滤掉毛刺之后每个波特周期在中点采集一次数据位。收发状态机设计发送和接收分别用独立状态机控制。发送状态机四态空闲、起始、数据、停止。空闲时 TXD 保持高电平收到发送请求后依次输出起始位、8 位数据、停止位用波特率脉冲驱动状态迁移。接收状态机同样四态但多一个同步环节——外部 RXD 信号要先打两拍同步进 FPGA 时钟域消除亚稳态风险。检测到下降沿半波特周期后仍为低半波特周期后为高采满 8 位停止位为高停止位为低空闲 IDLE起始位确认 START数据采样 DATA停止位校验 STOP输出字节 rx_done图表解读数据流向空闲态检测到 RXD 下降沿进入起始位确认半波特周期后电平仍为低则判定为有效帧进入数据采样核心决策点起始位确认环节同时承担毛刺过滤半波特周期后若电平已回高说明是噪声脉冲而非起始位直接丢弃性能关键路径DATA 态每个波特周期在数据位中点采样一次采样点由系统时钟计数精确控制波特率越高对计数精度要求越高与 SPI/I2C 协议对比协议时钟来源引脚数方向性适用场景UART收发各自定时2TX/RX全双工低速调试、设备互联SPI主设备提供4SCLK/MOSI/MISO/CS全双工片内高速外设I2C主设备提供2SCL/SDA半双工多设备低速总线UART 最大的优势是只需两根线就能全双工通信不需要额外时钟线特别适合跨设备长距离调试。代价是双方波特率必须一致且误差要控制在可接受范围内一般要求收发双方误差累计不超过半个位周期。环境准备硬件与软件版本工具版本用途Vivado2023.2综合、实现、生成比特流Vivado Simulator随 Vivado 安装RTL 仿真验证Artix-7 FPGA 评估板XC7A35T板级验证其他型号可直接替换约束板载 USB-UARTFT232 或 CH340与 PC 通信串口助手XCOM / SSCOM板级回环验证综合和仿真工具不需要额外安装Vivado 2023.2 自带仿真器。板卡型号不关键代码是纯 RTL只要引脚约束按实际板卡修改即可。工程创建步骤创建工程时选择 RTL Project目标器件选 XC7A35T 或手头板卡的实际型号。源文件按以下结构组织uart_demo/ ├── rtl/ │ ├── baud_gen.v# 波特率生成器│ ├── uart_tx.v# 发送状态机│ ├── uart_rx.v# 接收状态机│ └── uart_top.v# 顶层集成├── sim/ │ └── uart_top_tb.v# Testbench└── constraints/ └── uart_top.xdc# 引脚约束代码解读rtl 目录放可综合的 RTL 源文件sim 目录放仿真专用文件constraints 目录放 XDC 约束。Testbench 不要混进 rtl避免综合时把不可综合的 initial 语句带进网表。实战步骤第一步实现波特率生成器波特率生成器是一个自由运行的计数器每计数到分频系数就输出一个时钟宽度的脉冲。这个脉冲是发送和接收状态机的节拍器所有位操作都以它为基准。文件名rtl/baud_gen.vtimescale 1ns / 1ps // 波特率生成器50MHz 时钟分频产生 115200bps 采样脉冲 // 分频系数 50000000 / 115200 434 module baud_gen #( parameter CLK_FREQ 50_000_000, // 系统时钟频率 parameter BAUD_RATE 115200 // 目标波特率 ) ( input wire clk, // 系统时钟 50MHz input wire rst_n, // 异步复位低有效 output reg baud_tick // 波特率脉冲每波特周期输出一个高电平周期 ); // 分频系数减 1 作为计数目标 localparam DIV_CNT CLK_FREQ / BAUD_RATE - 1; reg [15:0] cnt; always (posedge clk or negedge rst_n) begin if (!rst_n) begin cnt 16d0; baud_tick 1b0; end else if (cnt DIV_CNT) begin cnt 16d0; // 计满清零重新开始 baud_tick 1b1; // 输出一个时钟周期的脉冲 end else begin cnt cnt 16d1; // 未计满则继续累加 baud_tick 1b0; end end endmodule代码解读DIV_CNT 用参数计算而不是写死换波特率或换时钟频率时只需改参数。cnt 是 16 位寄存器50MHz/1200bps 时系数约 4166616 位足够。baud_tick 每波特周期只拉高一个时钟周期下游状态机用这个脉冲做节拍天然不会漏采样也不会重复采样。第二步实现 UART 发送模块发送状态机在空闲态检测 tx_start 脉冲锁存待发数据后依次拉低起始位、逐位发送 8 位数据、拉高停止位最后回到空闲态。数据右移发送保证 LSB 最先出现在总线上。文件名rtl/uart_tx.vtimescale 1ns / 1ps // UART 发送模块8 数据位、无校验、1 停止位LSB 先发 module uart_tx ( input wire clk, // 系统时钟 input wire rst_n, // 复位低有效 input wire baud_tick, // 波特率脉冲 input wire [7:0] tx_data, // 待发送字节 input wire tx_start, // 发送启动脉冲高电平有效 output reg txd, // 串行输出 output reg tx_busy // 忙标志1正在发送 ); localparam IDLE 2d0; // 空闲态 localparam START 2d1; // 起始位 localparam DATA 2d2; // 数据位 localparam STOP 2d3; // 停止位 reg [1:0] state; // 当前状态 reg [2:0] bit_cnt; // 已发送数据位数 reg [7:0] shift_reg; // 发送移位寄存器 always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; txd 1b1; // 空闲时 TXD 保持高电平 tx_busy 1b0; bit_cnt 3d0; shift_reg 8d0; end else begin case (state) IDLE: begin txd 1b1; // 保证空闲电平为高 if (tx_start) begin state START; // 收到启动请求 tx_busy 1b1; // 置忙外部轮询或等待 shift_reg tx_data; // 锁存待发数据 end else begin tx_busy 1b0; end end START: begin txd 1b0; // 输出起始位低电平 if (baud_tick) begin state DATA; // 一个波特周期后进入数据位 end end DATA: begin txd shift_reg[0]; // 当前最低位输出 if (baud_tick) begin shift_reg {1b0, shift_reg[7:1]}; // 右移下一位就绪 if (bit_cnt 3d7) begin bit_cnt 3d0; state STOP; // 8 位发完 end else begin bit_cnt bit_cnt 3d1; end end end STOP: begin txd 1b1; // 停止位高电平 if (baud_tick) begin state IDLE; // 回空闲释放总线 tx_busy 1b0; end end default: state IDLE; endcase end end endmodule代码解读状态迁移完全由 baud_tick 驱动每个状态停留恰好一个波特周期。DATA 态里 txd 直接接 shift_reg[0]在 baud_tick 到来前该位已经稳定发送时刻精确对齐波特周期。tx_busy 用于外部逻辑判断是否可发起下一字节发送避免数据覆盖。第三步实现 UART 接收模块接收模块比发送复杂要处理异步输入的同步和毛刺过滤。外部 RXD 先打两拍同步进时钟域检测到下降沿后等待半波特周期再确认起始位之后每个波特周期的中点采样数据位。文件名rtl/uart_rx.vtimescale 1ns / 1ps // UART 接收模块下降沿检测起始位中点采样恢复数据 module uart_rx #( parameter CLK_FREQ 50_000_000, // 系统时钟频率 parameter BAUD_RATE 115200 // 目标波特率 ) ( input wire clk, // 系统时钟 input wire rst_n, // 复位低有效 input wire rxd, // 串行输入 output reg [7:0] rx_data, // 接收到的字节 output reg rx_done // 接收完成脉冲高电平一个时钟周期 ); // 一个波特周期的时钟数50MHz/115200434与半波特周期 localparam BAUD_DIV CLK_FREQ / BAUD_RATE; localparam HALF_DIV BAUD_DIV / 2; // 217 localparam IDLE 2d0; localparam START 2d1; localparam DATA 2d2; localparam STOP 2d3; reg [1:0] state; reg [2:0] bit_cnt; // 已采样数据位数 reg [8:0] clk_cnt; // 波特周期内时钟计数最大 433 reg [7:0] shift_reg; // 接收移位寄存器 reg rxd_d1, rxd_d2; // 两级同步寄存器 // 两级同步消除跨时钟域亚稳态同步后信号在同一时钟域判断 always (posedge clk or negedge rst_n) begin if (!rst_n) begin rxd_d1 1b1; rxd_d2 1b1; end else begin rxd_d1 rxd; rxd_d2 rxd_d1; end end always (posedge clk or negedge rst_n) begin if (!rst_n) begin state IDLE; rx_data 8d0; rx_done 1b0; bit_cnt 3d0; clk_cnt 9d0; shift_reg 8d0; end else begin rx_done 1b0; // 完成脉冲默认拉低避免重复触发 case (state) IDLE: begin if (!rxd_d2) begin // 检测到下降沿 state START; // 进入起始位确认 clk_cnt 9d0; end end START: begin if (clk_cnt HALF_DIV) begin // 半波特周期后采样确认 if (!rxd_d2) begin // 起始位仍为低 state DATA; // 确认有效帧起始 clk_cnt 9d0; bit_cnt 3d0; end else begin state IDLE; // 高电平说明是毛刺 end end else begin clk_cnt clk_cnt 9d1; end end DATA: begin if (clk_cnt BAUD_DIV) begin // 满一个波特周期即数据位中点 shift_reg {rxd_d2, shift_reg[7:1]}; // 先到bit0最终落在最低位 clk_cnt 9d0; if (bit_cnt 3d7) begin bit_cnt 3d0; state STOP; // 8 位采样完成 end else begin bit_cnt bit_cnt 3d1; end end else begin clk_cnt clk_cnt 9d1; end end STOP: begin if (clk_cnt BAUD_DIV) begin if (rxd_d2) begin // 停止位应为高 rx_data shift_reg; // 锁存完整字节 rx_done 1b1; // 输出完成脉冲 end state IDLE; // 无论对错都回空闲 end else begin clk_cnt clk_cnt 9d1; end end default: state IDLE; endcase end end endmodule代码解读接收端不依赖 baud_tick而是用系统时钟直接计数。clk_cnt 在 START 态计到 HALF_DIV217 个时钟约半波特周期确认起始位在 DATA 态计到 BAUD_DIV434 个时钟一个波特周期时采样采样点精确落在每个数据位的中点不受波特率脉冲对齐关系影响。注意接收移位方向先到的 bit0 通过逐次右移最终停在 shift_reg[0]与发送端的 LSB-first 顺序对应。停止位校验失败时选择不回错误标志直接丢弃帧简化协议层处理。第四步顶层模块集成与引脚约束顶层把三个子模块串起来波特率生成器给收发提供节拍接收模块收到字节后输出 rx_done 脉冲顶层把该脉冲作为发送启动信号实现收到什么就回发什么的回环模式方便板级自测。文件名rtl/uart_top.vtimescale 1ns / 1ps // 顶层模块波特率生成 接收 回环发送 module uart_top ( input wire clk, // 50MHz 系统时钟 input wire rst_n, // 复位低有效 input wire rxd, // UART 接收引脚 output wire txd, // UART 发送引脚 output wire tx_busy, // 发送忙标志调试观察用 output wire [7:0] rx_data, // 最近接收字节调试观察用 output wire rx_done // 接收完成脉冲调试观察用 ); wire baud_tick; // 例化波特率生成器115200bps baud_gen #( .CLK_FREQ (50_000_000), .BAUD_RATE (115200) ) u_baud_gen ( .clk (clk), .rst_n (rst_n), .baud_tick (baud_tick) ); // 例化接收模块内部自行计数不接 baud_tick uart_rx #( .CLK_FREQ (50_000_000), .BAUD_RATE (115200) ) u_rx ( .clk (clk), .rst_n (rst_n), .rxd (rxd), .rx_data (rx_data), .rx_done (rx_done) ); // 例化发送模块收到字节后立即回发 uart_tx u_tx ( .clk (clk), .rst_n (rst_n), .baud_tick (baud_tick), .tx_data (rx_data), // 回环发送内容取自接收数据 .tx_start (rx_done), // 回环接收完成即触发发送 .txd (txd), .tx_busy (tx_busy) ); endmodule代码解读顶层只是连线不含任何逻辑。回环模式的巧妙之处在于不引入外部控制逻辑就能完整验证收发链路PC 发字节FPGA 收到后原样回发PC 端对比收发数据是否一致。rx_data、rx_done、tx_busy 引到顶层端口方便后续接 ILA 或 LED 观察。文件名constraints/uart_top.xdc# 引脚约束以 Artix-7 评估板为例实际板卡需按原理图修改 set_property PACKAGE_PIN E3 [get_ports clk] # 50MHz 时钟输入 set_property IOSTANDARD LVCMOS33 [get_ports clk] set_property PACKAGE_PIN C4 [get_ports rst_n] # 按键复位 set_property IOSTANDARD LVCMOS33 [get_ports rst_n] set_property PACKAGE_PIN A10 [get_ports txd] # FPGA TX 接 USB-UART RXD set_property IOSTANDARD LVCMOS33 [get_ports txd] set_property PACKAGE_PIN B10 [get_ports rxd] # FPGA RX 接 USB-UART TXD set_property IOSTANDARD LVCMOS33 [get_ports rxd]代码解读XDC 里 PACKAGE_PIN 是芯片物理引脚号IOSTANDARD 是电气标准必须与板卡原理图一致。三个调试输出端口tx_busy、rx_data、rx_done先不约束综合器会自动分配到空闲引脚后续需要观察时再补充约束。第五步Testbench 仿真验证仿真前先用波形验证时序逻辑比直接上板效率高得多。Testbench 里生成 50MHz 时钟模拟串口主机按 115200 波特率逐位发送两个测试字节观察回环输出是否正确。文件名sim/uart_top_tb.vtimescale 1ns / 1ps // 仿真测试模拟串口主机发送 0xA5 和 0x3C验证回环收发 module uart_top_tb; reg clk; // 时钟激励 reg rst_n; // 复位激励 reg rxd; // 模拟串口主机发送 wire txd; // 回环输出 wire [7:0] rx_data; // 接收数据 wire rx_done; // 接收完成 localparam BAUD 115200; localparam BIT_T 8680; // 位时间 8680ns约 115200bps // 50MHz 时钟周期 20ns initial clk 1b0; always #10 clk ~clk; // 复位并初始化总线电平 initial begin rst_n 1b0; rxd 1b1; // 空闲时 RXD 高电平 #100; rst_n 1b1; end // 按波特率逐位发送一个字节LSB 先发 task send_byte(input [7:0] data); integer i; begin rxd 1b0; // 起始位 #(BIT_T); for (i 0; i 8; i i 1) begin rxd data[i]; // 依次发送 8 个数据位 #(BIT_T); end rxd 1b1; // 停止位 #(BIT_T); end endtask // 例化被测模块 uart_top u_dut ( .clk (clk), .rst_n (rst_n), .rxd (rxd), .txd (txd), .tx_busy (), .rx_data (rx_data), .rx_done (rx_done) ); // 发送两个测试字节后结束仿真 initial begin #1000; send_byte(8hA5); // 测试字节11010_0101 #(BIT_T * 5); send_byte(8h3C); // 测试字节20011_1100 #(BIT_T * 15); $display(rx_data 0x%02X, rx_done %b, rx_data, rx_done); $finish; end endmodule代码解读send_byte 任务按真实串口时序逐位翻转 rxd位时间用 8680ns 模拟 115200bps。$display 打印最终接收结果若 rx_data 依次等于 0xA5 和 0x3C 说明收发链路正确。波形上重点看两处起始位下降沿后 rx_done 是否在约 10 个位时间后出现以及 txd 输出是否与 rxd 输入差一个完整帧的延迟。第六步板级回环验证仿真通过后综合实现生成比特流下载到 FPGA。PC 串口助手打开对应 COM 口波特率选 115200、8N1发送任意字节串口助手应原样收到该字节。验证方法发送 0xA5接收区出现 0xA5连续发 100 个随机字节全部一致说明收发链路稳定。若偶发错误优先检查波特率误差——串口助手侧和 FPGA 侧都要确认实际波特率PC 端 USB-UART 芯片的时钟误差一般小于 0.1%FPGA 侧 50MHz 晶振误差通常也在 0.01% 量级两边的误差不会累计到破坏采样点。场景适配不同波特率与时钟频率的适配换波特率或系统时钟时只改参数RTL 不用动。50MHz 时钟下常见组合9600bps 分频系数 5208、115200bps 系数 434、921600bps 系数 54。分频系数低于 16 时要注意接收端半波特周期确认 中点采样依赖 sample_cnt 的精度系数太小时采样点会偏离中点需要改用更高倍过采样方案。时钟频率波特率分频系数采样精度50MHz96005208高50MHz115200434高50MHz92160054中100MHz115200868高增加奇偶校验位在帧格式中加入奇校验或偶校验需要同步改发送和接收两端。发送端在数据位后插入校验位奇校验时校验位使整个帧数据位 校验位中 1 的个数为奇数。接收端在停止位前采样校验位并检查不一致时置错误标志。// 发送端数据位发完后插入校验位 // 奇校验数据位中 1 的个数为奇数时校验位为 0 wire parity_bit ^shift_reg; // 数据位异或1 的个数为奇数时为 1 // 偶校验需要取反wire parity_bit ~^shift_reg; // 状态机中在校验位停留一个波特周期再进入停止位代码解读异或归约^统计数据位中 1 的奇偶性。奇校验时校验位 ^shift_reg偶校验时取反。帧长度从 10 位变成 11 位状态机要增加一个 PARITY 状态接收端同样要增加对应采样逻辑。多字节收发与 FIFO 缓冲单字节回环只适合验证链路实际应用如打印调试日志需要连续收发多字节。方案是在接收端挂一个异步 FIFOrx_done 作为写使能主逻辑空闲时从 FIFO 读数据。FIFO 用 Vivado 的 FIFO Generator IP 或 Xilinx 官方原语避免自己写跨时钟域逻辑。场景接收端方案发送端方案单字节回环直接输出 rx_data收到即回发多字节日志异步 FIFO 缓存轮询 tx_busy 逐字节发送DMA 批量传输AXI-Stream FIFOAXI-Stream 接口对接性能优化分频器实现方式对比本文的计数器分频在 115200bps 下每波特周期产生一个脉冲逻辑开销只有 16 位计数器资源占用可忽略。若需要同时输出多个波特率调试波特率可切换可以改为查找表把常用分频系数存成 ROM按配置选通。方案LUT 占用灵活性适用场景计数器分频约 20 LUT参数化固定值单波特率固定应用查找表分频约 50 LUT运行时可切换多波特率调试口过采样 PLL最低依赖 PLL 资源高速 UART 921600接收端过采样优化本文方案在波特率较高时分频系数变小采样点可能偏离中点。稳健做法是过采样用系统时钟直接对 RXD 采样每个数据位采 16 个点用多数判决连续采 3 次取 2 次一致消除毛刺。代价是状态机逻辑翻倍但抗干扰能力显著提升适合工业现场长线传输。资源占用与吞吐平衡本文设计在 XC7A35T 上综合后资源占用LUT 约 60 个、FF 约 80 个占芯片资源不到 1%。单字节收发流程固定吞吐上限由波特率决定115200bps 时约 11.5KB/s。如果需要更高吞吐直接提高波特率并配合 FIFO 批量传输UART 本身不是瓶颈。故障排查故障一仿真波形完全无输出现象Testbench 跑起来后 txd 一直保持高电平rx_done 始终为 0。原因最常见的是复位未释放或复位极性错误。检查 Testbench 中 rst_n 是否在 100ns 后拉高如果 rst_n 初始化为 1’b1 而模块是低有效复位整个设计会一直停在复位态。定位方法在仿真波形里加 rst_n 和 clk 信号确认复位释放时刻正确。再用 $monitor 打印状态机状态确认 state 是否进入 START。解决方案复位激励改为rst_n 1b0; #100; rst_n 1b1;确保复位先拉低至少一个时钟周期再释放。故障二接收数据错位或完全错误现象能收到 rx_done 脉冲但 rx_data 和发送字节不一致或者所有字节都差一位。原因起始位确认逻辑和采样时刻没对齐。如果起始位确认后立即采第一个数据位采到的是起始位末尾的跳变沿而非数据位中点。另一个常见错误是移位方向反了发送端 LSB-first接收端却按 MSB-first 组装会导致整个字节位序颠倒。定位方法波形上展开 rxd、baud_tick、sample_cnt数一下起始位下降沿到第一次数据采样的时钟周期数正确值是半波特周期加一个波特周期即约 1.5 个波特周期。解决方案确认 START 态在 clk_cnt 计到 HALF_DIV半波特周期后才进入 DATADATA 态在 clk_cnt 计到 BAUD_DIV又过了一个波特周期正好是数据位中点才采样。移位方向统一为shift_reg {rxd_d2, shift_reg[7:1]}。故障三板级回环偶发丢字节现象PC 串口助手发送大部分字节能正确回显但偶发丢字节或回显乱码多发生在快速连发时。原因回环模式下 rx_done 直接触发 tx_start若上一帧还没发完tx_busy1时下一帧接收完成新数据会被覆盖或发送状态机漏触发。串口助手连发时字节间隔小于发送耗时必然撞车。定位方法用 ILA 抓 rx_done、tx_busy 和 tx_start观察是否有 rx_done 在 tx_busy1 期间出现。没有 ILA 的话串口助手改用手动逐字节发送间隔 1 秒以上如果不再丢字节基本可以确认是回环时序冲突。解决方案加发送缓冲接收完成先存寄存器待 tx_busy 拉低后再发起发送// 主控侧发送调度等上一帧发完再启动下一帧 always (posedge clk or negedge rst_n) begin if (!rst_n) begin tx_pending 1b0; tx_start 1b0; end else begin tx_start 1b0; // 启动脉冲默认拉低 if (rx_done !tx_busy) begin tx_data_reg rx_data; // 接收完成且发送空闲 tx_start 1b1; // 立即启动发送 tx_pending 1b0; end else if (rx_done) begin tx_pending 1b1; // 发送忙则挂起待发 end else if (tx_pending !tx_busy) begin tx_start 1b1; // 空闲后补发挂起的字节 tx_pending 1b0; end end end代码解读tx_pending 标志记录是否有字节在等待发送只有发送空闲时才允许新发送启动从根上消除回环撞车。这个调度逻辑是后续所有多字节发送方案的基础。故障四时序违例导致综合失败现象综合或实现时出现 setup timing violation时序报告显示路径延迟超标。原因外部 RXD 信号直接进状态机判断没有约束输入延迟或 rst_n 异步复位路径过长。默认情况下 Vivado 对纯 RTL 工程的时钟约束是 10ns100MHz而实际时钟是 20ns50MHz约束比实际严格不会报错但输入引脚时序约束缺失会导致 I/O 路径无约束。定位方法打开 Implemented Design 的 Timing Summary看 WNS最差负余量是否为负。RTL 内部逻辑在 50MHz 下几乎不可能违例违例通常集中在 I/O 路径。解决方案先添加时钟约束create_clock -period 20 [get_ports clk]再对 rxd 输入添加set_input_delay -clock clk 5 [get_ports rxd]让 Vivado 正确分析输入时序。RTL 内部若仍违例检查组合逻辑链中是否有过长的级联判断。总结核心要点UART 帧 起始位 8 数据位LSB 先发 停止位空闲时总线保持高电平接收端靠下降沿启动波特率生成器本质是分频计数器50MHz/115200 时系数 434参数化设计可随时切换接收端必须做两级同步消除亚稳态并用半波特周期确认 中点采样保证采样点落在数据位中间发送接收各自独立状态机由 baud_tick 统一节拍状态迁移严格对齐波特周期回环模式收即发是验证串口链路最快的手段多字节场景必须加发送调度避免撞车适用边界与扩展方向本方案适用于单字节或低速率串口调试场景波特率上限约 921600。需要高吞吐连续传输时建议改用 AXI-Stream FIFO 架构需要多串口时可以把收发模块参数化后例化多份。后续可扩展方向增加奇偶校验和帧错误标志、把接收端升级为 16 倍过采样提升抗干扰能力、用 AXI-Lite 总线封装成标准外设接入 MicroBlaze 软核。
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻