前言
我们在上一章完成了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
- 时钟,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核
设置路径,我们一般会在工程目录下创建一个文件夹 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模块接收数据,然后发送给上位机control:
FIFO缓存模块- - -缓存uart_rx接收的数据并输出给uart_txtop:
顶层模块
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文章来源:https://www.toymoban.com/news/detail-412761.html
-
缓冲区(Buffer)就是在内存中预留指定大小的存储空间用来对I/O的数据做临时存储,这部分预留的内存空间叫缓冲区。
使用缓冲区有两个好处:
①减少实际物理读写次数
②缓冲区在创建时就被分配内存,这块内存区域一直被重用,可以减少动态分配和回收内存的次数 ↩︎文章来源地址https://www.toymoban.com/news/detail-412761.html
到了这里,关于【FPGA】UART串口通信---基于FIFO的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!