题解 | #异步FIFO#

异步FIFO

https://www.nowcoder.com/practice/40246577a1a04c08b3b7f529f9a268cf

`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
);

//这是参照题解中"周周~"的答案的,基本就是在她的基础上进行理解了下,逻辑看起来不难,但是要我自己写的话估计要找一天bug了

//分为5部分

parameter ADDR_WIDTH =$clog2(DEPTH);//对储存器深度取2对数,这是为了得到地址的位宽,后面的地址还要+1这样就得到了有高低位的地址(就格雷码那个表,比原地址位宽多一)

reg [ADDR_WIDTH:0] waddr_bin;
reg [ADDR_WIDTH:0] raddr_bin;//这分别是读,取地址,后面判断读写操作地址和读空写满操作

//1.1、对读地址进行写判断,
always @(posedge wclk or negedge wrstn) begin
	if(~wrstn)
	waddr_bin <= 'd0;
	else if(!wfull && winc)begin  //没有写满标志并且写使能拉高则写地址加1
		waddr_bin <= waddr_bin +1'd1;
	end
end

//1.2、对读地址进行读判断
always @(posedge rclk or negedge rrstn) begin
	if(~rrstn)
	raddr_bin <= 'd0;
	else if(!rempty && rinc)begin  //没有读满标志并且读使能拉高则写地址加1
		raddr_bin <= raddr_bin +1'd1;
	end
end


//2、将地址转为格雷码
wire [ADDR_WIDTH:0] waddr_gray;
wire [ADDR_WIDTH:0] raddr_gray;//这是中间变量过程

reg [ADDR_WIDTH:0] wptr;
reg [ADDR_WIDTH:0] rptr;//这是最后可用的格雷码,我在想为什么要经过一个中间过程,不能直接去掉上面的中间变量吗


assign waddr_gray = waddr_bin ^(waddr_bin>>1);//格雷码等于和右移1位的自身异或
assign raddr_gray = raddr_bin ^(raddr_bin>>1);

/*试了下直接这样写不行
assign wptr = waddr_gray;
assign rptr = raddr_gray; 

*/


always @(posedge wclk or negedge wrstn) begin
	if(~wrstn)
	wptr <= 'd0;
	else 
	wptr <= waddr_gray;
end

always @(posedge rclk or negedge rrstn) begin
	if(~rrstn)
	rptr <= 'd0;
	else 
	rptr <= raddr_gray;
end

//3、对地址打两拍操作,消除亚稳态

reg [ADDR_WIDTH:0] wptr_buff;
reg [ADDR_WIDTH:0] rptr_buff;
reg [ADDR_WIDTH:0] wptr_syn;
reg [ADDR_WIDTH:0] rptr_syn;

always @(posedge rclk or negedge rrstn) begin //这里注意因为是写指针同步到读时钟域,所以这里的时钟用读时钟,下面同理
	if(~rrstn)begin
		wptr_syn <= 'd0;
		wptr_buff <= 'd0;
	end
	else begin
		wptr_buff <= wptr;
		wptr_syn <= wptr_buff;
	end
end


always @(posedge wclk or negedge wrstn) begin
	if(~wrstn)begin
		rptr_syn <= 'd0;
		rptr_buff <= 'd0;
	end
	else begin
		rptr_buff <= rptr;
		rptr_syn <= rptr_buff;
	end
end


//4、判断写满和读空标志,读写地址若最高两位相反,其他位相同则写满,也就是写已经在第二个周期了读还是第一个周期
assign wfull = (wptr =={~rptr_syn[ADDR_WIDTH:ADDR_WIDTH-1],rptr_syn[ADDR_WIDTH-2:0]});

assign rempty =(rptr==wptr_syn);//若读写地址全都一样则读空了,读写都在一个周期


//5、对ram进行读写操作
wire wen,ren;
wire wren;
wire [ADDR_WIDTH-1:0] waddr,raddr;

assign wen = winc && !wfull;//这是读写使能端,写使能端和非满时写使能端拉高,则可以写入数据
assign ren = rinc && !rempty;

assign waddr = waddr_bin[ADDR_WIDTH-1:0]; //因为前面的地址是在真实位宽的地址加了一位最高位,所以真实写入ram的时候要去掉最高位,个人理解前面多加一个最高位是为了判断读写是不是在同一个周期,例如虽然读写真实地址位相同,但写地址已经进行了第二轮而写地址才在第一轮,这和读写都在第一轮混淆了无法判断,所以加一个最高位
assign raddr = raddr_bin[ADDR_WIDTH-1:0];

dual_port_RAM inst(
	.wclk(wclk),
    .wenc(wen),  
    .waddr(waddr),
    .wdata(wdata) ,
    .rclk(rclk) ,
    .renc(ren),
    .raddr(raddr)     ,
    .rdata(rdata)
);


    
endmodule

全部评论

相关推荐

牛客583549203号:腾讯还好,况且实习而已,实习生流动性很大,属于正常现象,记得和HR委婉解释
点赞 评论 收藏
分享
评论
点赞
收藏
分享

创作者周榜

更多
牛客网
牛客企业服务