概述
书接上文,上一篇介绍了略简单的同步fifo,接下来开始较为复杂的异步fifo。
1.同步fifo与异步fifo的区别
当设计中只有一个时钟时,所有的寄存器否用同一个,不会出现传输速度不匹配的情况;但是当设计中存在多个时钟信号,并且在这几个时钟域中传递数据,就可能出现因时钟信号不匹配而产生数据丢失的现象,这时候需要异步fifo进行数据的缓存,保证数据能够正常的传输,因此一般异步fifo会包含一个双端口的ram.
2.异步fifo组成
图1.异步fifo
模块组成(6个):顶层模块、双口RAM、2个跨时钟域同步指针模块、空判断逻辑、满判断逻辑。
1)顶层模块 将读写时钟域的的输入输出信号列出,定义所需的线网类型。
2)设计双端口的RAM 在写时钟域下的对RAM执行写操作、在读时钟域下进行读操作
3)两个跨时钟域对指针进行同步 在同步前要对比较指针进行格雷码的转化,同步方式为将转化后的比较指针在对方时钟域打两拍。 注意:比较指针要比 指针多一位, 做高位用来判断读写指针谁走的快。
4)空逻辑判断 读写的比较指针相等则为空
5)满逻辑判断 写的指针比读的指针多走一圈,及比较指针的最高位不相等,剩余位相等。
3.注意事项
1)避免使用二进制计数器实现指针,因为当前数值进位加一时,可能会出现所有的位都会发生改变,错误率可能会因此提高。eg:0111加1会变成1000,其中有三位发生改变,因此会提升错误率。
2)针对以上问题,建议直接使用格雷码计数。
3)将指针的位宽多定义一位举个例子说明:假设要设计深度为 8 的异步FIFO,此时定义读写指针只需要 3 位(2^3=8)就够用了,但是我们在设计时将指针的位宽设计成 4 位,最高位的作用就是区分是读空还是写满,具体理论 1 如下当最高位相同,其余位相同认为是读空;当最高位不同,其余位相同认为是写满
格雷码计数器设计理念:格雷码到二进制转换器、加法器、二进制码到格雷码转化器、用于保存格雷值得寄存器
3.相应模块代码段
1)顶层模块
//************顶层模块*************************
`timescale 1 ns/ 1 ps
module asyn_fifo_top #(
parameter DATA_WIDTH = 8,
parameter ADDR_WIDTH = 4,
parameter RAM_DEPTH = 16
)
(
input WCLK,
input WRSTn,
input RCLK,
input RRSTn,
input write,
input read,
input [ DATA_WIDTH - 1 : 0 ] wdata,
output [ DATA_WIDTH - 1 : 0 ] rdata,
output full,
output empty
);
wire [ ADDR_WIDTH : 0 ] wpt;
wire [ ADDR_WIDTH : 0 ] rpt;
wire [ ADDR_WIDTH : 0 ] rp2_wpt;
wire [ ADDR_WIDTH : 0 ] wp2_rpt;
wire [ ADDR_WIDTH - 1: 0 ] waddr;
wire [ ADDR_WIDTH - 1: 0 ] raddr;
double_ram#(.DATA_WIDTH(DATA_WIDTH),
.ADDR_WIDTH(ADDR_WIDTH),
.RAM_DEPTH(RAM_DEPTH))
ram_module (.WCLK(WCLK),
.write(write),
.waddr(waddr),
.wdata(wdata),
.raddr(raddr),
.rdata(rdata));
r2w_sync #(.ADDR_WIDTH(ADDR_WIDTH))
r2w_module (.WCLK(WCLK),
.WRSTn(WRSTn),
.rpt(rpt),
.rp2_wpt(rp2_wpt));
w2r_sync #(.ADDR_WIDTH(ADDR_WIDTH))
w2r_module (.RCLK(RCLK),
.RRSTn(RRSTn),
.wpt(wpt),
.wp2_rpt(wp2_rpt));
full#(.ADDR_WIDTH(ADDR_WIDTH))
full_module(.WCLK(WCLK),
.WRSTn(WRSTn),
.write(write),
.rp2_wpt(rp2_wpt),
.wpt(wpt),
.waddr(waddr),
.full(full));
empty #(.ADDR_WIDTH(ADDR_WIDTH))
empty_module(.RCLK(RCLK),
.RRSTn(RRSTn),
.read(read),
.wp2_rpt(wp2_rpt),
.rpt(rpt),
.raddr(raddr),
.empty(empty));
endmodule
3.2RAM模块
//******************双接口RAM******************
module double_ram#(
parameter DATA_WIDTH = 8,
parameter ADDR_WIDTH = 4,
parameter RAM_DEPTH = 16
)
(
input WCLK, //write clk
input write, //write en
input [ ADDR_WIDTH - 1:0 ] waddr, //write address from full.v
input [ DATA_WIDTH - 1:0 ] wdata, //write data
input [ ADDR_WIDTH - 1:0 ] raddr, //read address from empty.v
output [ DATA_WIDTH - 1:0 ] rdata //read data
);
reg [ DATA_WIDTH - 1:0 ] RAM [ RAM_DEPTH - 1:0 ]; //double Port ram
always @ ( posedge WCLK )
begin
if ( write == 1'b1 )
begin
RAM [ waddr ] <= wdata;
end
else
begin
RAM [ waddr ] <= RAM [ waddr ];
end
end
assign rdata = RAM [ raddr ];
endmodule
3)判断满逻辑模块
//*****************判断满逻辑****************
module full#(
parameter ADDR_WIDTH = 4
)
(
input WRSTn,
input WCLK,
input write,
input [ ADDR_WIDTH : 0 ] rp2_wpt,
output reg [ ADDR_WIDTH : 0 ] wpt,
output [ ADDR_WIDTH - 1:0 ] waddr,
output reg full
);
reg [ ADDR_WIDTH :0 ] wbin;
wire [ ADDR_WIDTH :0 ] wbin_next;
wire [ ADDR_WIDTH :0 ] wgray_next;
wire full_reg;
always @ ( posedge WCLK or negedge WRSTn )
begin
if (!WRSTn)
begin
wpt <= 0;
wbin <= 0;
end
else
begin
wpt <= wgray_next;
wbin <= wbin_next;
end
end
assign wbin_next = ( !full ) ? ( wbin + write ) : wbin;
assign wgray_next = ( wbin_next >> 1 ) ^ wbin_next;
assign waddr = wbin [ ADDR_WIDTH - 1:0 ];
always @ ( posedge WCLK or negedge WRSTn )
begin
if (!WRSTn)
begin
full <= 0;
end
else
begin
full <= full_reg;
end
end
assign full_reg = ( wgray_next == { -rp2_wpt [ ADDR_WIDTH : ADDR_WIDTH - 1],rp2_wpt [ ADDR_WIDTH - 2:0]});
endmodule
4)判断是否为空指针
//********************判断是否为空逻辑************
module empty#(
parameter ADDR_WIDTH = 4
)
(
input RRSTn,
input RCLK,
input read,
input [ ADDR_WIDTH : 0 ] wp2_rpt,
output reg [ ADDR_WIDTH : 0 ] rpt,
output reg [ ADDR_WIDTH - 1:0 ] raddr,
output reg empty
);
reg [ ADDR_WIDTH :0 ] rbin;
wire [ ADDR_WIDTH :0 ] rbin_next;
wire [ ADDR_WIDTH :0 ] rgray_next;
wire empty_reg;
always @ ( posedge RCLK or negedge RRSTn )
begin
if (!RRSTn)
begin
rpt <= 0;
rbin <= 0;
end
else
begin
rpt <= rgray_next;
rbin <= rbin_next;
end
end
assign rbin_next = ( !empty ) ? ( rbin + read ) : rbin;
assign rgray_next = ( rbin_next >> 1 ) ^ rbin_next;
//assign raddr = rbin [ ADDR_WIDTH - 1:0 ];
always @ ( posedge RCLK or negedge RRSTn )
begin
if (read)
begin
raddr <= rbin [ ADDR_WIDTH - 1:0 ];
end
end
always @ ( posedge RCLK or negedge RRSTn )
begin
if (!RRSTn)
begin
empty <= 0;
end
else
begin
empty <= empty_reg;
end
end
assign empty_reg = ( rgray_next == wp2_rpt );
endmodule
5)读到写跨时钟域
//****************读到写跨时钟域****************
module r2w_sync#(
parameter ADDR_WIDTH = 4
)
(
input WRSTn, //write RSTn
input WCLK, //write CLK
input [ ADDR_WIDTH : 0 ] rpt, //output to write port gray
output reg [ ADDR_WIDTH : 0 ] rp2_wpt //D trigger sync with two levels,second level
);
reg [ ADDR_WIDTH : 0 ] rp1_wpt; //frist level
//reg [ ADDR_WIDTH : 0 ] rp2_wpt;
always @ ( posedge WCLK or negedge WRSTn )
begin
if ( !WRSTn )
begin
{ rp2_wpt,rp1_wpt } <= 0;
end
else
begin
{ rp2_wpt,rp1_wpt } <= { rp1_wpt,rpt };
end
end
endmodule
6)写到读跨时钟域
/**Write to Read Sync module**/
module w2r_sync#(
parameter ADDR_WIDTH = 4
)
(
input RRSTn, //read RSTn
input RCLK, //reaf CLK
input [ ADDR_WIDTH : 0 ] wpt, //output to read port gray
output reg [ ADDR_WIDTH : 0 ] wp2_rpt //D trigger sync with two levels,second level
);
reg [ ADDR_WIDTH : 0 ] wp1_rpt; //frist level
//reg [ ADDR_WIDTH : 0 ] wp2_rpt;
always @ ( posedge RCLK or negedge RRSTn )
begin
if ( !RRSTn )
begin
{ wp2_rpt,wp1_rpt } <= 0;
end
else
begin
{ wp2_rpt,wp1_rpt } <= { wp1_rpt,wpt};
end
end
endmodule
4.tb
`timescale 1 ns/ 1 ps
module asyn_fifo_tb();
parameter DATA_WIDTH = 8;
reg WCLK;
reg WRSTn;
reg RCLK;
reg RRSTn;
reg write;
reg read;
reg [ DATA_WIDTH - 1 : 0 ] wdata;
wire [ DATA_WIDTH - 1 : 0 ] rdata;
wire full;
wire empty;
//integer i = 0;
asyn_fifo_top asyn_fifo(.WCLK(WCLK),
.WRSTn(WRSTn),
.RCLK(RCLK),
.RRSTn(RRSTn),
.write(write),
.read(read),
.wdata(wdata),
.rdata(rdata),
.full(full),
.empty(empty));
initial
begin
WCLK <= 0;
forever #100 WCLK = ~WCLK;
end
initial
begin
RCLK <= 0;
forever #200 RCLK = ~RCLK;
end
initial
begin
WRSTn = 0;
wdata = 0;
#100 WRSTn = 1;
end
initial
begin
RRSTn = 0;
#100 RRSTn = 1;
#10000;
$finish();
end
always @ ( posedge WCLK or negedge WRSTn )
begin
wdata <= wdata + 1'b1;
end
//always @(posedge WCLK or negedge WRSTn)
//begin
// if(WRSTn==1'b0)
// begin
// i <= 0;
// end
// else if(!full)
// begin
// i = i+1;
// end
// else begin
// i <= i;
// end
//end
//always @ (*)
//begin
// if (!full)
// wdata = i;
// else
// wdata = 0;
//end
always @ ( full or WRSTn )
begin
if (!WRSTn)
begin
write <= 0;
end
else if (!full)
begin
write <= 1;
end
else
begin
write <= 0;
end
end
always @ ( empty or RRSTn )
begin
if (!RRSTn)
begin
read <= 0;
end
else if (!empty)
begin
read <= 1;
end
else
begin
read <= 0;
end
end
initial begin
$fsdbDumpfile("jacky");
$fsdbDumpvars;
$vcdpluson;
end
endmodule
二、另一种设计思路(来自csdn:FPGA小学生)
//asyn_fifo代码
module asyn_fifo #(
parameter data_width = 16;
parameter data_depth = 8;
parameter ram_depth = 256;
)
(
input rst_n,
input wr_clk, //写时钟域的信号
input wr_en,
input [data_width-1:0] data_in,
output full,
input rd_clk, //读时钟域信号
input rd_en,
input [data_width-1:0] data_out,
output empty,
)
reg [data_depth-1:0] wr_adr; //定义读写指针的地址
reg [data_depth-1:0] rd_adr;
reg [data_depth:0] wr_adr_ptr ; //定义用来比较的指针
reg [data_depth:0] rd_adr_ptr ;
wire [data_depth:0] wr_adr_gray; //将用来比较的指针转格雷码
reg [data_depth:0] wr_adr_gray1 ; //在打两拍处用到
reg [data_depth:0] wr_adr_gray2 ;
wire [data_depth:0] rd_adr_gray; //将用来比较的指针转格雷码
reg [data_depth:0] rd_adr_gray1 ; //在打两拍处用到
reg [data_depth:0] rd_adr_gray2 ;
//*********************定义在双端口ram的读写数据模块******************
assign wr_adr = wr_adr_ptr[data_depth-1:0] //定义比较指针与读写指针的关系
assign rd_adr = rd_adr_ptr[data_depth-1:0]
integer i; //例化一个RAM
reg [data_width-1:0] ram_fifo [data_depth-1:0] ;
//在ram中写
always @ (posedge wr_clk or negedge rst_n)
begin
if(!rst_n)
begin //将ram全部置零
for(i;i<ram_depth;i=i+1)
ram_fifo[i] <= 'd0;
end
else if (wr_en && ~full)
ram_fifo[wr_adr] <= data_in;
else
ram_fifo[wr_adr] <= ram_fifo[wr_adr];
end
//在ram中读数据
always @ (posedge rd_clk or rst_n)
begin
if (!rst_n)
data_out <= 'd0;
else if (rd_en && ~empty)
data_out <= ram_fifo [rd_adr];
else
data_out <= 'd0;
end
//**********在进行读写后,对比较指针进行加1操作************
always @ (posedge wr_clk or !rst_n)
begin
if (!rst_n)
wr_adr_ptr <='b0;
else if (wr_en && ~full)
wr_adr_ptr <= wr_adr_ptr + 1'b1;
else
wr_adr_ptr <= wr_adr_ptr
end
always @ (posedge rd_clk or !rst_n)
begin
if (!rst_n)
rd_adr_ptr <= 'b0;
else if (rd_en && ~empty)
rd_adr_ptr <= rd_adr_ptr + 1'b0;
else
rd_adr_ptr <= rd_adr_ptr
end
//**********************将比较指针转为格雷码*******************
assign wr_adr_gray = (wr_adr_ptr >> 1)^ wr_adr_ptr;
assign rd_adr_gray = (rd_adr_ptr >> 1) ^ rd_adr_ptr;
//*************************比较格雷码的大小************
//判断是否为空时,要在读时钟域下判断转换后格雷码是否相等
//判断是否为满时,要在写时钟域下比较格雷码最高位不等并且剩余位相等
//分别将比较格雷码在相应时钟下打两拍,来同步时钟域
always @ (posedge wr_clk or !rst_n)
begin
if (!rst_n)
begin
rd_adr_gray1 <= 'b0;
rd_adr_gray2 <= 'b0;
end
else begin
rd_adr_gray1 <= rd_adr_gray;
rd_adr_gray2 <= rd_adr_gray1;
end
end
always @ (posedge rd_clk or !rst_n)
begin
if (!rst_n)
begin
wr_adr_gray1 <= 'b0;
wr_adr_gray2 <= 'b0;
end
else begin
wr_adr_gray1 <= wr_adr_gray;
wr_adr_gray2 <= wr_adr_gray1;
end
end
//判断空满
assign empty = (rd_adr_gray2 == wr_adr_gray2) ? 1'b:1'b0;
assign full = (rd_adr_gray2[data_depth : data_depth-1] != wr_adr_gray2[data_depth:data_depth-1]) && (wr_adr_gray2[data_depth-2:0] == rd_adr_gray2[data_depth-2:0])
endmodule
tb
//异步fifo的testbench
`timescale 1ns/1ps
module asyn_fifo_tb;
reg rst_n;
reg wr_clk;
reg wr_en;
reg [15:0] data_in;
wire full;
reg rd_clk;
reg rd_en;
reg [15:0] data_out;
wire empty;
asyn_fifo asyn_fifo_inst
(
.rst_n (rst_n),
.wr_clk (wr_clk),
.wr_en (wr_en),
.data_in (data_in),
.full (full),
.rd_clk (rd_clk),
.rd_en (rd_en),
.data_out (data_out),
.empty (empty)
)
//定义时钟
initial begin
wr_clk = 0;
forever #10 wr_clk = ~wr_clk;
end
initial begin
rd_clk = 0 ;
forever #30 rd_clk = ~rd_clk;
end
// 定义输入data_in
always @ (posedge wr_clk or negedge rst_n)
begin
if (!rst_n)
data_in <= 'd0;
else if (wr_en)
data_in <= data_in + 1'b1;
else
data_in <= data_in;
end
//定义其他输入信号
initial begin
rst_n = 0;
wr_en =0;
rd_en = 0;
#200
rst_n = 1;
wr_en = 1;
#20000
wr_en = 0;
rd_en = 1;
#20000
rd_en = 0;
$stop
end
endmodule
最后
以上就是尊敬帅哥为你收集整理的异步fifo的设计与验证的全部内容,希望文章能够帮你解决异步fifo的设计与验证所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复