FPGA:异步FIFO
前言
异步FIFO是面试中常提的问题
正文
一、什么是异步FIFO
1.1 什么是FIFO
FIFO可以被看做成一个水池:
- 只有写通道打开,表示往水池加水
- 只有读通道打开,表示放水
- 写速度>读速度,可能会写满
- 写速度<读速度,可能会读空
那如何理解先入先出呢?
同样用水池作比喻,我们只考虑理想情况下,先放到水池里的水在池子最下面,那么也先被放出水池
1.2 异步FIFO
同步FIFO:读、写时钟域相同
异步FIFO:读、写时钟域不同,读、写的位宽也可以不同
1.3 异步FIFO的重要概念
FIFO的作用:缓存数据
FIFO的原则:满不写、空不读(否则会造成数据丢失、读取无效数据)
FIFO的关键:设计产生full满信号、empty空信号
二、如何建立FIFO
2.1 FIFO的核心:双端口RAM
双端口:输入端口和输出端口独立,因此可以同时进行读、写;在RAM中,使能信号、数据、数据地址这三个数据是对应的,
2.1.1 SDPRAM:Simple Dual Port RAM
首先明确该模块中可以将读、写端的信号分为:
- 时钟
- 使能信号
- 数据
- 地址
- 读FIFO独有的的复位信号
其次,设定好RAM的宽度和深度
数据宽度:8bit,RAM中存储的数据位宽
地址深度:16bit,RAM中能存多少个位宽为8bit的数据
地址宽度:4bit(2^4=16,使用4位二进制数就能表示16个地址)
写法:16×8
该FIFO能存放:16个8bit的数据
双端口RAM设计文件
实现RAM的读、写操作
module SDPRAM_ly (input w_en,input w_clk,input [7:0] w_data,input [3:0] w_addr,input r_en,input r_clk,input r_rst_n,input [3:0] r_addr,output reg [7:0] r_data);// 首先声明一个内存用来放数据和地址
reg [7:0] SAVE [15:0];// 往SAVE写数据
always @(posedge w_clk) beginif(w_en)SAVE[w_addr] <= w_data;end// 从SAVE里读数据
always @(posedge r_clk or negedge r_rst_n) beginif(!r_rst_n)r_data <= 16'd0;else if(r_en)r_data <= SAVE[r_addr];elser_data <= r_data;endendmodule
2.2 RAM外围模块的核心:设计写满、读空信号
这部分的重点是处理跨时钟域的问题:
单比特:
2.2.1二进制地址自增
在有了双端口RAM后,我们要设计外围的模块去控制RAM正确进行读写操作,下面的核心是如何设计出写满和读空信号
地址自增条件:使能有效+可写/可读
reg [4:0] w_bin;// 对地址扩充一位
reg [4:0] r_bin;// =========================== 写 ===============================// 写地址扩充一位后,地址自加1
always @(posedge w_clk or negedge w_rst_n) beginif(!w_rst_n)w_bin <= 5'd0;else if(w_en && !w_full) // && 逻辑与,得到一位;&按位与,保持相与元素位宽w_bin <= w_bin + 5'd1;else w_bin <= w_bin;
end// =========================== 读 ===============================
always @(posedge r_clk or negedge r_rst_n) beginif(!r_rst_n)r_bin <= 5'd0;else if(r_en && !r_empty) // && 逻辑与,得到一位;&按位与,保持相与元素位宽r_bin <= r_bin + 5'd1;else r_bin <= r_bin;
end
2.2.2 二进制转格雷码:减小跨时钟域下发生亚稳态的概率
这样做的目的:当信号发生变化时,二进制的变化位数远远多于格雷码,因此发生亚稳态的几率就更大,而格雷码只有一位变化
转化方法:
二进制最高位不变,直接复制到格雷码最高位,其余位两两之间异或
二进制转格雷码本身是组合逻辑,但是为了避免产生竞争冒险,于是将组合逻辑的输出打一拍
如果不打拍,可以参照这里的写法:异步FIFO的Verilg实现方法-孤独的单刀
//地址指针从二进制转换成格雷码
assign wr_ptr_g = wr_ptr ^ (wr_ptr >> 1);
assign rd_ptr_g = rd_ptr ^ (rd_ptr >> 1);
打一拍的写法:
// 二进制转格雷码// =========================== 写 ===============================// 方法:最高位不变,按位异或
always @(posedge w_clk or negedge w_rst_n) beginif(!w_rst_n)w_gay <= 5'd0;else w_gay <= {w_bin[4],(w_bin[4:1]^w_bin[3:0])};
end// =========================== 读 ===============================
// 二进制转格雷码
always @(posedge r_clk or negedge r_rst_n) beginif(!r_rst_n)r_gay <= 5'd0;else r_gay <= {r_bin[4],(r_bin[4:1]^r_bin[3:0])};
end
2.2.3 将格雷码打2拍:多比特信号的跨时钟域
先将二进制编码为格雷码,然后对格雷码打2拍(同步器)
,将格雷码同步到另一个时钟域下
// =========================== 写 ===============================
// 将读部分得到的格雷码打2拍后,同步到写时钟下
always @(posedge w_clk or negedge w_rst_n) beginif(!w_rst_n)r_gay_dff1 <= 5'd0;r_gay_dff2 <= 5'd0;else r_gay_dff1 <= r_gay;r_gay_dff2 <= r_gay_dff1;
end// =========================== 读 ===============================
// 将写部分得到的格雷码打2拍后,同步到读时钟下
always @(posedge r_clk or negedge r_rst_n) beginif(!r_rst_n)w_gay_dff1 <= 5'd0;w_gay_dff2 <= 5'd0;else w_gay_dff1 <= w_gay;w_gay_dff2 <= w_gay_dff1;
end
2.2.4 将打2拍后的格雷码解码为二进制:组合逻辑
// =========================== 写 ===============================
// 将同步过来的读格雷码解码为读二进制,方便和写二进制地址比较
always @(*) beginfor(i=4;i>=0;i=i-1)beginif(i==4)r_gay_2_bin[i] <= r_gay_dff2[i];elser_gay_2_bin[i] <= r_gay_dff2[i] ^ r_gay_2_bin[i+1] end
end
// =========================== 读 ===============================
// 格雷码解码为二进制
integer j;
always @(*) beginfor(j=4;j>=0;j=j-1)beginif(j==4)w_gay_2_bin[j] <= w_gay_dff2[j];elsew_gay_2_bin[j] <= w_gay_dff2[j] ^ w_gay_2_bin[j+1] end
end
2.2.5 得到写满、读空信号:组合逻辑
// =========================== 写 ===============================
// 方法一:写满信号:最高位不同,其余位相同——用格雷码
assign w_full = ((w_bin[4] != r_gay_dff2[4]) && (w_bin[3] != r_gay_dff2[3]) && (w_bin[2:0] == r_gay_dff2[2:0]))?1'b1:1'b0;
// 方法二:写满信号:最高位、次高位不同,其余位相同——用格雷码解码后的二进制
assign w_full = ((w_bin[4] != r_gay_2_bin[4]) && (w_bin[3:0] != r_gay_2_bin[3:0]))?1'b1:1'b0;
// =========================== 读 ===============================
// 读空信号:所有位相同
assign r_empty = (r_bin == w_gay_2_bin)?1'b1:1'b0;
2.2.6 汇总
module top_FIFO_ly (//写input w_en,input w_clk,input w_rst_n,input [7:0] w_data,output w_full,//读input r_en,input r_clk,input r_rst_n,output r_empty,output [7:0] r_data
);reg [4:0] w_bin;// 对地址扩充一位
reg [4:0] r_bin;
reg [4:0] w_gay;
reg [4:0] r_gay;
reg [4:0] r_gay_dff1;
reg [4:0] r_gay_dff2;
reg [4:0] w_gay_dff1;
reg [4:0] w_gay_dff2;
reg [4:0] r_gay_2_bin;
reg [4:0] w_gay_2_bin;// =========================== 写 ===============================
// 写地址扩充一位后,地址自加1
always @(posedge w_clk or negedge w_rst_n) beginif(!w_rst_n)w_bin <= 5'd0;else if(w_en && !w_full) // && 逻辑与,得到一位;&按位与,保持相与元素位宽w_bin <= w_bin + 4'd1;else w_bin <= w_bin;
end// 二进制转格雷码
// 方法:最高位不变,按位异或
always @(posedge w_clk or negedge w_rst_n) beginif(!w_rst_n)w_gay <= 5'd0;else w_gay <= {w_bin[4],(w_bin[4:1]^w_bin[3:0])};
end// 将读部分得到的格雷码打2拍后,同步到写时钟下
always @(posedge w_clk or negedge w_rst_n) beginif(!w_rst_n)beginr_gay_dff1 <= 5'd0;r_gay_dff2 <= 5'd0;endelse beginr_gay_dff1 <= r_gay;r_gay_dff2 <= r_gay_dff1; endendinteger i;
// 将同步过来的读格雷码解码为读二进制,方便和写二进制地址比较
always @(*) beginfor(i=4;i>=0;i=i-1)beginif(i==4)r_gay_2_bin[i] <= r_gay_dff2[i];elser_gay_2_bin[i] <= r_gay_dff2[i] ^ r_gay_2_bin[i+1]; end
end// 方法一:写满信号:最高位不同,其余位相同——用格雷码
//assign w_full = ((w_bin[4] != r_gay_dff2[4]) && (w_bin[3] != r_gay_dff2[3]) && (w_bin[2:0] == r_gay_dff2[2:0]))?1'b1:1'b0;
// 方法二:写满信号:最高位、次高位不同,其余位相同——用格雷码解码后的二进制
assign w_full = ((w_bin[4] != r_gay_2_bin[4]) && (w_bin[3:0] == r_gay_2_bin[3:0]))?1'b1:1'b0;// =========================== 读 ===============================
always @(posedge r_clk or negedge r_rst_n) beginif(!r_rst_n)r_bin <= 5'd0;else if(r_en && !r_empty) // && 逻辑与,得到一位;&按位与,保持相与元素位宽r_bin <= r_bin + 4'd1;else r_bin <= r_bin;
end// 二进制转格雷码
always @(posedge r_clk or negedge r_rst_n) beginif(!r_rst_n)r_gay <= 5'd0;else r_gay <= {r_bin[4],(r_bin[4:1]^r_bin[3:0])};
end// 将写部分得到的格雷码打2拍后,同步到读时钟下
always @(posedge r_clk or negedge r_rst_n) beginif(!r_rst_n)beginw_gay_dff1 <= 5'd0;w_gay_dff2 <= 5'd0; endelse beginw_gay_dff1 <= w_gay;w_gay_dff2 <= w_gay_dff1; endend
// 格雷码解码为二进制
integer j;
always @(*) beginfor(j=4;j>=0;j=j-1)beginif(j==4)w_gay_2_bin[j] <= w_gay_dff2[j];elsew_gay_2_bin[j] <= w_gay_dff2[j] ^ w_gay_2_bin[j+1]; end
end// 读空信号:所有位相同
assign r_empty = (r_bin == w_gay_2_bin)?1'b1:1'b0;SDPRAM_ly SDPRAM_inst(.w_en(w_en_RAM),.w_clk(w_clk),.w_data(w_data),.w_addr(w_bin[3:0]),.r_en(r_en_RAM),.r_clk(r_clk),.r_rst_n(r_rst_n),.r_addr(r_bin[3:0]),.r_data(r_data));assign w_en_RAM = (w_en && (!w_full))?1'b1:1'b0;
assign r_en_RAM = (r_en && (!r_empty))?1'b1:1'b0;
endmodule
三、如何使用FIFO
这部分内容以后遇到了再进行补充
3.1 数据跨时钟域
3.2 缓存写速度快的数据
3.3 读写位宽不同
FPGA:异步FIFO相关推荐
- FPGA逻辑设计回顾(6)多比特信号的CDC处理方式之异步FIFO
文章目录 前言 异步FIFO的概念 异步FIFO为什么可以解决CDC问题? 异步FIFO的RTL实现 参考资料 前言 异步FIFO是处理多比特信号跨时钟域的最常用方法,简单来说,异步FIFO是双口RA ...
- (87)FPGA面试题-同步FIFO与异步FIFO区别?异步FIFO代码设计
1.1 FPGA面试题-同步FIFO与异步FIFO区别?异步FIFO代码设计 1.1.1 本节目录 1)本节目录: 2)本节引言: 3)FPGA简介: 4)FPGA面试题-同步FIFO与异步FIFO区 ...
- 基于FPGA的目标颜色识别追踪三——FIFO(同/异步FIFO)、DDR3
FIFO在数据处理过程中是十分重要的. 同步FIFO比较简单,面试过程中手撕代码可能会用到. module sFIFO #(parameter DATA_WIDTH = 8,ADDR_WIDTH = ...
- FPGA数字IC刷题58道Verilog题解代码及视频讲解【FPGA探索者】【同步/异步FIFO】【跨时钟】
牛客 Verilog 刷题入门篇1~24 + 进阶篇1~34 题解代码,所有代码均能通过测试,配合视频讲解效果更佳.为避免内容冗余,本文只给出代码,部分题目给出必要说明. 很多题目本身出题有些问题,着 ...
- 【FPGA学习记录1】异步FIFO的介绍
文章目录 写在前面 1.FIFO的定义以及为什么要用FIFO.FIFO的分类 1.1什么是FIFO 1.2为什么要用FIFO 1.3FIFO的分类 2.异步FIFO的工作原理 2.1FIFO的结构 2 ...
- Xilinx 异步FIFO核实现FPGA与DSP通过UPP(通用并口)进行数据传输
Xilinx 异步FIFO核实现FPGA与DSP通过UPP(通用并口)进行数据传输 一.Xilinx FIFO IP核介绍 二.UPP 仿真效果 总结 一.Xilinx FIFO IP核介绍 1. 因 ...
- FPGA基础知识极简教程(4)从FIFO设计讲起之异步FIFO篇
博文目录 写在前面 正文 同步FIFO回顾 $clog2()系统函数使用 综合属性控制资源使用 异步FIFO设计 FIFO用途回顾 异步FIFO原理回顾 异步FIFO设计 异步FIFO仿真 参考资料 ...
- 异步fifo的设计(FPGA)
本文首先对异步 FIFO 设计的重点难点进行分析 最后给出详细代码 一.FIFO简单讲解 FIFO的本质是RAM, 先进先出 重要参数:fifo深度(简单来说就是需要存多少个数据) fifo位宽(每个 ...
- 异步FIFO的FPGA实现
本文大部分内容来自Clifford E. Cummings的<Simulation and Synthesis Techniques for Asynchronous FIFO Design&g ...
- 基于FPGA的异步FIFO设计
今天要介绍的异步FIFO,可以有不同的读写时钟,即不同的时钟域.由于异步FIFO没有外部地址端口,因此内部采用读写指针并顺序读写,即先写进FIFO的数据先读取(简称先进先出).这里的读写指针是异步的, ...
最新文章
- 基于Python的完全监督机器学习教程 Complete Supervised Machine Learning With Python
- python中的字典推导式_python 字典推导式(经典代码)(22)
- 原博客文章(Apache初配2008/4/8)
- 驱动中的资源共享和临界代码保护
- Maven配置文件无法被导出或者生效的问题【已解决】
- mysql-front5.1的注册码
- Spring Boot + Spring Cloud 实现权限管理系统 后端篇(八):MyBatis分页功能实现
- 华硕主板破linux密码破解,华硕P8B75-M-LE老主板加持NVMe SSD bios(刷新软件和bios)...
- 即席查询—— Kylin使用
- ROS激光SLAM导航理解
- 弘辽科技:拼多多商品搜索热度如何提升?技巧分享
- Java线程池工作原理
- spyder顺眼配色方案记录
- 居然打不开msi文件?
- NetSuite Account Register报表详解
- 高中信息技术项目化教学开题报告
- 平面设计素材免费下载,无套路
- 警惕感冒不仅会头痛脑热,还可能引发致盲性眼病
- STM32移植uC/OSIII
- 计算机网络-关于信号的调制