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

1.序言

(1)什么是FIFO

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

(2)FIFO分类

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

2.异步FIFO

(1)序言

本文实现的是口异步fifo,具有异步读写数据功能。在输入输出设备各自的时钟驱动下,在使能信号有效时,对数据进行写入和读出,并保证输入设备先写入的数据在读出端先读出。

(2)接口说明(异步fifo读写操作分别位于不同的时钟域)

端口名I/O描述
端口名I/O描述
wclkinput写数据时钟,所有写数据操作在此时钟域下进行
wrstinput写数据复位,写数据操作的复位信号
wdatainput写入的数据
wincinput写入数据使能信号
wfulloutput存储空间写满反馈信号
rclkinput读数据时钟,所有读数据操作在此时钟域下进行
rrstinput读数据复位,读数据操作的复位信号
rdataoutput读出的数据
rincinput读出数据使能信号
remptyoutput存储空间读空反馈信号

在这里插入图片描述

(3)代码实现

A)序言

FIFO由双端口RAM存储器件外围控制电路构成:
a)双端口RAM存储器件存储空间配置读写操作电路部分实现;
b)外围控制电路读写使能信号产生(wenc,renc)读写操作地址产生(waddr,raddr)读写操作地址指针产生(wptr_g,rptr_g)地址跨时钟传输模块(wptr_r2w,rptr_r2w)写满/读空信号产生(wfull,rempty)部分实现。
(本文采用高位扩展法后续地址跨时钟传输模块会详细介绍)
在这里插入图片描述

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);
注意:
a)写入数据操作时:数据能够写入需要满足两个条件,第一个是写入数据有效(即写入数据使能信号winc为高);第二个是当前存储空间还有空位置,即存储空间没有被写满(full为低)。
b)读出数据操作时,数据能够读出需要满足两个条件:第一个是读出数据使能信号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)所以实际的数据地址应该为地址指针除去最高位。
d)写入数据和读出数据分别由对应时钟域的时钟和复位信号控制;
实现:

//读写操作地址产生
	reg [$clog2(DEPTH):0] waddr_ptr_b;
	reg [$clog2(DEPTH):0] raddr_ptr_b;
	wire [$clog2(DEPTH)-1:0] waddr;
	wire [$clog2(DEPTH)-1:0] raddr;
	always@(posedge wclk or negedge wrstn)begin
		if(!wrstn)
			waddr_ptr_b <= 'd0;
		else if(wenc)
			waddr_ptr_b <= waddr_ptr_b + 1'b1;
		else
			waddr_ptr_b <= waddr_ptr_b;
	end
	always@(posedge rclk or negedge rrstn)begin
		if(!rrstn)
			raddr_ptr_b <= 'd0;
		else if(wenc)
			raddr_ptr_b <= raddr_ptr_b + 1'b1;
		else
			raddr_ptr_b <= raddr_ptr_b;
	end
	//实际的读写操作地址
	assign waddr = waddr_ptr_b[$clog2(DEPTH)-1:0];
	assign raddr = raddr_ptr_b[$clog2(DEPTH)-1:0];

F)格雷编码和高位扩展法(产生写满和读空信号)

(a)跨时钟域数据传输与格雷编码

跨时钟域数据传输:在产生写满和读空信号时,需要比对当前写数据地址读数据地址,但是异步fifo中这两个数据处于不同的时钟域(由不同时钟触发)。两个时钟域的数据不能直接比较,因为两个时钟互不相关,不知道哪个快哪个慢、两者之间的相位差,直接比较有可能出现亚稳态。所以我们在比较前会将地址转换到需要操作的时钟域下。这里我们使用的方法是######打两拍####,对于不同类型的数据有不同的跨时钟域数据传输方法,这里不再赘述;
亚稳态:在异步时序逻辑电路中,在进行跨时钟域同步时,由于不同时钟时钟域的时钟存在相位差,这个相位差是变化的,会导致采样时不能满足建立时间和保持时间,使电路输出处于一个未知的状态(可能是一个中间值或者是一个震荡的值);
格雷编码:格雷码的特点是相邻两个状态,只有一位发生变化,跨时钟域采样时,也降低了亚稳态的概率;所以我们进行写满读空状态判断的地址指针,采用的是格雷编码,所以要将######地址指针变换为格雷码####

(b)高位扩展法(以数据存储深度16为例)

