> 技术文档 > 基于VerilogHDL硬件语言实现UART接收和UART发送(FPGA、QuartusⅡ)_verilog串口通信的接收与发送

基于VerilogHDL硬件语言实现UART接收和UART发送(FPGA、QuartusⅡ)_verilog串口通信的接收与发送

目录

一、前言

1.1 UART简介

1.2 FPGA简介

1.3VerilogHDL 简介

1.4 Quartus简介

二、设计理念与技巧

三、UART接收

3.1 模块参数

3.2 状态机设计

3.3 关键逻辑

3.4 Verilog HDL 代码

四、UART发送

4.1 模块参数

4.2 状态机设计

4.3 关键逻辑

4.4 Verilog HDL 代码

五、测试

六、结语


用VerilogHDL硬件语言实现UART接收和UART发送(无校验位),可以在FPGA开发板上直接测试、使用,作者在Intel Altera Cyclone开发板上测试、使用了此设计。初学者读者可以借鉴我的设计方法。

一、前言

对各个专业名词的简单介绍,全用AI写的,专业人员直接跳至第二章

1.1 UART简介

UART(Universal Asynchronous Receiver/Transmitter,通用异步收发器)是一种串行通信协议,用于设备间的异步数据传输。它通过两根信号线(TX发送、RX接收)实现全双工通信,无需时钟信号同步,依靠预定义的波特率协调数据传输。

关键特性

  • 异步通信:无共享时钟信号,依赖起始位、停止位和波特率同步。
  • 数据格式:每帧包含1个起始位(低电平)、5-9个数据位、可选的奇偶校验位和1-2个停止位(高电平)。
  • 波特率:常见值如9600、115200等,需通信双方一致。

典型应用场景

  • 微控制器与传感器、蓝牙/Wi-Fi模块的通信。
  • 调试接口(如通过USB转UART工具连接PC)。

代码示例(Arduino发送数据)

# Python示例:通过UART发送数据 import serial ser = serial.Serial(\'/dev/ttyUSB0\', baudrate=9600, timeout=1) ser.write(b\'Hello UART\\n\') ser.close() 

优缺点

  • 优点:硬件简单、成本低、广泛兼容。
  • 缺点:速率较低(通常<1Mbps)、易受波特率误差影响。

1.2 FPGA简介

FPGA(Field-Programmable Gate Array,现场可编程门阵列)是一种可通过编程配置实现特定功能的集成电路。与固定功能的ASIC(专用集成电路)不同,FPGA允许用户根据需要重新配置其逻辑结构,适用于快速原型设计、灵活硬件加速等场景。

FPGA 的核心组成

  1. 可编程逻辑单元(CLB):包含查找表(LUT)和触发器,实现组合逻辑和时序逻辑功能。
  2. 可编程互连资源:连接逻辑单元的信号通路,支持灵活布线。
  3. 输入输出块(IOB):负责与外部器件通信,支持多种电平标准(如LVDS、LVCMOS)。
  4. 嵌入式硬件资源:高端FPGA可能集成DSP模块、存储器(BRAM)或处理器核(如ARM Cortex)。

FPGA 的开发流程

  1. 设计输入:使用硬件描述语言(HDL)如Verilog或VHDL编写逻辑代码。
module led_blink ( input clk, output reg led); reg [31:0] counter; always @(posedge clk) begin counter  50000000) begin led <= ~led; counter <= 0; end endendmodule
  1. 综合与实现:通过工具(如Xilinx Vivado或Intel Quartus)将HDL代码转换为门级网表,并映射到FPGA资源。
  2. 时序分析与约束:确保设计满足时钟频率等时序要求。
  3. 烧写配置文件:生成比特流文件(.bit)并下载到FPGA。

FPGA 的应用场景

  • 通信系统:5G基带处理、协议加速。
  • 工业控制:实时信号处理、电机驱动。
  • 人工智能:定制化神经网络加速。
  • 航空航天:抗辐射加固设计。

FPGA 的优缺点

  • 优点:灵活性高、开发周期短、可重复编程。
  • 缺点:功耗和成本通常高于ASIC,性能受限于架构。

主流厂商与工具

  • Xilinx(AMD):Vivado设计套件,Versal系列芯片。
  • Intel(Altera):Quartus Prime工具,Stratix系列芯片。
  • Lattice Semiconductor:低功耗FPGA,适用于嵌入式场景。

