跨时钟域之异步FIFO
参考:https://www.cnblogs.com/aslmer/p/6114216.html
文章:Simulation and Synthesis Techniques for Asynchronous
Asynchronous FIFO Design
异步FIFO的读写指针
写指针
写指针指向当前将要写入数据的位置,复位之后,读写指针被置零。
执行写操作的时候,向写指针指向的存储区写入数据,之后写指针加1,指向接下来要被写入数据的位置。
On a FIFO-write operation, the memory location that is pointed to by the write pointer is written, and then the write pointer is incremented to point to the next location to be written.
读指针:
读指针指向当前要被读取数据的位置,复位时,读写指针被置零,FIFO为空读指针指向一个无效的数据(FIFO为空,empty信号有效——拉高)。当第一个有效数据被写入FIFO之后,写指针增加,empty flag信号被拉低,且读指针一直指向FIFO第一个数据的存储区域。接收逻辑没必要使用两个时钟周期读取数据,这样会使得效率很低。
FIFO空标志:
当读写指针是相等的时候:分两种情况
1.当读写指针执行复位操作的时候。
2.当读指针赶上写指针的时候,最后一笔数据从FIFO读出后FIFO为空
FIFO满标志:
读写指针相等,当FIFO里面的写指针写满一圈之后又转回到和读指针同样的位置。有个问题,读写指针相等的时候怎么判断FIFO是empty还是full?
设计的时候增加一位bit去辅助判断FIFO是空还是满。当写指针超过FIFO的最大寻址范围时,写指针将使辅助位zhi高.
FIFO满的时候:读写指针的低位(n-1位bit)相等,高位(第n位bit)不同。
FIFO空的时候,读写指针的低位和高位都相等。(针对二进制)
但是二进制FIFO指针综合电路复杂,一般采用**格雷码**,文章中采用二进制转换格雷码的方法,判断FIFO的空满标志4位二进制格雷码,有效地址位为三位。
二进制转换为格雷码的算法:rgraynext = (rbinnext>>1) ^ rbinnext;
采用格列码判断FIFO空满:
**
当最高位和次高位相同,其余位相同认为是读空
当最高位和次高位不同,其余位相同认为是写满
**
采用双寄存器同步后也不能直接判断空满
因为二进制编码判断空满的时候会有较多位电平的同时跳转,容易产生亚稳态现象,而格雷码相邻码组之间只有一位码元发生变化,故可以有效避免这个问题,此时及时产生相邻码元之间电平不跳转的现象,也不会产生危害(最多是让空标志在FIFO不是真正空的时候产生,而不会出现空读的情形)。防止亚稳态带来的危害。
采用双寄存器同步方法判断空满标志会使得电路额外消耗两个时钟周期,从而会使得空满信号的判断有所延迟,这种延迟会有危害吗?
- 读空标志的判断:现将写指针通过读时钟信号同步到读时钟域,然后将同步后的写指针同读指针比较。如果此时读时钟快,写时钟慢,同步消耗了两个时钟周期,使得同步后的读指针落后于当前的实际的读指针,从而使得满标志会提前产生,这样为FIFO满预留了一定的容限空间,只有稍微影响一下FIFO资源利用,对实际功能没有影响;如果读时钟慢,写时钟快,会造成同步的写指针不是当前时刻的写指针或者说会漏掉一部分写指针,使得FIFO判断是否空的时候,实际并不是空,同样为FIFO带来了一定的空容限。
- 写满标志的判断:现将读时钟同步到写时钟域,然后将同步后的读时钟和写时钟进行比较
1.顶层模块fifo:例化各个子模块
//顶层模块 实例化各个子模块
module fifo
#(
parameter DSIZE = 8, //读写数据位宽均设置为8位
parameter ASIZE = 4 // 存储地址位宽设置
)
(
output [DSIZE-1:0] rdata,
output wfull,
output rempty,
input [DSIZE-1:0] wdata,
input winc, wclk, wrst_n,
input rinc, rclk, rrst_n
);
wire [ASIZE-1:0] waddr, raddr;
wire [ASIZE:0] wptr, rptr, wq2_rptr, rq2_wptr;// 内部线网
// synchronize the read pointer into the write-clock domain
sync_r2w sync_r2w
(
.wq2_rptr (wq2_rptr),
.rptr (rptr ),
.wclk (wclk ),
.wrst_n (wrst_n )
);
// synchronize the write pointer into the read-clock domain
sync_w2r sync_w2r
(
.rq2_wptr(rq2_wptr),
.wptr(wptr),
.rclk(rclk),
.rrst_n(rrst_n)
);
//this is the FIFO memory buffer that is accessed by both the write and read clock domains.
//This buffer is most likely an instantiated, synchronous dual-port RAM.
//Other memory styles can be adapted to function as the FIFO buffer.
fifomem
#(DSIZE, ASIZE)
fifomem
(
.rdata(rdata),
.wdata(wdata),
.waddr(waddr),
.raddr(raddr),
.wclken(winc),
.wfull(wfull),
.wclk(wclk)
);
//this module is completely synchronous to the read-clock domain and contains the FIFO read pointer and empty-flag logic.
rptr_empty
#(ASIZE)
rptr_empty
(
.rempty(rempty),
.raddr(raddr),
.rptr(rptr),
.rq2_wptr(rq2_wptr),
.rinc(rinc),
.rclk(rclk),
.rrst_n(rrst_n)
);
//this module is completely synchronous to the write-clock domain and contains the FIFO write pointer and full-flag logic
wptr_full
#(ASIZE)
wptr_full
(
.wfull(wfull),
.waddr(waddr),
.wptr(wptr),
.wq2_rptr(wq2_rptr),
.winc(winc),
.wclk(wclk),
.wrst_n(wrst_n)
);
endmodule
2.时钟域同步模块sync_r2w:读指针同步到写时钟域wclk
// 采用两级寄存器同步读指针到写时钟域
module sync_r2w
#(
parameter ADDRSIZE = 4
)
(
output reg [ADDRSIZE:0] wq2_rptr, //读指针同步到写时钟域
input [ADDRSIZE:0] rptr, // 格雷码形式的读指针,格雷码的好处后面会细说
input wclk, wrst_n
);
reg [ADDRSIZE:0] wq1_rptr;
always @(posedge wclk or negedge wrst_n)
if (!wrst_n) begin
wq1_rptr <= 0;
wq2_rptr <= 0;
end
else begin
wq1_rptr<= rptr;
wq2_rptr<=wq1_rptr;
end
endmodule
原理图
3.时钟域同步模块sync_w2r:写指针同步到读时钟域rclk
//采用两级寄存器同步写指针到读时钟域
module sync_w2r
#(parameter ADDRSIZE = 4)
(
output reg [ADDRSIZE:0] rq2_wptr, //写指针同步到读时钟域
input [ADDRSIZE:0] wptr, //格雷码形式的写指针
input rclk, rrst_n
);
reg [ADDRSIZE:0] rq1_wptr;
always @(posedge rclk or negedge rrst_n)
if (!rrst_n)begin
rq1_wptr <= 0;
rq2_wptr <= 0;
end
else begin
rq1_wptr <= wptr;
rq2_wptr <= rq1_wptr;
end
endmodule
RTL原理图
4.存储模块
//存储模块
module fifomem
#(
parameter DATASIZE = 8, // Memory data word width
parameter ADDRSIZE = 4 // 深度为8即地址为3位即可,这里多定义一位的原因是用来判断是空还是满,详细在后文讲到
) // Number of mem address bits
(
output [DATASIZE-1:0] rdata,
input [DATASIZE-1:0] wdata,
input [ADDRSIZE-1:0] waddr, raddr,
input wclken, wfull, wclk
);
////////////////////////////////这部分没用到,可以单独写一个模块来调用//////////////
`ifdef RAM //可以调用一个RAM IP核
// instantiation of a vendor's dual-port RAM
my_ram mem
(
.dout(rdata),
.din(wdata),
.waddr(waddr),
.raddr(raddr),
.wclken(wclken),
.wclken_n(wfull),
.clk(wclk)
);
//////////////////////////这部分没用到,可以单独写一个模块来调用//////////////////
`else //用数组生成存储体
// RTL Verilog memory model
localparam DEPTH = 1<<ADDRSIZE; // 左移相当于乘法,2^4 将1左移4位
reg [DATASIZE-1:0] mem [0:DEPTH-1]; //生成2^4个位宽位8的数组
assign rdata = mem[raddr];
always @(posedge wclk) //当写使能有效且还未写满的时候将数据写入存储实体中,注意这里是与wclk同步的
if (wclken && !wfull)
mem[waddr] <= wdata;
`endif
endmodule
原理图
5. rptr_empty模块:产生rempty和raddr信号
//产生empty信号和raddar信号的模块
module rptr_empty
#(
parameter ADDRSIZE = 4
)
(
output reg rempty,
output [ADDRSIZE-1:0] raddr, //二进制形式的读指针
output reg [ADDRSIZE :0] rptr, //格雷码形式的读指针
input [ADDRSIZE :0] rq2_wptr, //同步后的写指针 同步到读时钟域
input rinc, rclk, rrst_n
);
reg [ADDRSIZE:0] rbin;
wire [ADDRSIZE:0] rgraynext, rbinnext;
// GRAYSTYLE2 pointer
//将二进制的读指针与格雷码进制的读指针同步
always @(posedge rclk or negedge rrst_n)
if (!rrst_n) begin
rbin <= 0;
rptr <= 0;
end
else begin
rbin<=rbinnext; //直接作为存储实体的地址
rptr<=rgraynext;//输出到 sync_r2w.v模块,被同步到 wrclk 时钟域
end
// Memory read-address pointer (okay to use binary to address memory)
assign raddr = rbin[ADDRSIZE-1:0]; //直接作为存储实体的地址,比如连接到RAM存储实体的读地址端。
assign rbinnext = rbin + (rinc & ~rempty); //不空且有读请求的时候读指针加1,//否则输出原先地址的数据waq
assign rgraynext = (rbinnext>>1) ^ rbinnext; //将二进制的读指针转为格雷码 先右移一位然后与原二进制数异或
// FIFO empty when the next rptr == synchronized wptr or on reset
assign rempty_val = (rgraynext == rq2_wptr); //当读指针等于同步后的写指针,则为空。
always @(posedge rclk or negedge rrst_n)
if (!rrst_n)
rempty <= 1'b0;
else
rempty <= rempty_val;
endmodule
RTL原理图
6.wfull和waddr信号产生的模块
//产生写满信号(wptr_full)以及写地址的逻辑部分
module wptr_full
#(
parameter ADDRSIZE = 4
)
(
output reg wfull,
output [ADDRSIZE-1:0] waddr,
output reg [ADDRSIZE :0] wptr,
//同步后的读指针,注意是多了一个位,用作判断是否fifo满(已经在rptr_empty模块将二进制读地址转化为格雷码形式,经过写时钟域同步后为wq2_rptr)
input [ADDRSIZE :0] wq2_rptr,
input winc, wclk, wrst_n //
);
reg [ADDRSIZE:0] wbin;
wire [ADDRSIZE:0] wgraynext, wbinnext;
// GRAYSTYLE2 pointer
always @(posedge wclk or negedge wrst_n)
if (!wrst_n)
{wbin, wptr} <= 0;
else
{wbin, wptr} <= {wbinnext, wgraynext};// wptr 被同步到sync_w2r.v读时钟域rclk
// Memory write-address pointer (okay to use binary to address memory)
assign waddr = wbin[ADDRSIZE-1:0];
assign wbinnext = wbin + (winc & ~wfull);// winc为1的时候数据才能写入对应的地址中,否则即使wdata=0,数据是没有被写入地址的!
assign wgraynext = (wbinnext>>1) ^ wbinnext; //二进制转为格雷码
//-----------------------------------------------------------------
assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]}); //当最高位和次高位不同其余位相同时则写指针超前于读指针一圈,即写满。
// assign wfull_val = ( (wgraynext[ADDRSIZE] !=wq2_rptr[ADDRSIZE])&&
// (wgraynext[ADDRSIZE-1] !=wq2_rptr[ADDRSIZE-1])&&
// (wgraynext[ADDRSIZE-2] ==wq2_rptr[ADDRSIZE-2]) );
//写满举例:假设读地址为fifo起始位置:00000(格雷码)
//当写地址由(01111)b增加1后变为(10000)b,此时要先判断存储区域是否写满,然后才会考虑是否写入数据
//转换为格雷码01000^10000=11000
//此时对比最高位和次高位可知,FIFO已满,实际上写指针和读指针此时刚好跨越一圈重合
always @(posedge wclk or negedge wrst_n)
if (!wrst_n)
wfull <= 1'b0;
else
wfull <= wfull_val;
endmodule
RTL原理图
testbench文件
`timescale 1ns /1ns
module test();
reg [7:0] wdata;
reg winc, wclk, wrst_n;
reg rinc, rclk, rrst_n;
wire [7:0] rdata;
wire wfull;
wire rempty;
fifo
u_fifo (
.rdata(rdata),
.wfull(wfull),
.rempty(rempty),
.wdata (wdata),
.winc (winc),
.wclk (wclk),
.wrst_n(wrst_n),
.rinc(rinc),
.rclk(rclk),
.rrst_n(rrst_n)
);
localparam CYCLE = 20;
localparam CYCLE1 = 40;
//时钟周期,单位为ns,可在此修改时钟周期。
//生成本地时钟50M (写时钟50M)
initial begin
wclk = 0;
forever
#(CYCLE/2)
wclk=~wclk;
end
// 读时钟25M
initial begin
rclk = 0;
forever
#(CYCLE1/2)
rclk=~rclk;
end
//产生复位信号
initial begin
wrst_n = 1;
#2;
wrst_n = 0;
#(CYCLE*3);
wrst_n = 1;
end
initial begin
rrst_n = 1;
#2;
rrst_n = 0;
#(CYCLE*3);
rrst_n = 1;
end
always @(posedge wclk or negedge wrst_n)begin
if(wrst_n==1'b0)begin
winc <= 0;
// rinc <= 0;
end
else begin
winc <= $random;
$display ("winc=%h", winc);
// rinc <= $random;
// $display ("winc=%h", winc);
end
end
always @(posedge rclk or negedge rrst_n)begin
if(rrst_n==1'b0)begin
rinc <= 0;
end
else begin
rinc <= $random;
$display ("rinc=%h", rinc);
end
end
always@(*)begin
if(winc == 1)
wdata= $random ;
else
wdata = 0;
end
endmodule
仿真图:
原理图:
跨时钟域之异步FIFO的更多相关文章
- 异步FIFO跨时钟域亚稳态如何解决?
跨时钟域的问题:前一篇已经提到要通过比较读写指针来判断产生读空和写满信号,但是读指针是属于读时钟域的,写指针是属于写时钟域的,而异步FIFO的读写时钟域不同,是异步的,要是将读时钟域的读指针与写时钟域 ...
- FPGA跨时钟域处理方法
文章主要是基于学习后的总结. 1. 时钟域 假如设计中所有的触发器都使用一个全局网络,比如FPGA的主时钟输入,那么我们说这个设计只有一个时钟域.假如设计有两个输入时钟,如图1所示,一个时钟给接口1使 ...
- FPGA中亚稳态相关问题及跨时钟域处理
前言 触发器输入端口的数据在时间窗口内发生变化,会导致时序违例.触发器的输出在一段时间内徘徊在一个中间电平,既不是0也不是1.这段时间称为决断时间(resolution time).经过resolut ...
- FPGA基础学习(3) -- 跨时钟域处理方法
文章主要是基于学习后的总结. 1. 时钟域 假如设计中所有的触发器都使用一个全局网络,比如FPGA的主时钟输入,那么我们说这个设计只有一个时钟域.假如设计有两个输入时钟,如图1所示,一个时钟给接口1使 ...
- 基于FPGA的跨时钟域信号处理——专用握手信号
在逻辑设计领域,只涉及单个时钟域的设计并不多.尤其对于一些复杂的应用,FPGA往往需要和多个时钟域的信号进行通信.异步时钟域所涉及的两个时钟之间可能存在相位差,也可能没有任何频率关系,即通常所说的不同 ...
- cdc跨时钟域处理-结绳握手法
参考文档 https://blog.csdn.net/u011412586/article/details/10009761 前言 对于信号需要跨时钟域处理而言,最重要的就是确保数据能稳定的传送到采样 ...
- 跨时钟域设计【一】——Slow to fast clock domain
跨时钟域设计是FPGA设计中经常遇到的问题,特别是对Trigger信号进行同步设计,往往需要把慢时钟域的Trigger信号同步到快时钟域下,下面是我工作中用到的慢时钟域到快时钟域的Verilog HD ...
- 跨时钟域设计【二】——Fast to slow clock domain
跨时钟域设计中,对快时钟域的Trigger信号同步到慢时钟域,可以采用上面的电路实现,Verilog HDL设计如下: // Trigger signal sync, Fast clock dom ...
- 【iCore、iCore2、iBoard例程】【异步FIFO跨时钟域通信(通过ARM 读FPGA FIFO)】
欢迎访问电子工程师学堂,以便了解更多内容:http://www.eeschool.org 一.本实验基于iCore2 完成,通过简单改动,即可用在 iCore 核心板.iBoard 电子学堂上. iC ...
- FPGA跨时钟域握手信号的结构
FPGA跨时钟数据传输,是我们经常遇到的问题的,下面给出一种跨时钟握手操作的电路结构.先上图 先对与其他人的结构,这个结构最大的特点是使用 req 从低到高或者高到低的变化 来表示DIN数据有效并开始 ...
随机推荐
- 前端JavaScript深拷贝的三种方法,看了不后悔!!!
深拷⻉ 深拷⻉开辟⼀个新的栈,两个对象属完成相同,但是对应两个不同的地址,修改⼀个对象的属性,不会 改变另⼀个对象的属性 常⻅的深拷⻉⽅式有: _.cloneDeep() jQuery.extend( ...
- python虚拟环境解决不能执行脚本的问题
1 安装虚拟环境 pip install virtualenv 2 创建虚拟文件夹 mkdir .venvs 3.设置虚拟目录 virtualenv --system-site-packages .v ...
- vue+elementUI表格实现自定义右键菜单
组件代码: <template> <div id="contextmenu" class="contextmenu open"> < ...
- virtualenv指定使用本地某个版本python
virtualenv -p D:\env\py37_1\Scripts\python3.exe time01 红色的地方是 你本地python解释器的安装路径,后面黄色部分是创建的虚拟环境的名称. 另 ...
- python通过接口执行shell命令
需求:通过网站url方式直接执行服务器的shell命令 实现: 1.安装依赖 pip3 install falsk 2.python脚本 [root@localhost tmp]# more fals ...
- c++练习270题:三角形个数
*270题 原题传送门:http://oj.tfls.net/p/270 题解: #include<bits/stdc++.h>using namespace std;int a,b,c, ...
- leecode72. 编辑距离
72. 编辑距离 给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 . 你可以对一个单词进行如下三种操作: 插入一个字符 删除一个字符 替换一个 ...
- Selenium无浏览器页面执行测试用例—静默执行
在执行WebUI自动化用例的时候,经常需要不打开浏览器执行自动化测试,这时就需要用到浏览器的静默执行.浏览器静默执行要点:1.定义Chrome的选项,两种方式任选 chrome_options = w ...
- win10系统每次重启桌面图标排列都会改动怎么办
鼠标右键点击个性化>主题>找到桌面图标设置>把计算机 回收站 用户的文件 控制面板 网络等前面框复选框全部勾选掉,然后在桌面新建文件夹把桌面所有的图标剪切到新建文件里面,然后把新建文 ...
- 创建一个httpserver、httpclient
最近因为要和java进行通信.约定好使用http协议进行消息传递.在网上找了很久server编写发现有个博主写的很详细,因此把东西记录下来以便下次使用.这是原博主网址:https://blog.csd ...