FPGA|【FPGA】UART串口通信---基于FIFO


目录

  • 前言
  • 一丶FIFO介绍
    • 1.什么是FIFO?
    • 2.FIFO分类
    • 3.FIFO主要参数
    • 4.测试
    • 5.仿真
  • 二丶UART引入FIFO
    • 1.模块原理图
    • 2.代码设计
    • 3.仿真与分析
  • 三丶上板验证
  • 四丶源码

前言 我们在上一章完成了UART串口通信的收发模块,这一章我们将FIFO引入进来,使用FIFO进行缓存数据,来连接串口通信的收发模块
一丶FIFO介绍 1.什么是FIFO?
FIFO即First In First Out,是一种先进先出数据存储、缓冲器,我们知道一般的存储器是用外部的读写地址来进行读写,而FIFO这种存储器的结构并不需要外部的读写地址而是通过自动的加一操作来控制读写,这也就决定了FIFO只能顺序的读写数据
2.FIFO分类 【FPGA|【FPGA】UART串口通信---基于FIFO】同步FIFO
读和写应用同一个时钟。它的作用一般是做交互数据的一个缓冲,也就是说它的主要作用就是一个buffer1。
异步FIFO
读写应用不同的时钟,它有两个主要的作用,一个是实现数据在不同时钟域进行传递,另一个作用就是实现不同数据宽度的数据接口。
3.FIFO主要参数 同步FIFO和异步FIFO略有不同,下面的参数适用于两者。
宽度,用参数FIFO_data_size表示,也就是FIFO存储的数据宽度;
深度,用参数FIFO_addr_size表示,也就是地址的大小,也就是说能存储多少个数据;
满标志,full,当FIFO中的数据满了以后将不再能进行数据的写入;
空标志,empty,当FIFO为空的时候将不能进行数据的读出;
写地址,w_addr,由自动加一生成,将数据写入该地址;
读地址,r_addr,由自动加一生成,将该地址上的数据读出;
同步FIFO和异步FIFO的最主要的不同就体现在空满标志产生的方式上,由此引出两者一些不同的参数。
同步FIFO
  • 时钟,clk,rst,读写应用同一个时钟;
  • 计数器,count,用计数器来进行空满标志的判断;
异步FIFO
  • 时钟,clk_w,rst_w,clk_r,rst_r,读写应用不同的时钟;
  • 指针,w_pointer_gray,r_pointer_gray,用指针来判断空满标识;
  • 同步指针,w_pointer_gray_sync,r_pointer_gray_sync,指针的同步操作,用来做对比产生空满标志符;
4.测试 首先配置IP核
FPGA|【FPGA】UART串口通信---基于FIFO
文章图片

设置路径,我们一般会在工程目录下创建一个文件夹 ip 用来存放IP核文件
FPGA|【FPGA】UART串口通信---基于FIFO
文章图片

FPGA|【FPGA】UART串口通信---基于FIFO
文章图片
配置参数
FPGA|【FPGA】UART串口通信---基于FIFO
文章图片
FPGA|【FPGA】UART串口通信---基于FIFO
文章图片

正常模式与前显模式:
FPGA|【FPGA】UART串口通信---基于FIFO
文章图片

区别:正常模式,输出数据与读请求信号差一个时钟周期;前显模式,将数据放于数据线上,在读请求信号拉高时,在下一个时钟周期,输出FIFO中的第二个数据。
FPGA|【FPGA】UART串口通信---基于FIFO
文章图片

FPGA|【FPGA】UART串口通信---基于FIFO
文章图片

FPGA|【FPGA】UART串口通信---基于FIFO
文章图片

最后这样就成功引入FIFO了
FPGA|【FPGA】UART串口通信---基于FIFO
文章图片

