【牛客网verilog刷题】跨时钟域传输,VL46同步FIFO(详细讲解了同步fifo)

1.序言

(1)什么是FIFO

FIFO(First in,First out),先入先出缓冲器,对数据起缓冲作用,常用在数据的跨时钟域传输。

(2)FIFO分类

FIFO由数据存取电路外围的控制电路构成,通过外围控制电路将需要写入的数据写入存储器件,并在需要时将数据读出。
写入数据操作读出数据操作分别在写数据时钟读数据时钟的控制下进行。当这两个时钟为同一个时,我们称之为同步FIFO;当这两个时钟不是同一个时,我们称之为异步FIFO。(异步fifo涉及到跨时钟域数据传输问题,设计更为复杂)
在这里插入图片描述

2.同步FIFO

(1)接口说明(同步fifo同用一个时钟和复位)

端口名I/O描述
端口名I/O描述
clkinput系统时钟,读写操作共用
rst_ninput系统复位,读写操作共用
wdatainput写入的数据
wincinput写入数据使能信号
wfulloutput存储空间写满反馈信号
rdataoutput读出的数据
rincinput读出数据使能信号
wemptyoutput存储空间读空反馈信号

在这里插入图片描述

(2)代码实现

A)序言
FIFO由数据存取电路外围的控制电路构成。数据存取电路由存储空间配置读写操作电路实现,外围控制电路由读写使能信号产生,读写操作地址产生写满/读空信号产生两部分实现。(本文采用高位扩展法实现写满/读空信号产生,还有一种计数器的方法本文没有说明)
B)存储空间配置
作用:根据需要存取数据的位宽和深度分配一个存储空间。用来存储写入的数据。
注意:为了适应不同的数据位宽存储深度,我们通常将位宽和深度参数化,使其可配置。即在调用此fifo函数时,通过传参实现不同数据位宽存储深度的fifo。需要注意的是,fifo存储深度通常给的参数是10进制,就需要根据10进制数计算所需要的二进制存储空间,使用了系统函数**$clog2**,比如当存储深度为16时,我们所需要的数据地址位宽为log216-1,所以定义数据位宽:

	input [$clog2(DEPTH)-1:0] waddr;

实现:

	reg [WIDTH-1:0] RAM_MEM [0:DEPTH-1];

C)读写操作电路
作用:当读写使能信号有效时,根据当前需要读出或写入数据的地址,进行读写数据操作。
实现:

always @(posedge wclk) begin
	if(wenc)
		RAM_MEM[waddr] <= wdata;
end 

always @(posedge rclk) begin
	if(renc)
		rdata <= RAM_MEM[raddr];
end 

D)读写使能信号产生
作用:提供给读写操作电路读写数据操作有效信号(wenc,renc),
写入数据操作时:数据能够写入需要满足两个条件,第一个是写入数据有效(即写入数据使能信号winc为高);第二个是当前存储空间还有空位置,即存储空间没有被写满(full为低)。
读出数据操作时,数据能够读出需要满足两个条件:第一个是读出数据使能信号rinc为高;第二个是当前存储空间还有没有被读出的数据,即存储空间没有被读空(empty为低)。
实现:

	//读写操作有效信号
	wire wenc;
	wire renc;
	assign wenc = (winc && (!wfull))?1'b1:1'b0;
	assign renc = (rinc && (!rempty))?1'b1:1'b0;

E)读写操作地址
作用:提供读写操作数据的读写地址;
注意:a)当读写操作有效信号有效,就会按照当前地址读写数据,说明地址是提前准备好的,所以读写一个数据的同时,需要准备好下一时刻的数据地址;
时序图:(以写数据操作为例)
在这里插入图片描述
b)为了方便产生写满信号读空信号,将读/写数据地址扩展了一位(高位扩展法)构成地址指针,高位扩展法在下一步详细说明;
c)所以实际的数据地址应该为地址指针除去最高位。
实现:

	//读写操作地址指针
	reg [$clog2(DEPTH):0] waddr_ptr;
	reg [$clog2(DEPTH):0] raddr_ptr;	
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			waddr_ptr <= 1'b0;
		else if(wenc)
			waddr_ptr <= waddr_ptr +1'b1;
		else
			waddr_ptr <= waddr_ptr;
	end
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			raddr_ptr <= 1'b0;
		else if(renc)
			raddr_ptr <= raddr_ptr +1'b1;
		else
			raddr_ptr <= raddr_ptr;
	end
	//实际的地址为地址指针除去最高位;
	assign waddr = waddr_ptr[$clog2(DEPTH)-1:0];
	assign raddr = raddr_ptr[$clog2(DEPTH)-1:0];

