news 2026/9/7 19:20:47

FPGA入门(十一):受控线性序列机 UART 发送升级

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
FPGA入门(十一):受控线性序列机 UART 发送升级

FPGA入门


文章目录

  • FPGA入门
  • 前言
  • 一、新版模块各功能改动逐段解析
    • 完整代码
    • 1.1 参数化通用分频设计
    • 1.2 新增触发信号 Send_Go:按需启动发送
  • 二、本文核心重点:组合逻辑毛刺科普 + W_Tx_Done 分层时序设计
    • 2.1 基础科普:什么是组合逻辑,它有什么致命缺陷?
    • 2.2 工业标准解决方案:对外控制 / 完成标志统一用时序逻辑输出
    • 2.3 分层信号讲解:W_Tx_Done 与 Tx_Done 各自定位
    • 2.4 单独拆分 W_Tx_Done 三层核心意义
    • 2.5 通俗类比理解
  • 三、仿真波形
    • 仿真 Testbench 代码:
    • 波形分析
  • 总结

前言

在上一篇教程中,我们基于受控线性序列机实现了内置 1 秒定时器、自动循环发送的串口发送模块,适合简单调试打印场景。
但实际项目开发里,串口很少需要固定周期自动发送,更多是按需触发:按键按下发送、FIFO 数据就绪发送、外部指令应答发送。同时旧版本缺少发送完成反馈信号,上层逻辑联动十分麻烦。
因此本次对 UART 发送模块做全面工程化迭代,所有改动如下:

  1. 移除内置 1s 延时计数器delay_cnt,把延时控制交给上层,模块解耦通用;
  2. 新增触发输入Send_Go,外部脉冲即可启动一次串口发送;
  3. 新增缓存逻辑:仅Send_Go有效时,用 D 触发器锁存输入数据,避免发送中途数据跳变乱码;
  4. 参数化设计CLOCK_FREQ、BAUD,自动计算波特率分频值,无需手动计算;
  5. 新增发送完成标志Tx_Done,反馈一帧数据发送完毕;
  6. 核心时序优化(本文重点):拆分组合逻辑中间信号W_Tx_Done,寄存器打一拍同步输出Tx_Done,解决组合逻辑毛刺,普及工业标准时序输出规范。

一、新版模块各功能改动逐段解析

完整代码