数据写入和读出方式:
存储深度为16,对应16个存储单元,对应地址为0~15,需要用一个4位宽的二进制数进行指示。
在写入数据时,会从最小地址存储单元到最大地址存储单元依次写入数据,当写完最后一个存储单元后,又会从最小地址存储单元开始(前提是这个单元数据已经被读出了);
在读出数据时,会从最先写入数据的存储单元往后依次读出数据;
在这里插入图片描述
当读和写操作地址指针指向单元,对应着两种情况,第一种是写数据操作已经进行了一周,即写数据地址已经超过读数据地址一周了,即写满状态;第二种是所有被写入的数据均已经被读出去了,即读空状态;所以为了区别指示这两种状态,将地址指针拓展了一位
格雷码地址指针表如下图所示:
在这里插入图片描述

写满和读空状态判断依据,总结表中第一列和第三列数据可以得到(格雷码相差地址指针相差一轮,即16):
当写数据地址指针,和读数据地址指针,最高两位不同,低三维相同时,为写满状态(写地址指针超前读地址指针一周);
当写数据地址指针和读数据地址指针完全相同时,为读空状态(读数据地址已经赶上了写数据地址);

©实现

步骤1:二进制编码变格雷码
方法:从最右边一位起,依次将每一位与左边一位异或(XOR),作为对应格雷码该位的值,最左边一位不变(0与任何值异或,这个值等于本身);

	//二进制编码变格雷编码(最高位0异或任何数等于本身)
	wire [$clog2(DEPTH):0] waddr_ptr_g;
	wire [$clog2(DEPTH):0] raddr_ptr_g;
	assign waddr_ptr_g = (waddr_ptr_b>>1) ^ waddr_ptr_b;     
	assign raddr_ptr_g = (raddr_ptr_b>>1) ^ raddr_ptr_b;   

步骤2:跨时钟域数据传输(在被比较的时钟域打两拍)

	//使用被比较的时钟域的时钟,打两拍
	reg [$clog2(DEPTH):0] wptr_w2r_reg;
	reg [$clog2(DEPTH):0] wptr_w2r;	
	reg [$clog2(DEPTH):0] rptr_r2w_reg;
	reg [$clog2(DEPTH):0] rptr_r2w;	
	//写数据格雷码地址同步到读数据时钟域
	always@(posedge rclk or negedge rrstn)begin
		if(!rrstn)begin
			wptr_w2r_reg <= 'd0;
			wptr_w2r <= 'd0;
		end
		else begin
			wptr_w2r_reg <= waddr_ptr_g;
			wptr_w2r <= wptr_w2r_reg;
		end
	end
	//读数据格雷码地址同步到写数据时钟域
	always@(posedge rclk or negedge rrstn)begin
		if(!rrstn)begin
			rptr_r2w_reg <= 'd0;
			rptr_r2w <= 'd0;
		end
		else begin
			rptr_r2w_reg <= raddr_ptr_g;
			rptr_r2w <= rptr_r2w_reg;
		end
	end

步骤3:写满和读空信号产生

	assign 	wfull = ({(~rptr_r2w[$clog2(DEPTH):($clog2(DEPTH)-1)]),rptr_r2w[($clog2(DEPTH)-2):0]}==waddr_ptr_g)?1'b1:1'b0;
	assign rempty = (wptr_w2r == raddr_ptr_g)?1'b1:1'b0;

G)两级D触发器的引入对fifo性能的影响

a)现象描述
写满信号判断时,需要将当前写数据地址指针和使用写数据时钟域的时钟打了两拍的读数据地址指针进行比较,即当前时刻的写数据地址指针和两个时钟之前的读数据地址指针进行比较;
读空信号判断时,需要将当前读数据地址指针和使用读数据时钟域的时钟打了两拍的写数据地址指针进行比较,即当前时刻的读数据地址指针和两个时钟之前的写数据地址指针进行比较;
b)结果
会降低fifo的效率,不会影响fifo的功能;
c)原因
写满信号判断时:这种情况一定是此时写数据时钟快于读数据时钟,读数据地址指针为两个写数据时钟之前的读数据地址指针,而实际的读数据地址指针是两个写数据时钟后的读数据地址指针(即多读了两拍写数据时钟的数据),所以这是一种将满的状态,把将满状态判断为已经写满状态,这时fifo的利用率就下降了也变慢了,但是这种情况能提前给前端设备提供写满信号,让其停止传输数据,用少传数据的方式来避免多传数据,因为多传数据,数据未存入存储空间,数据会丢失。
读空信号判断时:这种情况一定是此时读数据时钟快于写数据时钟,写数据地址指针为两个读数据时钟之前的写数据地址指针,而实际的写数据地址指针是两个读数据时钟后的写数据地址指针(即多写了两拍读数据时钟的数据),所以这是一种将空的状态,把将空状态判断为已经读空状态,这时fifo的利用率就下降了也变慢了,但是这种情况能提前给后端设备提供读空信号,让其停止读取数据,用少读数据的方式来避免多读数据,因为多读数据,有的数据被读了多次或者有的空间根本没有数据,输出数据会出现很多多余数据或乱码。少读的数据只是暂时不读,数据仍然在存储空间,后续存入新的数据后,这些数据会被正常读出。

