目录
- 前言
- 一丶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,用计数器来进行空满标志的判断;
- 时钟,clk_w,rst_w,clk_r,rst_r,读写应用不同的时钟;
- 指针,w_pointer_gray,r_pointer_gray,用指针来判断空满标识;
- 同步指针,w_pointer_gray_sync,r_pointer_gray_sync,指针的同步操作,用来做对比产生空满标志符;
文章图片
设置路径,我们一般会在工程目录下创建一个文件夹 ip 用来存放IP核文件
文章图片
文章图片
配置参数
文章图片
文章图片
正常模式与前显模式:
文章图片
区别:正常模式,输出数据与读请求信号差一个时钟周期;前显模式,将数据放于数据线上,在读请求信号拉高时,在下一个时钟周期,输出FIFO中的第二个数据。
文章图片
文章图片
文章图片
最后这样就成功引入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
写数据:
文章图片
读数据:
文章图片
二丶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 (
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
分析:
文章图片
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
- 缓冲区(Buffer)就是在内存中预留指定大小的存储空间用来对I/O的数据做临时存储,这部分预留的内存空间叫缓冲区。
使用缓冲区有两个好处:
①减少实际物理读写次数
②缓冲区在创建时就被分配内存,这块内存区域一直被重用,可以减少动态分配和回收内存的次数 ??
推荐阅读
- 基于FPGA的串口指令帧接收与解析的verilog代码
- HDLbits|Circuits--Sequential Logic--Latches and Flip-Flops--Edgecapture
- HDLbits|Circuits--Sequential Logic--Latches and Flip-Flops--Dualedge
- HDLbits|Circuits--Sequential Logic--Shift Registers--Lfsr32
- HDLbits|Circuits--Sequential Logic--Latches and Flip-Flops--Exams/2014 q4a
- HDLbits|Circuits--Sequential Logic--Latches and Flip-Flops--Edgedetect2
- FPGA学习笔记|FPGA学习笔记——知识点总结
- 前端|逻辑门图解—与门、或门、非门、与非门、或非门、异或门、同或门
- FPGA|基于FPGA(多目标运动检测(手把手教学①))