F)写满/读空信号产生
作用:
指示当前存储空间的状态,是否被写满或读空;
说明:#############关键知识点###############
实际的读写数据地址位宽为[WIDTH-1:0],但是上一步我们给的数据地址指针(raddr_ptr,waddr_ptr)位宽为[WIDTH:0],拓展了一位(后面称为拓展位),这一位就是为了方便产生写满和读空信号。如下图所示,数据进行写入和读出的数据地址从小到大递增,当操作到最后一个地址后,又会再一次跳到0000。当读和写操作地址指针指向同一个存储单元,其实有两种可能性,第一种是所有被写入的数据均已经被读出去了,即读空状态;第二种是写数据操作已经进行了一周,即写数据地址已经超过读数据地址一周了,即写满状态。所以地址指针通过增加拓展位,来区分读空和写满状态。
读空写满信号判断依据
读空信号:读写地址指针完全一致;
写满信号:读写地址指针,只有拓展位不同。
在这里插入图片描述
代码:

	//读空写满信号产生模块
	assign rempty = (waddr_ptr == raddr_ptr)?1'b1:1'b0;
	assign wfull = ((waddr_ptr[$clog2(DEPTH)] != raddr_ptr[$clog2(DEPTH)])&&((waddr_ptr[$clog2(DEPTH)-1:0] == raddr_ptr[[$clog2(DEPTH)-1:0]])))?1'b1:1'b0;

3.同步完整代码

(1)完整代码(前面说明的代码也是基于此的)

`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  

/**********************************SFIFO************************************/
module sfifo#(
	parameter	WIDTH = 8,
	parameter 	DEPTH = 16
)(
	input 					clk		, 
	input 					rst_n	,
	input 					winc	,
	input 			 		rinc	,
	input 		[WIDTH-1:0]	wdata	,

	// output reg				wfull	,
	// output reg				rempty	,
	output 				wfull	,
	output 				rempty	,
	output wire [WIDTH-1:0]	rdata
);
	//读写操作有效信号
	wire wenc;
	wire renc;
	assign wenc = (winc && (!wfull))?1'b1:1'b0;
	assign renc = (rinc && (!rempty))?1'b1:1'b0;
	//读写操作地址指针
	reg [$clog2(DEPTH):0] waddr_ptr;
	reg [$clog2(DEPTH):0] raddr_ptr;	
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			waddr_ptr <= 1'b0;
		else if(wenc)
			waddr_ptr <= waddr_ptr +1'b1;
		else
			waddr_ptr <= waddr_ptr;
	end
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			raddr_ptr <= 1'b0;
		else if(renc)
			raddr_ptr <= raddr_ptr +1'b1;
		else
			raddr_ptr <= raddr_ptr;
	end
	// //读空写满信号产生模块
	// always@(posedge clk or negedge rst_n)begin
	// 	if(!rst_n)
	// 		rempty <= 1'b0;
	// 	else if(waddr_ptr == raddr_ptr)
	// 		rempty <= 1'b1;
	// 	else
	// 		rempty <= 1'b0;
	// end
	// always@(posedge clk or negedge rst_n)begin
	// 	if(!rst_n)
	// 		wfull <= 1'b0;
	// 	else if((waddr_ptr[$clog2(DEPTH)] != raddr_ptr[$clog2(DEPTH)])&&((waddr_ptr[$clog2(DEPTH)-1:0] == raddr_ptr[$clog2(DEPTH)-1:0])))
	// 		wfull <= 1'b1;
	// 	else
	// 		wfull <= 1'b0;
	// end
	assign rempty = (waddr_ptr == raddr_ptr)?1'b1:1'b0;
	assign wfull = ((waddr_ptr[$clog2(DEPTH)] != raddr_ptr[$clog2(DEPTH)])&&((waddr_ptr[$clog2(DEPTH)-1:0] == raddr_ptr[$clog2(DEPTH)-1:0])))?1'b1:1'b0;
	//实际的数据地址
	wire [3:0]	waddr;
	wire [3:0]	raddr;
	assign waddr = waddr_ptr[$clog2(DEPTH)-1:0];
	assign raddr = raddr_ptr[$clog2(DEPTH)-1:0];
	
	//模块例化
	dual_port_RAM #(
		.DEPTH(DEPTH),
		.WIDTH(WIDTH)
	)
	u_dual_port_RAM
	(
		.wclk	(clk	),
		.wenc	(wenc	),
		.waddr  (waddr	),
		.wdata	(wdata	),
		.rclk	(clk	),
		.renc	(renc	),
		.raddr  (raddr	),
		.rdata 	(rdata)	
);
endmodule

(2)牛客网提交通过的代码(为提交通过做了修改)

说明:
牛客网的提交通过的代码存在bug,即存在和实际情况不符合的地方:
(1)牛客网的读空和写满信号是reg类型,且相较于正常情况延迟了一拍,当读写地址指一致的时候(读空),读出使能信号先到,会误读一位数据;
(2)牛客网的读空和写满信号是reg类型,且读空信号复位为0才能通过,但是实际情况是系统复位清空,存储空间没有数据,所以实际读空信号应该复位为1。
牛客网完整代码:

`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  

