FPGA存储器(FIFO+RAM+ROM)存储实战

上篇文章我已经详细讲述了FPGA的3种常见存储器的调用方法,链接如下

https://blog.csdn.net/weixin_46188211/article/details/122839616

这篇文章我将依次讲解3种存储器的代码,对这三种存储器的使用进行实战

目录

一、RAM读写

二、ROM

 三、FIFO


一、RAM读写

首先,设计一个程序框图,由于既要用到存储器的接收,又要用到存储器的发送,所以我们让存储器接收自己发送的数据,再对数据进行发送:

 在这里我使用真双口RAM,首先A口写(数据依次+1,地址依次+1),A口写满之后,然后B口读,B口读空了之后(应该把地址和数据清空),B口写(数据依次+1,地址依次+1),B口写满了之后,A口读,A口读空了之后(应该把地址和数据清空)然后A口写,进行循环。

 首先创建一个真双口RAM ,它的数据位宽为16,地址深度为4,把RAM例化进程序

wire clka; //由于clk用assign连接,所以修改为wire型
reg ena;
reg wea;
reg [3:0] addra;
reg [15:0] dina;
wire[15:0] douta;

wire clkb;
reg enb;
reg web;
reg [3:0] addrb;
reg [15:0] dinb;
wire[15:0] doutb;
    
RAM inst_RAM (
  .clka(clka),    // input wire clka
  .ena(ena),      // input wire ena
  .wea(wea),      // input wire [0 : 0] wea
  .addra(addra),  // input wire [3 : 0] addra
  .dina(dina),    // input wire [15 : 0] dina
  .douta(douta),  // output wire [15 : 0] douta
  .clkb(clkb),    // input wire clkb
  .enb(enb),      // input wire enb
  .web(web),      // input wire [0 : 0] web
  .addrb(addrb),  // input wire [3 : 0] addrb
  .dinb(dinb),    // input wire [15 : 0] dinb
  .doutb(doutb)  // output wire [15 : 0] doutb
);


assign clka=clk;
assign clkb=clk;

实现循环需要使用状态机,状态转移图如下:

 这里需要注意的是,在每一次写满或者读空的时候,最好将din和addr清零,保证下次存储时A/B口为空,代码如下:

reg [2:0] state;
parameter IDLE=3'd0;
parameter S0  =3'd1;//A写
parameter S1  =3'd2;//B读
parameter S2  =3'd3;//B写
parameter S3  =3'd4;//A读

parameter maxA=4'd15;//A口地址已满
parameter maxB=4'd15;//B口地址已满

always@(posedge clk)begin
	if(reset)begin
		ena  <=1'd0;
    wea  <=1'd0;
    addra<=4'd0;
    dina <=16'd0;

    enb  <=1'd0;
    web  <=1'd0;
    addrb<=4'd0;
    dinb <=16'd0;

	end
	else begin
		case(state)
			IDLE:begin
				ena  <=1'd0;
        enb  <=1'd0;
        state<=S0;
			end
      S0  :begin//A写
      	if(addra==maxA)begin
      		state<=S1;
          wea  <=1'd0;
          addra<=4'd0;
          dina <=16'd0;
      	end
      	else begin
      		state<=S0;
      		ena  <=1'd1;
          wea  <=1'd1;
          addra<=addra+4'd1;//地址+1
          dina <=dina+16'd1;//数据+1
      	end
      end
      S1  :begin//B读
      	if(addrb==maxB)begin
          web  <=1'd1;
          addrb<=4'd0;
          dinb <=16'd0;
          state<=S2;
      	end
      	else begin
      		state<=S1;
      		enb  <=1'd1;
          web  <=1'd0;
          addrb<=addrb+4'd1;
      	end
      end
      S2  :begin//B写
      	if(addrb==maxB)begin
      		state<=S3;
      		web  <=1'd1;
          addrb<=4'd0;
          dinb <=16'd0;
      	end
      	else begin
      		state<=S2;
      		web<=1'd1;
      		dinb<=dinb+16'd1;
      		addrb<=addrb+4'd1;
      	end
      end
      S3  :begin//A读
      	if(addra==maxA)begin
      		state<=S0;
      		wea<=1'd1;
      		dina<=15'd0;
      		addra<=1'd0;
      	end
      	else begin
      		state<=S3;
      		wea<=1'd0;
      		addra<=addra+4'd1;
      	end
      end
      default:begin
      	state<=IDLE;
      end
			
		endcase
	end
end

对RAM进行仿真: 

A写完了B读

B口读完B口写

B写完了A读

A读完了A写

二、ROM

与RAM类似,首先创建一个ROM

这里与RAM不同的是,需要将ROM初始化,添加初始化文件(即把数据写入ROM)

​ 添加coe文件(在文件名后面后缀.coe即可)

MEMORY_INITIALIZATION_RADIX=16;        //表示ROM内容的数据格式是16进制
MEMORY_INITIALIZATION_VECTOR= 
1,
2,
3,
4,
5,
6,
7,
8,
9,
a,
b,
c,
d,
e,
f;       //每个数据后面用逗号或者空格或者换行符隔开,最后一个数据后面加分号

把.coe文件放在IP核里面, 具体位置在:文件名.srcs\sources_1\ip

若 Design  Runs下面的ip核出现“”说明IP核生成成功

生成IP核完成之后,接下来将ROM例化到程序里面

