异步FIFO总结+Verilog实现
异步FIFO简介
异步FIFO(First In First Out)可以很好解决多比特数据跨时钟域的数据传输与同步问题。异步FIFO的作用就像一个蓄水池,用于调节上下游水量。
FIFO
FIFO是一种先进先出的存储结构,其与普通存储器的区别是,FIFO没有读写地址总线,读写简单,但相应缺点是无法控制读写的位置,只能由内部的读写指针自动加,顺序读写数据。FIFO示意图如下:
图1
如图1所示,输入信号有读写时钟、读写复位信号、读写使能信号、写数据;输出信号有空满信号、读数据。
异步时序电路
异步时序逻辑指电路时序逻辑没有接在统一的时钟脉冲上,或者电路中无时钟脉冲,如SR锁存器构成的时序电路,电路中各存储单元的状态不是同时发生的。
图2
如上图所示,常见的异步时序电路有不同的时钟。
相应地,同步时序电路指电路中所有受时钟控制的单元都接在统一的全局时钟源上,存储电路状态的转换在同一时钟源的同一脉冲边沿下同步进行。
异步时序电路触发器状态刷新不同步,信号延迟可能会累积导致输出结果异常,应当避免。
目前ASIC与FPGA的设计中,通常是全局异步,局部同步的设计方法,但需注意异步信号与同步电路的交互问题。
常用的异步时序逻辑同步的方法有:
- 单比特信号同步:结绳法
- 多比特信号同步:SRAM、异步FIFO
注意,时钟域是否相同针对的是时钟源点,如果不同时钟都是从同一个PLL生成,则这些时钟相位和倍数都可控,认为是同步时钟;若不同时钟是由不同PLL生成,则即使这些时钟为相同频率,也认为是异步时钟,因为这些时钟间的相位关系无法确定。
亚稳态
亚稳态指触发器无法在某个规定的时间内到达一个可确定的状态。当一个触发器进入亚稳态时,无法确定该单元的输出电平,也无法确定其何时能稳定在正确的电平。在此期间,触发器输出的一些不确定电平,可能沿着信号通道上的各个触发器传递下去。
设计详解
思路
如图1,
- 异步FIFO是一个存储结构,并且可读可写,因此需要一个双口RAM;
- 其次异步FIFO读写时钟域分别控制读写地址,因此需要读写地址生成模块;
- 异步FIFO需要判断是否已写满,或已读空,且读写时钟为异步时钟,因此需要同步逻辑与判断空满逻辑
图3
如图3所示,DualRAM
为双口RAM模块,sync_w2r
模块用于控制写地址,判断写满信号,sync_r2w
模块用于控制读地址,判断读空信号。
细节
双口RAM
双口RAM设计较为简单,主要为生成一块memory,将写数据写入写地址对应内存空间,从读地址对应内存空间读取读数据,原理图如下:
图4
小细节,已知地址宽度ASIZE
,求FIFO深度DEPTH
,DEPTH = 2 ^ ASIZE
,可用移位实现:
DEPTH = 1 << ASIZE;
判断空满
上文提到,FIFO只能通过内部地址指针自动加,因此需要有空满判断逻辑,以免写数据溢出,读数据已空。本设计中判断空满采用的方法是比较读地址与写地址。读写时钟为异步时钟,在判断空满时需要用格雷码比较读写读写地址,因此需要对读写地址进行同步。
同步
同步使用打一拍的方法,即将待同步信号延时一个时钟周期,原理图如下:
图5
在写时钟域同步读地址,在读时钟域同步写地址。
格雷码
上述提到的打一拍的同步方法适合于单比特信号,但显然读写地址都大概率不为单比特信号。我们知道格雷码的特征为相邻格雷码间只有一位不同。将读写地址转换为格雷码即可应用打一拍的同步方法。
table { margin: auto }
Binary | Gray |
---|---|
000 | 000 |
001 | 001 |
010 | 011 |
011 | 010 |
100 | 110 |
101 | 111 |
110 | 101 |
111 | 100 |
图6
上图为三位二进制码与格雷码的转换。可以看出,二进制向格雷码转换时,格雷码最高位为二进制码最高位,格雷码次高位为二进制码最高位与次高位的异或,其余各位规律一致。
assign graynext = (binnext >> 1) ^ binnext; `
本设计为地址位宽为4位,利用格雷码判断空满时需扩展1位.
判断空
判断FIFO是否为空,在读时钟域同步转换为格雷码的写地址,与转换为格雷码的写地址比较,如果读写地址的格雷码完全相等,则说明FIFO已空。
//判断空信号
assign empty = (graynext == rq2_wptr);
always @(posedge rclk or negedge rrst_n) begin
if (!rrst_n) begin
rempty <= 1'b1;
end
else begin
rempty <= empty;
end
end
判断满
判断FIFO是否已满也是比较读写地址。在二进制地址中,FIFO已满时,读写地址相等,与已空时一样,无法判断。上述说到判断空满时地址需扩展一位,实际上如果已写满,说明写比都快,那写地址比读地址多走一轮,此时扩展的最高位不相同,如4'b0000
与4'b1000
,最高位扩展位,4'b1000
说明比4'b0000
多走一轮。转换为格雷码分别为4'b0000
与4'b1100
。可知FIFO已满时,读写地址格雷码最高位与次高位相反,其余位相同。
//判断满信号
assign full = (graynext == {~wq2_rptr[ASIZE: ASIZE - 1],
wq2_rptr[ASIZE - 2: 0]});
always @(posedge wclk or negedge wrst_n) begin
if (!wrst_n) begin
wfull <= 1'b0;
end
else begin
wfull <= full;
end
end
控制读写地址
控制读写地址时,若读写使能为0,则不读写,读写地址不变;若读写使能为1,则读写地址加1。
小技巧,可将读写地址加读写使能信号,则可巧妙实现上述功能。
assign binnext = !wfull? (wbin + write_en): wbin;
assign binnext = !rempty? (rbin + read_en): rbin;
仿真
VCS仿真结果如下
图7
完整代码
top.v
点击查看代码
`timescale 1ns / 1ns
module Top #(
parameter WIDTH = 8,
parameter ASIZE = 4 //地址位宽
) (
input wire [WIDTH - 1: 0] data_w,
input wire wclk,
input wire rclk,
input wire wrst_n,
input wire rrst_n,
input wire write_en,
input wire read_en,
output wire wfull,
output wire rempty,
output wire [WIDTH - 1: 0] data_r
);
wire [ASIZE - 1: 0] waddr, raddr;
wire [ASIZE: 0] wq2_rptr, rq2_wptr;
DualRAM #(WIDTH, ASIZE) u1(
.wclk(wclk),
.data_w(data_w),
.write_en(write_en),
.addr_w(waddr),
.addr_r(raddr),
.data_r(data_r)
);
sync_r2w #(WIDTH, ASIZE) u2(
.rclk(rclk),
.wclk(wclk),
.wrst_n(wrst_n),
.rrst_n(rrst_n),
.read_en(read_en),
.wq2_rptr(wq2_rptr),
.raddr(raddr),
.rq2_wptr(rq2_wptr),
.rempty(rempty)
);
sync_w2r #(WIDTH, ASIZE) u3(
.rclk(rclk),
.wclk(wclk),
.wrst_n(wrst_n),
.rrst_n(rrst_n),
.write_en(write_en),
.wq2_rptr(wq2_rptr),
.waddr(waddr),
.rq2_wptr(rq2_wptr),
.wfull(wfull)
);
endmodule
sync.v
点击查看代码
`timescale 1ns / 1ns
module sync #(
parameter WIDTH = 8,
parameter ASIZE = 4
) (
input wire clk,
input wire rst_n,
input wire [ASIZE: 0] ptr,
output reg [ASIZE: 0] q2ptr
);
reg [ASIZE: 0] q1ptr;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
{q2ptr, q1ptr} <= 0;
end
else begin
{q2ptr, q1ptr} <= {q1ptr, ptr};
end
end
endmodule
sync_w2r.v
点击查看代码
`timescale 1ns / 1ns
module sync_w2r #(
parameter WIDTH = 8,
parameter ASIZE = 4 //地址位宽
) (
input wire rclk,
input wire wclk,
input wire wrst_n,
input wire rrst_n,
input wire write_en,
input wire [ASIZE: 0] wq2_rptr,
output wire [ASIZE: 0] rq2_wptr,
output wire [ASIZE - 1: 0] waddr,
output reg wfull
);
wire [ASIZE: 0] graynext, binnext;
reg [ASIZE: 0] wbin, wptr;
// 写地址控制
always @(posedge wclk or negedge wrst_n) begin
if (!wrst_n) begin
wbin <= 'd0;
wptr <= 'd0;
end
else begin
wbin <= binnext;
wptr <= graynext;
end
end
assign binnext = !wfull? (wbin + write_en): wbin;
assign graynext = (binnext >> 1) ^ binnext;
assign waddr = wbin[ASIZE - 1: 0];
//同步写地址
sync #(WIDTH, ASIZE) u_sync(
.clk(rclk),
.rst_n(rrst_n),
.ptr(wptr),
.q2ptr(rq2_wptr)
);
//判断满信号
assign full = (graynext == {~wq2_rptr[ASIZE: ASIZE - 1], wq2_rptr[ASIZE - 2: 0]});
always @(posedge wclk or negedge wrst_n) begin
if (!wrst_n) begin
wfull <= 1'b0;
end
else begin
wfull <= full;
end
end
endmodule
sync_r2w.v
点击查看代码
`timescale 1ns / 1ns
module sync_r2w #(
parameter WIDTH = 8,
parameter ASIZE = 4 //地址位宽
) (
input wire rclk,
input wire wclk,
input wire wrst_n,
input wire rrst_n,
input wire read_en,
input wire [ASIZE: 0] rq2_wptr,
output wire [ASIZE: 0] wq2_rptr,
output wire [ASIZE - 1: 0] raddr,
output reg rempty
);
reg [ASIZE: 0] rbin, rptr;
wire [ASIZE: 0] graynext, binnext;
// 读地址逻辑
always @(posedge rclk or negedge rrst_n) begin
if (!rrst_n) begin
rbin <= 'd0;
rptr <= 'd0;
end
else begin
rbin <= binnext;
rptr <= graynext;
end
end
assign binnext = !rempty? (rbin + read_en): rbin;
assign graynext = (binnext >> 1) ^ binnext;
assign raddr = rbin[ASIZE - 1: 0];
// 同步读地址
sync #(WIDTH, ASIZE) u_sync(
.clk(wclk),
.rst_n(wrst_n),
.ptr(rptr),
.q2ptr(wq2_rptr)
);
// 判断空信号
assign empty = (graynext == rq2_wptr);
always @(posedge rclk or negedge rrst_n) begin
if (!rrst_n) begin
rempty <= 1'b1;
end
else begin
rempty <= empty;
end
end
endmodule
DualRAM.v
点击查看代码
`timescale 1ns / 1ns
module DualRAM #(
parameter WIDTH = 8,
parameter ASIZE = 4 //地址位宽
) (
input wire [WIDTH - 1: 0] data_w,
input wire wclk,
input wire write_en,
input wire [ASIZE - 1: 0] addr_w,
input wire [ASIZE - 1: 0] addr_r,
output wire [WIDTH - 1: 0] data_r
);
parameter DEPTH = 1 << ASIZE;
reg [WIDTH - 1: 0] mem [DEPTH - 1: 0];
reg [ASIZE - 1: 0] raddr_r;
always @(posedge wclk) begin
if (write_en) begin
mem[addr_w] <= data_w;
end
end
assign data_r = mem[addr_r];
endmodule
top_tb.sv
点击查看代码
`timescale 1ns / 1ns
module top_tb #(
parameter WIDTH = 8,
parameter ASIZE = 4 //地址位宽
) (
);
reg [WIDTH - 1: 0] data_w;
reg wclk;
reg rclk;
reg wrst_n;
reg rrst_n;
reg write_en;
reg read_en;
wire wfull;
wire rempty;
wire [WIDTH - 1: 0] data_r;
Top u1(
.data_w(data_w),
.wclk(wclk),
.rclk(rclk),
.wrst_n(wrst_n),
.rrst_n(rrst_n),
.write_en(write_en),
.read_en(read_en),
.wfull(wfull),
.rempty(rempty),
.data_r(data_r)
);
initial begin
data_w = 1'b0;
wclk = 1'b0;
rclk = 1'b0;
wrst_n = 1'b1;
rrst_n = 1'b1;
write_en = 1'b1;
read_en = 1'b1;
#10 wrst_n = 1'b0;
rrst_n = 1'b0;
#10 wrst_n = 1'b1;
rrst_n = 1'b1;
#400 read_en = 1'b0;
end
initial begin
forever begin
#10 wclk = ~wclk;
end
end
initial begin
forever begin
#7 rclk = ~rclk;
end
end
reg [7: 0] i;
reg [7: 0] mem [15: 0];
initial begin
for (i = 8'd0; i < 8'd16; i = i + 8'd1) begin
mem[i] = i;
end
end
reg [7: 0] j = 8'd0;
always @(posedge wclk or negedge wrst_n) begin
if (!wrst_n) begin
j = 8'd0;
end
if (j > 8'd15) begin
j = 8'd0;
end
else begin
data_w = mem[j];
j = j + 8'd1;
end
end
endmodule
参考链接
Verilog描述——异步时序电路与同步时序电路浅析_我要变强Wow-CSDN博客_同步时序电路和异步时序电路差异
同步时序电路和异步时序电路_ltfysa的博客-CSDN博客_同步时序电路和异步时序电路差异
芯动力——硬件加速设计方法_中国大学MOOC(慕课) (icourse163.org)
异步FIFO设计 - 知乎 (zhihu.com)
异步FIFO总结 - 乔_木 - 博客园 (cnblogs.com)
异步FIFO总结+Verilog实现的更多相关文章
- 异步fifo的Verilog实现
一.分析 由于是异步FIFO的设计,读写时钟不一样,在产生读空信号和写满信号时,会涉及到跨时钟域的问题,如何解决? 跨时钟域的问题:由于读指针是属于读时钟域的,写指针是属于写时钟域的,而异步FIFO ...
- 异步FIFO的verilog实现与简单验证(调试成功)
最近在写一个异步FIFO的时候,从网上找了许多资料,文章都写的相当不错,只是附在后面的代码都多多少少有些小错误. 于是自己写了一个调试成功的代码,放上来供大家参考. 非原创 原理参考下面: 原文 ht ...
- 异步FIFO及verilog原码
这几天看了Clifford E. Cummings的两篇大作<Simulation and Synthesis Techniques for Asynchronous FIFO Design&g ...
- 同步fifo与异步fifo
参考以下帖子: https://blog.csdn.net/hengzo/article/details/49683707 https://blog.csdn.net/Times_poem/artic ...
- 怎么用Verilog语言描述同步FIFO和异步FIFO
感谢 知乎龚大佬 打杂大佬 网上几个nice的博客(忘了是哪个了....) 前言 虽然FIFO都有IP可以使用,但理解原理还是自己写一个来得透彻. 什么是FIFO? Fist in first out ...
- Verilog设计异步FIFO
转自http://ninghechuan.com 异步FIFO有两个异步时钟,一个端口写入数据,一个端口读出数据.通常被用于数据的跨时钟域的传输. 同步FIFO的设计.一个时钟控制一个计数器,计数器增 ...
- 异步fifo的设计
本文首先对异步 FIFO 设计的重点难点进行分析 最后给出详细代码 一.FIFO简单讲解 FIFO的本质是RAM, 先进先出 重要参数:fifo深度(简单来说就是需要存多少个数据) ...
- 基于FPGA的异步FIFO设计
今天要介绍的异步FIFO,可以有不同的读写时钟,即不同的时钟域.由于异步FIFO没有外部地址端口,因此内部采用读写指针并顺序读写,即先写进FIFO的数据先读取(简称先进先出).这里的读写指针是异步的, ...
- 异步fifo的设计(FPGA)
本文首先对异步 FIFO 设计的重点难点进行分析 最后给出详细代码 一.FIFO简单讲解 FIFO的本质是RAM, 先进先出 重要参数:fifo深度(简单来说就是需要存多少个数据) ...
随机推荐
- [bzoj1303]中位数图
由于是排列,因此b一定只出现了一次,找到出现的位置并向左右扩展考虑如何判定是否满足条件,当且仅当$[左边比b小的数ls]+[右边比b小的数rs]=[左边比b大的数lb]+[右边比b大的数rb]$,暴力 ...
- 如何用webgl(three.js)搭建处理3D园区、3D楼层、3D机房管线问题(机房升级版)-第九课(一)
写在前面的话: 说点啥好呢?就讲讲前两天的小故事吧,让我确实好好反省了一下. 前两天跟朋友一次技术对话,对方问了一下Geometry与BufferGeometry的具体不同,我一下子脑袋短路,没点到重 ...
- 如何用LOTO示波器TDR方法测试电线长度?
TDR也就是时域反射(Time-domain reflectometer),它可以通过观察导线中反射回来的电信号波形对导线长度进行测量,或者对传输导线的阻抗特性进行分析评估. 我们经常会碰到的TDR的 ...
- 接上篇:Git Worktree 高级使用,这样清爽多了
前言 上一篇文章 Git Worktree 大法真香 带大家了解了 git worktree 是如何帮助我同时在多个分支工作,并且互不影响的.但是创建 worktree 的目录位置不是在当前项目下,总 ...
- vue 3 学习笔记 (八)——provide 和 inject 用法及原理
在父子组件传递数据时,通常使用的是 props 和 emit,父传子时,使用的是 props,如果是父组件传孙组件时,就需要先传给子组件,子组件再传给孙组件,如果多个子组件或多个孙组件使用时,就需要传 ...
- js数组常用添加方法有两种
//头部 //this.list.unshift({name:this.itemName,date:new Date()}); //尾部 this.list.p ...
- 钓鱼小技巧-XLM
前言 XLM钓鱼不是一项新的技术,自从公开以后,网上有很多对其的分析文章,这里仅仅做一个分享和摸索记录.文章中有问题的地方还请指出. 一个简单的例子 新建一个excel表格,右键选择表,选择插入 插入 ...
- 一些 tips
在本博客中,一般写题解的题都是我认为比较有价值的题,然而我还做过一些有一定价值,但并没有达到值得写一篇题解的程度,故将这些题目总结出的套路用一句话概括在这里: 当然如果看到我太久不更请在评论区里催我一 ...
- 洛谷 P6071 『MdOI R1』Treequery(LCA+线段树+主席树)
题目链接 题意:给出一棵树,有边权,\(m\) 次询问,每次给出三个数 \(p,l,r\),求边集 \(\bigcap\limits_{i=l}^rE(p,i)\) 中所有边的权值和. 其中 \(E( ...
- CentOS7忘记root密码如何重置
1.重启服务器 2.修改启动文件 3.修改密码 4.重启,测试 ① 重启服务器,按"e"键进入修改系统开机项界面 ② 修改启动文件 "ro" -> ...