当前位置: 首页 > article >正文

【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

读数据

参考代码如下:

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)

设计读指针和写指针

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

//读指针和写指针部分
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 = '01011',RP = '00011',cnt = WP-RP = '01000'=8; \\ WP = '11011',RP = '10011',cnt = WP-RP = '01000'=8; \\ WP = '01111',RP = '01111',cnt = WP-RP = '00000'=0

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

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

cnt = DEPTH+WP[3:0]-RP[3:0]=16+(-10)=6

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

例如:WP = '10010', RP = '00010'\\ cnt = DEPTH+WP[3:0]-RP[3:0]=16

//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;

//判断是否写满
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;

//判断当前是否为空
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例化部分
//例化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)   				  //输出数据	
);
代码如下
/**********************************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)   				  //输出数据	
);


http://www.kler.cn/a/324540.html

相关文章:

  • torchvision库在进行图片转换操作中报antialias参数没有显式设置会导致不同图片后端中的值不统一的警告信息
  • GoogleCloud服务器的SSH连接配置
  • go 集成swagger 在线接口文档
  • SpringSecurity 鉴权认证入门讲解
  • transformers 操作篇
  • 【计算机毕设】无查重 基于python豆瓣电影评论舆情数据可视化系统(完整系统源码+数据库+开发笔记+详细部署教程)✅
  • [240929] 12 款最佳免费开源隐写工具 | Llama 3.2: 开源、可定制模型,革新边缘人工智能和视觉体验
  • Windows 10 on ARM, version 22H2 (updated Sep 2024) ARM64 AArch64 中文版、英文版下载
  • Flask 实现文件下载
  • Vue3 项目环境变量配置(Vite)
  • 2024年7月大众点评全国小吃快餐前百名城市分析
  • U盘打开提示要格式化:深度解析、恢复策略与预防之道
  • CSS列表
  • 每天学习一个技术栈 ——【Celery】篇(2)
  • FTP 服务器 linux安装
  • 面试速通宝典——5
  • 解锁免费数据恢复工具的潜力,找回珍贵数据记忆
  • android设计模式的建造者模式,请举例
  • RabbitMQ的各类工作模式介绍
  • SOCKS5代理和HTTP代理哪个快?深度解析两者的速度差异
  • 支持云边协同的「物联网平台+边缘计算底座」
  • tcpdump捕获指定端口的网络流量并实时输出
  • OpenCV_自定义线性滤波(filter2D)应用详解
  • 护眼灯行业分水岭渐显,书客革命性创新成为企业扩容市场的第一动力
  • qt6 使用QPSQL
  • 【k8s】:DevOps 模式详解