【Verilog学习日常】—牛客网刷题—Verilog企业真题—VL68

同步FIFO

描述

请设计带有空满信号的同步FIFO ,FIFO的深度和宽度可配置。双口RAM的参考代码和接口信号已给出,请在答案中添加并例化此部分代码。

电路的接口如下图所示。端口说明如下表。

接口电路图如下:

双口RAM端口说明:

|-----------|-----------|---------|
| 端口名 | I/O | 描述 |
| wclk | input | 写数据时钟 |
| wenc | input | 写使能 |
| waddr | input | 写地址 |
| wdata | input | 输入数据 |
| rclk | input | 读数据时钟 |
| renc | input | 读使能 |
| raddr | input | 读地址 |
| rdata | output | 输出数据 |

同步FIFO端口说明:

|------------|---------|--------|
| 端口名 | I/O | 描述 |
| clk | input | 时钟 |
| rst_n | input | 异步复位 |
| winc | input | 写使能 |
| rinc | input | 读使能 |
| wdata | input | 写数据 |
| wfull | output | 写满信号 |
| rempty | output | 读空信号 |
| rdata | output | 读数据 |

参考代码如下:

cpp 复制代码
module dual_port_RAM #(parameter DEPTH = 16,
                       parameter WIDTH = 8)(
     input wclk
    ,input wenc
    ,input [$clog2(DEPTH)-1:0] waddr  
    ,input [WIDTH-1:0] wdata        
    ,input rclk
    ,input renc
    ,input [$clog2(DEPTH)-1:0] raddr  
    ,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 clk ,

input rst_n ,

input winc ,

input rinc ,

input wdata ,

输出描述:

output reg wfull ,

output reg rempty ,

output wire rdata

解题思路:

同步FIFO的相关知识:

主要参考以下博文:

(博客园)数字设计------同步fifo

(知乎)手写同步FIFO

(知乎)同步FIFO笔记

(CSDN)FPGA基础知识极简教程(3)从FIFO设计讲起之同步FIFO篇

FIFO(First-In-Frist-Out)是一种先进先出的数据交互方式,在数字ASIC设计中常常被使用。

FIFO与普通存储器RAM的区别是没有外部读写地址线,使用起来非常简单,但缺点就是只能顺序写入数据,顺序的读出数据 ,其数据地址由内部读写指针自动加1完成。不能像普通存储器那样可以由地址线决定读取或写入某个指定的地址。FIFO本质上是由RAM(或者寄存器)加读写控制逻辑构成的一种先进先出的数据缓冲器

同步FIFO的端口
  • FIFO的宽度:即FIFO一次读写操作的数据位;
  • FIFO的深度:FIFO可以存储多少个N位的数据(如果宽度为N)。
  • 满标志 :FIFO已满或将要满时由FIFO的状态电路送出的一个信号,以++阻止FIFO的写操作++继续向FIFO中写数据而造成溢出(overflow)
  • 空标志 :FIFO已空或将要空时由FIFO的状态电路送出的一个信号,以++阻止FIFO的读操作++继续从FIFO中读出数据而造成无效数据的输出(underflow)
  • 读时钟:读操作所遵循的时钟,在每个时钟沿来临时读数据。
  • 写时钟:写操作所遵循的时钟,在每个时钟沿来临时写数据。
  • 将满标志 (almost full):FIFO将要满时由FIFO的状态电路送出的一个信号。
  • 将空信号 (almost empty):FIFO将要空时由FIFO的状态电路送出的一个信号。
FIFO指针的工作方式

根据下面一张图我们来了解FIFO读写指针 的工作方式;其中WP写指针RP读指针

写指针 总是指向下一个时钟要写的地址读指针 总是指向下一个时钟要读的地址 。读指针等于写指针的时候可能为空,有可能为满。(读指针也可以指向当前正在读的地址,但相应的根据地址读取数据的逻辑会有所不同,前面读指针指向下一个时钟要读的地址是用时序逻辑去读,指向当前正在读的地址用组合逻辑去读。)

FIFO没有写满 并且写使能拉高 时,写指针加一 ,当FIFO没有读空 并且读使能拉高 时,读指针加一

FIFO的空满检测

可以通过在FIFO内部设立一个计数器用来计数FIFO内的数据量;

  • 当FIFO没有写满且写使能拉高时(或者写指针加一时),计数器加一
  • 当FIFO没有读空且读使能拉高时(或者读指针加一时),计数器减一
  • 当FIFO既没有读空又没有写满,且读写使能同时拉高有效时,这时的计数器不加也不减

还可以采用在读写地址前增加一位 的策略。如果FIFO的深度为16,则地址需要4位二进制数来表示,那么在表示FIFO地址时用5位二进制数来表示。

当有数据进入时,写指针继续增大,当写指针为15**(01111)时,继续增大来到了0地址处,这时第五位置为1(10000)**,继续增大;

当读指针与写指针低四位相同最高为相反时,表示fifo已经写满

当读指针与写指针最高位相反,低四位不同时,使用写指针的低4位减去读指针的低4位 (表示当前FIFO空余的位置),在加FIFO的深度 ,即可表示++计数器cnt的值++(即FIFO队列中有多少数据)

解题:

输出信号有:写满信号(wfull)、读空信号(rempty)、读数据(rdata)

设计读指针和写指针

指针 指向的位置即为当前读数据或写数据的地址

cpp 复制代码
//读指针和写指针部分
reg [$clog2(DEPTH):0] wpoint, rpoint;  //读指针、写指针

wire wenc, renc;
assign wenc = winc && (!wfull); //当写使能为1且FIFO未满时
assign renc = rinc && (!rempty);//当读使能为1且FIFO未空时
//读指针
always @(posedge clk or negedge rst_n) begin
	if (!rst_n)	wpoint <= 'd0; 
	else begin 
		if (wenc)	wpoint <= wpoint + 1'd1;
	end
end
//写指针
always @(posedge clk or negedge rst_n) begin
	if (!rst_n)	rpoint <= 'd0;
	else begin 
		if (renc)	rpoint <= rpoint + 1'd1;
	end
end
②计数器cnt部分设置(难点)

判断WP指针的最高位与RP指针的最高位是否完全相等;当最高位为1时,说明指针已经经过了FIFO的最后一位数并且回到了最初;

①当**WP [4]= RP[4]**时,说明此时FIFO队列还未写满,cnt等于写指针地址-读指针地址;

例如:

②当WP[4]!=RP[4] 时,说明此时FIFO队列中写指针比读指针先循环了一个FIFO周期

例如:,表明写指针已经经过了最后一个地址01111,返回到0010 地址,并将最高位标为1

当读指针与写指针的后四位相同 ,但是最高位相反 时,说明FIFO已满

例如:

cpp 复制代码
//cnt部分
wire [$clog2(DEPTH) : 0]    cnt;
 
assign cnt = (wpoint[$clog2(DEPTH)] == rpoint[$clog2(DEPTH)]) ? 
			 (wpoint[$clog2(DEPTH):0] - rpoint[$clog2(DEPTH):0]) :
             (DEPTH + wpoint[$clog2(DEPTH)-1:0] - rpoint[$clog2(DEPTH)-1:0]);
②判断写满信号(wfull)

当计数器cnt 计数达到FIFO深度值(DEPTH)时,则wfull = 1'b1,否则wfull = 1'b0;

cpp 复制代码
//判断是否写满
always @(posedge clk or negedge rst_n) begin
	if (!rst_n)	wfull = 1'b0;
	else begin
		if (cnt == DEPTH)	wfull = 1'b1;
		else wfull = 1'b0;
	end
end
③判断读空信号(rempty):

当计数器cnt 计数为0时rempty = 1'b1,否则rempty = 1'b0;

cpp 复制代码
//判断当前是否为空
always @(posedge clk or negedge rst_n) begin
	if (!rst_n) rempty = 1'b0;
	else begin
		if (cnt == 4'd0)	 rempty = 1'b1;
		else	rempty = 1'b0;
	end
end
④RAM例化部分
cpp 复制代码
//例化RAM
dual_port_RAM #(.DEPTH(DEPTH),
				.WIDTH (WIDTH))
	RR(
	.wclk(clk),                       //写数据时钟
	.wenc(wenc),                      //写使能
	.waddr(wpoint[$clog2(DEPTH)-1:0]),//写地址
	.wdata(wdata),          		  //输入数据 	
	.rclk(clk),						  //读数据时钟
	.renc(renc),                      //读使能
	.raddr(rpoint[$clog2(DEPTH)-1:0]), //读地址
	.rdata(rdata)   				  //输出数据	
);
代码如下
cpp 复制代码
/**********************************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 wire [WIDTH-1:0]	rdata      //读数据
);

//读指针和写指针部分
reg [$clog2(DEPTH):0] wpoint, rpoint;  //读指针、写指针

wire wenc, renc;
assign wenc = winc && (!wfull); //当写使能为1且FIFO未满时
assign renc = rinc && (!rempty);//当读使能为1且FIFO未空时
//读指针
always @(posedge clk or negedge rst_n) begin
	if (!rst_n)	wpoint <= 'd0; 
	else begin 
		if (wenc)	wpoint <= wpoint + 1'd1;
	end
end
//写指针
always @(posedge clk or negedge rst_n) begin
	if (!rst_n)	rpoint <= 'd0;
	else begin 
		if (renc)	rpoint <= rpoint + 1'd1;
	end
end

//cnt部分
wire [$clog2(DEPTH) : 0]    cnt;
 
assign cnt = (wpoint[$clog2(DEPTH)] == rpoint[$clog2(DEPTH)]) ? 
			 (wpoint[$clog2(DEPTH):0] - rpoint[$clog2(DEPTH):0]) :
             (DEPTH + wpoint[$clog2(DEPTH)-1:0] - rpoint[$clog2(DEPTH)-1:0]);

//判断是否写满
always @(posedge clk or negedge rst_n) begin
	if (!rst_n)	wfull = 1'b0;
	else begin
		if (cnt == DEPTH)	wfull = 1'b1;
		else wfull = 1'b0;
	end
end

//判断当前是否为空
always @(posedge clk or negedge rst_n) begin
	if (!rst_n) rempty = 1'b0;
	else begin
		if (cnt == 4'd0)	 rempty = 1'b1;
		else	rempty = 1'b0;
	end
end


//例化RAM
dual_port_RAM #(.DEPTH(DEPTH),
				.WIDTH (WIDTH))
	RR(
	.wclk(clk),                       //写数据时钟
	.wenc(wenc),                      //写使能
	.waddr(wpoint[$clog2(DEPTH)-1:0]),//写地址
	.wdata(wdata),          		  //输入数据 	
	.rclk(clk),						  //读数据时钟
	.renc(renc),                      //读使能
	.raddr(rpoint[$clog2(DEPTH)-1:0]), //读地址
	.rdata(rdata)   				  //输出数据	
);
相关推荐
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
ZPC82105 天前
docker 镜像备份
人工智能·算法·fpga开发·机器人
ZPC82105 天前
docker 使用GUI ROS2
人工智能·算法·fpga开发·机器人
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛5 天前
计算机系统概论——校验码
学习
babe小鑫5 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms6 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下6 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。6 天前
2026.2.25监控学习
学习