1.3VerilogHDL 简介

VerilogHDL(Verilog Hardware Description Language)是一种用于数字电路设计和验证的硬件描述语言(HDL)。它广泛应用于集成电路(IC)和现场可编程门阵列(FPGA)的设计中,支持从系统级到门级的多层次抽象建模。

主要特点

  1. 结构化建模:支持模块化设计,可将复杂电路分解为可重用的子模块。
  2. 行为级描述:允许通过高级语言描述电路功能,无需直接关注硬件细节。
  3. 并行执行:体现硬件并发的特性,与软件编程的串行逻辑不同。
  4. 多层级抽象:支持开关级、门级、寄存器传输级(RTL)和行为级建模。

基本语法结构

VerilogHDL 的核心是模块(module),以下是一个简单的代码示例:

module and_gate ( input a, input b, output y); assign y = a & b; // 逻辑与操作endmodule

应用场景

  • 数字逻辑设计:如组合逻辑、时序电路(计数器、状态机等)。
  • 仿真验证:通过测试平台(Testbench)验证设计功能。
  • 综合实现:将RTL代码转换为实际的门级网表。

与其他HDL对比

  • VHDL:语法更严格,常用于欧洲军工和航空航天领域。
  • SystemVerilog:Verilog的扩展,增加了面向对象和验证特性。

学习资源

  • 官方标准文档(IEEE Std 1364)
  • 开源工具:如Icarus Verilog(仿真)、Yosys(综合)
  • 实践平台:FPGA开发板(如Xilinx、Altera系列)

VerilogHDL的简洁性和高效性使其成为硬件设计工程师的核心技能之一。

1.4 Quartus简介

Quartus是由英特尔(Intel)旗下FPGA公司(原Altera)开发的集成开发环境(IDE),主要用于FPGA和CPLD的设计、仿真、综合与编程。它支持硬件描述语言(如VHDL、Verilog HDL)和图形化设计输入,广泛应用于数字电路设计、嵌入式系统和原型验证领域。

