双口FIFO与RAM
FIFO
该部分转载自异步FIFO—Verilog实现与异步FIFO设计
一、FIFO简介
FIFO是英文First In First Out 的缩写,是一种先进先出的数据缓存器,它与普通存储器的区别是没有外部读写地址线,这样使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。
用途1:
异步FIFO读写分别采用相互异步的不同时钟。在现代集成电路芯片中,随着设计规模的不断扩大,一个系统中往往含有数个时钟,多时钟域带来的一个问题就是,如何设计异步时钟之间的接口电路。异步FIFO是这个问题的一种简便、快捷的解决方案,使用异步FIFO可以在两个不同时钟系统之间快速而方便地传输实时数据。
用途2:
对于不同宽度的数据接口也可以用FIFO,例如单片机位8位数据输出,而DSP可能是16位数据输入,在单片机与DSP连接时就可以使用FIFO来达到数据匹配的目的。
二、分类
同步FIFO是指读时钟和写时钟为同一个时钟,在时钟沿来临时同时发生读写操作;
异步FIFO是指读写时钟不一致,读写时钟是互相独立的。
三、FIFO的常见参数
FIFO的宽度:即FIFO一次读写操作的数据位;
FIFO的深度:指的是FIFO可以存储多少个N位的数据(如果宽度为N)。
满标志:FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以阻止FIFO的写操作继续向FIFO中写数据而造成溢出(overflow)。
空标志:FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以阻止FIFO的读操作继续从FIFO中读出数据而造成无效数据的读出(underflow)。
读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。
写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据。
===============================分 隔 符 ==============================
读写指针的工作原理
读指针:总是指向下一个将要被写入的单元,复位时,指向第1个单元(编号为0)。
写指针:总是指向当前要被读出的数据,复位时,指向第1个单元(编号为0)
FIFO的“空”/“满”检测
FIFO设计的关键:产生可靠的FIFO读写指针和生成FIFO“空”/“满”状态标志。
当读写指针相等时,表明FIFO为空,这种情况发生在复位操作时,或者当读指针读出FIFO中最后一个字后,追赶上了写指针时,如下图所示:
当读写指针再次相等时,表明FIFO为满,这种情况发生在,当写指针转了一圈,折回来(wrapped around)又追上了读指针,如下图:
为了区分到底是满状态还是空状态,可以采用以下方法:
方法1:在指针中添加一个额外的位(extra bit),当写指针增加并越过最后一个FIFO地址时,就将写指针这个未用的MSB加1,其它位回零。对读指针也进行同样的操作。此时,对于深度为2n的FIFO,需要的读/写指针位宽为(n+1)位,如对于深度为8的FIFO,需要采用4bit的计数器,0000~1000、1001~1111,MSB作为折回标志位,而低3位作为地址指针。
如果两个指针的MSB不同,说明写指针比读指针多折回了一次;如r_addr=0000,而w_addr = 1000,为满。
如果两个指针的MSB相同,则说明两个指针折回的次数相等。其余位相等,说明FIFO为空;
3.二进制FIFO指针的考虑
将一个二进制的计数值从一个时钟域同步到另一个时钟域的时候很容易出现问题,因为采用二进制计数器时所有位都可能同时变化,在同一个时钟沿同步多个信号的变化会产生亚稳态问题。而使用格雷码只有一位变化,因此在两个时钟域间同步多个位不会产生问题。所以需要一个二进制到gray码的转换电路,将地址值转换为相应的gray码,然后将该gray码同步到另一个时钟域进行对比,作为空满状态的检测。
使用gray码进行对比,如何判断“空”与“满”
使用gray码解决了一个问题,但同时也带来另一个问题,即在格雷码域如何判断空与满。
对于“空”的判断依然依据二者完全相等(包括MSB);
而对于“满”的判断,如下图,由于gray码除了MSB外,具有镜像对称的特点,当读指针指向7,写指针指向8时,除了MSB,其余位皆相同,不能说它为满。因此不能单纯的只检测最高位了,在gray码上判断为满必须同时满足以下3条:
wptr和同步过来的rptr的MSB不相等,因为wptr必须比rptr多折回一次。
wptr与rptr的次高位不相等,如上图位置7和位置15,转化为二进制对应的是0111和1111,MSB不同说明多折回一次,111相同代表同一位置。
剩下的其余位完全相等。
5.总体实现
系统的总体框图如下:
四、同步化分析
由于是异步FIFO的设计,读写时钟不一样,在产生读空信号和写满信号时,会涉及到跨时钟域的问题,如何解决?
跨时钟域的问题:由于读指针是属于读时钟域的,写指针是属于写时钟域的,而异步FIFO的读写时钟域不同,是异步的,要是将读时钟域的读指针与写时钟域的写指针不做任何处理直接比较肯定是错误的,因此我们需要进行同步处理以后仔进行比较
解决方法:加两级寄存器同步 + 格雷码(目的都是消除亚稳态)
1.使用异步信号进行使用的时候,好的设计都会对异步信号进行同步处理,同步一般采用多级D触发器级联处理,如下图。这种模型大部分资料都说的是第一级寄存器产生亚稳态后,第二级寄存器稳定输出概率为90%,第三极寄存器稳定输出的概率为99%,如果亚稳态跟随电路一直传递下去,那就会另自我修护能力较弱的系统直接崩溃。
2.将一个二进制的计数值从一个时钟域同步到另一个时钟域的时候很容易出现问题,因为采用二进制计数器时所有位都可能同时变化,在同一个时钟沿同步多个信号的变化会产生亚稳态问题。而使用格雷码只有一位变化,因此在两个时钟域间同步多个位不会产生问题。所以需要一个二进制到gray码的转换电路,将地址值转换为相应的gray码,然后将该gray码同步到另一个时钟域进行对比,作为空满状态的检测。
那么,多位二进制码如何转化为格雷码?
换一种描述方法:
verilog代码实现就一句:assign gray_code = (bin_code>>1) ^ bin_code;
使用gray码解决了一个问题,但同时也带来另一个问题,即在格雷码域如何判断空与满。
这里直接给出结论:
判断读空时:需要读时钟域的格雷码rgray_next和被同步到读时钟域的写指针rd2_wp每一位完全相同;
判断写满时:需要写时钟域的格雷码wgray_next和被同步到写时钟域的读指针wr2_rp高两位不相同,其余各位完全相同;
assign full = (wr_addr_gray == {~(rd_addr_gray_d2[addr_width-:2]),rd_addr_gray_d2[addr_width-2:0]}) ;//高两位不同 assign empty = ( rd_addr_gray == wr_addr_gray_d2 );
五、Verilog实现
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2019/06/14 10:28:56
// Design Name:
// Module Name: fifo
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module fifo #(
parameter DSIZE = 8,
parameter ASIZE = 8,
parameter RAM_DEPTH = 1>>ASIZE
)
(
input wclk,
input wrst_n,
input wr_en,
input [DSIZE-1: 0] wdata,
output wfull,
input rclk,
input rrst_n,
input rd_en,
output reg [DSIZE-1: 0] rdata,
output rempty
);
wire [ASIZE-1: 0] wr_addr;
wire [ASIZE-1: 0] rd_addr;
reg [ASIZE: 0] wr_addr_ptr;
reg [ASIZE: 0] rd_addr_ptr;
wire [ASIZE: 0] wr_addr_gray;
reg [ASIZE: 0] wr_addr_gray_t1;
reg [ASIZE: 0] wr_addr_gray_t2;
wire [ASIZE: 0] rd_addr_gray;
reg [ASIZE: 0] rd_addr_gray_t1;
reg [ASIZE: 0] rd_addr_gray_t2;
reg [DSIZE-1: 0] mem [RAM_DEPTH-1: 0];
//Write fifo------------------------
generate
genvar i;
for (i=0; i<(RAM_DEPTH-1); i=i+1)
begin: fifo_init
always @ (posedge wclk or negedge wrst_n)
begin
if (!wrst_n)
mem[i] <= 'h0;
else if (wr_en && !wfull)
mem[wr_addr] <= wdata;
else
mem[wr_addr] <= mem[wr_addr];
end
end
endgenerate
//Read fifo-------------------------
always @ (posedge rclk or negedge rrst_n)
begin
if(!rrst_n)
rdata <= 'h0;
else if (rd_en && !rempty)
rdata <= mem[rd_addr];
else
rdata <= 'h0;
end
//Adress point---------------------------
always @ (posedge wclk or negedge wrst_n)
begin
if(!wrst_n)
wr_addr_ptr <= 'h0;
else if (wr_en && !wfull)
wr_addr_ptr <= wr_addr_ptr + 1'b1;
else
wr_addr_ptr <= wr_addr_ptr;
end
always @ (posedge rclk or negedge rrst_n)
begin
if(!wrst_n)
rd_addr_ptr <= 'h0;
else if (wr_en && !wfull)
rd_addr_ptr <= rd_addr_ptr + 1'b1;
else
rd_addr_ptr <= rd_addr_ptr;
end
//==========Generate write and read address
assign wr_addr = wr_addr_ptr[ASIZE-1: 0];
assign rd_addr = rd_addr_ptr[ASIZE-1: 0];
//================= translation gary code
assign wr_addr_gray = (wr_addr_ptr>>1) ^ wr_addr_ptr;
assign rd_addr_gray = (rd_addr_ptr>>1) ^ rd_addr_ptr;
//==========================格雷码同步化
[email protected](posedge rclk or negedge rrst_n)
begin
if(!rrst_n)
begin
wr_addr_gray_t1 <= 'h0;
wr_addr_gray_t2 <= 'h0;
end
else
begin
wr_addr_gray_t1 <= wr_addr_gray;
wr_addr_gray_t2 <= wr_addr_gray_t1;
end
end
[email protected](posedge rclk or negedge rrst_n)
begin
if(!wrst_n)
begin
rd_addr_gray_t1 <= 'h0;
rd_addr_gray_t2 <= 'h0;
end
else
begin
rd_addr_gray_t1 <= rd_addr_gray;
rd_addr_gray_t2 <= rd_addr_gray_t1;
end
end
//generate full and empty
assign rempty = (rd_addr_gray == wr_addr_gray_t2);
assign wfull = (wr_addr_gray == {~rd_addr_gray_t2[ASIZE: ASIZE-1], rd_addr_gray_t2[ASIZE-2: 0]});
endmodule
RAM
该部分转载自使用Verilog实现RAM的构造并读写数据
Verilog实现
module ram
#(parameter DWIDTH=8,//data width
AWIDTH=8)//address width
(
input wr_clk,
input[DWIDTH-1:0] wr_data,
input wr_en,
input[AWIDTH-1:0] wr_addr,
input rd_clk,
output [DWIDTH-1:0] rd_data,
input rd_en,
input[AWIDTH-1:0] rd_addr
);
reg[DWIDTH-1:0] mem [2**AWIDTH-1:0];//define the memory
reg[AWIDTH-1:0] raddr;
[email protected](posedge wr_clk )
begin
if(wr_en) begin
mem[wr_addr]<=wr_data;
end
end
[email protected](posedge rd_clk)
begin
if(rd_en) begin
raddr<=rd_addr;
end
end
assign rd_data=mem[raddr];
endmodule