加载中…
个人资料
  • 博客等级:
  • 博客积分:
  • 博客访问:
  • 关注人气:
  • 获赠金笔:0支
  • 赠出金笔:0支
  • 荣誉徽章:
正文 字体大小:

FPGA学习笔记--11--异步FIFO的实现

(2017-01-06 20:32:42)
分类: RAM/ROM/FIFO
2017年1月4日20:21:56 使用双口ram实现异步FIFO

1、FIFO的地址可以理解为实一个环形的存储器0-最大-0-最大
2、写满--写数据指针追上读数据指针。读空--读数据指针追上写数据指针。--如何区分两者??
例如:8个存储深度的存储器地址二进制码:(多加1位用于区分写满读空,相应的地址增加1倍)
0 = 4'b0_000
4'b0_001
4'b0_010
4'b0_011
4'b0_100
4'b0_101
4'b0_110
4'b0_111---r---0_100(格雷码)
4'b1_000
4'b1_001
4'b1_010
4'b1_011
4'b1_100
4'b1_101
4'b1_110
4'b1_111---w---1_000(格雷码)---满            
            
写满:assign w_full = ({r_addr[2:0] == w_addr[2:0]} && r_addr[3:2] == {~w_addr[3:2]}); //比较的是格雷码
读空:assign r_empty = (r_addr[3:0] == w_addr[3:0]);
读写地址完全相等的时候,为读空;当读写地址低三位完全相等,最高位相反的时候,为写地址读完一圈(写地址大于读地址整个存储深度大小)又赶上读地址,为写满。---如何避免亚稳态产生??--通过编码地址解决--格雷码
3、fifo中读写地址分别在读写时钟域产生,写地址要给到读时钟域产生读标志并且读写指针分别要给到对方的时钟域,这就存在异步时钟域的信号转换。
4、格雷码对FIFO进行地址编码:相邻两个地址之间只有1bit的不同。十进制数 右移1位->异或原来的十进制数本身 得到这个十进制数的格雷码编码。 
使用格雷码进行地址编码的好处:由于相邻两个地址数据只有1bit数据不同,那么相邻两个地址之间的跳变只有1bit数据变化,这等同于打两拍可以降低亚稳态产生的特性。
5、异步FIFO的读写时钟是可以不同频不同相的。
6、逻辑框图中的waddr是使用二进制编码的4bit地址(7个FIFO深度),wptr是使用4bit格雷码编码的地址指针
7、格雷码计数器:产生格雷码地址指针。框图中的inc--FIFO的两个输入控制信号,也就是读/写使能。格雷码计数器的图中,最终输出的二进制地址和格雷码地址指针是同步的,都只通过了一级寄存器打拍。两个地址(二进制地址和格雷码地址指针)产生后,一个(二进制地址)传给ram作为读/写的地址,另一个(格雷码地址指针)传给另一个时钟域进行比较来产生空 和 满 标志
8、ram可不设置读使能信号,给地址出数据即可
9、模块例化的时候,模块的所有接口一定要与wire类型变量相连

//-------------顶层模块代码-ex_fifo.v---------------------------------------
module ex_fifo(
input wire w_clk,
input wire r_clk,
input wire rst_n,
input wire w_en,
input wire [7:0] w_data,
output wire w_full,
input wire r_en,
output wire r_empty,
output wire [7:0] r_data
);
wire [8:0] r_gaddr;
wire [8:0] w_addr;
wire [8:0] w_gaddr;
wire [8:0] r_addr;  

w_ctrl w_ctrl_inst(
.w_clk (w_clk ), //写时钟--图中wclk
.rst_n (rst_n ), //复位
.w_en (w_en ), //写使能--图中winc
.r_gaddr (r_gaddr), //读时钟域过来的格雷码读地址指针--相当于图Verilog实现异步FIFO的逻辑图中的rptr
.w_full (w_full ), //写满标志--直接在always块中进行赋值的,使用reg类型
.w_addr (w_addr ), //256深度的FIFO的写二进制地址
.w_gaddr (w_gaddr) //写FIFO地址格雷码编码
);

r_ctrl r_ctrl_inst(
.r_clk (r_clk ), //读时钟
.rst_n (rst_n ),
.r_en (r_en ), //ram读使能--图中rinc
.w_gaddr (w_gaddr), //写时钟域的写地址格雷码指针
.r_empty (r_empty), //读空标志
.r_addr (r_addr ), //输出到ram的二进制码的读地址--图中raddr
.r_gaddr (r_gaddr)  //输出的读地址格雷码地址--图中rptr
);