3.异步FIFO完整代码

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

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 = 0 		//数据输出
);

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
);
    //读写数据使能信号
	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_b;
	reg [$clog2(DEPTH):0] raddr_ptr_b;
	wire [$clog2(DEPTH)-1:0] waddr;
	wire [$clog2(DEPTH)-1:0] raddr;
	always@(posedge wclk or negedge wrstn)begin
		if(!wrstn)
			waddr_ptr_b <= 'd0;
		else if(wenc)
			waddr_ptr_b <= waddr_ptr_b + 1'b1;
		else
			waddr_ptr_b <= waddr_ptr_b;
	end
	always@(posedge rclk or negedge rrstn)begin
		if(!rrstn)
			raddr_ptr_b <= 'd0;
		else if(renc)
			raddr_ptr_b <= raddr_ptr_b + 1'b1;
		else
			raddr_ptr_b <= raddr_ptr_b;
	end
	//实际的读写操作地址
	assign waddr = waddr_ptr_b[$clog2(DEPTH)-1:0];
	assign raddr = raddr_ptr_b[$clog2(DEPTH)-1:0];
	//二进制编码变格雷编码(最高位0异或任何数等于本身)
	wire [$clog2(DEPTH):0] waddr_ptr_g;
	wire [$clog2(DEPTH):0] raddr_ptr_g;
	assign waddr_ptr_g = (waddr_ptr_b>>1) ^ waddr_ptr_b;     
	assign raddr_ptr_g = (raddr_ptr_b>>1) ^ raddr_ptr_b;
	// 牛客网代码需要修改,时域打一拍
	// reg [$clog2(DEPTH):0] waddr_ptr_g;
	// reg [$clog2(DEPTH):0] raddr_ptr_g;
	// always@(posedge wclk or negedge wrstn)begin
	// 	if(!wrstn)
	// 		waddr_ptr_g <= 'd0;
	// 	else
	// 		waddr_ptr_g <= (waddr_ptr_b>>1) ^ waddr_ptr_b;
	// end
	// always@(posedge rclk or negedge rrstn)begin
	// 	if(!rrstn)
	// 		raddr_ptr_g <= 'd0;
	// 	else
	// 		raddr_ptr_g <= (raddr_ptr_b>>1) ^ raddr_ptr_b;
	// end
	
	//使用被比较的时钟域的时钟,打两拍
	reg [$clog2(DEPTH):0] wptr_w2r_reg;
	reg [$clog2(DEPTH):0] wptr_w2r;	
	reg [$clog2(DEPTH):0] rptr_r2w_reg;
	reg [$clog2(DEPTH):0] rptr_r2w;	
	//写数据格雷码地址同步到读数据时钟域
	always@(posedge rclk or negedge rrstn)begin
		if(!rrstn)begin
			wptr_w2r_reg <= 'd0;
			wptr_w2r <= 'd0;
		end
		else begin
			wptr_w2r_reg <= waddr_ptr_g;
			wptr_w2r <= wptr_w2r_reg;
		end
	end
	//读数据格雷码地址同步到写数据时钟域
	always@(posedge wclk or negedge wrstn)begin
		if(!wrstn)begin
			rptr_r2w_reg <= 'd0;
			rptr_r2w <= 'd0;
		end
		else begin
			rptr_r2w_reg <= raddr_ptr_g;
			rptr_r2w <= rptr_r2w_reg;
		end
	end
	//写满和读空信号产生模块(组合逻辑测试先######)
	assign 	wfull = ({(~rptr_r2w[$clog2(DEPTH):($clog2(DEPTH)-1)]),rptr_r2w[($clog2(DEPTH)-2):0]}==waddr_ptr_g)?1'b1:1'b0;
	assign rempty = (wptr_w2r == raddr_ptr_g)?1'b1:1'b0;
	//模块例化
	dual_port_RAM #(
		.DEPTH(DEPTH),
		.WIDTH(WIDTH)
	)
	u_dual_port_RAM(
		.wclk	(wclk),
		.wenc	(wenc),
		.waddr  (waddr),//深度对2取对数,得到地址的位宽。
		.wdata  (wdata),    	//数据写入
		.rclk	(rclk),
		.renc	(renc),
		.raddr  (raddr),//深度对2取对数,得到地址的位宽。
		.rdata 	(rdata)	//数据输出
	);
