1.1异步fifo的产生
1,异步FIFO主要是为了解决跨时钟域传输的操作,一个电路系统中可能出现不同的时钟频率和不同的接口电路,同时异步fifo主要采用格雷码的编码方式进行传输,因为格雷码相邻位之间只有一位发生变化,相反如果采用二进制编码的手段,同一码流之间,可能会出现信号的多位同时发生变化,这对信号的传输会产生非稳态过程,对系统而言是不理想的。同时由于格雷码在0~15的编码方式是关于镜像对称的。同步fifo中用于判断写满的最高位相等的判断,用于异步fifo的判断是不恰当的。应选择最高位和次高位同时进行判断。
1.2异步fifo verilog代码段设计
//异步fifo
module asnyc_fifo #(
parameter DATA_WIDTH = 'd8 , //FIFO位宽
parameter DATA_DEPTH = 'd16 //FIFO深度
)
(
//写数据
input wr_clk , //写时钟
input wr_rst_n , //复位信号
input wr_en , //写使能信号
input [DATA_WIDTH - 1: 0 ] data_in , // 写入的数据
//读数据
input rd_clk , //读时钟
input rd_rst_n , //读复位信号
input rd_en , //读使能信号
output reg [DATA_WIDTH - 1 :0 ] data_out , // 输出信号
output empty , // 满标志
output full //空标志
);
//用二维数组实现ram
reg [DATA_WIDTH - 1 : 0 ] fifo_buffer [DATA_DEPTH - 1 : 0 ] ;
reg [$clog2(DATA_DEPTH ) : 0] wr_ptr ; //写地址指针,binary
reg [$clog2(DATA_DEPTH ) : 0] rd_ptr ; //读地址指针,binary
reg [$clog2(DATA_DEPTH ) : 0] rd_ptr_g_d1 ; //读指针格雷码在写时钟域下同步一拍
reg [$clog2(DATA_DEPTH ) : 0] rd_ptr_g_d2 ; //读指针格雷码在写时钟域下同步两拍
reg [$clog2(DATA_DEPTH ) : 0] wr_ptr_g_d1 ; //写指针格雷码在读时钟域下同步一拍
reg [$clog2(DATA_DEPTH ) : 0] wr_ptr_g_d2 ; //写指针格雷码在读时钟域下同步两拍
//wire define
wire [$clog2(DATA_DEPTH) : 0] wr_ptr_g ; //写地址指针,格雷码
wire [$clog2(DATA_DEPTH) : 0] rd_ptr_g ; //读地址指针,格雷码
wire [$clog2(DATA_DEPTH) - 1 : 0 ] wr_ptr_true ; //真实的写地址指针,作为写ram的地址
wire [$clog2(DATA_DEPTH) - 1 : 0 ] rd_ptr_true ; //真实的读地址指针,作为读ram的地址
//地址指针从二进制转换到格雷码
assign wr_ptr_g = wr_ptr ^ (wr_ptr >> 1) ;
assign rd_ptr_g = rd_ptr ^ (rd_ptr >> 1) ;
//读写ram地址赋值
assign wr_ptr_ture = wr_ptr[$clog2(DATA_DEPTH ) - 1 : 0] ;
assign rd_ptr_true = rd_ptr[$clog2(DATA_DEPTH) - 1 : 0] ;
//写操作,更新写地址
always @ (posedge wr_clk or negedge wr_rst_n) begin
if (!wr_rst_n) begin
wr_ptr <= 0 ;
end
else if (!full && wr_en) begin
wr_ptr <= wr_ptr + 1'b1 ;
fifo_buffer[wr_ptr_ture] <= data_in ;
end
end
//将读指针的格雷码同步到写时钟域,来判断是否写满
always @ (posedge wr_clk or negedge wr_rst_n ) begin
if (!wr_rst_n) begin
rd_ptr_g_d1 <= 0; // 寄存一拍
rd_ptr_g_d2 <= 0; // 寄存两拍
end
else begin
rd_ptr_g_d1 <= rd_ptr_g ; //寄存一拍
rd_ptr_g_d2 <= rd_ptr_g_d1 ; //寄存两拍
end
end
//读操作,更新读地址
always @ (posedge wr_clk or negedge wr_rst_n) begin
if (!wr_rst_n) begin
rd_ptr <= 0 ;
end
else if (!full && wr_en) begin
data_out <= fifo_buffer[rd_ptr_true] ;
rd_ptr <= rd_ptr + 1'b1 ;
end
end
//将写时钟的格雷码同步到读时钟域上,来判断是否读空
always @ (posedge rd_clk or negedge rd_rst_n ) begin
if (!rd_rst_n) begin
wr_ptr_g_d1 <= 0; // 寄存一拍
wr_ptr_g_d2 <= 0; // 寄存两拍
end
else begin
wr_ptr_g_d1 <= wr_ptr_g ; //寄存一拍
wr_ptr_g_d2 <= wr_ptr_g_d1 ; //寄存两拍
end
end
//更新指示信号
//当所有位都相等时,读指针追到写指针,fifo被读空了
assign empty = (wr_ptr_g_d2 == rd_ptr_g) ? 1'b1 : 1'b0 ;
//当高位相反,且其他位相等的时候,写指针超过读指针,FIFO被写满
//同步后的读指针格雷码高两位取反,再拼接上余下位
assign full = ( wr_ptr_g == {~(rd_ptr_g_d2[$clog2(DATA_DEPTH) : $clog2(DATA_DEPTH) - 1]) ,rd_ptr_g_d2[$clog2(DATA_DEPTH) - 2 : 0 ]}) ? 1'b1 : 1'b0 ;
endmodule
总结,参考孤独的单刀博主文章 :
FIFO:写满:将读时钟同步到写时钟域上,来判断是否写满;
FIFO: 读空:将写时钟同步到读时钟域上,来判断是否读空;
同步两拍+格雷码编码的目的是为了消除电路中可能出现的亚稳态;
1.3牛客网vl_45刷题总结
描述
请根据题目中给出的双口RAM代码和接口描述,实现异步FIFO,要求FIFO位宽和深度参数化可配置。
电路的接口如下图所示。
双口RAM端口说明:
端口名 | I/O | 描述 |
wclk | input | 写数据时钟 |
wenc | input | 写使能 |
waddr | input | 写地址 |
wdata | input | 输入数据 |
rclk | input | 读数据时钟 |
renc | input | 读使能 |
raddr | input | 读地址 |
rdata | output | 输出数据 |
同步FIFO端口说明:
端口名 | I/O | 描述 |
wclk | input | 写时钟 |
rclk | input | 读时钟 |
wrstn | input | 写时钟域异步复位 |
rrstn | input | 读时钟域异步复位 |
winc | input | 写使能 |
rinc | input | 读使能 |
wdata | input | 写数据 |
wfull | output | 写满信号 |
rempty | output | 读空信号 |
rdata | output | 读数据 |
双口RAM代码如下,可在本题答案中添加并例化此代码。
module dual_port_RAM #(parameter DEPTH = 16, parameter WIDTH = 8)( input wclk ,input wenc ,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。 ,input [WIDTH-1:0] wdata //数据写入 ,input rclk ,input renc ,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。 ,output reg [WIDTH-1:0] rdata //数据输出 ); reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1]; always @(posedge wclk) begin if(wenc) RAM_MEM[waddr] <= wdata; end always @(posedge rclk) begin if(renc) rdata <= RAM_MEM[raddr]; end endmodule
输入描述:
input wclk ,
input rclk ,
input wrstn ,
input rrstn ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata
输出描述:
output wire wfull ,
output wire rempty ,
output wire [WIDTH-1:0] rdata
`timescale 1ns/1ns
/***************************************RAM*****************************************/
module dual_port_RAM #(parameter DEPTH = 16,
parameter WIDTH = 8)(
input wclk
,input wenc
,input [$clog2(DEPTH)-1:0] waddr //深度对2取对数,得到地址的位宽。
,input [WIDTH-1:0] wdata //数据写入
,input rclk
,input renc
,input [$clog2(DEPTH)-1:0] raddr //深度对2取对数,得到地址的位宽。
,output reg [WIDTH-1:0] rdata //数据输出
);
reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];
always @(posedge wclk) begin
if(wenc)
RAM_MEM[waddr] <= wdata;
end
always @(posedge rclk) begin
if(renc)
rdata <= RAM_MEM[raddr];
end
endmodule
/***************************************AFIFO*****************************************/
module asyn_fifo#(
parameter WIDTH = 8,
parameter DEPTH = 16
)(
input wclk ,
input rclk ,
input wrstn ,
input rrstn ,
input winc ,
input rinc ,
input [WIDTH-1:0] wdata ,
output wire wfull ,
output wire rempty ,
output wire [WIDTH-1:0] rdata
);
parameter ADDR_WIDTH = $clog2(DEPTH);
/***************************************Bin Logic*****************************************/
reg [ADDR_WIDTH:0] wptr_bin;
reg [ADDR_WIDTH:0] rptr_bin;
wire [ADDR_WIDTH:0] wptr_bin_next;
wire [ADDR_WIDTH:0] rptr_bin_next;
assign wptr_bin_next = wptr_bin + (winc & (!wfull));
assign rptr_bin_next = rptr_bin + (rinc & (!rempty));
/***************************************Gray Logic*****************************************/
reg [ADDR_WIDTH:0] wptr_gray;
reg [ADDR_WIDTH:0] rptr_gray;
wire [ADDR_WIDTH:0] wptr_gray_next;
wire [ADDR_WIDTH:0] rptr_gray_next;
assign wptr_gray_next = wptr_bin ^ (wptr_bin >> 1);
assign rptr_gray_next = rptr_bin ^ (rptr_bin >> 1);
always @ (posedge wclk or negedge wrstn) begin
if (!wrstn) begin
{wptr_bin,wptr_gray} <= 'd0;
end
else
{wptr_bin,wptr_gray} <= {wptr_bin_next, wptr_gray_next};
end
always @ (posedge rclk or negedge rrstn) begin
if (!rrstn) begin
{rptr_bin,rptr_gray} <= 'd0;
end
else
{rptr_bin,rptr_gray} <= {rptr_bin_next, rptr_gray_next};
end
/***************************************Full Logic*****************************************/
reg [ADDR_WIDTH:0] rptr_gray_wr,rptr_gray_wr2;
always @ (posedge wclk or negedge wrstn) begin
if (!wrstn) begin
{rptr_gray_wr,rptr_gray_wr2} <= 'd0;
end
else
{rptr_gray_wr2,rptr_gray_wr} <= {rptr_gray_wr,rptr_gray};
end
assign wfull = wptr_gray == {~rptr_gray_wr2[ADDR_WIDTH:ADDR_WIDTH-1], rptr_gray_wr2[ADDR_WIDTH-2:0]};
/***************************************Empty Logic*****************************************/
reg [ADDR_WIDTH:0] wptr_gray_rr,wptr_gray_rr2;
always @ (posedge rclk or negedge rrstn) begin
if (!rrstn) begin
{wptr_gray_rr2,wptr_gray_rr} <= 'd0;
end
else
{wptr_gray_rr2,wptr_gray_rr} <= {wptr_gray_rr,wptr_gray};
end
assign rempty = rptr_gray == wptr_gray_rr2;
/***************************************Dual RAM*****************************************/
wire wenc, renc;
wire [ADDR_WIDTH-1:0] raddr, waddr;
assign wenc = winc & !wfull;
assign renc = rinc & !rempty;
assign raddr = rptr_bin[ADDR_WIDTH-1:0];
assign waddr = wptr_bin[ADDR_WIDTH-1:0];
dual_port_RAM #(.DEPTH(DEPTH), .WIDTH(WIDTH))
u_dual_port_RAM (
.wclk(wclk),
.rclk(rclk),
.wenc(wenc),
.renc(renc),
.raddr(raddr),
.waddr(waddr),
.wdata(wdata),
.rdata(rdata)
);
/***************************************AFIFO*****************************************/
endmodule