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

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

目录

前言

我们在上一章完成了UART串口通信的收发模块这一章我们将FIFO引入进来使用FIFO进行缓存数据来连接串口通信的收发模块

一丶FIFO介绍

1.什么是FIFO

FIFO即First In First Out是一种先进先出数据存储、缓冲器我们知道一般的存储器是用外部的读写地址来进行读写而FIFO这种存储器的结构并不需要外部的读写地址而是通过自动的加一操作来控制读写这也就决定了FIFO只能顺序的读写数据

2.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

  • 时钟clkrst读写应用同一个时钟
  • 计数器count用计数器来进行空满标志的判断

异步FIFO

  • 时钟clk_wrst_wclk_rrst_r读写应用不同的时钟
  • 指针w_pointer_grayr_pointer_gray用指针来判断空满标识
  • 同步指针w_pointer_gray_syncr_pointer_gray_sync指针的同步操作用来做对比产生空满标志符

4.测试

首先配置IP核

在这里插入图片描述
设置路径我们一般会在工程目录下创建一个文件夹 ip 用来存放IP核文件

在这里插入图片描述
在这里插入图片描述配置参数
在这里插入图片描述在这里插入图片描述
正常模式与前显模式
在这里插入图片描述

区别正常模式输出数据与读请求信号差一个时钟周期前显模式将数据放于数据线上在读请求信号拉高时在下一个时钟周期输出FIFO中的第二个数据。

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
最后这样就成功引入FIFO了
在这里插入图片描述

5.仿真

调用ip核

module control (
    input   	        clk     ,
    input       	    rst_n   ,
    input	[7:0]       data    ,
    input   	        rdreq   ,
    input           	wrreq   ,
    output         	    empty   ,
    output       	    full    ,
    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 ();
reg            clk      ; 
reg            rst_n    ; 
reg  [7:0]     data     ; 
reg            rdreq    ; 
reg            wrreq    ; 
wire           empty    ; 
wire           full     ; 
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=0;
    #200.1;
    rst_n=0;   //复位
    rdreq=0;
    wrreq=0;
    #(CYCLE*10);
    rst_n=1;
    #(CYCLE*10)
   //wrreq  50M
    for(i=0;i<256;i=i+1)begin     //因为我们的数据深度设置的是256所以这里写进去256个数据
        wrreq = 1'b1;//写使能
        data = {$random};
        #CYCLE;
    end
    wrreq = 1'b0;//写完拉低
    #(CYCLE*5);

    //rdreq  50M
    for(j=0;j<256;j=j+1)begin
        rdreq = 1'b1;//读使能
        #CYCLE;
    end
    rdreq = 1'b0;
    #(CYCLE*10);
    

    $stop;
end

endmodule //tb_control

写数据
在这里插入图片描述

读数据

在这里插入图片描述

二丶UART引入FIFO

思路
首先我们将整个项目分为4个模块

uart_rx接收模块- - -从上位机接收数据然后将数据发送给control模块
uart_tx发送模块- - -从control模块接收数据然后发送给上位机
controlFIFO缓存模块- - -缓存uart_rx接收的数据并输出给uart_tx
top 顶层模块

1.模块原理图

在这里插入图片描述
其中发送模块uart_tx增加了一个ready输出信号因为发送模块每434个周期发送一位数据为了防止FIFO不停的输出数据给发送模块使用ready信号控制FIFO输出数据

2.代码设计

由于只改动了发送模块和新增了control模块这里只展示这两部分源码见文章末尾

control

module control (
    input   	        clk        ,
    input       	    rst_n      ,

    input	[7:0]       dout       ,
    input               dout_vld   ,

    input               ready      , 

    output  [7:0]       din        ,
    output	            din_vld          
);
wire     [7:0]      data ;
wire     	        rdreq;
wire             	wrreq;
wire            	empty;
wire          	    full ;
wire     [7:0]      q    ;
wire     [7:0]      usedw;
reg                 flag ;

assign data=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
end

assign 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 (
    input  wire         clk,
    input  wire         rst_n,
    input  wire [7:0]   din,
    input  wire         din_vld,
    output reg          tx,
    output reg          ready
);
//定义一个寄存器来锁存 din_vld 时的din
reg [9:0]       data;

//波特率计数器
reg [8:0]       cnt_bps;
wire            add_cnt_bps;
wire            end_cnt_bps;

//比特计数器
reg [4:0]       cnt_bit;
wire            add_cnt_bit;
wire            end_cnt_bit;

reg             flag;   //计数器开启标志位

parameter       BPS_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
end

assign 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
end

assign 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 ();
reg         clk;
reg         rst_n;
reg [7:0]   din;
reg         din_vld;
wire        tx_r;   //用来连接上位机的tx和从机的rx
wire        rx;

parameter       CYCLE=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;

end

task send;
    input [7:0] send_data;
    begin
        din=send_data;
        din_vld=1;
        #CYCLE;
        din_vld=0;
        #(CYCLE*434*22);
    end
endtask

endmodule //tb_uart_tx

分析
在这里插入图片描述
1.上位机发送数据到FPGA之后由FPGA的接收模块将数据dout数据有效信号dout_vld输出给FIFO缓存
2.dout_vld作为写使能信号在写使能开启的时候存储dout
在这里插入图片描述
3.在FIFO中存储的数据大于7个的时候开启读使能因为FIFO模式设置的前显模式所以在读使能生效前第一位数据就有效了也就是时序图中的q信号8’h11

在这里插入图片描述

然后来看发送数据
在这里插入图片描述
箭头处din_vld拉高一个周期目的是为了在我们发送完一帧数据之前只锁存一次数据保证发送一帧数据期间数据不改变将数据din拼接起始位和停止位锁存到data

三丶上板验证

因为设置的FIFO存储满8个数据才开始读数据所以这里看到发送8’h88之后才收到数据
请添加图片描述

四丶源码

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的数据做临时存储这部分预留的内存空间叫缓冲区。
    使用缓冲区有两个好处
    ①减少实际物理读写次数
    ②缓冲区在创建时就被分配内存这块内存区域一直被重用可以减少动态分配和回收内存的次数 ↩︎

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6