/**********************************SFIFO************************************/
module sfifo#(
	parameter	WIDTH = 8,
	parameter 	DEPTH = 16
)(
	input 					clk		, 
	input 					rst_n	,
	input 					winc	,
	input 			 		rinc	,
	input 		[WIDTH-1:0]	wdata	,

	output reg				wfull	,
	output reg				rempty	,
	// output 				wfull	,
	// output 				rempty	,
	output wire [WIDTH-1:0]	rdata
);
	//读写操作有效信号
	wire wenc;
	wire renc;
	assign wenc = (winc && (!wfull))?1'b1:1'b0;
	assign renc = (rinc && (!rempty))?1'b1:1'b0;
	//读写操作地址指针
	reg [4:0] waddr_ptr;
	reg [4:0] raddr_ptr;	
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			waddr_ptr <= 1'b0;
		else if(wenc)
			waddr_ptr <= waddr_ptr +1'b1;
		else
			waddr_ptr <= waddr_ptr;
	end
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			raddr_ptr <= 1'b0;
		else if(renc)
			raddr_ptr <= raddr_ptr +1'b1;
		else
			raddr_ptr <= raddr_ptr;
	end
	//读空写满信号产生模块
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			rempty <= 1'b0;
		else if(waddr_ptr == raddr_ptr)
			rempty <= 1'b1;
		else
			rempty <= 1'b0;
	end
	always@(posedge clk or negedge rst_n)begin
		if(!rst_n)
			wfull <= 1'b0;
		else if((waddr_ptr[$clog2(DEPTH)] != raddr_ptr[$clog2(DEPTH)])&&((waddr_ptr[$clog2(DEPTH)-1:0] == raddr_ptr[$clog2(DEPTH)-1:0])))
			wfull <= 1'b1;
		else
			wfull <= 1'b0;
	end
	// assign rempty = (waddr_ptr == raddr_ptr)?1'b1:1'b0;
	// assign wfull = ((waddr_ptr[$clog2(DEPTH)] != raddr_ptr[$clog2(DEPTH)])&&((waddr_ptr[$clog2(DEPTH)-1:0] == raddr_ptr[$clog2(DEPTH)-1:0])))?1'b1:1'b0;
	//实际的数据地址
	wire [3:0]	waddr;
	wire [3:0]	raddr;
	assign waddr = waddr_ptr[$clog2(DEPTH)-1:0];
	assign raddr = raddr_ptr[$clog2(DEPTH)-1:0];
	
	//模块例化
	dual_port_RAM #(
		.DEPTH(DEPTH),
		.WIDTH(WIDTH)
	)
	u_dual_port_RAM
	(
		.wclk	(clk	),
		.wenc	(wenc	),
		.waddr  (waddr	),
		.wdata	(wdata	),
		.rclk	(clk	),
		.renc	(renc	),
		.raddr  (raddr	),
		.rdata 	(rdata)	
);
endmodule
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值