wire clka; //由于clk用assign连接,所以修改为wire型
reg ena;
reg [3:0] addra;
wire[31:0] douta;

wire clkb;
reg enb;
reg [3:0] addrb;
wire[31:0] doutb;

ROM inst_ROM (
  .clka(clka),    // input wire clka
  .ena(ena),      // input wire ena
  .addra(addra),  // input wire [4 : 0] addra
  .douta(douta),  // output wire [31 : 0] douta
  .clkb(clkb),    // input wire clkb
  .enb(enb),      // input wire enb
  .addrb(addrb),  // input wire [4 : 0] addrb
  .doutb(doutb)  // output wire [31 : 0] doutb
);   

由于ROM为只读存储器,只能读取ROM,不能往里面写(只有dout没有din)所以程序设计比较简单:

 一共有0~9、a~f个数据,A口读前10个数据,B口读后面6个数据,将ROM读空,代码如下:

reg [1:0] state;
    
parameter IDLE=2'd0;
parameter S0  =2'd1;
parameter S1  =2'd2;

always@(posedge clk)begin
	if(reset)begin
		ena<=1'd0;
		enb<=1'd0;
		addra<=4'd0;
		addrb<=4'd0;
		state<=IDLE;
	end
	else begin
		case(state)
			IDLE:begin
				ena<=1'd0;
		    enb<=1'd0;
		    addra<=4'd0;
		    addrb<=4'd0;
		    state<=S0;
			end
			S0:begin
				if(addra==4'd9)begin
					ena<=1'd0;
					addra<=addra;
					state<=S1;
				end
				else begin
					ena<=1'd1;
					addra<=addra+4'd1;
					state<=S0;
			  end
			end
			S1:begin
				if(addra==4'd5)begin
					enb<=1'd0;
					addrb<=addrb;
					state<=IDLE;
				end
				else begin
					enb<=1'd1;
					addrb<=addrb+4'd1;
					state<=S1;
			  end
			end
		endcase
	end
	
end

然后我们可以看一下仿真结果,a端口实现了1-9的读取;但是发现在b端口读的时候依然是从1开始的,而不是期待的从a开始,可见ROM的两个端口的读取是互相独立的,不会因为其中一个读走了前面的数据,而影响另一个端口 ,每一个端口是独立的地址,这一点要注意

 三、FIFO

FIFO是一种先入先出的存储器,先写入的数据先读出,它没有地址

创建一个fifo,这里我创建的是异步fifo,因为fifo通常用来跨时钟域

 接下来我们把fifo例化到程序里面

reg [3:0] din;
reg  wr_en;
reg  rd_en;
wire almost_full;
wire almost_empty;
wire rd_rst_busy;   
wire wr_rst_busy;
    
FIFO inst_FIFO (
  .rst(rst),                    // input wire rst
  .wr_clk(clk),              // input wire wr_clk
  .rd_clk(clk),              // input wire rd_clk
  .din(din),                    // input wire [3 : 0] din
  .wr_en(wr_en),                // input wire wr_en
  .rd_en(rd_en),                // input wire rd_en
  .dout(dout),                  // output wire [3 : 0] dout
  .full(full),                  // output wire full
  .almost_full(almost_full),    // output wire almost_full
  .empty(empty),                // output wire empty
  .almost_empty(almost_empty),  // output wire almost_empty
  .wr_rst_busy(wr_rst_busy),    // output wire wr_rst_busy
  .rd_rst_busy(rd_rst_busy)    // output wire rd_rst_busy
);

用fifo实现以下功能:先向fifo中写入16‘d0~f,之后再把数据从fifo中读出来,代码如下:

reg [1:0] state;

parameter IDLE=2'd0;
parameter WR=2'd1;//写FIFo
parameter RD=2'd2;  //读FIFO 
    
    always@(posedge clk)begin
    	if(reset)begin
    		state<=IDLE;
    		din  <=4'd0; 
        wr_en<=1'd0;    
        rd_en<=1'd0;    
    	end
    	else begin
    		case(state)
    			IDLE:begin
    				state<=WR;
    		    din  <=4'd0; 
            wr_en<=1'd0;    
            rd_en<=1'd0; 
    			end
    			WR:begin
    				if(!wr_rst_busy)begin//写复位不忙
    					wr_en<=1'd1;
    					din<=4'd0;
    					if(almost_full)begin//写满
    						wr_en<=1'd0;
    						din<=din;
    						state<=RD;
    				  end
    				  else begin
    				  	wr_en<=1'd1;
    				  	din<=din+4'd1;//写数据+1
    				  end
    				end
    			end
    				RD:begin
    				if(!rd_rst_busy)begin
    					rd_en<=1'd1;
    					
    					if(almost_empty)begin
    						rd_en<=1'd0;
    						state<=IDLE;
    				  end
    				  else begin
    				  	rd_en<=1'd1;
    				  	
    				  end
    				end
    			end
    		endcase
    	end
    end

仿真结果如下:

这里fifo从0~f之后又记数了0和1,这是因为fifo的实际深度是18 

数据写过程:wr_rst_busy=0,写使能拉高,如果almost_full=1表示写满,写使能拉低;否则输入数据din加1

数据读过程:rd_rst_busy=0,读使能拉高,如果almost_empty=1表示读空,读使能拉低;否则读使能拉高


这篇文章把FPGA里面的三种存储器都做了小工程,完整工程集中在一个文件里,需要的话可以自行下载,已上传csdn

  • 7
    点赞
  • 59
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值