moduleuart_0(input Clk,input Reset_n,input Send_Go,// 单次发送触发脉冲input[7:0]Data,// 待发送8bit数据output reg Led,// 发送状态指示灯output reg Tx_Done,// 同步发送完成标志output reg uart_tx// 串口发送引脚);reg[29:0]baud_div_cnt;// 波特率分频计数器reg en_baud_div_cnt;// 发送序列使能开关reg[3:0]bit_cnt;// 序列步数计数器 0~9reg[7:0]r_Data;// D触发器缓存待发送数据// 通用参数,适配任意时钟、波特率parameter BAUD=9600;parameter CLOCK_FREQ=50_000_000;parameter MCNT_BAUD=CLOCK_FREQ/BAUD-1;parameter MCNT_BIT=9;// 串口序列总步数:0起始位~9停止位// 组合逻辑中间信号:瞬时抓拍发送完成临界状态(存在毛刺)wire W_Tx_Done;assign W_Tx_Done=(bit_cnt==MCNT_BIT&&baud_div_cnt==MCNT_BAUD);// 时序逻辑打一拍,输出无毛刺同步完成脉冲(对外标准接口)always@(posedge Clk)Tx_Done<=W_Tx_Done;// 波特率分频计数器,仅发送使能开启时计数always@(posedge Clk or negedge Reset_n)if(!Reset_n)baud_div_cnt<=0;elseif(en_baud_div_cnt)beginif(baud_div_cnt==MCNT_BAUD)baud_div_cnt<=0;elsebaud_div_cnt<=baud_div_cnt+1'd1;endelsebaud_div_cnt<=1'd0;// 序列步数计数器,发送完成自动清零always@(posedge Clk or negedge Reset_n)if(!Reset_n)bit_cnt<=0;elseif(Tx_Done)bit_cnt<=0;elseif(baud_div_cnt==MCNT_BAUD)bit_cnt<=bit_cnt+1'd1;// D触发器锁存待发送数据,Send_Go触发时缓存输入Dataalways@(posedge Clk or negedge Reset_n)if(!Reset_n)r_Data<=0;elseif(Send_Go)r_Data<=Data;// 发送使能控制:触发打开,发送完成自动关闭always@(posedge Clk or negedge Reset_n)if(!Reset_n)en_baud_div_cnt<=0;elseif(Tx_Done)en_baud_div_cnt<=0;elseif(Send_Go)en_baud_div_cnt<=1;// 线性序列查表输出uart_tx电平always@(posedge Clk or negedge Reset_n)if(!Reset_n)uart_tx<=1;elseif(!en_baud_div_cnt)uart_tx<=1;// 串口空闲默认高电平elsebegincase(bit_cnt)0:uart_tx<=1'd0;// 第0步:起始位低电平1:uart_tx<=r_Data[0];2:uart_tx<=r_Data[1];3:uart_tx<=r_Data[2];4:uart_tx<=r_Data[3];5:uart_tx<=r_Data[4];6:uart_tx<=r_Data[5];7:uart_tx<=r_Data[6];8:uart_tx<=r_Data[7];9:uart_tx<=1'd1;// 第9步:停止位高电平default:uart_tx<=uart_tx;endcase end// LED状态指示:每完整发送一帧翻转一次always@(posedge Clk or negedge Reset_n)if(!Reset_n)Led<=0;elseif(Tx_Done)Led<=!Led;endmodule

1.1 参数化通用分频设计

parameter BAUD=9600;parameter CLOCK_FREQ=50_000_000;parameter MCNT_BAUD=CLOCK_FREQ/BAUD-1;parameter MCNT_BIT=9;

旧版本分频阈值固定写死,更换 100M 时钟、115200 波特需要手动重新计算数值;新版通过参数自动运算,只需要修改顶层传入参数,代码复用性大幅提升。
分频公式原理回顾:单个 bit 所需时钟周期 = 系统时钟频率 ÷ 波特率,计数器从 0 开始计数,因此末尾-1。

1.2 新增触发信号 Send_Go:按需启动发送

always@(posedge Clk or negedge Reset_n)if(!Reset_n)r_Data<=0;elseif(Send_Go)r_Data<=Data;always@(posedge Clk or negedge Reset_n)if(!Reset_n)en_baud_div_cnt<=0;elseif(Tx_Done)en_baud_div_cnt<=0;elseif(Send_Go)en_baud_div_cnt<=1;

功能说明:

  1. 检测到Send_Go高电平时,利用 D 触发器锁存当前输入 8bit 数据到r_Data;
  2. 同时拉高发送使能en_baud_div_cnt,启动波特率计数器,开始输出串口时序;
  3. 一帧发送完成后,Tx_Done拉高,自动关闭发送使能,等待下一次触发脉冲。

二、本文核心重点:组合逻辑毛刺科普 + W_Tx_Done 分层时序设计

2.1 基础科普:什么是组合逻辑,它有什么致命缺陷?

  1. 组合逻辑定义
    没有时钟、没有寄存器,输入实时直接映射输出,典型写法:assign 信号 = 判断条件,代码中W_Tx_Done就是标准组合逻辑信号。
  2. 组合逻辑固有缺陷:毛刺(竞争冒险)
    FPGA 内部由大量门电路组成,不同寄存器、线路的信号传输延迟不一致。
    当判断条件依赖两个及以上独立寄存器时,寄存器更新不会完全同步,会产生几纳秒~几十纳秒的窄脉冲,也就是毛刺。

本模块中判断条bit_cnt9&&baud_div_cntMCNT_BAUD同时依赖两组独立计数器:

  • bit_cnt:记录序列步数
  • baud_div_cnt:波特率分频计数器
    同一个时钟上升沿,两组寄存器更新速度存在硬件延迟差,会短暂出现 “条件同时成立” 的瞬时窗口,组合逻辑信号会产生毛刺。

2.2 工业标准解决方案:对外控制 / 完成标志统一用时序逻辑输出

工程规范:所有对外有效标志、完成脉冲、触发信号,禁止直接输出组合逻辑,必须用寄存器打一拍同步输出。
用时钟寄存器锁存一拍,可以过滤窄毛刺,输出脉冲宽度固定为 1 个完整时钟周期,波形干净、时序稳定,不会出现随机误触发。

2.3 分层信号讲解:W_Tx_Done 与 Tx_Done 各自定位

  1. wire W_Tx_Done:纯组合中间信号
wire W_Tx_Done;assign W_Tx_Done=(bit_cnt==MCNT_BIT&&baud_div_cnt==MCNT_BAUD);

作用:实时瞬时抓拍 “一帧发送完毕” 的临界瞬间
只要两个计数条件同时满足,信号立刻拉高;条件不满足立刻拉低。
它仅用于内部临界状态检测,本身带有硬件毛刺,不能直接对外输出,也不能直接用于内部复位控制。
2. 时序寄存器 Tx_Done:模块对外标准输出

always@(posedge Clk)Tx_Done<=W_Tx_Done;

作用:同步缓存W_Tx_Done瞬时状态,输出无毛刺、宽度固定 1 个时钟周期的标准脉冲。
模块内部bit_cnt清零、发送使能关闭、LED 翻转,外部上层逻辑全部使用同步后的Tx_Done,彻底规避毛刺干扰。

2.4 单独拆分 W_Tx_Done 三层核心意义

意义 1:代码分层解耦,调试更直观
如果不单独定义W_Tx_Done,直接写:

always@(posedge Clk)Tx_Done<=(bit_cnt==MCNT_BIT&&baud_div_cnt==MCNT_BAUD);

语法可以正常运行,但仿真波形无法单独观测内部瞬时判断信号。拆分后仿真可同时拉出W_Tx_Done、Tx_Done对比,一眼看到毛刺、时序延迟,快速定位时序 bug。
意义 2:逻辑修改只改一处,维护简单
后续如果需要修改发送完成判定规则(比如增加校验位、停止位延时),仅修改W_Tx_Done一行赋值即可,无需改动时序输出代码,逻辑耦合度低。
意义 3:隔离内部瞬时组合信号,保护全局时序逻辑
模块内部所有复位、使能控制全部使用同步后的Tx_Done,不会直接读取带毛刺的W_Tx_Done,从根源避免毛刺造成的随机逻辑异常,提升模块稳定性。

2.5 通俗类比理解

W_Tx_Done= 高速相机抓拍一瞬间画面,抓拍瞬间存在抖动、杂点(毛刺);
Tx_Done= 把抓拍画面存入内存缓存一整帧,画面稳定无杂点,交给内部、外部逻辑读取使用。
直接用抖动的抓拍画面做判断,很容易看错;缓存一帧稳定后再使用,行为完全可预期。

三、仿真波形

仿真 Testbench 代码:

`timescale1ns/1ns moduleuart_0_tb();reg Clk;reg Reset_n;wire Led;reg[7:0]Data;wire uart_tx;wire Tx_Done;reg Send_Go;uart_0uart_0(.Clk(Clk),.Reset_n(Reset_n),.Data(Data),.Led(Led),.uart_tx(uart_tx),.Tx_Done(Tx_Done),.Send_Go(Send_Go));initial Clk=1;always #10Clk=~Clk;initial begin Reset_n=0;#201;Reset_n=1;Send_Go=1;Data=8'b0010_1001;//0x29#20;Send_Go=0;#30000000;Send_Go=1;Data=8'b1101_0110;//0xD6#20;Send_Go=0;#30000000;Send_Go=1;Data=8'b0010_1001;//0x29#20;Send_Go=0;#30000000;$stop;end endmodule

波形分析


波形现象总结:

  1. 每次拉高Send_Go一个时钟周期,模块锁存输入数据并启动串口发送;
  2. uart_tx输出标准 UART 8N1 时序,空闲电平保持高;
  3. 核心观测点:Tx_Done在整帧串口数据完全发送结束后,输出宽度 1 个时钟周期的同步高脉冲。
    结合前文时序知识点:
    Tx_Done是经过寄存器打一拍后的同步时序信号,波形稳定无毛刺,适合直接对接上层状态机、FIFO 控制逻辑;
    如果拉出内部组合信号W_Tx_Done对比,可以直观观察到两者相差一个时钟周期,验证 “组合瞬时判断信号 → 时序同步输出” 分层设计思路。

总结

  1. 架构延续:完全复用前文受控线性序列机时序模型,学习门槛低,适合系列教程连贯学习;
  2. 功能升级:移除内置延时,新增Send_Go触发、参数化分频、Tx_Done完成标志,适配绝大多数工程串口场景;
  3. 时序核心知识点(本文重点):
    组合逻辑天生存在竞争冒险,会产生毛刺;
    对外完成、有效标志信号,工程标准做法是时序寄存器打一拍输出;
    W_Tx_Done作为中间组合信号,用于瞬时临界判断、波形调试,分层设计提升代码可维护性与稳定性;
  4. 底层逻辑不变:依托 D 触发器边沿锁存特性缓存数据,避免发送过程数据跳变导致串口乱码。
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/7 19:20:16

L1-005考试座位号:从数组下标到哈希映射的入门题

有人看到"L1-005 考试座位号 -15分"这个标题&#xff0c;第一反应多半是&#xff1a;这不就是个查表题&#xff1f;15分的送分题能写出什么花来&#xff1f; 说实话&#xff0c;我第一次在PTA题库里刷到这道题时也是这么想的。但真正耐下心把题目读透、把代码跑通、…

作者头像 李华
网站建设 2026/9/7 19:19:08

两分钟白嫖 WeMod Pro:Wand-Enhancer 本地解锁上手笔记

两分钟白嫖 WeMod Pro&#xff1a;Wand-Enhancer 本地解锁上手笔记 【免费下载链接】Wand-Enhancer Advanced UX and interoperability extension for Wand (WeMod) app 项目地址: https://gitcode.com/GitHub_Trending/we/Wand-Enhancer AI 游戏指南灰着打不开&#xf…

作者头像 李华
网站建设 2026/9/7 19:17:50

OpenCV数字识别实战:从图像预处理到模板匹配完整指南

简介&#xff1a;OpenCV数字识别基础示例包&#xff0c;面向刚接触计算机视觉或希望快速上手图像识别的开发者&#xff0c;围绕“用OpenCV识别印刷体数字”这一任务&#xff0c;演示如何基于C完成从图像读取、灰度化、二值化、滤波等预处理&#xff0c;到模板匹配、OCR识别输出…

作者头像 李华
网站建设 2026/9/7 19:17:50

C# 语言自学笔记 03:变量常量

一、折叠代码补充内容&#xff1a;#region 和 #endrigon配对出现&#xff0c;代码写在中间可以被折叠&#xff0c;让我们编程时更有逻辑&#xff0c;避免代码太凌乱。#region 这是一个折叠代码 //里面可以代码 #endregion二、变量1、变量声明变量就相当于一个容器&#xff0c;可…

作者头像 李华
网站建设 2026/9/7 19:17:40

MinIO对象存储实战:从S3兼容架构到mc命令行运维全攻略

接手一台快被视频素材塞满的服务器&#xff0c;或者维护一套靠“年份/月份/项目名”层层建目录的共享盘&#xff0c;你很快就会意识到传统“文件名目录结构”这套存储方案在大文件面前有多吃力。目录越建越深&#xff0c;扩容要么挂新盘要么迁移数据&#xff0c;想跨机器扩容更…

作者头像 李华
网站建设 2026/9/7 19:16:47

(13/99)汽车电子--DV(EMC/电性能)+PV防水防盗

按项目阶段分&#xff1a;DV测试 (设计验证) → PV测试 (生产验证)。按测试内容分&#xff1a;EMC测试、电气性能测试、环境可靠性测试、材料化学测试、功能/软件测试、生产工艺验证等。SW是功能EMC电性能环境&#xff0c;PV单独。EMC整改&#xff1a; 确定是哪里问题&#xff…

作者头像 李华