赞
踩
先进先出的数据缓存器,与普通存储器的区别是没有外部读写地址线,使用方便,缺点是只能顺序读写,不能随机读写。其数据地址由内部读写指针自动加 1 完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
1、数据缓冲:也就是数据写入过快,并且间隔时间长,也就是突发写入。那么通过设置一定深度的FIFO,可以起到数据暂存的功能,且使得后续处理流程平滑。
2、时钟域的隔离:主要用异步FIFO。对于不同时钟域的数据传输,可以通过FIFO进行隔离,避免跨时钟域的数据传输带来的设计和约束上的复杂度。比如FIFO的一端是AD,另一端是PCI;AD的采集速率是16位100KSPS,每秒的数据量是1.6Mbps。而PCI总线的速度是33MHz,总线宽度是32位
3、用于不同宽度的数据接口
读写时钟为同一个时钟
读写时钟不为同一个时钟
FIFO一次读写操作的数据位
指FIFO可以存储多少个N位的数据(如果宽度为N)
FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow)
FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow)
读操作所遵循的时钟,在每个时钟沿来临时读数据
写操作所遵循的时钟,在每个时钟沿来临时写数据
FIFO 读写指针(读写指针就是读写地址)的工作原理:
FIFO 设计的关键:产生可靠的 FIFO 读写指针和生成 FIFO“空”/“满”状态标志。
当读写指针相等时,表明 FIFO 为空,这种情况发生在复位操作时,或者当读指针读出 FIFO 中最后一 个字后,追赶上了写指针时。
当读写指针再次相等时,表明 FIFO 为满,这种情况发生在,当写指针转了一圈,折回来(wrapped around) 又追上了读指针。
构建一个计数器,该计数器(fifo_cnt)用于指示当前 FIFO 中数据的个数:
//计数器法实现同步FIFO module sync_fifo_cnt #( parameter DATA_WIDTH = 8 , //FIFO位宽 parameter DATA_DEPTH = 16 //FIFO深度 ) ( input clk , //系统时钟 input rst_n , //低电平有效的复位信号 input [DATA_WIDTH-1:0] data_in , //写入的数据 input rd_en , //读使能信号,高电平有效 input wr_en , //写使能信号,高电平有效 output reg [DATA_WIDTH-1:0] data_out, //输出的数据 output empty , //空标志,高电平表示当前FIFO已被写满 output full , //满标志,高电平表示当前FIFO已被读空 output reg [$clog2(DATA_DEPTH) : 0] fifo_cnt //$clog2是以2为底取对数 ); //reg define reg [DATA_WIDTH - 1 : 0] fifo_buffer[DATA_DEPTH - 1 : 0]; //用二维数组实现RAM reg [$clog2(DATA_DEPTH) - 1 : 0] wr_addr; //写地址 reg [$clog2(DATA_DEPTH) - 1 : 0] rd_addr; //读地址 //读操作,更新读地址 always @ (posedge clk or negedge rst_n) begin if (!rst_n) rd_addr <= 0; else if (!empty && rd_en)begin //读使能有效且非空 rd_addr <= rd_addr + 1'd1; data_out <= fifo_buffer[rd_addr]; end end //写操作,更新写地址 always @ (posedge clk or negedge rst_n) begin if (!rst_n) wr_addr <= 0; else if (!full && wr_en)begin //写使能有效且非满 wr_addr <= wr_addr + 1'd1; fifo_buffer[wr_addr]<=data_in; end end //更新计数器 always @ (posedge clk or negedge rst_n) begin if (!rst_n) fifo_cnt <= 0; else begin case({wr_en,rd_en}) //拼接读写使能信号进行判断 2'b00:fifo_cnt <= fifo_cnt; //不读不写 2'b01: //仅仅读 if(fifo_cnt != 0) //fifo没有被读空 fifo_cnt <= fifo_cnt - 1'b1; //fifo个数-1 2'b10: //仅仅写 if(fifo_cnt != DATA_DEPTH) //fifo没有被写满 fifo_cnt <= fifo_cnt + 1'b1; //fifo个数+1 2'b11:fifo_cnt <= fifo_cnt; //读写同时 default:; endcase end end //依据计数器状态更新指示信号 //依据不同阈值还可以设计半空、半满 、几乎空、几乎满 assign full = (fifo_cnt == DATA_DEPTH) ? 1'b1 : 1'b0; //空信号 assign empty = (fifo_cnt == 0)? 1'b1 : 1'b0; //满信号 endmodule
其中计数器和读写地址以为为底去对数的原因在于用多少位的二进制表示深度。
采取扩展最高位与读写地址位共同判断是为满或为空。
高位扩展法示意图:
module sync_fifo_ptr #( parameter DATA_WIDTH = 'd8 , //FIFO位宽 parameter DATA_DEPTH = 'd16 //FIFO深度 ) ( input clk , //系统时钟 input rst_n , //低电平有效的复位信号 input [DATA_WIDTH-1:0] data_in , //写入的数据 input rd_en , //读使能信号,高电平有效 input wr_en , //写使能信号,高电平有效 output reg [DATA_WIDTH-1:0] data_out, //输出的数据 output empty , //空标志,高电平表示当前FIFO已被写满 output full //满标志,高电平表示当前FIFO已被读空 ); //reg define //用二维数组实现RAM reg [DATA_WIDTH - 1 : 0] fifo_buffer[DATA_DEPTH - 1 : 0]; reg [$clog2(DATA_DEPTH) : 0] wr_ptr; //写地址指针,位宽多一位 reg [$clog2(DATA_DEPTH) : 0] rd_ptr; //读地址指针,位宽多一位 //wire define wire [$clog2(DATA_DEPTH) - 1 : 0] wr_ptr_true; //真实写地址指针 wire [$clog2(DATA_DEPTH) - 1 : 0] rd_ptr_true; //真实读地址指针 wire wr_ptr_msb; //写地址指针地址最高位 wire rd_ptr_msb; //读地址指针地址最高位 assign {wr_ptr_msb,wr_ptr_true} = wr_ptr; //将最高位与其他位拼接 assign {rd_ptr_msb,rd_ptr_true} = rd_ptr; //将最高位与其他位拼接 //读操作,更新读地址 always @ (posedge clk or negedge rst_n) begin if (rst_n == 1'b0) rd_ptr <= 'd0; else if (rd_en && !empty)begin //读使能有效且非空 data_out <= fifo_buffer[rd_ptr_true]; rd_ptr <= rd_ptr + 1'd1; end end //写操作,更新写地址 always @ (posedge clk or negedge rst_n) begin if (!rst_n) wr_ptr <= 0; else if (!full && wr_en)begin //写使能有效且非满 wr_ptr <= wr_ptr + 1'd1; fifo_buffer[wr_ptr_true] <= data_in; end end //更新指示信号 //当所有位相等时,读指针追到到了写指针,FIFO被读空 assign empty = ( wr_ptr == rd_ptr ) ? 1'b1 : 1'b10; //当最高位不同但是其他位相等时,写指针超过读指针一圈,FIFO被写满 assign full = ( (wr_ptr_msb != rd_ptr_msb ) && ( wr_ptr_true == rd_ptr_true ) )? 1'b1 : 1'b0; endmodule
`timescale 1ns / 1ps module tb_sync_fifo_ptr; // sync_fifo_ptr Parameters parameter PERIOD = 5 ; parameter DATA_WIDTH = 'd8 ; parameter DATA_DEPTH = 'd16; // sync_fifo_ptr Inputs reg clk = 0 ; reg rst_n = 0 ; reg [DATA_WIDTH-1:0] data_in = 0 ; reg rd_en = 0 ; reg wr_en = 0 ; // sync_fifo_ptr Outputs wire [DATA_WIDTH-1:0] data_out ; wire empty ; wire full ; initial begin forever #(PERIOD/2) clk=~clk; end initial begin #(PERIOD*2) rst_n = 1; end sync_fifo_ptr #( .DATA_WIDTH ( DATA_WIDTH ), .DATA_DEPTH ( DATA_DEPTH )) u_sync_fifo_ptr ( .clk ( clk ), .rst_n ( rst_n ), .data_in ( data_in [DATA_WIDTH-1:0] ), .rd_en ( rd_en ), .wr_en ( wr_en ), .data_out ( data_out [DATA_WIDTH-1:0] ), .empty ( empty ), .full ( full ) ); reg [7:0] tempdata = 0; initial begin $dumpfile("wave.vcd"); //生成的vcd文件名称 $dumpvars(0, tb_sync_fifo_ptr); //tb模块名称 clk = 0; rst_n = 0; wr_en = 0; rd_en = 0; data_in = 0; #10 rst_n = 1; push(1); fork push(2); pop(tempdata); join //push and pop together push(10); push(20); push(30); push(40); push(50); push(60); push(70); push(80); push(90); push(100); push(110); push(120); push(130); pop(tempdata); push(tempdata); pop(tempdata); pop(tempdata); pop(tempdata); pop(tempdata); push(140); pop(tempdata); push(tempdata);// pop(tempdata); pop(tempdata); pop(tempdata); pop(tempdata); pop(tempdata); pop(tempdata); pop(tempdata); pop(tempdata); pop(tempdata); pop(tempdata); pop(tempdata); push(5); pop(tempdata); $finish; end task push (input [7:0] data); if(full) $display("---Cannot push %d: Buffer Full---",data); else begin $display("Push",data); data_in = data; wr_en = 1; @(posedge clk); #5 wr_en = 0; end endtask task pop(output[7:0] data); if(empty) $display("---Cannot Pop: Buffer Empty---"); else begin rd_en = 1; @(posedge clk); #3 rd_en = 0; data = data_out; $display("------Poped:",data); end endtask endmodule
异步FIFO需要考虑的是亚稳态的问题,如果采用二进制数的计数值从一个时钟域到另一个时钟域的时候就会出问题,因为二进制计数器的所有位都有可能发生变化。使用格雷码的话只有一位变化,在两个时钟域间的同步多个位不会产生问题。所以需要一个二进制到gray码的转换电路,将地址值转换成相应的gray码,然后将gray码同步到另一个时钟域进行对比,作为空满状态的检测。
对于“空”的判断依然依据读写指针二者完全相等(包括MSB);
因为gray码的特性,与同步不同的是MSB最高位不同时,其他位相同,不能作为FIFO满的判断。
在gray码上判断为满必须同时满足以下3条:
代码:
module AsyncFIFO#( parameter ASIZE = 4, //地址位宽 parameter DSIZE = 8 //数据位宽 )( input [DSIZE-1:0] wdata, input winc,wclk,wrst_n, //写请求信号,写时钟,写复位 input rinc,rclk,rrst_n, //读请求信号,读时钟,读复位 output [DSIZE-1:0] rdata, output wfull, output rempty ); wire [ASIZE-1:0] waddr, raddr; wire [ASIZE:0] wptr, rptr, wq2_rptr, rq2_wptr; /*在检测“满”或“空”状态之前,需要将指针同步到其它时钟域时,使用格雷码,可以降低同步过程中亚稳态出现的概率*/ sync_r2w#( .ADDRSIZE ( 4 ) )u_sync_r2w( .wq2_rptr ( wq2_rptr ), //out .rptr ( rptr ), .wclk ( wclk ), .wrst_n ( wrst_n ) ); sync_w2r#( .ADDRSIZE ( 4 ) )u_sync_w2r( .rq2_wptr ( rq2_wptr ), .wptr ( wptr ), .rclk ( rclk ), .rrst_n ( rrst_n ) ); fifomem#( .DATASIZE ( 8 ), .ADDRSIZE ( 4 ) )u_fifomem( .wclken ( wclken ), .wfull ( wfull ), .wclk ( wclk ), .wdata ( wdata ), .waddr ( waddr ), .raddr ( raddr ), .rdata ( rdata ) ); rptr_empty#( .ADDRSIZE ( 4 ) )u_rptr_empty( .rempty ( rempty ), .raddr ( raddr ), .rptr ( rptr ), .rq2_wptr ( rq2_wptr ), .rinc ( rinc ), .rclk ( rclk ), .rrst_n ( rrst_n ) ); wptr_full#( .ADDRSIZE ( 4 ) )u_wptr_full( .wfull ( wfull ), .waddr ( waddr ), .wptr ( wptr ), .wq2_rptr ( wq2_rptr ), .winc ( winc ), .wclk ( wclk ), .wrst_n ( wrst_n ) ); endmodule
信号描述
信号名称 | 位宽 | 信号描述 |
---|---|---|
Wdata | 数据位宽 | 写入数据 |
Wfull | 1 | 写满信号 |
Winc | 1 | 写请求信号(写使能信号) |
Wclk | 1 | 写时钟 |
Wrst_n | 1 | 写复位信号(低电平有效 |
Rdata | 数据位宽 | 读出数据 |
Rempty | 1 | 读空信号 |
Rinc | 1 | 读请求信号(读使能信号) |
Rrst_n | 1 | 读复位信号(低电平有效) |
信号描述:
信号名称 | 位宽 | 信号描述 |
---|---|---|
wclken | 1 | 写使能信号 |
wclk | 1 | 写时钟信号 |
raddr | 地址位宽 | 读地址 |
waddr | 地址位宽 | 写地址 |
wdata | 数据位宽 | 写入的数据 |
rdata | 数据位宽 | 读数据 |
wfull | 1 | 写满信号 |
代码:
根据上面RAM的总概图,定义的一个宽度为8位,深度为8的RAM(即定义了8个寄存器,每个寄存器的宽度为8)
module fifomem #( parameter DATASIZE = 8, // Memory data word width parameter ADDRSIZE = 4 // 指针地址位宽设置为4,因为8=2^3,应该加一判断写满或读空 ) // Number of mem address bits ( input wclken, wfull, wclk, input [DATASIZE-1:0] wdata, //write data input [ADDRSIZE-1:0] waddr, raddr, output [DATASIZE-1:0] rdata //read data ); // RTL Verilog memory model localparam DEPTH = 1<<ADDRSIZE; // leftshift is equal divide two reg [DATASIZE-1:0] mem [0:DEPTH-1]; always @(posedge wclk) begin //当使能信号有效且还未写满的时候将数据写入实体中,与wclk时钟信号同步 if (wclken && !wfull) mem[waddr] <= wdata; end assign rdata = mem[raddr]; endmodule
主要是控制是否可以写入数据,写指针与写满的顶层模块图如图所示:
信号描述:
信号名称 | 位宽 | 信号描述 |
---|---|---|
Winc | 1 | 写请求信号 |
Wclk | 1 | 写时钟信号 |
Wrst_n | 1 | 写复位信号 |
Wq2_rptr | 地址位宽+1 | 同步之后的读指针(格雷码形式) |
Wfull | 1 | 写满信号 |
Waddr | 地址位宽 | 二进制形式的写地址 |
Wptr | 地址位宽+1 | 格雷码形式的写指针 |
模块作用:当时钟信号来临时且写请求信号有效时,写一组数据,并且同时写地址向下加一位,然后讲写地址转化为格雷码,判断是否写满
写满的判断:写地址指针再次追上读地址指针
二进制转格雷码:
二进制的最右一位(最低位)起,依次将每一位与左边一为进行异或操作,作为格雷码该位的值,而最左一位不变。
代码:
module wptr_full#( parameter ADDRSIZE = 4 ) ( output reg wfull, output [ADDRSIZE-1:0] waddr,//二进制形式的写地址 output reg [ADDRSIZE:0] wptr, //格雷码形式的写指针 input [ADDRSIZE:0] wq2_rptr,//同步后的读指针 input winc, wclk, wrst_n ); reg [ADDRSIZE:0] wbin; wire [ADDRSIZE:0] wgraynext, wbinnext; wire wfull_val; // GRAYSTYLE2 pointer always @(posedge wclk or negedge wrst_n) begin if (!wrst_n) {wbin, wptr} <= 0; else {wbin, wptr} <= {wbinnext, wgraynext}; end // Memory write-address pointer (okay to use binary to address memory) assign waddr = wbin[ADDRSIZE-1:0]; assign wbinnext = wbin + (winc & ~wfull);//写请求且没写满时,地址加一 assign wgraynext = (wbinnext>>1) ^ wbinnext; //将二进制码转换为格雷码 //最高位表示多这一次,但由于格雷码特性,次高位也需要不同,其余位需要相同 assign wfull_val = (wgraynext=={~wq2_rptr[ADDRSIZE:ADDRSIZE-1],wq2_rptr[ADDRSIZE-2:0]}); always @(posedge wclk or negedge wrst_n) begin if (!wrst_n) wfull <= 1'b0; else wfull <= wfull_val; end endmodule
读控制端口主要用于是否可以读取数据,读指针与读空的顶层模块图如下图:
信号描述:
信号名称 | 位宽 | 信号描述 |
---|---|---|
Rinc | 1 | 读请求信号 |
Rclk | 1 | 读时钟信号 |
Rrst_n | 1 | 读复位信号 |
Rq2_rptr | 地址位宽+1 | 同步之后的写指针(格雷码形式) |
Rempty | 1 | 读空信号 |
Raddr | 地址位宽 | 二进制形式的读地址 |
Rptr | 地址位宽+1 | 格雷码形式的读指针 |
**作用:**当时钟信号来且读请求信号有效时,读出一组数据,并且同时读地址向下加一位。然后将读地址转换为格雷码,判断是否为读空。
代码:
1、寄存二进制地址,方便转换成格雷码
2、根据读使能以及是否为空,赋值下一地址
3、每到上升沿采样将下一拍地址传给当前地址变量
4、当同步之后的写指针与读指针(格雷码形式)相同时说明为空
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; wire rempty_val; //------------------- // GRAYSTYLE2 pointer: gray码读地址指针 //------------------- always @(posedge rclk or negedge rrst_n) begin if (!rrst_n) begin rbin <= 0; rptr <= 0; end else begin rbin <= rbinnext ; rptr <= rgraynext; end end // gray码计数逻辑 assign raddr = rbin[ADDRSIZE-1:0]; assign rbinnext = rbin + (rinc & ~rempty); //不空且有读请求的时候读地址加1 assign rgraynext = (rbinnext>>1) ^ rbinnext; //二进制到gray码的转换 //--------------------------------------------------------------- // FIFO empty when the next rptr == synchronized wptr or on reset //--------------------------------------------------------------- /* * 读指针是一个n位的gray码计数器,比FIFO寻址所需的位宽大一位 * 当读指针和同步过来的写指针完全相等时(包括MSB),说明二者折回次数一致,FIFO为空 * */ assign rempty_val = (rgraynext == rq2_wptr); always @(posedge rclk or negedge rrst_n) if (!rrst_n) rempty <= 1'b1; else rempty <= rempty_val; endmodule
示意图如下:
信号名称 | 位宽 | 信号描述 |
---|---|---|
Rclk | 1 | 读时钟信号 |
Rrst_n | 1 | 读复位信号 |
Wptr | 地址位宽+1 | 写指针地址 |
Rq2_wptr | 地址位宽+1 | 同步写指针地址 |
代码:
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) begin if (!rrst_n) {rq2_wptr,rq1_wptr} <= 0; else begin // {rq2_wptr,rq1_wptr} <= {rq1_wptr,wptr}; rq1_wptr <= wptr; rq2_wptr <= rq1_wptr; end end endmodule
示意图如下:
信号名称 | 位宽 | 信号描述 |
---|---|---|
Wclk | 1 | 写时钟信号 |
Wrst_n | 1 | 写复位信号 |
Rptr | 地址位宽+1 | 读指针地址 |
Wq2_rptr | 地址位宽+1 | 同步读指针地址 |
代码:
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) begin
if (!wrst_n)
{wq2_rptr,wq1_rptr} <= 0;
else
{wq2_rptr,wq1_rptr} <= {wq1_rptr,rptr};
end
endmodule
1、输入信号初始化
2、FIFO复位
3、写读时钟
4、读写控制(写数据、读数据)
TestBench代码:
`timescale 1ns / 1ps module tb_AsyncFIFO; // AsyncFIFO Parameters parameter ASIZE = 4; parameter DSIZE = 8; // AsyncFIFO Inputs reg [DSIZE-1:0] wdata = 0 ; reg winc = 0 ; reg wclk = 0 ; reg wrst_n = 0 ; reg rinc = 0 ; reg rclk = 0 ; reg rrst_n = 0 ; // AsyncFIFO Outputs wire [DSIZE-1:0] rdata ; wire wfull ; wire rempty ; initial begin //输入信号初始化 wrst_n = 1; rrst_n = 1; wclk = 0; rclk = 0; winc = 0; rinc = 0; wdata = 0; //raddr = 0; //复位信号 # 30 wrst_n = 0; rrst_n = 0; #30 wrst_n = 1; rrst_n = 1; end always //写时钟 #2 wclk = ~wclk; always //读时钟 #4 rclk = ~rclk; always @(*) begin if(!wfull) begin winc = 1; end else begin winc =0; end end always @(*) begin if(!rempty) begin rinc = 1; end else begin rinc =0; end end always @(posedge wclk) begin if(!wfull) begin wdata <= wdata + 1; end else begin wdata <= wdata; end end AsyncFIFO #( .ASIZE ( ASIZE ), .DSIZE ( DSIZE )) u_AsyncFIFO ( .wdata ( wdata ), .winc ( winc ), .wclk ( wclk ), .wrst_n ( wrst_n ), .rinc ( rinc ), .rclk ( rclk ), .rrst_n ( rrst_n ), .rdata ( rdata ), .wfull ( wfull ), .rempty ( rempty ) ); initial begin $dumpfile("wave_test.vcd"); //生成的vcd文件名称 $dumpvars(0, tb_AsyncFIFO); //tb模块名称 #1000 $stop; //$finish; end endmodule
仿真的波形图:
异步fifo的重点,第一是对读写信号的同步,主要用两级缓存对读写信号的同步,第二是对写满信号的判断需要二进制到格雷码之间的转换
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。