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

大概就是这么个道理 (类似于媒介)
在这里插入图片描述

三. 结果展示

3.1 写入数据

在这里插入图片描述
仔细观察,发现写入数据并不是从0开始的。这个没有关系,博主试了几次,它每次的初始值都不固定,咱们只要能保证读出的数据第一个是0就OK了。因为FIFO核最大的特点就是先入先出。先出去的0一定是先写进去的.


3.2 full信号

在这里插入图片描述
写到255时,full信号拉高,证明此时已经写满,即满足实验要求之一。


3.3 读出数据

在这里插入图片描述
full拉低时,证明开始读出了。可见第一个读出的确实是0,证明咱们写入数据是没有问题的。实验要求达成.

实验结束!!!

四. 问题总结

4.1 仿真

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


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


4.2 always 语句

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

哪怕你的时序,你的逻辑都是OK的,也不允许!否则会在布局布线的时候被优化掉,导致出错。

  • 28
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值