5.仿真 调用ip核
module control ( inputclk, inputrst_n, input [7:0]data, inputrdreq, inputwrreq, outputempty, outputfull, output [7:0]q, output [7:0]usedw ); fifo fifo_inst ( .aclr( ~rst_n),//复位信号取反 .clock( clk),//系统时钟 .data( data),//写入数据 .rdreq( rdreq),//读使能 .wrreq( wrreq),//写使能 .empty( empty),//fifo为空信号 .full( full),//fifo存满信号 .q( q),//读出数据 .usedw( usedw)//可用数据量 ); endmodule //control

testbench编写
`timescale 1ns/1ps module tb_control (); regclk; regrst_n; reg[7:0]data; regrdreq; regwrreq; wireempty; wirefull; wire [7:0]q; wire [7:0]usedw; control control( .clk(clk), .rst_n(rst_n), .data(data), .rdreq(rdreq), .wrreq(wrreq), .empty(empty), .full(full), .q(q), .usedw(usedw) ); parameter CYCLE = 20; always #(CYCLE/2) clk=~clk; integer i=0,j=0; initial begin clk=1; rst_n=1; data=https://www.it610.com/article/0; #200.1; rst_n=0; //复位 rdreq=0; wrreq=0; #(CYCLE*10); rst_n=1; #(CYCLE*10) //wrreq50M for(i=0; i<256; i=i+1)begin//因为我们的数据深度设置的是256,所以这里写进去256个数据 wrreq = 1'b1; //写使能 data = https://www.it610.com/article/{$random}; #CYCLE; end wrreq = 1'b0; //写完拉低 #(CYCLE*5); //rdreq50M for(j=0; j<256; j=j+1)begin rdreq = 1'b1; //读使能 #CYCLE; end rdreq = 1'b0; #(CYCLE*10); $stop; endendmodule //tb_control

写数据:
FPGA|【FPGA】UART串口通信---基于FIFO
文章图片

读数据:
FPGA|【FPGA】UART串口通信---基于FIFO
文章图片

二丶UART引入FIFO 思路:
首先我们将整个项目分为4个模块
uart_rx:接收模块- - -从上位机接收数据,然后将数据发送给control模块
uart_tx:发送模块- - -从control模块接收数据,然后发送给上位机
control:FIFO缓存模块- - -缓存uart_rx接收的数据并输出给uart_tx
top: 顶层模块
1.模块原理图 FPGA|【FPGA】UART串口通信---基于FIFO
文章图片

其中发送模块uart_tx增加了一个ready输出信号,因为发送模块每434个周期发送一位数据,为了防止FIFO不停的输出数据给发送模块,使用ready信号控制FIFO输出数据
2.代码设计 由于只改动了发送模块和新增了control模块,这里只展示这两部分,源码见文章末尾
control:
module control ( inputclk, inputrst_n,input [7:0]dout, inputdout_vld,inputready, output[7:0]din, outputdin_vld ); wire[7:0]data ; wirerdreq; wirewrreq; wireempty; wirefull ; wire[7:0]q; wire[7:0]usedw; regflag ; assign data=https://www.it610.com/article/dout; assign wrreq=dout_vld&&~full; assign din=q; //flag always @(posedge clk or negedge rst_n) begin if (!rst_n) begin flag<=0; end else if(usedw>7) begin//存满8个字节拉高flag flag<=1; end else if (empty) begin flag<=0; end endassign rdreq=flag&&ready&&~empty; assign din_vld=rdreq; //每次将din_vld拉高一个周期,输出一字节数据fifo fifo_inst ( .aclr( ~rst_n),//复位信号取反 .clock( clk),//系统时钟 .data( data),//写入数据 .rdreq( rdreq),//读使能 .wrreq( wrreq),//写使能 .empty( empty),//fifo为空信号 .full( full),//fifo存满信号 .q( q),//读出数据 .usedw( usedw)//可用数据量 ); endmodule //control

uart_tx:
module uart_tx ( inputwireclk, inputwirerst_n, inputwire [7:0]din, inputwiredin_vld, output regtx, output regready ); //定义一个寄存器来锁存 din_vld 时的din reg [9:0]data; //波特率计数器 reg [8:0]cnt_bps; wireadd_cnt_bps; wireend_cnt_bps; //比特计数器 reg [4:0]cnt_bit; wireadd_cnt_bit; wireend_cnt_bit; regflag; //计数器开启标志位parameterBPS_115200=434; //发送一bit数据需要的周期数//data always @(posedge clk or negedge rst_n) begin if (!rst_n) begin data<=0; end else if(din_vld) begin data<={1'b1,din,1'b0}; //拼接起始位和停止位 end else data<=data; end//发送数据 tx always @(posedge clk or negedge rst_n) begin if (!rst_n) begin tx<=1'b1; end else if(cnt_bps==1) begin tx<=data[cnt_bit]; end end//flag always @(posedge clk or negedge rst_n) begin if (!rst_n) begin flag<=0; end else if(din_vld) begin flag<=1; end else if(end_cnt_bit) begin//发送完成关闭计数器 flag<=0; end else flag<=flag; end//ready always @(*) begin if (!rst_n) begin ready<=1; end else if(flag) begin ready<=0; end else begin ready<=1; end end//cnt_bps always @(posedge clk or negedge rst_n) begin if (!rst_n) begin cnt_bps<=0; end else if(add_cnt_bps) begin if (end_cnt_bps) begin cnt_bps<=0; end else cnt_bps<=cnt_bps+1; end endassign add_cnt_bps=flag; assign end_cnt_bps=add_cnt_bps&&cnt_bps==BPS_115200-1; //cnt_bit always @(posedge clk or negedge rst_n) begin if (!rst_n) begin cnt_bit<=0; end else if(add_cnt_bit) begin if (end_cnt_bit) begin cnt_bit<=0; end else cnt_bit<=cnt_bit+1; end endassign add_cnt_bit=end_cnt_bps; assign end_cnt_bit=add_cnt_bit&&cnt_bit==9; endmodule //uart_tx

3.仿真与分析 testbench:
`timescale 1ns/1ps module tb_uart (); regclk; regrst_n; reg [7:0]din; regdin_vld; wiretx_r; //用来连接上位机的tx和从机的rx wirerx; parameterCYCLE=20; //例化从机(顶层模块,包含了一个uart_rx和一个uart_tx) uart uart( .clk(clk), .rst_n(rst_n), .rx(tx_r),//接收 .tx(tx)//发送 ); //例化上位机(用来给从机发送数据) uart_tx uart_tx( .clk(clk), .rst_n(rst_n), .din(din), .din_vld(din_vld), .tx(tx_r) ); always #(CYCLE/2)clk=~clk; initial begin clk=1; rst_n=1; #200; rst_n=0; din_vld=0; #(CYCLE*10); rst_n=1; send(8'h11); send(8'h22); send(8'h33); send(8'h44); send(8'h55); send(8'h66); send(8'h77); send(8'h88); #2000000; $stop; endtask send; input [7:0] send_data; begin din=send_data; din_vld=1; #CYCLE; din_vld=0; #(CYCLE*434*22); end endtaskendmodule //tb_uart_tx

分析:
FPGA|【FPGA】UART串口通信---基于FIFO
文章图片

1.上位机发送数据到FPGA之后由FPGA的接收模块将数据dout数据有效信号dout_vld输出给FIFO缓存
2.dout_vld作为写使能信号,在写使能开启的时候存储dout
FPGA|【FPGA】UART串口通信---基于FIFO
文章图片

3.在FIFO中存储的数据大于7个的时候开启读使能,因为FIFO模式设置的前显模式,所以在读使能生效前,第一位数据就有效了,也就是时序图中的q信号:8’h11
FPGA|【FPGA】UART串口通信---基于FIFO
文章图片

然后来看发送数据:
FPGA|【FPGA】UART串口通信---基于FIFO
文章图片

箭头处,din_vld拉高一个周期,目的是为了在我们发送完一帧数据之前,只锁存一次数据,保证发送一帧数据期间数据不改变,将数据din拼接起始位和停止位锁存到data
三丶上板验证 因为设置的FIFO存储满8个数据才开始读数据,所以这里看到发送8’h88之后才收到数据!!!
FPGA|【FPGA】UART串口通信---基于FIFO
文章图片

四丶源码 https://github.com/xuranww/uart_fifo.git
参考文章:
1.https://www.cnblogs.com/xuqing125/p/8337586.html
2.https://blog.csdn.net/QWERTYzxw/article/details/121295258
  1. 缓冲区(Buffer)就是在内存中预留指定大小的存储空间用来对I/O的数据做临时存储,这部分预留的内存空间叫缓冲区。
    使用缓冲区有两个好处:
    ①减少实际物理读写次数
    ②缓冲区在创建时就被分配内存,这块内存区域一直被重用,可以减少动态分配和回收内存的次数 ??

    推荐阅读