来源:EETOP BLOG 阿昏豆的个人空间 作者:lihui_140601
平行宇宙的追逐--异步FIFO控制器的设计。
在逻辑设计中,通常会用到异步FIFO,异步FIFO控制器是经典的异步信号传输的范例,通过FIFO控制器与异步双端口RAM的组合实现数据的从一个时钟域到另一个时钟域的传输。在IC设计/FPGA设计中,可以通过designware或其他IP生成器可以例化相关设计,但是如何设计一个适合系统的异步fifo控制器,不依赖其他IP,是本文所讨论的重点。
上图所示:FIFO由fifo控制器生成,FIFO控制器为逻辑生成,而RAM可使用memory complier生成。对于异步FIFO控制器,最重要控制的两个信号就是满与空,其他信号都产生机理都可以此类推, 如何产生这两个信号?下面就引出了平行宇宙的追逐故事。
平行宇宙的追逐故事,假设存在存在两个平行宇宙(PUSH和POP),平行宇宙上有两个人(W和R),这两个人都沿着平行宇宙的圈在追逐,但是有一个规则:
(1)R不能超过W。
(2)W不能超过R的对角线。
如上图所示,W和R就可以一直沿着所制定的规则进行追逐游戏,总是W先跑,R在后面追,W超不过R半圈,R不能超过W。
但是存在一个问题,W和R在两个平行宇宙中,W不知道R现在的位置,R也不知道W现在的位置,因此W和R需要知道彼此的位置,才能按照预设的规则运行。
因此W位置和R的位置需要通过一种机制传输到对面的宇宙上。这个位置分别就是W_b 和R_b; 因此,上述规则变成了,R不能超过W_b和W不能超过R_b的对角线。如果W跑到了R_b的对角线,则这种事件叫做满(FULL),而如果R跑到W_b的位置,则这种事件叫做空(empty)。W和R分别叫做读指针和写指针。因此,异步FIFO的机制就类似上述的平行宇宙的追逐关系。
上图所示:若W跑到R_b的对面,则为full,此时RAM中存储N的深度;若R赶上W_b,则RAM中存储深度为0.则为empty。
那如何在平行宇宙PUSH和POP之间传递W和R当前位置的信号?答案如下:
需要将先讲W转换成格雷码W_gray=(W^(W>>1)),然后将将W_gray码寄存两拍,然后将寄存两拍后数据W-_gray_d2转换成二进制的(W_b)(具体装换方式见代码实现),通过上述操作,则我们得到的W_b, 此时能够保证 W_b ≤W ,同样以此类推得到R_b;此时能够保证在跨时钟域操作中,因为格雷码每次只变化1bit,因此跨时钟域采样时要么变化1bit,要么不变化。因此保证W在变化时(例如W+1),要么W_b的状态保持不变(为W),要么为(W+1),而不会是其他状态。
综上:我们就可以设计一个FIFO控制器,通过比较读指针和写指针以及读指针和写指针异步同步信号(W_b,R_b),可以产生空满状态,以及其他控制状态,从而实现异步时钟域的数据的传输。
另外:如果需要数据在还没有POP读出时,就首先在dout端口上(frist word full through ),即在非空状态下,输出最早写入的值,读信号有效是,读信号与读数据同一拍。则需要RAM的读数据提前读出值来,即在非空标示的前一拍,为空时,就提前将数据读出,并且在非空时,要将读地址+1;详细见代码所示;
如果写数据位宽与读数据位宽不匹配,假如为整数关系,则也可以用上述模型来实现,只不过W和R的平行宇宙的大小按比例缩小或放大即可,其相位关系不变。
附录:fifo控制器源码:
//=================================================================
//MODULE NAME :async_fifo_ctrl
//FUNCTION : fifo fucntion
// : 1,transmit data cross asynchronouc clock domain
// : 2,generate control signal, empty/full and so on;
// : 3,support first word down through.
// AUTHOR : lihui_140601 @163.com
// modify info : v1.0 build this module,10/30/15
//=================================================================
module async_fifo_ctrl(
push_clk,
push_rst_n,
pop_clk,
pop_rst_n,
push,
push_din,
pop,
pop_dout,
push_full,
pop_empty,
push_almost_full,
pop_almost_empty,
push_progfull,
pop_progempty,
push_cnt,
pop_cnt,
ram_din,
ram_we,
ram_we_addr,
ram_dout,
ram_rd,
ram_rd_addr
);
parameter fifo_depth = 8;
parameter wr_data_w = 32;
parameter rd_data_w = 32;
parameter wr_depth_add = (wr_data_w/rd_data_w == 32)? 5:
(wr_data_w/rd_data_w == 16)? 4:
(wr_data_w/rd_data_w == 8)? 3:
(wr_data_w/rd_data_w == 4)? 2:
(wr_data_w/rd_data_w == 2)? 1:0;
parameter rd_depth_add = (rd_data_w/wr_data_w == 32)? 5:
(rd_data_w/wr_data_w == 16)? 4:
(rd_data_w/wr_data_w == 8)? 3:
(rd_data_w/wr_data_w == 4)? 2:
(rd_data_w/wr_data_w == 2)? 1:0;
parameter wr_depth = fifo_depth + wr_depth_add;
parameter rd_depth = fifo_depth + rd_depth_add;
parameter fwdt = 1; //first word down through
input push_clk;
input push_rst_n;
input pop_clk;
input pop_rst_n;
input push;
input [(wr_data_w-1):0] push_din;
input pop;
output [(rd_data_w-1):0] pop_dout;
output push_full;
output pop_empty;
output push_almost_full;
output pop_almost_empty;
output push_progfull;
output pop_progempty;
output [(wr_depth-1):0] push_cnt;
output [(rd_depth-1):0] pop_cnt;
output [(wr_data_w-1):0] ram_din;
output ram_we;
output [(wr_depth-1):0] ram_we_addr;
input [(rd_data_w-1):0] ram_dout;
output ram_rd;
output [(rd_depth-1):0] ram_rd_addr;
//internal signal define
reg push_full;
reg pop_empty;
reg push_almost_full;
reg pop_almost_empty;
reg [wr_depth:0] push_wr_cnt;
wire [wr_depth:0] push_wr_cnt_tmp;
wire [wr_depth:0] push_wr_cnt_gray;
reg [wr_depth:0] push_wr_cnt_gray_d1;
reg [wr_depth:0] push_wr_cnt_gray_d2;
wire [wr_depth:0] push_wr_cnt_sync;
reg [rd_depth:0] pop_rd_cnt;
wire [rd_depth:0] pop_rd_cnt_tmp;
wire [rd_depth:0] pop_rd_cnt_gray;
reg [rd_depth:0] pop_rd_cnt_gray_d1;
reg [rd_depth:0] pop_rd_cnt_gray_d2;
wire [rd_depth:0] pop_rd_cnt_sync;
wire [wr_depth+wr_depth_add:0] push_wr_cnt_tmp_cmp;
wire [wr_depth+wr_depth_add:0] push_wr_cnt_sync_cmp;
wire [rd_depth+rd_depth_add:0] pop_rd_cnt_tmp_cmp;
wire [rd_depth+rd_depth_add:0] pop_rd_cnt_sync_cmp;
wire [(wr_depth+wr_depth_add):0] push_cnt_tmp;
wire [(rd_depth+rd_depth_add):0] pop_cnt_tmp;
//-----------------------------------------------------------------------------
//push clk space //write to ram
//-----------------------------------------------------------------------------
assign push_wr_cnt_tmp = (push && !push_full) ? push_wr_cnt + 1'b1 : push_wr_cnt;
always@(posedge push_clk or negedge push_rst_n)
if(!push_rst_n)
push_wr_cnt <= 0;
else
push_wr_cnt <= push_wr_cnt_tmp;
assign push_wr_cnt_gray = push_wr_cnt ^ (push_wr_cnt >> 1) ;//
//-----------------------------------------------------------------------------
//pop clk space // read from ram
//-----------------------------------------------------------------------------
assign pop_rd_cnt_tmp = (pop && !pop_empty)? pop_rd_cnt + 1 : pop_rd_cnt;
always@(posedge pop_clk or negedge pop_rst_n)
if(!push_rst_n)
pop_rd_cnt <= 0;
else
pop_rd_cnt <= pop_rd_cnt_tmp;
assign pop_rd_cnt_gray = pop_rd_cnt ^ (pop_rd_cnt>>1) ;//
//-----------------------------------------------------------------------------
//binay to gray code ,
//-----------------------------------------------------------------------------
always@(posedge push_clk or negedge push_rst_n)
if(!push_rst_n) begin
pop_rd_cnt_gray_d1 <= 0;
pop_rd_cnt_gray_d2 <= 0;
end
else begin
pop_rd_cnt_gray_d1 <= pop_rd_cnt_gray;
pop_rd_cnt_gray_d2 <= pop_rd_cnt_gray_d1;
end
generate
genvar i;
assign pop_rd_cnt_sync[fifo_depth] = pop_rd_cnt_gray_d2[fifo_depth];//
for(i=fifo_depth-1;i>=0 ;i=i-1) begin: pop_gray_binay
assign pop_rd_cnt_sync[i] = pop_rd_cnt_sync[i+1] ^ pop_rd_cnt_gray_d2[i];//
end
endgenerate
always@(posedge pop_clk or negedge pop_rst_n)
if(!pop_rst_n) begin
push_wr_cnt_gray_d1 <= 0;
push_wr_cnt_gray_d2 <= 0;
end
else begin
push_wr_cnt_gray_d1 <= push_wr_cnt_gray;
push_wr_cnt_gray_d2 <= push_wr_cnt_gray_d1;
end
generate
genvar j;
assign push_wr_cnt_sync[fifo_depth] = push_wr_cnt_gray_d2[fifo_depth];//
for(j=fifo_depth-1;j>=0 ;j=j-1) begin: push_gray2binay
assign push_wr_cnt_sync[j] = push_wr_cnt_sync[j+1] ^ push_wr_cnt_gray_d2[j];//
end
endgenerate
//genrate fifo empty
always@(posedge pop_clk or negedge pop_rst_n)
if(!pop_rst_n)
pop_empty <= 1'b0;
else if({pop_rd_cnt_tmp,{rd_depth_add{1'b0}}} == {push_wr_cnt_sync,{wr_depth_add{1'b0}}})//pop read count and push write count
pop_empty <= 1'b1;
else
pop_empty <= 1'b0;
//generate push fifo full;
always@(posedge push_clk or negedge push_rst_n)
if(!push_rst_n)
push_full <= 1'b0;
else if({push_wr_cnt_tmp,{wr_depth_add{1'b0}} } == {!pop_rd_cnt_sync[fifo_depth], pop_rd_cnt_sync[(fifo_depth-1):0],{rd_depth_add{1'b0}}})
push_full <= 1'b1;
else
push_full <= 1'b0;
//genrate fifo almost empty
always@(posedge pop_clk or negedge pop_rst_n)
if(!pop_rst_n)
pop_almost_empty <= 1'b0;
else if({(pop_rd_cnt_tmp + 1),{rd_depth_add{1'b0}}} == {push_wr_cnt_sync,{wr_depth_add{1'b0}}})
pop_almost_empty <= 1'b1;
else
pop_almost_empty <= 1'b0;
//generate push almost full;
always@(posedge push_clk or negedge push_rst_n)
if(!push_rst_n)
push_almost_full <= 1'b0;
else if({(push_wr_cnt_tmp + 1),{wr_depth_add{1'b0}}} == {!pop_rd_cnt_sync[fifo_depth], pop_rd_cnt_sync[(fifo_depth-1):0],{rd_depth_add{1'b0}}})
push_almost_full <= 1'b1;
else
push_almost_full <= 1'b0;
//generate push count for push clk and pop count for pop clk, used for generating progfull and other signal
assign pop_rd_cnt_tmp_cmp = {pop_rd_cnt_tmp,{rd_depth_add{1'b0}}};//
assign push_wr_cnt_sync_cmp = {push_wr_cnt_sync,{wr_depth_add{1'b0}}};//
assign push_wr_cnt_tmp_cmp = {push_wr_cnt_tmp,{wr_depth_add{1'b0}}};//
assign pop_rd_cnt_sync_cmp = {pop_rd_cnt_sync,{rd_depth_add{1'b0}}};//
assign push_cnt_tmp = (push_wr_cnt_tmp_cmp > pop_rd_cnt_sync_cmp) ?
push_wr_cnt_tmp_cmp - pop_rd_cnt_sync_cmp :
{1'b1,push_wr_cnt_tmp_cmp} - pop_rd_cnt_sync_cmp;
assign pop_cnt_tmp = (push_wr_cnt_sync_cmp > pop_rd_cnt_tmp_cmp) ?
push_wr_cnt_sync_cmp - pop_rd_cnt_tmp_cmp :
{1'b1,push_wr_cnt_sync_cmp} - pop_rd_cnt_tmp_cmp ;
assign push_cnt = push_cnt_tmp[(wr_depth+wr_depth_add-1):wr_depth_add];
assign pop_cnt = pop_cnt_tmp[(rd_depth+rd_depth_add-1):rd_depth_add];
//-----------------------------------------------------------------------------
//ram interface
//-----------------------------------------------------------------------------
assign ram_we_addr = push_wr_cnt[(wr_depth-1):0] ;//
assign ram_we = push & !push_full ;//
assign ram_din = push_din;
assign pop_dout = ram_dout;
generate if(fwdt == 1) begin
assign ram_rd_addr = pop_rd_cnt[(rd_depth-1):0] + !pop_empty ;//
assign ram_rd = pop & !pop_empty | pop_empty;//
end
else begin
assign ram_rd_addr = pop_rd_cnt[(rd_depth-1):0] ;//
assign ram_rd = pop & !pop_empty ;//
end
endgenerate
endmodule
//以下为双端口RAM的仿真模型,将控制器与模型连接在一起,即可仿真。
module dual_ram(
we_clk,
rd_clk,
we,
rd,
we_data,
rd_data,
we_addr ,
rd_addr
);
parameter ram_depth = 8;
parameter wr_data_w = 32;
parameter rd_data_w = 32;
parameter wr_depth_add = (wr_data_w/rd_data_w == 32)? 5:
(wr_data_w/rd_data_w == 16)? 4:
(wr_data_w/rd_data_w == 8)? 3:
(wr_data_w/rd_data_w == 4)? 2:
(wr_data_w/rd_data_w == 2)? 1:0;
parameter rd_depth_add = (rd_data_w/wr_data_w == 32)? 5:
(rd_data_w/wr_data_w == 16)? 4:
(rd_data_w/wr_data_w == 8)? 3:
(rd_data_w/wr_data_w == 4)? 2:
(rd_data_w/wr_data_w == 2)? 1:0;
parameter wr_depth = ram_depth + wr_depth_add;
parameter rd_depth = ram_depth + rd_depth_add;
input we_clk;
input rd_clk;
input we;
input rd;
input [(wr_data_w-1):0] we_data;
input [(wr_depth-1):0] we_addr ;//
input [(rd_depth-1):0] rd_addr ;//
output [(rd_data_w-1):0] rd_data;
reg [(rd_data_w-1):0] rd_data;
reg [(wr_data_w-1):0] ram_reg[(1 << ram_depth) :0] ;
always@(posedge we_clk)
if(we)
ram_reg[we_addr] <= we_data;
always@(posedge rd_clk)
if(rd)
rd_data <= ram_reg[rd_addr];
endmodule
源码文件可以点击下载: fifo.txt(10.8 KB)
本视频基于Xilinx公司的Artix-7FPGA器件以及各种丰富的入门和进阶外设,提供了一些典型的工程实例,帮助读者从FPGA基础知识、逻辑设计概念
本课程为“从零开始大战FPGA”系列课程的基础篇。课程通俗易懂、逻辑性强、示例丰富,课程中尤其强调在设计过程中对“时序”和“逻辑”的把控,以及硬件描述语言与硬件电路相对应的“
课程中首先会给大家讲解在企业中一般数字电路从算法到流片这整个过程中会涉及到哪些流程,都分别使用什么工具,以及其中每个流程都分别做了
@2003-2020 中国电子顶级开发网