fifomem fifomem_inst(
.w_clk (w_clk ),
.r_clk (r_clk ),
.w_en (w_en ), //from FIFO的写控制模块
.w_full (w_full ), //来自于FIFO的写控制模块w_ctrl
.w_data (w_data ), //from 外部数据源
.w_addr (w_addr ), //from w_ctrl模块
.r_empty (r_empty), //from r_ctrl模块
.r_addr (r_addr ), //from r_ctrl模块
.r_data (r_data )//读数据是从内部ram中读取
);              

endmodule 

//---------------------------r_ctrl.v--------------------------------------------------------
module r_ctrl(
input wire r_clk, //读时钟
input wire rst_n,
input wire r_en, //ram读使能--图中rinc
input wire [8:0] w_gaddr, //写时钟域的写地址格雷码指针
output reg r_empty, //读空标志
output wire [8:0] r_addr, //输出到ram的二进制码的读地址--图中raddr
output wire [8:0] r_gaddr //输出的读地址格雷码地址--图中rptr
);
reg [8:0] addr;
reg [8:0] gaddr;
wire [8:0] addr_wire;
wire [8:0] gaddr_wire;
reg [8:0] w_gaddr_d1;
reg [8:0] w_gaddr_d2;
//输入的读指针打两拍--异步时钟域同步
always @ (posedge r_clk or negedge rst_n)
if(rst_n == 18'd0)
{w_gaddr_d2, w_gaddr_d1} <= 18'd0;
else 
{w_gaddr_d2, w_gaddr_d1} <= {w_gaddr_d1, w_gaddr};

//二进制读地址
assign r_addr = addr;
always @ (posedge r_clk or negedge rst_n)
if(rst_n == 1'b0)
addr <= 9'd0;
else
addr <= addr_wire;

assign addr_wire = addr + ((~r_empty) & r_en);

//格雷码的读地址
assign r_gaddr = gaddr;
assign gaddr_wire = (addr_wire >> 1) ^ addr_wire;

always @ (posedge r_clk or negedge rst_n)
if(rst_n == 1'b0)
gaddr <= 9'd0;
else 
gaddr <= gaddr_wire;

//读空标志的产生
always @ (posedge r_clk or negedge rst_n)
if(rst_n == 1'b0)
r_empty <= 1'b1;
else if(gaddr_wire == w_gaddr_d2)
r_empty <= 1'b1;
else 
r_empty <= 1'b0;

endmodule 
//---------------w_ctrl.v-----------------------------------------------
module w_ctrl(
input wire w_clk, //写时钟--图中wclk
input wire rst_n, //复位
input wire w_en, //写使能--图中winc
input wire [8:0] r_gaddr, //读时钟域过来的格雷码读地址指针--相当于图Verilog实现异步FIFO的逻辑图中的rptr
output reg w_full, //写满标志--直接在always块中进行赋值的,使用reg类型
output wire [8:0] w_addr, //256深度的FIFO的写二进制地址
output wire [8:0] w_gaddr //写FIFO地址格雷码编码
);
reg [8:0] addr; //addr是格雷码计数器图中Binary reg的输出q
reg [8:0] gaddr; //对应格雷码计数器图中Gray reg 的q,gray reg的输入为wire类型,输出为reg类型 
wire [8:0] addr_wire; //addr_wire是格雷码计数器格雷码计数器图中Binary reg后的输入d
wire [8:0] gaddr_wire; //对应格雷码计数器图中Gray reg 的d
reg [8:0] r_gaddr_d1;
reg [8:0] r_gaddr_d2;

 //输入的读指针打两拍
always @ (posedge w_clk or negedge rst_n)
if(rst_n == 18'd0)
{r_gaddr_d2, r_gaddr_d1} <= 18'd0;
else 
{r_gaddr_d2, r_gaddr_d1} <= {r_gaddr_d1, r_gaddr};

//产生写ram的二进制地址指针 
always @ (posedge w_clk or negedge rst_n)
if(rst_n == 1'b0)
addr <= 9'd0;
else 
addr <= addr_wire;
assign addr_wire = addr + ((~w_full) & w_en);  //格雷码计数器图中加法器位置的组合逻辑
assign w_addr = addr;
//转换格雷码编码地址
assign gaddr_wire = (addr_wire >> 1) ^ addr_wire; //组合逻辑实现,并非addr,否则会导致gaddr多延时1拍 不同步
always @ (posedge w_clk or negedge rst_n)
if(rst_n == 1'b0)
gaddr <= 9'd0;
else 
gaddr <= gaddr_wire; //左边reg--右边wire

assign w_gaddr = gaddr;

//产生写满标志
always @ (posedge w_clk or negedge rst_n)
if(rst_n == 1'b0)
w_full <= 1'b0;
else if({~gaddr_wire[8:7],gaddr_wire[6:0]} == r_gaddr_d2) 
w_full <= 1'b1;
else 
w_full <= 1'b0;

endmodule  
//------------fifomem.v--------------------------------------------------
module fifomem(
input wire w_clk,
input wire r_clk,
input wire w_en, //from FIFO的写控制模块
input wire w_full, //来自于FIFO的写控制模块w_ctrl
input wire [7:0] w_data, //from 外部数据源
input wire [8:0] w_addr, //from w_ctrl模块
input wire r_empty, //from r_ctrl模块
output wire [8:0] r_addr, //from r_ctrl模块
output wire [7:0] r_data //读数据是从内部ram中读取
);

wire ram_w_en;

assign ram_w_en = w_en & (~w_full);

//ipcore已经改为256深度,名字没改
dp_ram_512x8 dp_ram_512x8_inst (
//写数据接口
.wrclock ( w_clk ),
.wren ( ram_w_en ),
.wraddress ( w_addr[7:0] ), // 此处为[7:0]
.data ( w_data ),
//读数据接口
.rdclock ( r_clk ),
.rdaddress ( r_addr[7:0] ), //此处为[7:0]
.q ( r_data )
);

endmodule 

//-----------------------tb.ex_fifo.v---------------------------------------------
`timescale 1ns/1ns
module tb_ex_fifo;
reg r_clk;
reg w_clk;
reg rst_n;
reg w_en;
reg [7:0] w_data;
reg r_en;     

wire w_full;
wire r_empty;
wire [7:0] r_data;

parameter CLK_P = 20;

initial begin
r_clk = 0;
w_clk = 0;
rst_n = 0;
#200;
rst_n = 1;
end 

initial begin
w_en = 0;
w_data = 0;
#300;
write_data(256);
end 

initial begin
r_en = 0;
@(posedge w_full);
#40;
read_data(256);
end 

always #(CLK_P/2) r_clk <= ~r_clk;
always #(CLK_P/2) w_clk <= ~w_clk;

ex_fifo ex_fifo_inst(
.w_clk (w_clk ),
.r_clk (r_clk ),
.rst_n (rst_n ),
.w_en (w_en ),
.w_data (w_data ),
.w_full (w_full ),
.r_en (r_en ),
.r_empty (r_empty),
.r_data     (r_data )
);

task write_data(len);
integer i;
integer len;
begin
for(i=0;i
begin
@(posedge w_clk);
w_en = 1'b1;
w_data = i;
end 
@(posedge w_clk);
w_en = 1'b0;
w_data = 0;
end 
endtask 

task read_data(len);
integer i;
integer len;
begin
for(i=0;i
begin
@(posedge r_clk);
r_en = 1'b1;
end 
@(posedge r_clk);
r_en = 1'b0;
end 
endtask 


endmodule 

//--------------------run.do----------------------------------------
quit -sim
.main clear

vlib work
vlog ./tb_ex_fifo.v
vlog ./../design/*.v
vlog ./../quartus_prj/ipcore_dir/dp_ram_512x8.v
vlog ./altera_lib/*.v

vsim -voptargs=+acc work.tb_ex_fifo

add wave tb_ex_fifo/ex_fifo_inst/*
add wave tb_ex_fifo/*

run 20us

//------------------------ppt.jpeg------------------------------
图1 Verilog实现异步FIFO的逻辑图

0

阅读 收藏 喜欢 打印举报/Report
  

新浪BLOG意见反馈留言板 欢迎批评指正

新浪简介 | About Sina | 广告服务 | 联系我们 | 招聘信息 | 网站律师 | SINA English | 产品答疑

新浪公司 版权所有