核心功能

  1. 设计输入
    支持多种设计输入方式,包括原理图编辑、硬件描述语言(HDL)代码编写,以及基于模型的设计工具(如DSP Builder)。

    module hello_world; initial begin $display(\"Hello Quartus\"); endendmodule
  2. 综合与布局布线
    将HDL代码转换为目标器件(如Intel Cyclone、Arria系列)的可配置逻辑块(CLB)和互联资源,优化时序和面积。

  3. 仿真与调试
    内置仿真工具(如ModelSim-Altera)支持功能仿真和时序仿真,SignalTap II逻辑分析器用于实时调试。

  4. 编程与配置
    生成配置文件(如.sof、.pof)并通过JTAG或Active Serial接口下载到FPGA/CPLD器件。

适用器件

  • FPGA系列:Cyclone、Arria、Stratix、MAX 10等。
  • CPLD系列:MAX 3000、MAX 7000等。

版本与平台

  • 免费版本:Quartus Prime Lite Edition(功能受限,支持部分器件)。
  • 付费版本:Quartus Prime Standard/Pro Edition(完整功能,支持高端器件)。
  • 操作系统:Windows、Linux(部分版本)。

典型设计流程

  1. 创建项目:选择目标器件型号和设计语言。
  2. 编写代码/绘制原理图:实现逻辑功能。
  3. 编译与优化:综合、布局布线并生成报告。
  4. 仿真验证:通过波形仿真检查功能正确性。
  5. 下载测试:将配置数据烧录至硬件。

扩展工具

  • Platform Designer:用于构建基于Nios II的嵌入式系统。
  • Intel FPGA SDK for OpenCL:支持OpenCL加速开发。
  • DSP Builder:集成MATLAB/Simulink进行DSP设计。

应用场景

  • 数字信号处理(DSP)。
  • 高速通信接口(如PCIe、DDR)。
  • 嵌入式处理器(Nios II)开发。
  • 原型验证与ASIC前端设计。

二、设计理念与技巧

相信不是对这方面有简单学习的读者根本不会看到这里,作者默认基本的知识大家都是会的。所以这里就抛开基础理论,只描述关键点。

大家都知道UART通信的时序,那么在FPGA硬件语言中我们要注意什么呢?众所周知,硬件语言是直接对寄存器操作的,我们可以精细的操作每一个CLK周期的端口电平,所以我们不需要去考虑太多事情,把每个端口按照UART时序一一操作就行了,非常简单。但与此同时,我们又不得不考虑以下几个问题:

(1):如何判断接收信号的起始状态

(2):如何让发送和接收不断循环,长久保持功能

(3):如何避免方波信号的抖动和失真

首先起始状态的判断十分简单,我们只需要加入下降沿边沿触发就可以判断起始位。而接收与发送的流程采用状态机去控制,可以解决程序逻辑问题。而在本设计中,我设计了自握手机制(指一个输出端来输出状态位,一个接收端接收自身发出的状态位)来实现简单的流控,这样能避免数据的丢失。信号的抖动和失真主要是在信号改变的那段时间内,我们无法预料这个时期的电平状态,但中间信号一般是较为稳定的,所以我们把采样的那个瞬间设定为信号的时间中值就可以很大程度上解决问题。

三、UART接收

3.1 模块参数

CLK_FRE:系统时钟频率(MHz)。

BAUD_RATE:波特率。

CYCLE:每个bit的时钟周期数,计算公式为 CLK_FRE * 1e6 / BAUD_RATE。

input clk, 时钟输入 input  rst_n, 异步复位 下降沿有效 input[7:0] tx_data, 要发送的数据(8位) input tx_data_valid, 发送数据使能(高电平有效) output reg tx_data_ready, 已成功发送信号(高电平有效) output tx_pin 发送端口

3.2 状态机设计

S_IDLE:检测起始位的下降沿(通过双寄存器同步消除亚稳态)。

S_START:等待一个完整bit时间(CYCLE周期),确保起始位结束。

S_REC_BYTE:接收8个数据位,每个bit的中间点(cycle_cnt = CYCLE/2 - 1)采样数据,避免边沿抖动。

S_STOP:等待半个bit时间(CYCLE/2周期),快速回到空闲状态以检测下一帧。

S_DATA:输出有效信号 rx_data_valid,直到外部确认接收完成(rx_data_ready 为高)。

3.3 关键逻辑

下降沿检测:通过 rx_d0 和 rx_d1 同步输入信号,检测到 rx_negedge 后触发状态转换。

数据采样:在数据位的中间点采样(cycle_cnt = CYCLE/2 - 1),提升抗干扰能力。

数据输出:接收完8位后,锁存数据到 rx_data,并置位 rx_data_valid,等待外部确认。

3.4 Verilog HDL 代码

UART_RX.v

module uart_rx#(parameter CLK_FRE = 50, //10 //系统频率(Mhz)parameter BAUD_RATE = 12500000// 9600//波特率)(input clk,  //时钟输入input rst_n, //异步复位 下降沿有效output reg[7:0]  rx_data, //接收到的串口数据(8位)output reg  rx_data_valid, //接收到的串口数据有效 (高电平有效)input rx_data_ready, //可以接受数据(高有效)input rx_pin //串口接收数据输入);//计算波特率时钟周期localparam CYCLE = CLK_FRE * 1000000 / BAUD_RATE; //一个bit需要多少时钟脉冲//状态机codelocalparam S_IDLE = 1; //空闲状态localparam S_START = 2; //起始位状态(0.5bit+1bit)localparam S_REC_BYTE = 3; //接收数据状态(8bit)localparam S_STOP = 4; //接收停止位状态 (0.5bit)localparam S_DATA = 5; //等待接收数据信号 reg[2:0] state;//状态机当前状态reg[2:0] next_state;//状态机下一状态reg rx_d0; //延迟reg rx_d1; //延迟wire rx_negedge; //下降沿边沿reg[7:0] rx_bits; //接受数据的临时储存reg[15:0] cycle_cnt; //波特计数reg[2:0] bit_cnt; //比特计数//-------下降沿边沿触发判断模块-------//assign rx_negedge = rx_d1 && ~rx_d0; //下降沿边沿触发判断always@(posedge clk or negedge rst_n) //延迟1clock beginif(rst_n == 1\'b0)beginrx_d0 <= 1\'b0;rx_d1 <= 1\'b0;endelsebeginrx_d0 <= rx_pin;rx_d1 <= rx_d0;endend//--------------状态机---------------//always@(posedge clk or negedge rst_n)//状态机beginif(rst_n == 1\'b0)state <= S_IDLE;elsestate <= next_state;endalways@(*)//状态机程序begincase(state)S_IDLE://1:rx_pin下降沿边沿触发起始状态,不然则保持if(rx_negedge)next_state <= S_START;elsenext_state <= S_IDLE;S_START://2:等待一个bit后,进入数据接收状态if(cycle_cnt == CYCLE - 1)next_state <= S_REC_BYTE;elsenext_state <= S_START;S_REC_BYTE://3:等待接收8bit数据后,进入停止状态if(cycle_cnt == CYCLE - 1 && bit_cnt == 3\'d7) next_state <= S_STOP;elsenext_state <= S_REC_BYTE;S_STOP:if(cycle_cnt == CYCLE/2 - 1)//4:等待半个bit的时间,避免错过下个数据起始位判断next_state <= S_DATA;elsenext_state <= S_STOP;S_DATA://5:数据接收完成,等待可以接收数据信号,进入空闲状态 rx_data_readyif(rx_data_ready) next_state <= S_IDLE;elsenext_state <= S_DATA;default:next_state <= S_IDLE;//else:回到空闲状态endcaseend//-----------判断是否可以继续接收数据-----------//always@(posedge clk or negedge rst_n)beginif(rst_n == 1\'b0)rx_data_valid <= 1\'b0;else if(state == S_STOP && next_state != state)//停止状态后进入等待状态,则有效rx_data_valid <= 1\'b1;else if(state == S_DATA && rx_data_ready)//重新进入待命状态,清零 rx_data_readyrx_data_valid <= 1\'b0;end//-----------锁存数据-----------//always@(posedge clk or negedge rst_n)beginif(rst_n == 1\'b0)rx_data <= 8\'d0;else if(state == S_STOP && next_state != state)//停止状态后进入空闲状态rx_data <= rx_bits;//接收到的数据送入输出口end//-----------bit计数-----------//always@(posedge clk or negedge rst_n)beginif(rst_n == 1\'b0)beginbit_cnt <= 3\'d0;endelse if(state == S_REC_BYTE)//接收数据中if(cycle_cnt == CYCLE - 1)//一个bit时间后bit加一bit_cnt <= bit_cnt + 3\'d1;elsebit_cnt <= bit_cnt;elsebit_cnt <= 3\'d0;end//-----------波特计数-----------//always@(posedge clk or negedge rst_n)//接收数据中begin//一个时钟脉冲波特加一if(rst_n == 1\'b0)//计数到1bit后清零,或进入下一状态清零cycle_cnt <= 16\'d0;else if((state == S_REC_BYTE && cycle_cnt == CYCLE - 1) || next_state != state)cycle_cnt <= 16\'d0;elsecycle_cnt <= cycle_cnt + 16\'d1;end//-----------接收数据-----------//always@(posedge clk or negedge rst_n)//接收数据中begin//在波特计数中点采样if(rst_n == 1\'b0)//将串口接收的数据依次送入rx_bitsrx_bits <= 8\'d0;else if(state == S_REC_BYTE && cycle_cnt == CYCLE/2 - 1)rx_bits[bit_cnt] <= rx_pin;elserx_bits <= rx_bits; endendmodule 

四、UART发送

发送比接收要简单很多,因为发送数据只需要猛猛发送就行了,但接收数据要考虑的就很多了。

4.1 模块参数

参数定义与接收模块一致。

4.2 状态机设计

S_IDLE:检测 tx_data_valid 有效后锁存数据。

S_START:发送起始位(低电平),持续1个完整bit时间。

S_SEND_BYTE:依次发送8个数据位,每个bit持续1个完整周期。

S_STOP:发送停止位(高电平),持续1个完整周期,完成后返回空闲状态。

4.3 关键逻辑

数据锁存:在空闲状态下锁存 tx_data,确保发送过程中数据稳定。

逐位发送:根据 bit_cnt 选择当前发送的bit,通过 tx_reg 输出。

完成信号:发送完成后置位 tx_data_ready,表示可接收新数据。

4.4 Verilog HDL 代码

UART_TX.v

module uart_tx#(parameter CLK_FRE = 50, //10 //系统频率(Mhz)parameter BAUD_RATE = 12500000//9600 //波特率)(input clk,  //时钟输入input rst_n, //异步复位 下降沿有效input[7:0]  tx_data, //要发送的数据(8位)input tx_data_valid, //发送数据使能(高电平有效)output reg  tx_data_ready, //已成功发送信号(高电平有效)output tx_pin //发送端口);//计算波特率时钟周期localparam CYCLE = CLK_FRE * 1000000 / BAUD_RATE;//状态机codelocalparam S_IDLE = 1;//空闲状态localparam S_START = 2;//开始状态(1bit)localparam S_SEND_BYTE = 3;//发送状态(8bit)localparam S_STOP = 4;//停止状态(1bit)reg[2:0] state;//当前状态reg[2:0] next_state;//下一状态reg[15:0] cycle_cnt; //波特计数reg[2:0] bit_cnt;//bit计数reg[7:0] tx_data_latch; //发送数据锁存reg tx_reg; //位数据assign tx_pin = tx_reg;//锁存//-----------------状态机------------------//always@(posedge clk or negedge rst_n)beginif(rst_n == 1\'b0)state <= S_IDLE;elsestate <= next_state;endalways@(*)begincase(state)S_IDLE://发送使能信号,进入起始状态if(tx_data_valid == 1\'b1)next_state <= S_START;elsenext_state <= S_IDLE;S_START://等待1bit,进入发送状态if(cycle_cnt == CYCLE - 1)next_state <= S_SEND_BYTE;elsenext_state <= S_START;S_SEND_BYTE://发送8bit,进入停止状态if(cycle_cnt == CYCLE - 1 && bit_cnt == 3\'d7)next_state <= S_STOP;elsenext_state <= S_SEND_BYTE;S_STOP://等待1bit,回到空闲状态if(cycle_cnt == CYCLE - 1 )next_state <= S_IDLE;elsenext_state <= S_STOP;default://else:回到初始状态next_state <= S_IDLE;endcaseend//-----------------成功发送------------------//always@(posedge clk or negedge rst_n)//在发送过程中低电平(发送未完成)beginif(rst_n == 1\'b0)begintx_data_ready <= 1\'b0;endelse if(state == S_IDLE)if(tx_data_valid == 1\'b1)tx_data_ready <= 1\'b0;elsetx_data_ready <= 1\'b1;else if(state == S_STOP && cycle_cnt == CYCLE - 1)tx_data_ready <= 1\'b1;end//-----------------空闲状态中将数据锁存------------------//always@(posedge clk or negedge rst_n)beginif(rst_n == 1\'b0)begintx_data_latch <= 8\'d0;endelse if(state == S_IDLE && tx_data_valid == 1\'b1)tx_data_latch <= tx_data;end//-----------------bit计数------------------//always@(posedge clk or negedge rst_n)//发送状态中begin//每周期加一if(rst_n == 1\'b0)beginbit_cnt <= 3\'d0;endelse if(state == S_SEND_BYTE)if(cycle_cnt == CYCLE - 1)bit_cnt <= bit_cnt + 3\'d1;elsebit_cnt <= bit_cnt;elsebit_cnt <= 3\'d0;end//-----------------波特计数------------------//always@(posedge clk or negedge rst_n)//发送状态中begin//每时钟加一if(rst_n == 1\'b0)cycle_cnt <= 16\'d0;else if((state == S_SEND_BYTE && cycle_cnt == CYCLE - 1) || next_state != state)cycle_cnt <= 16\'d0;elsecycle_cnt <= cycle_cnt + 16\'d1;end//-----------------发送状态中每bit按位发送信号------------------//always@(posedge clk or negedge rst_n)//默认高电平begin//起始位置低if(rst_n == 1\'b0)//按位发送数据tx_reg <= 1\'b1;elsecase(state)S_IDLE,S_STOP:tx_reg <= 1\'b1; S_START:tx_reg <= 1\'b0; S_SEND_BYTE:tx_reg <= tx_data_latch[bit_cnt];default:tx_reg <= 1\'b1; endcaseendendmodule 

五、测试

接收数据测试

发送数据测试

结果达到目的

六、结语

我们单独完成了发送和接收的UART通信协议,那有人就会问了,那UART不是同时有发送和接收的功能吗?是的,但你把这两个硬件模块简单连连线就行了啊,或者你把这俩代码写一起嘛。

本文章仅供参考和初学者学习。

本躯水平有限,若内容有误恳请斧正。