FPGA 学习分享-- 04 FIFO核的使用(2)

这篇具有很好参考价值的文章主要介绍了FPGA 学习分享-- 04 FIFO核的使用(2)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

写在前面:
博主耗费了四天!!!完成了FIFO核的第二部分。
在这个部分,博主遇到了很多问题,对着代码不停修改,询问学长和老师,好在终于没有报错,可以向大家交作业了!当然,我会在本文详细的帮助大家学习FIFO核的代码编写,带着大家剖析每个部分,尽可能地通俗易懂。
因为淋过雨,所以想撑开你们的伞…

一. 核心代码

1.1 总代码

还是老样子,我直接给出我所编写的总代码,方便大家学习。

module fifo_ip(
		input			clk,
		input			rst_n
    );
    
parameter  		 CLK_FREQ = 26'd1250_000;  //25ms

reg				wr_en_reg;   		//写使能
reg				rd_en_reg;   		//读使能
reg  [3:0]      cnt_dly;    		//延迟计数器
reg	 [25:0] 	cnt;        		//计数器
reg	 [1:0]      sys_state;  	    //定义状态机,位数2位,四种状态
reg	 [7:0]		wr_deep;			//写深度
reg	 [7:0]   	rd_deep;			//读深度
reg  [7:0]		wr_data_reg;		//写数据
reg  [7:0]		rd_data_reg;		//读数据


wire			wr_rd_flag;   		//寄存实时状态    
wire	[7:0]	wr_data;    
wire			wr_en;          
wire	[7:0]	rd_data;    
wire			rd_en;          
wire			full;               //写满     
wire			empty;	          	//读空


//定义状态机的四种状态---> 休息,写入,读取,延迟读出
parameter    STATE_REST  = 2'b00;
parameter    STATE_WRITE = 2'b01;
parameter    STATE_READ  = 2'b10;
parameter    STATE_DLY  =  2'b11;