endmodule

(2)牛客网通过提交代码

注意
牛客网代码相较于前面分析的代码,操作有一个地方不同:就是将二进制地址指针转换为格雷码地址指针时,用的是时序逻辑电路,相当于延迟了一拍;

`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
);
    //读写数据使能信号
	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_b;
	reg [$clog2(DEPTH):0] raddr_ptr_b;
	wire [$clog2(DEPTH)-1:0] waddr;
	wire [$clog2(DEPTH)-1:0] raddr;
	always@(posedge wclk or negedge wrstn)begin
		if(!wrstn)
			waddr_ptr_b <= 'd0;
		else if(wenc)
			waddr_ptr_b <= waddr_ptr_b + 1'b1;
		else
			waddr_ptr_b <= waddr_ptr_b;
	end
	always@(posedge rclk or negedge rrstn)begin
		if(!rrstn)
			raddr_ptr_b <= 'd0;
		else if(renc)
			raddr_ptr_b <= raddr_ptr_b + 1'b1;
		else
			raddr_ptr_b <= raddr_ptr_b;
	end
	//实际的读写操作地址
	assign waddr = waddr_ptr_b[$clog2(DEPTH)-1:0];
	assign raddr = raddr_ptr_b[$clog2(DEPTH)-1:0];
	// //二进制编码变格雷编码(最高位0异或任何数等于本身)
	// wire [$clog2(DEPTH):0] waddr_ptr_g;
	// wire [$clog2(DEPTH):0] raddr_ptr_g;
	// assign waddr_ptr_g = (waddr_ptr_b>>1) ^ waddr_ptr_b;     
	// assign raddr_ptr_g = (raddr_ptr_b>>1) ^ raddr_ptr_b;
	// 牛客网代码需要修改,时域打一拍
	reg [$clog2(DEPTH):0] waddr_ptr_g;
	reg [$clog2(DEPTH):0] raddr_ptr_g;
	always@(posedge wclk or negedge wrstn)begin
		if(!wrstn)
			waddr_ptr_g <= 'd0;
		else
			waddr_ptr_g <= (waddr_ptr_b>>1) ^ waddr_ptr_b;
	end
	always@(posedge rclk or negedge rrstn)begin
		if(!rrstn)
			raddr_ptr_g <= 'd0;
		else
			raddr_ptr_g <= (raddr_ptr_b>>1) ^ raddr_ptr_b;
	end
	
	//使用被比较的时钟域的时钟,打两拍
	reg [$clog2(DEPTH):0] wptr_w2r_reg;
	reg [$clog2(DEPTH):0] wptr_w2r;	
	reg [$clog2(DEPTH):0] rptr_r2w_reg;
	reg [$clog2(DEPTH):0] rptr_r2w;	
	//写数据格雷码地址同步到读数据时钟域
	always@(posedge rclk or negedge rrstn)begin
		if(!rrstn)begin
			wptr_w2r_reg <= 'd0;
			wptr_w2r <= 'd0;
		end
		else begin
			wptr_w2r_reg <= waddr_ptr_g;
			wptr_w2r <= wptr_w2r_reg;
		end
	end
	//读数据格雷码地址同步到写数据时钟域
	always@(posedge wclk or negedge wrstn)begin
		if(!wrstn)begin
			rptr_r2w_reg <= 'd0;
			rptr_r2w <= 'd0;
		end
		else begin
			rptr_r2w_reg <= raddr_ptr_g;
			rptr_r2w <= rptr_r2w_reg;
		end
	end
	//写满和读空信号产生模块(组合逻辑测试先######)
	assign 	wfull = ({(~rptr_r2w[$clog2(DEPTH):($clog2(DEPTH)-1)]),rptr_r2w[($clog2(DEPTH)-2):0]}==waddr_ptr_g)?1'b1:1'b0;
	assign rempty = (wptr_w2r == raddr_ptr_g)?1'b1:1'b0;
	//模块例化
	dual_port_RAM #(
		.DEPTH(DEPTH),
		.WIDTH(WIDTH)
	)
	u_dual_port_RAM(
		.wclk	(wclk),
		.wenc	(wenc),
		.waddr  (waddr),//深度对2取对数,得到地址的位宽。
		.wdata  (wdata),    	//数据写入
		.rclk	(rclk),
		.renc	(renc),
		.raddr  (raddr),//深度对2取对数,得到地址的位宽。
		.rdata 	(rdata)	//数据输出
	);
endmodule

4.异步FIFO深度计算

###注意:这个问题很重要,很多笔试、面试都会问##

(1)前提

保证在写入数据速率快于读出数据速率时,在突发传输的情况下,fifo 不会溢出(需要存储的数据量超出fifo的存储容量)。

(2)不采用握手机制

理论上,我们使用fifo缓存数据是基于握手机制,即当fifo被写满时,fifo向前端输入设备反馈写满信号,此时输入设备就会停止传输数据,直到fifo不处于写满状态时才会继续传入数据;但是,fifo深度计算考虑的是极端情况——不采用握手机制,即输入设备以突发的形式不断地传输数据,保证在整个数据传输过程中,fifo不会溢出。

(3)突发传输

原因:当写入数据速率一直快于读出数据速率时,如果数据一致连续不断地传,不采用握手机制,fifo一定会溢出,所以我们考虑的情况是突发传输,这与实际应用也更符合;
突发传输:相邻存储单元连续进行数据传输的方式,连续传输的周期数就是突发长度(数据个数有限)。

(4)问题的根本

只要保证在写入数据速率快于读出数据速率的前提下,以突发形式传输数据(连续传输有限个数据),在突发过程中不会出现fifo溢出的情况,即(三大表达式):
fifo被填满的时间 >= 突发传输的时间;
突发传输的时间 = 数据量/写入数据时钟速率;
FIFO被写满的时间 = FIFO深度/(写入数据速率 - 读出数据速率);

(5)例题

A)连续读写数据
写数据速率:fw = 60MHz;
读数据频率:fr = 50MHz;
突发传输长度:burst_l = 120;
解:
突发传输时间=120/60M;
fifo被写满的时间=fifo_deep/(60M-50M);
fifo被写满的时间 >= 突发传输时间;
解得:fifo_deep >= 20,所以fifo_deep = 20;
B)读写数据有空闲周期(前提实际的写数据速率大于实际的读数据速率
写数据速率:fw = 40MHz
读数据速率:fs = 50MHz
突发长度:burst_l = 120
写入数据间隔一个时钟周期;
读出数据间隔3个时钟周期;
解:
实际的写数据速率:fw = 40MHz/2
实际的读数据速率:fs = 50MHz/4
突发传输时间=120/(40M/2);
fifo被写满的时间=fifo_deep/(40M/2-50/4);
fifo被写满的时间 >= 突发传输时间;
解得:fifo_deep >= 45,所以fifo_deep = 45;

5.仿真

(1)test_bench(为3(1)的testbench,牛客网的程序我没有写测试代码)

`timescale 1ns / 1ps
module asyn_fifo_tb();
    reg  [7:0]      wdata               ;
    reg             winc, wclk, wrstn   ; 
    wire            wfull               ;  
    wire [7:0]      rdata               ;  
    reg             rinc, rclk, rrstn   ;
    wire            rempty              ;  
	parameter	    WIDTH   = 8         ;
	parameter 	    DEPTH   = 16        ;
    localparam      CYCLE   = 20        ;
    localparam      CYCLE1   = 80       ;
    //时钟信号产生:写时钟远大于读时钟
    initial begin
        wclk = 0;
        wdata = 0;
        forever
        #(CYCLE/2)
        wclk=~wclk;
    end
    initial begin
        rclk = 0;
        forever
        #(CYCLE1/2)
        rclk=~rclk;
    end
    //复位信号
    initial begin
        wrstn = 1;
        #2;
        wrstn = 0;
        #(CYCLE*3);
        wrstn = 1;
    end
    initial begin
        rrstn = 1;
        #2;
        rrstn = 0;
        #(CYCLE*3);
        rrstn = 1;
    end

    //模块例化
    asyn_fifo #(
	    .WIDTH(WIDTH ),
	    .DEPTH(DEPTH )
    )
    u_asyn_fifo(  
	    .wclk	(wclk), 
	    .rclk	(rclk),  
	    .wrstn	(wrstn),
	    .rrstn	(rrstn),
	    .winc	(winc),
	    .rinc	(rinc),
	    .wdata	(wdata),

	    .wfull	(wfull),
	    .rempty (rempty),
	    .rdata  (rdata )
        );


    //读写使能信号产生
        initial begin
            winc=0;
            #5 winc=1;
        end 

        initial begin
           rinc=0;
            #5 rinc=1;
        end

    //   #5 out_mem_rinc =1;
    //             always  @(posedge wclk or negedge wrst_n)begin
    //                 if(wrst_n==1'b0)begin
    //                     winc <= 0;
    //                     rinc <= 0;
    //                 end
    //                 else begin
    //                     winc <= $random % (2^8);
    //                     rinc <= $random % (2^8);
    //                 end
    //             end
    
    //             always  @(posedge rclk or negedge rrst_n)begin
    //                 if(rrst_n==1'b0)begin                  
    //                     rinc <= 0;
    //                 end
    //                 else begin                
    //                     rinc <= $random % (2^8);
    //                 end
    //             end
    // always@(*)begin
    //   if(winc == 1)
    //     in_mem_wdata= $random  % (2^8);
    //   else
    //     in_mem_wdata = 0;
    // end  
    
    //随机数的生成,产生写入的随机数
    always #30 wdata= $random % (2^8) ;


