目录
一、单端口RAM原理及实现
1.1、原理
在内存空间中开辟出一段固定大小的内存用于存储数据,每一个数据所占的bit位称之为位宽,这段内存空间中数据的总数称之为深度。例如reg [7:0] mem [255:0],这段内存空间中每一个数据的位宽为8bit,深度为256。
在这段内存空间中,每个数据分配给一个地址,如上例深度为256,可以用8bit的地址来表示所有的数据,0000_0000则表示第0个数据,1111_1111则表示第255个数据。
外部信号通过固定的时钟节拍,通过使能信号及地址信号来读取RAM中特定位置的数据或者向RAM中特定位置写入数据。
1.2、Verilog实现
module Single_Port_RAM
(
//system input
input clk
,input rst_n
//data input
,input enable_wr //allow the user to read or write the data
//when enable_wr == 0,it means to write the data from RAM
//when enable_wr == 1,it means to read the data from RAM
,input [7:0] data_write
,input [7:0] address
//data output
,output reg [7:0] data_read
);
//setup RAM which has the width of 8 and depth of 256
localparam mem_width = 7;
localparam mem_depth = 255;
reg [mem_width:0] mem [mem_depth:0];
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
data_read <= 8'd0;
else if (!enable_wr)
mem[address] <= data_write;
else if (enable_wr)
data_read <= mem[address];
end
endmodule
1.3、优缺点分析
缺点:单一时刻只能进行读操作或者写操作,而不能同时进行;地址处的数据为0时也会读取,可能会发生错误。
优点:简单,不需要协调读写操作;
2、双端口RAM原理及实现
2.1、原理
参考如上的单端口RAM,双端口RAM在单端口RAM的基础上,将使能信号分为读使能信号和写使能信号,将地址信号分为读地址信号和写地址信号。
2.2、Verilog实现
module Dual_Port_RAM
(
//system input
input clk
,input rst_n
//signal of read
,input read_en //when read_en == 1,read the data from mem according to read_addr
,input [7:0] read_addr
,output reg [7:0] read_data
//signal of write
,input write_en //when write_en ==1,write the data to mem according to write_addr
,input [7:0] write_addr
,input [7:0] write_data
);
//setup RAM which has the width of 8 and depth of 256
localparam mem_width = 7;
localparam mem_depth = 255;
reg [mem_width:0] mem [mem_depth:0];
reg [7:0] i;
always @ (posedge clk or negedge rst_n)
begin
if (~rst_n)
for (i=0;i<=(mem_depth);i=i+1)
mem[i] <= 8'd0;
else if (write_en)
mem[write_addr] <= write_data;
end
always @ (posedge clk or negedge rst_n)
begin
if (~rst_n)
read_data <= 8'd0;
else if (read_en)
read_data <= mem[read_addr];
else
read_data <= read_data; //when read_en is disabled, read_data maintains the data before
end
endmodule
2.3、优缺点分析
优点:可以同时对RAM进行读写操作
3、 同步FIFO
3.1、原理
在双端口RAM的基础上增加了一个限制项:即最先写进去的数据被最先读出来,此时不再对RAM的固定地址进行读写操作,而是按照写入顺序进行读出。
同步FIFO主要用于①同一时钟域但是速度不同的模块之间充当buffer的功能;②用于不同数据宽度的模块之间,作为数据匹配(如前一个模块位宽为8bit,后一个模块为16bit,中间加一个FIFO,则每次读出两个数据进入后一级)
按顺序写入和读出就会衍生出两个限制:RAM为空时不允许读出,RAM已经满后不允许写入。因此针对同步FIFO的难点主要在这儿。
设置两个位置指针,其中一个指向下一次要写入数据的位置write_addr,另外一个指向本次要读取数据的位置read_addr,每次读写一次数据write_addr和read_addr各增加一,当二者均等于RAM深度时,下一次置零,重新计数。(相当于两个人从一楼到顶楼,然后跳下来接着往上走。)
针对以上难点,出现了两种解决问题的方式:
①设定一个factor选项,每次写入数据,factor+1,;每次读出数据,factor-1。若此CLK有效边沿既要写入数据,又要读出数据,则Factor不变。则当factor的大小等于0时,表示RAM为空;当factor的大小等于RAM深度时,表示RAM为满;
3.2、Verilog实现(1)
//--==============================================================================
// THIS FILE DESCRIBE A SYNCHRONIZATION FIFO (FIRST IN FIRST OUT)
// USING DATA COUNT TO DETECT FIFO FULL OR FIFO EMPTY
//
// Project : Verilog_Learning
// File_name : Sync_FIFO_1.v
// Creator(s) : Moshang
// Date : 2020/09/29
// Description : A synchronization FIFO control
//
// Modification :
// (1) Initial Design 2020/09/29
//
//
//--==============================================================================
module Sync_FIFO_1
(
//System input
clk
,rst_n
//read control
,fifo_read_en
,fifo_read_data
,fifo_read_err
,fifo_empty
//write control
,fifo_write_en
,fifo_write_data
,fifo_write_err
,fifo_full
//data count in mem
,fifo_data_cnt
);
// PARAMETER DECLARATION
parameter FIFO_DATA_WIDTH = 8;
parameter FIFO_ADDR_WIDTH = 4;
// INPUT AND OUTPUT DECLARATION
input clk; //SYSTEM CLOCK INPUT
input rst_n; //SYSTEM RESET (RESET: 0)
input fifo_read_en; //FIFO READ ENABLE (ENABLE: 1)
output reg [FIFO_DATA_WIDTH-1:0] fifo_read_data; //DATA READ FROM FIFO
output wire fifo_read_err; //FIFO IS EMPTY BUT READ IS ENABLED(ERR: 1)
output wire fifo_empty; //FIFO IS EMPTY(EMPTY: 1)
input fifo_write_en; //FIFO WRITE ENABLE(ENABLE: 1)
input [FIFO_DATA_WIDTH-1:0] fifo_write_data; //DATR WRITE TO FIFO
output wire fifo_write_err; //FIFO IS FULL BUT WIRTE IS ENABLED(ERR: 1)
output wire fifo_full; //FIFO IS FULL(FULL: 1)
output reg [FIFO_ADDR_WIDTH :0] fifo_data_cnt; //FIFO DATA COUNT
// INNER DECLARATION
reg [FIFO_ADDR_WIDTH-1:0] fifo_read_addr; //FIFO ADDR WHICH IS GOING TO WRITE DATA NEXT TIME
reg [FIFO_ADDR_WIDTH-1:0] fifo_write_addr; //FIFO ADDR WHICH IS READ DATA FROM NEXT TIME
//FIFO MEMORY INSTANCE
reg [FIFO_DATA_WIDTH-1:0] FIFO_MEM [{(FIFO_ADDR_WIDTH){1'b1}}:0];
integer i;
//--=========================== MODULE SOURCE CODE ===========================--
//--===========================================--
// SRAM INSTANCE :
// FIFO Rdata & FIFO Wdata
//--===========================================--
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
fifo_read_data <= {(FIFO_DATA_WIDTH){1'b0}};
else if (fifo_read_en && (!fifo_empty))
fifo_read_data <= FIFO_MEM[fifo_read_addr];
else
fifo_read_data <= fifo_read_data;
end
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
for (i=0;i<=({(FIFO_ADDR_WIDTH){1'b1}});i=i+1)
begin
FIFO_MEM[i] <= {(FIFO_DATA_WIDTH){1'b0}};
end
else if (fifo_write_en && (!fifo_full))
FIFO_MEM[fifo_write_addr] <= fifo_write_data;
else
FIFO_MEM[fifo_write_addr] <= FIFO_MEM[fifo_write_addr];
end
//--===========================================--
// READ ADDR & WRITE ADDR CONTROL
//--===========================================--
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
fifo_read_addr <= {(FIFO_ADDR_WIDTH){1'b0}};
else if (fifo_read_en && (!fifo_empty))
fifo_read_addr <= fifo_read_addr + 1'b1;
else
fifo_read_addr <= fifo_read_addr;
end
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
fifo_write_addr <= {(FIFO_ADDR_WIDTH){1'b0}};
else if (fifo_write_en && (!fifo_full))
fifo_write_addr <= fifo_write_addr + 1'b1;
else
fifo_write_addr <= fifo_write_addr;
end
//--===========================================--
// FIFO DATA CNT
// VALID WRITE ONLY, DATA COUNT INCREASE
// VALID READ ONLY , DATA COUNT DECREASE
//--===========================================--
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
fifo_data_cnt <= {(FIFO_ADDR_WIDTH + 1'b1){1'b0}};
else if (fifo_read_en && (!fifo_empty) && (!(fifo_write_en && (!fifo_full)))) //FIFO VALID READ ONLY , COUINT DECREASE
fifo_data_cnt <= fifo_data_cnt - 1'b1;
else if (!(fifo_read_en && (!fifo_empty)) && (fifo_write_en && (!fifo_full))) //FIFO VALID WRITE ONLY, COUNT INCREASE
fifo_data_cnt <= fifo_data_cnt + 1'b1;
else
fifo_data_cnt <= fifo_data_cnt;
end
//--===========================================--
// FIFO DATA CNT
// EMPTY, FULL , READ_ERR,WRITE_ERR
// 因为fifo_data_cnt的增减是以空满信号为判断依据的,反过来,空满信号也是以fifo_data_cnt作为判断依据的,因此可以理解为互相驱动;
// 且二者之间必须是实时响应的,如果两个都用always块来进行赋值,则二者之间会出现一个时钟周期的延迟,最终导致逻辑错误
// 如果不想要这个延迟,则将其中一个作为assign类型来处理就可以
//--===========================================--
assign fifo_empty = (fifo_data_cnt == 0) & (rst_n);
assign fifo_full = (fifo_data_cnt == {1'b1,{(FIFO_ADDR_WIDTH){1'b0}}}) & (rst_n);
assign fifo_read_err = (fifo_empty & fifo_read_en) & (rst_n);
assign fifo_write_err = (fifo_full & fifo_write_en) & (rst_n);
endmodule
②在addr前面再加一bit标示位来标识是否已经转了一圈了来判决究竟是空还是满。
当write_addr == read_addr时,可能是已经读操作赶上了写操作,即当前RAM为空;也可能是写操作已经转了一圈了到达读操作的位置,即RAM当前已经满了。在addr前面再加一bit标示位来标识是否已经转了一圈了来判决究竟是空还是满,正常逻辑情况下,写操作永远领先于读操作。如果最高位不同,而其他位相同,则说明写操作已经领先于读操作一圈,则FIFO状态为满;如果最高位相同,其他位也相同,则说明二者处于同一圈的同一位置,则FIFO状态为空)
3.3、Verilog实现(2)
//--==============================================================================
// THIS FILE DESCRIBE A SYNCHRONIZATION FIFO (FIRST IN FIRST OUT)
// USING EXTRA BIT OF WRITE_ADDR AND READ_ADDR TO DETECT FIFO FULL AND FIFO EMPTY
//
// Project : Verilog_Learning
// File_name : Sync_FIFO_2.v
// Creator(s) : Moshang
// Date : 2020/09/29
// Description : A synchronization FIFO control
//
// Modification :
// (1) Initial Design 2020/09/29
//
//
//--==============================================================================
module Sync_FIFO_2
(
//System input
clk
,rst_n
//read control
,fifo_read_en
,fifo_read_data
,fifo_read_err
,fifo_empty
//write control
,fifo_write_en
,fifo_write_data
,fifo_write_err
,fifo_full
);
// PARAMETER DECLARATION
parameter FIFO_DATA_WIDTH = 8;
parameter FIFO_ADDR_WIDTH = 3;
// INPUT DECLARATION
input clk; // SYSTEM CLOCK INPUT
input rst_n; // SYSTEM RESET (RESET:1)
input fifo_read_en; // FIFO READ ENABLE (ENABLE:1)
input fifo_write_en; // FIFO WRITE ENABLE (ENABLE:1)
input wire [FIFO_DATA_WIDTH-1'b1 : 0] fifo_write_data; // DATA WRITE TO FIFO
// OUTPUT DECLARATION
output reg [FIFO_DATA_WIDTH-1'b1 : 0] fifo_read_data; // DATA READ FROM FIFO
output fifo_read_err; // ERR CAUSED WHEN FIFO IS EMPTY BUT READ IS DESIRED
output fifo_empty; // FIFO EMPTY (EMPTY:1)
output fifo_write_err; // ERROR CAUSED WHEN FIFO IS FULL BUT WIRTE IS DESIRED
output fifo_full; // FIFO FULL (FULL:1)
// FIFO MEMORY DECLARATION
reg [FIFO_DATA_WIDTH-1'b1:0] FIFO_MEM [{(FIFO_ADDR_WIDTH){1'b1}} : 0];
integer i;
// WRITE ADDRESS AND READ ADDRESS DECLARATION
// WHERE FIFO_READ_ADDR AND FIFO_WRITE_ADDR IS ONE BIT MORE THAN FIFO_ADDR_WIDTH
reg [FIFO_ADDR_WIDTH : 0] fifo_read_addr;
reg [FIFO_ADDR_WIDTH : 0] fifo_write_addr;
//--========================== MODULE SOURCE CODE ============================--
//--==================================--
// SRAM INSTANCE
// FIFO READ DATA & FIFO WRITE DATA
//--==================================--
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
fifo_read_data <= {(FIFO_DATA_WIDTH){1'b0}};
else if (fifo_read_en && (!fifo_empty) )
fifo_read_data <= FIFO_MEM[{fifo_read_addr[(FIFO_ADDR_WIDTH - 1'b1) : 0]}];
else
fifo_read_data <= fifo_read_data;
end
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
for (i=0;i<={(FIFO_ADDR_WIDTH){1'b1}};i=i+1)
FIFO_MEM[i] <= {(FIFO_DATA_WIDTH){1'b0}};
else if (fifo_write_en && (!fifo_full))
FIFO_MEM[{fifo_write_addr[(FIFO_ADDR_WIDTH - 1'b1) : 0]}] <= fifo_write_data;
else
FIFO_MEM[{fifo_write_addr[(FIFO_ADDR_WIDTH - 1'b1) : 0]}] <= FIFO_MEM[{fifo_write_addr[(FIFO_ADDR_WIDTH - 1'b1) : 0]}];
end
// FIFO CONTROL OF WRITE ADDRESS AND READ ADDRESS
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
fifo_read_addr <= {(FIFO_ADDR_WIDTH + 1'b1){1'b0}};
else if (fifo_read_en && (!fifo_empty) )
fifo_read_addr <= fifo_read_addr + 1'b1;
else
fifo_read_addr <= fifo_read_addr;
end
always @ (posedge clk or negedge rst_n)
begin
if (!rst_n)
fifo_write_addr <= {(FIFO_ADDR_WIDTH + 1'b1){1'b0}};
else if (fifo_write_en && (!fifo_full) )
fifo_write_addr <= fifo_write_addr + 1'b1;
else
fifo_write_addr <= fifo_write_addr;
end
// DECTION OF FIFO EMPTY AND FIFO FULL
assign fifo_empty = ((fifo_read_addr == fifo_write_addr) && rst_n);
assign fifo_read_err = ((fifo_empty && fifo_read_en) && rst_n);
assign fifo_full = (((fifo_write_addr - fifo_read_addr) == {1'b1,{(FIFO_ADDR_WIDTH){1'b0}}}) && rst_n);
assign fifo_write_err = ((fifo_full && fifo_write_en) && rst_n);
endmodule
3.4、优缺点分析
-
利用factor来记数当前FIFO中的数据量来判断空满
优点:逻辑简单,便于理解,且能直接知道当前FIFO中的剩余数据量
缺点:使用寄存器资源较多,代码量增加
-
利用位置指针多加1bit来判断空满
优点:代码量总体来说比上一个小,逻辑也较为简单;使用的寄存器资源较少
缺点:无法准确知道当前FIFO中一共有多少个数
4、异步FIFO
4.1、原理
由于读写时钟来自不同的时钟域,且复位信号也有可能来自另外一个时钟域,则有可能会产生以下问题
① 异步复位信号在读写时钟域分别产生亚稳态;
② 空满信号判断时产生亚稳态,如 判断空信号时,需要在“读时钟域”内采样写地址信号,并在此时钟域与读地址信号进行对比,判断是否为空;由于对写地址信号为跨时钟域采样,可能会产生亚稳态。满信号的判断也是如此;
③ 跨时钟域对读地址/写地址 进行采样时,可能会产生误判;如读地址正好处在从“0001”到"0010"的变化边沿上,其实此时有两个bit发生变化,而两个bit一般不会是同时变化的,总会有先后顺序,即有可能采集到“0001、0010、0000、0011”这四个状态中的某一个。从而使得读写地址产生误判;
针对以上三种问题,分别做出了处理方式:
① 异步复位产生亚稳态
解决方式:对异步复位信号进行同步化处理,即分别在读时钟域和写时钟域内进行异步复位同步释放的操作;
② 空满信号产生亚稳态
解决方式:在读时钟域内对写地址进行两级触发器采样,这样可以降低亚稳态发生的概率;(两级触发器采样实质上就是对异步信号进行同步化处理,降低亚稳态发生的概率;复位信号的异步复位同步释放的操作本质上也是这样)
③ 读写地址采样时产生误判
解决方式:对读写地址采用格雷码编码,使得每次变化时只有1bit发生变化;如从“0001(0001的格雷码)”到“0011(0010的格雷码)”,此时只有1bit发生变化,采样时只有可能是0011或0001;如果采集错误(本来是0011,结果采集到了0001),也仅仅是慢一个时钟周期而已,因为两个数是相邻的,直接跳转到其他意料之外的地址,也不会产生重大的逻辑错误。
4.2、格雷码
格雷码:通常我们的计数采用8421码,如0000表示0,0001表示1,0010表示2;格雷码其特点是相邻编码之间只有1bit发生变化,如0000表示0,0001表示1,0011表示2等,从8421码转化为格雷码的方法如下:
方法1: 最高位(MSB)不变,其余每一位与其相邻的高一位进行亦或,从而得到格雷码;
方法2:8421码右移1bit,最高为补0,然后此新的代码与原来的8421码求亦或,从而得到格雷码(两种方法本质上是一样的)
4.3、Verilog代码实现
//--===================================================================--
// THIS FILE DESCRIBE A MODULE OF ASYNCHRONIZATION FIFO WHICH IS OFTEN USED IN
// CLOCK DOMAIN CROSSING
// Project : Verilog_Learning
// File_name : Async_FIFO.v
// Creator(s) : Moshang
// Date : 2020/09/30
// Description : a module of asynchronization FIFO
// Modification:
// (1) Initial Design 2020/09/30
//
//
//--===================================================================--
module Async_FIFO
(
rst_n // [UNKNOW DOMAIN]
,FIFO_read_clk // [READ DOMAIN]
,FIFO_write_clk // [WRITE DOMAIN]
,FIFO_read_en // [READ DOMAIN]
,FIFO_read_err // [READ DOMAIN]
,FIFO_read_data // [READ DOMAIN]
,FIFO_write_en // [WRITE DOMAIN]
,FIFO_write_err // [WRITE DOMAIN]
,FIFO_write_data // [WRITE DOMAIN]
,FIFO_empty // [READ DOMAIN]
,FIFO_full // [WRITE DOMAIN]
);
// PARAMETER DECLARATION
parameter FIFO_DATA_WIDTH = 8;
parameter FIFO_ADDR_WIDTH = 3;
// FIFO MEMORY DECLARATION
reg [FIFO_DATA_WIDTH-1 : 0] FIFO_MEM [{(FIFO_ADDR_WIDTH){1'b1}} : 0];
integer i;
// INPUT AND OUTPUT DECLARATION
input rst_n; // SYSTEM RESET (RESET:0)
input FIFO_read_clk; // READ CLOCK OF READ DOMAIN
input FIFO_write_clk; // WRITE CLOCK OF WRITE DOMAIN
input FIFO_read_en; // READ ENABLE FROM READ DOMAIN (ENABLE: 1)
input FIFO_write_en; // WRITE ENABLE FROM WRITE DOMAIN (ENABLE: 1)
input [FIFO_DATA_WIDTH-1 : 0] FIFO_write_data; // DATA WRITE TO FIFO
output FIFO_read_err; // ERROR OCCURED WHEN READ IS ENABLED BUT FIFO IS EMPTY (ERR:1)
output reg [FIFO_DATA_WIDTH-1 : 0] FIFO_read_data; // DATA READ FROM FIFO
output FIFO_write_err; // ERROR OCCURED WHEN WRITE IS ENABLED BUT FIFO IS FULL (ERR: 1)
output FIFO_empty; // FIFO EMPTY (EMPTY :1)
output FIFO_full; // FIFO FULL (FULL :1)
// READ ADDRESS AND WRITE ADDRESS DECLARATION
reg [FIFO_ADDR_WIDTH : 0] FIFO_read_addr; // [READ DOMAIN] READ ADDRESS WHEN EXTRA BIT IS USED TO DETECT EMPTY OR FULL
reg [FIFO_ADDR_WIDTH : 0] FIFO_write_addr; // [WRITE DOMAIN] WRITE ADDRESS WHEN EXTRA BIT IS USED TO DETECT EMPTY OR FULL
// REG DECLARATION WHICH WILL BE USED WHEN TWO-LEVEL SYNCHRONIZATION
reg read_rst_n_1; // THE FIRST REG OUT WHEN SYNCHRONIZE RST_N TO READ DOMAIN
reg read_rst_n_2; // THE SECOND REG OUT WHEN SYNCHRONIZE RST_N TO READ DOMAIN
reg write_rst_n_1; // THE FIRST REG OUT WHEN SYNCHRONIZE RST_N TO WRITE DOMAIN
reg write_rst_n_2; // THE SECONDE REG OUT WHEN SYNCHRONIZE RST_N TO WRITE DOMAIN
wire [FIFO_ADDR_WIDTH : 0] FIFO_read_addr_grey_code; // TRANSFER THE READ ADDRESS FROM 8421 TO GREY CODE
wire [FIFO_ADDR_WIDTH : 0] FIFO_write_addr_grey_code; // TRANSFER THE WRITE ADDRESS FROM 8421 TO GREY CODE
reg [FIFO_ADDR_WIDTH : 0] FIFO_read_addr_to_write_1; // THE FIRST REG OUT WHEN SYNCHRONIZE FIFO_read_addr TO WRITE DOMAIN
reg [FIFO_ADDR_WIDTH : 0] FIFO_read_addr_to_write_2; // THE SECOND REG OUT WHEN SYNCHRONIZE FIFO_read_addr TO WRITE DOMAIN
reg [FIFO_ADDR_WIDTH : 0] FIFO_write_addr_to_read_1; // THE FIRST REG OUT WHEN SYNCHRONIZE FIFO_write_addr TO READ DOMAIN
reg [FIFO_ADDR_WIDTH : 0] FIFO_write_addr_to_read_2; // THE SECOND REG OUT WHEN SYNCHRONIZE FIFO_write_addr TO READ DOMAIN
//--========================== MODULE SOURCE CODE ==========================--
//Synchronize the rst_n to read clock domain and write clock domain
always @ (posedge FIFO_read_clk or negedge rst_n)
begin
if (!rst_n)
begin
read_rst_n_1 <= 1'b0;
read_rst_n_2 <= 1'b0;
end
else
begin
read_rst_n_1 <= 1'b1;
read_rst_n_2 <= read_rst_n_1;
end
end
always @ (posedge FIFO_write_clk or negedge rst_n)
begin
if (!rst_n)
begin
write_rst_n_1 <= 1'b0;
write_rst_n_2 <= 1'b0;
end
else
begin
write_rst_n_1 <= 1'b1;
write_rst_n_2 <= write_rst_n_1;
end
end
// READ DATA FROM FIFO MEMORY OR WRITE DATA TO FIFO MEMORY
always @ (posedge FIFO_read_clk)
begin
if (!read_rst_n_2)
FIFO_read_data <= {(FIFO_DATA_WIDTH){1'b0}};
else if (FIFO_read_en && (!FIFO_empty))
FIFO_read_data <= FIFO_MEM[FIFO_read_addr];
else
FIFO_read_data <= FIFO_read_data;
end
always @ (posedge FIFO_write_clk)
begin
if (!write_rst_n_2)
for (i=0;i<=({(FIFO_ADDR_WIDTH){1'b1}});i=i+1)
FIFO_MEM[i] <= {(FIFO_DATA_WIDTH){1'b0}};
else if (FIFO_write_en && (!FIFO_full))
FIFO_MEM[FIFO_write_addr] <= FIFO_write_data;
else
FIFO_MEM[FIFO_write_addr] <= FIFO_MEM[FIFO_write_addr];
end
// FIFO READ ADDR INCREASE WHEN DATA IS READ OUT FROM FIFO IN READ DOMAIN
// FIFO WRITE ADDR INCREASE WHEN DATA IS WRITE INTO FIFO IN WRITE DOMAIN
always @ (posedge FIFO_read_clk)
begin
if (!read_rst_n_2)
FIFO_read_addr <= {(FIFO_ADDR_WIDTH+1){1'b0}};
else if (FIFO_read_en && (!FIFO_empty))
FIFO_read_addr <= FIFO_read_addr + 1;
else
FIFO_read_addr <= FIFO_read_addr;
end
always @ (posedge FIFO_write_clk)
begin
if (!write_rst_n_2)
FIFO_write_addr <= {(FIFO_ADDR_WIDTH+1){1'b0}};
else if (FIFO_write_en && (!FIFO_full))
FIFO_write_addr <= FIFO_write_addr + 1;
else
FIFO_write_addr <= FIFO_write_addr;
end
// TRANSFER FIFO_write_addr FROM 8421 BIN CODE TO GREY CODE
// TRANSDER FIFO_read_addr FROM 8421 BIN CODE TO GREY CODE
// THE WAYS IS TO MOVE 8421 CODE TO RIGHT SIDE OF ONE BIT, AND MSB WILL BE FILL BY 0;
// THEN (^) WILL BE USED BETWEEN FORMER AND LATER TO GET GREY CODE
assign FIFO_read_addr_grey_code = ((FIFO_read_addr >> 1) ^ FIFO_read_addr);
assign FIFO_write_addr_grey_code = ((FIFO_write_addr >> 1) ^ FIFO_write_addr);
// TRANSFER FIFO_write_addr_grey_code TO READ CLOCK DOMAIN THROUGH TWO LEVEL REG
// TRANSFER FIFO_read_addr_grey_code TO WRITE CLOCK DOMAIN THROUGH TWO LEVEL REG
always @ (posedge FIFO_read_clk)
begin
if (!read_rst_n_2)
begin
FIFO_write_addr_to_read_1 <= {(FIFO_ADDR_WIDTH+1){1'b0}};
FIFO_write_addr_to_read_2 <= {(FIFO_ADDR_WIDTH+1){1'b0}};
end
else
begin
FIFO_write_addr_to_read_1 <= FIFO_write_addr_grey_code;
FIFO_write_addr_to_read_2 <= FIFO_write_addr_to_read_1;
end
end
always @ (posedge FIFO_write_clk)
begin
if (!write_rst_n_2)
begin
FIFO_read_addr_to_write_1 <= {(FIFO_ADDR_WIDTH+1){1'b0}};
FIFO_read_addr_to_write_2 <= {(FIFO_ADDR_WIDTH+1){1'b0}};
end
else
begin
FIFO_read_addr_to_write_1 <= FIFO_read_addr_grey_code;
FIFO_read_addr_to_write_2 <= FIFO_read_addr_to_write_1;
end
end
// ACCORDING TO THE READ ADDRESS GREY CODE AND WRITE ADDRESS GREY CODE IN READ CLOCK DOMAIN TO DETERMIN FIFO ENPTY
// ACCORDING TO THE READ ADDRESS GREY CODE AND WRITE ADDRESS GREY CODE IN WRITE CLOCK DOMAIN TO DETERMIN FIFO FULL
// FIFO EMPTY WHEN READ ADDRESS GREY CODE IS THE SAME AS WRITE ADDRESS GREY CODE
// FIFO FULL WHEN THE MOST SIGNIFICENT TWO CODE OF WRITE AND READ ADDRESS GREY CODE ARE BOTH DIFFERENT AND THE OTHERS ARE THE SAME
assign FIFO_empty = (FIFO_read_addr_grey_code == FIFO_write_addr_to_read_2);
assign FIFO_full = ((!(FIFO_write_addr_grey_code[FIFO_ADDR_WIDTH]
== FIFO_read_addr_to_write_2[FIFO_ADDR_WIDTH]))
&& (!(FIFO_write_addr_grey_code[FIFO_ADDR_WIDTH-1]
== FIFO_read_addr_to_write_2[FIFO_ADDR_WIDTH-1]))
&& ( FIFO_write_addr_grey_code[FIFO_ADDR_WIDTH -2:0]
== FIFO_read_addr_to_write_2[FIFO_ADDR_WIDTH -2:0]));
assign FIFO_read_err = FIFO_empty && FIFO_read_en;
assign FIFO_write_err = FIFO_full && FIFO_write_en;
endmodule
4.4 、优缺点分析
优点:读写可以处于不同的时钟域,从而分割模块设计,之后通过FIFO连接起来就行
缺点:由于采用了两级寄存器来在不同时钟域之间做同步化处理,实际上地址信号的传递慢了两个时钟周期,对于低速信号尚可以接受,但是高速信号就不太好了。
总结
本文从单端口RAM、双端口RAM、同步FIFO、异步FIFO出发,分析各个模块的应用场景、代码实现及优缺点分析。