//定义计时器模块
always @(posedge clk or negedge rst_n) begin
		if(!rst_n) begin
				cnt <= 1'b0;
		end
		else begin
				if(cnt >= (CLK_FREQ - 1'b1))
					cnt <= 1'b0;
				else 
					cnt <= cnt + 1'b1;
		end
end

//定义延时读出模块,类似于计时器模块
always @(posedge clk or negedge rst_n) begin
		if(!rst_n) begin
				cnt_dly <= 1'b0;
		end
		else begin
				if(cnt_dly > 4'b1010)
					cnt_dly <= 1'b0;
				else 
					cnt_dly <= cnt_dly + 1'b1;
		end
end


//给出状态机的状态
assign	wr_rd_flag = ( sys_state[1:0] == STATE_REST)? 1'b0 : 1'b1; //0不工作;1工作

//定义状态机
always @(posedge clk or negedge rst_n) begin
		if(!rst_n) begin
				sys_state <= STATE_REST;
				wr_deep <= 1'b0;
		        rd_deep <= 1'b0;
		end
		else begin
				if(cnt == (CLK_FREQ - 1'b1)) begin
						sys_state <= STATE_WRITE;
						wr_deep <= 1'b0;
				end
				else begin
				case(sys_state)
						STATE_REST:  begin 
				 						wr_deep <= 1'b0;
				 						rd_deep <= 1'b0;
				 					 end
				 					 
				 		STATE_WRITE: begin
				 						//记录写入数据的深度
				 						if(wr_deep >= 8'd255) begin
				 								sys_state <= STATE_DLY;
				 								rd_deep <= 1'b0;
				 						end
				 						else
				 								wr_deep <= wr_deep + 1'b1;
				 					 end
				 					 
				 		STATE_DLY: begin
				 						if(cnt_dly == 4'b1010) begin
				 								sys_state <= STATE_READ;
				 						end
				 						else 
				 								sys_state <= sys_state;
				 					 end
				 			
				 		STATE_READ:  begin
				 						//记录读取数据的深度
				 						if(rd_deep >= 8'd255) begin
				 								sys_state <= STATE_REST;
				 						end
				 						else
				 								rd_deep <= rd_deep + 1'b1;
				 					 end
				 					 
				 		default:     sys_state <= STATE_REST;
				 	endcase
		       end
	    end
end

//定义写入数据的内容
always @(posedge clk or negedge rst_n) begin
		if(!rst_n) begin
			wr_data_reg <= 1'b0;
			wr_en_reg <= 1'b0;
			rd_en_reg <= 1'b0;
		end
		else begin
			if(cnt == (CLK_FREQ - 1'b1)) begin
						wr_en_reg <= 1'b1;
			end
		    else begin
		    	case(sys_state)
		    		STATE_REST:  begin 
		    						wr_data_reg <= 1'b0;
		    						wr_en_reg <= 1'b0;
		    						rd_en_reg <= 1'b0;
		    					 end
		    					 
		    		STATE_WRITE: begin
		    						wr_data_reg <= wr_data_reg + 1'b1;
		    						wr_en_reg <= 1'b1;
		    						rd_en_reg <= 1'b0;
		    					end
		    					
		    		STATE_DLY:  begin 
		    						wr_data_reg <= 1'b0;
		    						wr_en_reg <= 1'b0;
		    						rd_en_reg <= 1'b0;
		    					 end
		    					 		    		
		    		STATE_READ:  begin
		    						wr_data_reg <= 1'b0;
		    						wr_en_reg <= 1'b0;
		    						rd_en_reg <= 1'b1;
		    					end
		    	endcase
		end
end
end


//例化FIFO核
assign	wr_data[7:0] = wr_data_reg;
assign	rd_data[7:0] = rd_data_reg;
assign  wr_en = wr_en_reg;
assign  rd_en = rd_en_reg; 

fifo_generator_0 u_fifo_generator_0 (
  .clk					(clk),         
  .srst					(~rst_n),   		
  .din					(wr_data),     
  .wr_en				(wr_en),  
  .rd_en				(rd_en),  
  .dout					(rd_data),    
  .full					(full),    
  .empty				(empty)  
);

//例化ILA模块
wire   [26:0]  probe0;

assign probe0[0] = clk;      // 可能会抓取不到信号,因为频率不匹配    
assign probe0[1] = rst_n;
assign probe0[2] = wr_rd_flag;
assign probe0[3] = wr_en;
assign probe0[4] = rd_en;
assign probe0[12:5] = wr_data[7:0];
assign probe0[20:13] = rd_data[7:0];
assign probe0[21] = full;
assign probe0[22] = empty;
assign probe0[26:23] = cnt_dly[3:0];


ila_0 u_ila_0 (
	.clk             (clk), 			// input wire clk
	.probe0          (probe0) 			// input wire [26:0] probe0
);

endmodule

二. 剖析代码

2.1 状态机模块

  1. 根据上一节的知识,咱们首先要确定FIFO核应该有几个状态。
    肯定有写入,读出两个状态。为了让整个代码顺畅且富有逻辑,FIFO核应还具有休息状态(也就是什么都不干),和延迟读出状态(防止写入的数据FIFO核没反应过来,给它一点反应时间)。所以一共有四种状态。
//定义状态机的四种状态---> 休息,写入,读取,延迟
parameter    STATE_REST  = 2'b00;
parameter    STATE_WRITE = 2'b01;
parameter    STATE_READ  = 2'b10;
parameter    STATE_DLY  =  2'b11;

由此可见,状态机的位数应该是2位(00,01,10,11,刚好代表了四种状态)

reg				wr_en_reg;
reg				rd_en_reg;
reg  [3:0]      cnt_dly;
reg	 [25:0] 	cnt;
reg	 [1:0]      sys_state;    //定义状态机,位数2位,四种状态
reg	 [7:0]		wr_deep;
reg	 [7:0]   	rd_deep;
reg  [7:0]		wr_data_reg;
reg  [7:0]		rd_data_reg;
  1. 当状态机为REST状态,FIFO核不工作,即没有写入,也没有读出,那么此时的写深度和读深度就应该都为0.
STATE_REST:  begin
		wr_deep <= 1'b0;
		rd_deep <= 1'b0;
			end

当状态机是WRITE状态时,如果wr_deep显示满了,就让状态机进入延迟读出,暂时休息休息。如果没满,就让wr_deep加1,表示写了一位。(毕竟到写入状态了,写深度肯定要加一次的)

STATE_WRITE: begin
			//记录写入数据的深度
				if(wr_deep >= 8'd255) begin
				 		sys_state <= STATE_DLY;
				 		rd_deep <= 1'b0;
				 end
				 else
				 		wr_deep <= wr_deep + 1'b1;
			end

当状态机是DLY状态时,FIFO核写入读出都在“怠工”,让FIFO核一人刷新数据着呢。当刷新完成时,FIFO核进入READ状态,如果没有刷新好呢,就让FIFO核保持现在状态就行

STATE_DLY: begin
			if(cnt_dly == 4'b1010) begin
						sys_state <= STATE_READ;
				 					end
			else 
				 		sys_state <= sys_state;
			end

当状态机是READ状态时,和WRITE类似,读深度要好好考虑一下。读完了就让FIFO核休息去吧,没读完,就让他读深度加1.继续读。

STATE_READ:  begin
		//记录读取数据的深度
		if(rd_deep >= 8'd255) begin
				 sys_state <= STATE_REST;
				 				end
		else
				 rd_deep <= rd_deep + 1'b1;
		end

这个时候四大状态已经全部编写好了,状态机雏形已经具备。但为了防止有其他情况,最后再加一个逻辑判断,避免锁相环,占用太多空间。

default:     sys_state <= STATE_REST;

那怎么让状态机启动呢,状态机的起始状态是什么呢。
给他一个always时序,给他一个初始化。(超大声!
好了,不开玩笑了。
逻辑地讲,在系统开始,应该先让状态机到REST状态,先休息着。此时没读没写,读深度,写深度自然为0。假设到了一定的时间(博主把这个时间定义为clk有1250000个上升沿的时间,即25ms。大家也可以根据自己需要来配置这个时间),后,FIFO核开始写入,即状态机到达WRITE状态。
所以,代码如下:

//定义状态机
always @(posedge clk or negedge rst_n) begin
		if(!rst_n) begin
				sys_state <= STATE_REST;
				wr_deep <= 1'b0;
		        rd_deep <= 1'b0;
		end
		else begin
				if(cnt == (CLK_FREQ - 1'b1)) begin
						sys_state <= STATE_WRITE;
						wr_deep <= 1'b0;
				end
				......
				......
				......

至此,状态机模块基本成型!下面开始另一个模块的剖析

2.2 写入数据模块

写入数据必须要考虑到写的数据是什么,什么时候写,什么时候不写。
由此看来,这个模块也需要状态机的加持,只不过不像上一个模块,专门给大家讲解状态机罢了,此时我们把重点放在代码的逻辑上。


复位的时候,理所应当的,写数据应该为0,写使能应该为0,读使能应该为0.(没有人质疑吧~

//定义写入数据的内容
always @(posedge clk or negedge rst_n) begin
		if(!rst_n) begin
			wr_data_reg <= 1'b0;
			wr_en_reg <= 1'b0;
			rd_en_reg <= 1'b0;
		end
		......
		......
		......

当状态机WRITE状态启动的时候,写使能此时就要立刻拉高以配合写入数据。不然他俩就要差一个时钟位。读出的数据就不会从0开始,而从1开始。

if(cnt == (CLK_FREQ - 1'b1)) begin
				wr_en_reg <= 1'b1;

当状态机为REST状态,FIFO核不工作,也就是不写入,不读出。即wr_data,wr_en,rd_en仍为0.

STATE_REST:  begin 
		    		wr_data_reg <= 1'b0;
		    		wr_en_reg <= 1'b0;
		    		rd_en_reg <= 1'b0;
		    end

当状态机为WRITE状态,FIFO核开始写入数据。根据实验目的,写数据的内容应该从0到255连续加1.记得此时要打开写使能,读使能依旧为0.因为此时不读出数据,只写入数据

STATE_WRITE: begin
		    	wr_data_reg <= wr_data_reg + 1'b1;
		    	wr_en_reg <= 1'b1;
		    	rd_en_reg <= 1'b0;
		    end

当状态机为DLY状态,FIFO核暂时休息,写使能读使能均为0.

STATE_DLY:  begin 
		    	wr_data_reg <= 1'b0;
		    	wr_en_reg <= 1'b0;
		    	rd_en_reg <= 1'b0;
		     end

当状态机为READ状态,FIFO核开始读出数据。此时要关闭写使能,打开读使能。

STATE_READ:  begin
		    	wr_data_reg <= 1'b0;
		    	wr_en_reg <= 1'b0;
		    	rd_en_reg <= 1'b1;
		     end

2.3 其他细节

2.3.1 FIFO核状态展示

在代码的运行过程中,为了清楚的知道此时FIFO核处于什么状态,我们专门引入一个变量来寄存实时状态

//给出状态机的状态
assign	wr_rd_flag = ( sys_state[1:0] == STATE_REST)? 1'b0 : 1'b1; //0不工作;1工作

2.3.2 例化

本文设计了两个IP核的例化,分别是FIFO核和ILA核
(ILA是方便板子上电的时候查看波形图的,其类似于仿真图。同时ILA具有debug功能,可以直观查看错误。不久博主就要专门写一篇来讲解Vivado里的特殊功能,其中就包含例化。)

因为例化中只接受wire类型的变量,所以博主定义了大量的wire变量,并且通过一定的assign语句由reg变量向wire变量赋值,以达到两个模块完美相连的功能。
(相当于C语言数组在两个函数间的值传递)

注意:只有reg变量才能在always模块中被赋值!!

reg				wr_en_reg;   		//写使能
reg				rd_en_reg;   		//读使能
reg  [3:0]      cnt_dly;    		//延迟计数器
reg	 [25:0] 	cnt;        		//计数器
reg	 [1:0]      sys_state;  	    //定义状态机,位数2位,四种状态
reg	 [7:0]		wr_deep;			//写深度
reg	 [7:0]   	rd_deep;			//读深度
reg  [7:0]		wr_data_reg;		//写数据
reg  [7:0]		rd_data_reg;		//读数据
wire			wr_rd_flag;   //寄存实时状态    
wire	[7:0]	wr_data;    
wire			wr_en;          
wire	[7:0]	rd_data;    
wire			rd_en;          
wire			full;           
wire			empty;	   
//例化FIFO核
assign	wr_data[7:0] = wr_data_reg;
assign	rd_data[7:0] = rd_data_reg;
assign  wr_en = wr_en_reg;
assign  rd_en = rd_en_reg; 

fifo_generator_0 u_fifo_generator_0 (
  .clk					(clk),         
  .srst					(~rst_n),   		
  .din					(wr_data),     
  .wr_en				(wr_en),  
  .rd_en				(rd_en),  
  .dout					(rd_data),    
  .full					(full),    
  .empty				(empty)  
);

//例化ILA模块
wire   [26:0]  probe0;

assign probe0[0] = clk;      // 可能会抓取不到信号,因为频率不匹配    
assign probe0[1] = rst_n;
assign probe0[2] = wr_rd_flag;
assign probe0[3] = wr_en;
assign probe0[4] = rd_en;
assign probe0[12:5] = wr_data[7:0];
assign probe0[20:13] = rd_data[7:0];
assign probe0[21] = full;
assign probe0[22] = empty;
assign probe0[26:23] = cnt_dly[3:0];


ila_0 u_ila_0 (
	.clk             (clk), 			// input wire clk
	.probe0          (probe0) 			// input wire [26:0] probe0
);

大概就是这么个道理 (类似于媒介)
FPGA 学习分享-- 04 FIFO核的使用(2),学习,fpga开发

三. 结果展示

3.1 写入数据

FPGA 学习分享-- 04 FIFO核的使用(2),学习,fpga开发
仔细观察,发现写入数据并不是从0开始的。这个没有关系,博主试了几次,它每次的初始值都不固定,咱们只要能保证读出的数据第一个是0就OK了。因为FIFO核最大的特点就是先入先出。先出去的0一定是先写进去的.


3.2 full信号

FPGA 学习分享-- 04 FIFO核的使用(2),学习,fpga开发
写到255时,full信号拉高,证明此时已经写满,即满足实验要求之一。


3.3 读出数据

FPGA 学习分享-- 04 FIFO核的使用(2),学习,fpga开发
full拉低时,证明开始读出了。可见第一个读出的确实是0,证明咱们写入数据是没有问题的。实验要求达成.

实验结束!!!

四. 问题总结

4.1 仿真

细心的同学就会发现,这个实验我没有给大家仿真代码,也没有仿真图。不是因为博主懒,不想仿真,而是因为这个实验就无法仿真!
在仿真的过程中,为了看到仿真图,必然要在module主模块中加入input变量,如下图:
FPGA 学习分享-- 04 FIFO核的使用(2),学习,fpga开发
这些变量,不像clk,是系统时钟,板子自带的。这些输入变量是没有外来输入值的,都是自己在代码中利用状态机去给值的。那么就会使这些接口成为悬空接口,进一步在生成仿真波形图时,导致rd_data无法读出具体的数字,只能是X态。(其他数据为什么可以在仿真图中展示呢,是因为其他数据我在状态机中已经赋值了,只有rd_data从来没有赋值,自然只能读出来x态。)
并且,即使不仿真,在生成bit文件时,也会出现多重驱动器驱动一个变量的错误。多达100个。


切记切记:
为了看见波形图,此处只能选择利用ILA进行查看!!!


4.2 always 语句

不允许对同一个变量在两个always时序语句中赋值。

哪怕你的时序,你的逻辑都是OK的,也不允许!否则会在布局布线的时候被优化掉,导致出错。文章来源地址https://www.toymoban.com/news/detail-769877.html

到了这里,关于FPGA 学习分享-- 04 FIFO核的使用(2)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • FPGA学习笔记(二):clk IP核的使用

     1.打开VIVADO,点击IP Catalog   2. 搜索clk,选择Clocking Wizard   3. 配置参数 (1) 选择MMCM      (2) 设置主频50Mhz  (3) 设置输出的4个clk的参数,分别是50M,25M,100M,100M反相。Phase表示相位,0表示同相,180表示反相。      (3) 其他参数  4. 测试文件 其中clk_wiz的例化可参考模板。点击

    2024年02月12日
    浏览(51)
  • FPGA之Quartus II 自带的IP核的使用(IP核仅自己学习,未完成)

    1、锁相环:       锁相环是对接收到的信号进行处理,并从其中提取某个时钟的相位信息。锁相环由鉴相器、环路滤波器和压控振荡器组成。  锁相环原理图 鉴相器:         用来鉴别输入信号输入信号Ui与输出信号输出信号Uo之间的相位差相位差,并输出误差电压Ud。Ud

    2023年04月09日
    浏览(49)
  • 【Lattice FPGA 开发】IP核的调用

    本文介绍Diamond开发软件进行IP核调用与对应官方文档查找方法。 Diamond软件中,根据所选目标FPGA器件型号的不同,调用IP核的方式不同。共两种:一种是“IPexpress”;另一种是“Clarity Designer”。 IPexpress调用IP核: Clarity Designer调用IP核: 接下来分别对两种情况的调用进行说明

    2024年04月12日
    浏览(49)
  • FPGA — FIFO学习笔记

    使用软件: Vivado 参考文档 :FIFO Generator v13.2 FIFO(Fist In Fist Out),即为先进先出,常被用于数据的缓存或高速异步数据的交互,与普通存储器区别是没有外部读写地址线,使用简单,缺点是只能顺序写入数据,顺序的读出数据,其数据地址由内部读写指针自动加1完成,不能像

    2024年02月16日
    浏览(38)
  • FPGA学习笔记——FIFO读写

    【实验任务】向FIFO中以50MHz的频率按顺序写入0~254,再从FIFO中以5Hz的频率按顺序读出0~254,输出到LED中点亮。 FIFO全称为“First-In-First-Out”,是FPGA内部的存储数据的缓冲器,读写数据具有先入先出的特点,与数据结构中的“队列”有异曲同工之妙。 本博客例化紫光同创官方提

    2024年02月15日
    浏览(35)
  • FPGA学习笔记(3):FIFO

    FIFO是First Input First Output的英文简写 代表先进的数据先出 ,后进的数据后出。 FIFO存储器是系统的缓冲环节,如果没有FIFO存储器,整个系统就不可能正常工作。 FIFO的功能可以概括为 (1)对连续的数据流进行缓存,防止在进机和存储操作时丢失数据; (2)数据集中起来进行

    2024年01月19日
    浏览(40)
  • FPGA平台以太网学习:涉及1G/2.5G Ethernet 和Tri Mode Ethernet MAC两个IP核的学习记录(二)——IP学习使用

      学习不能稀里糊涂,要学会多思考,发散式学习以及总结:   FPGA作为一种器件,只是实现目的的一种方法,过度追求实现的技术细节(用hdl还是hls,用啥芯片,用啥接口)容易只见树木不见森林。工具软件的用法也好,器件的架构也好,语言孰优孰劣的争论也罢。工程应

    2023年04月10日
    浏览(45)
  • FPGA原理与结构——FIFO IP核原理学习

    系列文章目录:FPGA原理与结构(0)——目录与传送门         FIFO是英文First-In-First-Out的缩写,是一种先入先出的数据缓冲器,与一般的存储器的区别在于没有地址线, 使用起来简单,缺点是只能顺序读写数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器

    2024年02月11日
    浏览(48)
  • FPGA原理与结构——时钟IP核的使用与测试

    系列文章目录:FPGA原理与结构(0)——目录与传送门         本文介绍xilinx的时钟IP核 Clocking Wizard v6.0的具体使用与测试过程,在学习一个IP核的使用之前,首先需要对于IP核的具体参数和原理有一个基本的了解,具体可以参考: FPGA原理与结构——时钟IP核原理学习 https:/

    2024年02月09日
    浏览(38)
  • FPGA原理与结构(12)——FIFO IP核原理学习

    系列文章目录:FPGA原理与结构(0)——目录与传送门         FIFO是英文First-In-First-Out的缩写,是一种先入先出的数据缓冲器,与一般的存储器的区别在于没有地址线, 使用起来简单,缺点是只能顺序读写数据,其数据地址由内部读写指针自动加1完成,不能像普通存储器

    2024年02月08日
    浏览(44)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包