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

news/2024/9/29 17:54:09 标签: 学习, fpga开发

同步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.niftyadmin.cn/n/5683478.html

相关文章

Python | 第七章 | 函数

P60 多重循环控制&#xff08;1&#xff09; 2024/8/30 一、基本介绍 将一个循环放在另一个循环体内&#xff0c;就形成了嵌套循环。其中&#xff0c;for ,while均可以作为外层循环和内层循环。【建议一般使用两层&#xff0c;最多不要超过3层&#xff0c;否则&#xff0c;代…

数据归组工具

利用C#将数据 [ {"name":"A","fzh":1}, {"name":"A","fzh":2}, {"name":"A","fzh":3}, {"name":"B","fzh":4}, {"name":"B",&…

Notion is all you need(文献管理利器)

&#x1f308; 个人主页&#xff1a;十二月的猫-CSDN博客 &#x1f525; 系列专栏&#xff1a; &#x1f3c0;科研杂谈_十二月的猫的博客-CSDN博客 &#x1f4aa;&#x1f3fb; 十二月的寒冬阻挡不了春天的脚步&#xff0c;十二点的黑夜遮蔽不住黎明的曙光 目录 1. 前言 2. …

不同类型RWA在智能合约开发中的差异

RWA&#xff08;现实世界资产&#xff09;的类型多样&#xff0c;从房地产、艺术品到股票、债券等&#xff0c;每种资产的特性、价值评估方式、监管要求等都存在差异。这些差异直接影响到智能合约的设计和开发。 1.房地产RWA 复杂性高&#xff1a; 房地产涉及到土地使用权、建…

Android中大量使用建造者模式(Builder Pattern)的原因可以归结为以下几点:

1. 解耦对象的构建与表示 建造者模式将复杂对象的构建过程与其表示分离&#xff0c;这使得同样的构建过程可以创建不同的表示。在Android开发中&#xff0c;许多组件和视图需要配置多个属性和参数&#xff0c;通过建造者模式可以清晰地将这些属性的设置与对象的实际构造过程分…

kubevirt基于CDI创建虚拟机

CDI介绍 KubeVirt 的 Containerized Data Importer (CDI) 是一个 Kubernetes 原生的数据管理组件&#xff0c;专门为虚拟机 (VM) 提供存储支持&#xff0c;尤其在虚拟机的镜像管理和数据导入方面非常有用。CDI 的主要用途是帮助用户轻松地将外部数据源导入到 Kubernetes 集群中…

在创建字典时不知道键(key)的值,但想要在每次接收到新的数字时将它们追加到相应的列表中

需求 在创建字典时不知道键&#xff08;key&#xff09;的值&#xff0c;但想要在每次接收到新的数字时将它们追加到相应的列表中 示例代码 from collections import defaultdict# 创建一个默认字典&#xff0c;其中键是字符串&#xff0c;值是列表 my_dict defaultdict(li…

SpringMVC4-SpringMVC获取请求参数

目录 通过ServletAPI获取&#xff08;不常用&#xff09; 通过控制器方法的形参获取请求参数 RequestParam RequestHeader CookieValue 通过POJO获取请求参数 解决获取请求参数的乱码问题 test_param.html&#xff1a; <!DOCTYPE html> <html lang"en&qu…