endmodule

仿真结果:
在这里插入图片描述
仿真说明:
仿真的情况:写数据速率为读数据速率的4倍,fifo存储数据位宽为8,深度为16;
注意:写入的数据是使用系统函数$ramdoms产生,所以可能有的连续两三个写时钟周期值不变,即连续写入了多个相同的值,这也是为什么读出的数据连续几个时钟读出的值相同;
在仿真时序的后期:因为写数据速率快于读数据速率,所以后存储空间被写满了,只有读出一个数据才能写入一个数据;

<think>好的,我需要帮助用户找到与Verilog相关的练习或学习资源,特别是牛客网的资料。首先,用户提到了牛客网Verilog答案目录,引用[1]里提到了基础篇和进阶篇的不同章节,比如序列检测、时序逻辑、跨时钟域传输等。这些应该是牛客网提供的练习分类,可以用来推荐给用户。 接下来,引用[2]和[3]涉及Verilog中函数和任务的区别,以及具体的模块设计例子。这说明牛客网目可能涵盖基础语法到实际应用,适合不同层次的学习者。用户可能需要从基础开始逐步深入,因此分阶段的资源推荐会比较合适。 根据用户的需求,我应该先整理牛客网的资源结构,然后建议学习路径,比如从基础篇开始,再进入进阶篇。同时,用户可能需要了解如何访问这些资源,比如直接搜索牛客网Verilog库,或者关注相关专栏。此外,用户可能对实战项目感兴趣,所以补充其他学习平台如EDA Playground和GitHub上的开源项目也是有帮助的。 需要注意回答的结构要清晰,分点列出,并且确保每个建议都有对应的引用支持。另外,生成的相关问需要围绕Verilog学习资源、基础语法练习、跨时钟域处理等用户可能进一步关心的主。</think>### Verilog学习资源及练习推荐 根据你的需求,牛客网提供了系统化的Verilog练习资源库,以下是具体推荐: 1. **牛客网Verilog库分类** - **基础篇**:包含组合逻辑设计、时序电路基础、状态机实现等入门内容,适合熟悉Verilog基本语法和模块化设计[^1] - **进阶篇**: - 序列检测(如:10110模式识别) - 时序逻辑设计(建立保持时间计算) - 跨时钟域处理(同步器设计) - 存储器建模(RAM/ROM实现)[^1] 2. **推荐学习路径** ```verilog // 示例:组合逻辑设计目 module top_module( input a,b,c,d, output e,f ); wire out_xor = (a & b) ^ (c ^ d); assign e = ~out_xor; assign f = out_xor | d; endmodule ``` 这类目训练基础语法和运算符使用[^3] 3. **实战训练平台** - 牛客网「硬件专」提供在线仿真环境 - 配套答案详解帮助理解设计思路 - 推荐完成率较高的目: - VL11 多位信号 - VL25 脉冲同步器 - VL33 异步FIFO设计 4. **补充学习资源** - EDA Playground在线仿真工具 - GitHub开源项目:verilog-design-patterns - 《Verilog数字系统设计教程》配套实验
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值