vivado中verilog编写RAM与IP核生成RAM

在一些工程中我们需要用到RAM存储,就需要使用RAM,本文介绍两种RAM的实现方式,一种是用verilog编写的RAM,另一种就是基于vivado用IP核生成的RAM,在vivado中生成的RAM可能在其他的环境下编译不同过,但是用verilog编写的RAM就不会出现这种问题。接下来先看如何用verilog编写RAM。

下面代码所写的是一个准双端口的RAM。外部调用RAM模块时需要初始化DW,AW,和初始化文件名称。用于计算RAM块的大小,RAM块的容量等于1左右AW位乘12,例如下面代码的RAM大小 = (1 << 18)*DW =  256K * 12 (位)。

module RAM#(
parameter DW = 12,	//位宽
parameter AW = 18,	//长度
parameter INIT_FILE = "init.mem" //初始化文件名
)(
input 				rclk,	//读时钟
input 	[AW-1:0] 	raddr,	//读地址
output 	[DW-1:0]	dout,	//输出数据

input				wclk,	//写时钟 
input				we,		//写使能 
input	[AW-1:0]	waddr,	//写地址 
input	[DW-1:0]	din,	//写数据 

input               en_mem //是否初始化RAM
);

//注意生成的RAM块会自动向上取整,这个整为2的次方,
//例如申请一个255K的储存器,则会生成的一个256K的储存器RAM,
//申请一个257k的储存器,则会生成一个512k的储存器RAM,
//为了节约资源我们可以分开生成两个RAM一个256K的,一个2k的
reg [DW-1:0] mem [(1<<AW)-1:0];			//生成RAM块

//读操作
reg [AW-1:0] ra;						//读地址
always@(posedge rclk) ra <= #1 raddr;	//把读地址延迟一个周期,为保持数据稳定,可以不延迟
assign dout = mem[ra];					//输出读到的数据

//写操作
always@(posedge wclk) if(we) mem[waddr] <= #1 din;	//把数据写入对应地址,延迟保持数据稳定


initial 
begin
if(en_mem) $readmemh(INIT_FILE,mem);	//初始化储存单元,把文件内容读到RAM中
end
endmodule

下面演示调用上述函数生成两个不同大小的RAM。

module RAM_256Kx12_64Kx12(
//写RAM参数
input 			wrclk	,	//写时钟
input [11:0] 	wr_din	,	//写数据
input [31:0] 	wr_addr	,	//写地址
input 		 	wr		,	//写使能
//读RAM参数
input 			rd_clk	,	//读时钟,由于输出时给VGA传输,所以需要使用VGA时钟
input [11:0]	rd_dout	,	//读数据
input [31:0] 	rd_addr		//读地址
);

//要使用两个RAM块,分别存储读到数据
wire [11:0] rd_dout_256K,rd_dout_64K;

//获取读地址的第19位,以区分是读哪一个RAM块,1为256K,0为64K
//前18地址刚好对应256K的地址,如果超过第18位,第19位为1,也就选择64K的RAM为读写目标
reg addr18;
always@(posedge rd_clk) addr18 <= rd_addr[18];
assign rd_dout = (~addr18) ? rd_dout_256K : rd_dout_64K;

RAM#(
	.DW (12),	//位宽
	.AW (18),	//长度
	.INIT_FILE("init_256Kx12.mem")	//文件要在同一目录下,或加上文件路径
)
RAM_256Kx12		// 2^18 = 256Kx12
(
	.rclk	(rd_clk),			//读时钟
	.raddr	(rd_addr[17:0]),	//读地址
	.dout	(rd_dout_256K),		//输出数据
	
	.wclk	(wrclk),			//写时钟 
	.we		(wr & ~addr18),		//写使能 
	.waddr	(wr_addr[17:0]),	//写地址 
	.din	(wr_din),			//写数据 
	.en_init(1'b1)				//使能初始化文件
);

RAM#(
	.DW (12),	//位宽
	.AW (16)	//长度
	.INIT_FILE("init_64Kx12.mem")	//文件要在同一目录下,或加上文件路径
)
RAM_64Kx12		// 2^16 = 64Kx12
(
	.rclk	(rd_clk),			//读时钟
	.raddr	(rd_addr[17:0]),	//读地址
	.dout	(rd_dout_64K),		//输出数据
	
	.wclk	(wrclk),			//写时钟 
	.we		(wr & addr18),		//写使能 
	.waddr	(wr_addr[17:0]),	//写地址 
	.din	(wr_din)			//写数据 
	.en_init(1'b1)				//使能初始化文件
);

endmodule

为什么要生成两个RAM呢,直接生成一个300Kx12的RAM不可以吗,答案是不可以,由于RAM 的生成规则,使用这种方式生成的RAM必须是2的次方数,  如果选择生成一个300K的RAM,那么系统会直接优化成512K的空间,这样会浪费很多没用上的RAM。但是用IP核生成的RAM,就不会有这个问题,需要多大就可以生成多大的RAM。
下面是利用IP核生成RAM的流程,新建IP核,修改名称,添加IP核地址需要修改到ip核的bd文件夹内。

 搜索BRAM,选择Block Memory Generator ,双击IP核打开设置界面,设置如下,我们这里选择准双口IP核,真双IP核与准双IP核的区别是,真双口IP核的每一个口都能读写,准双口IP核一个口只能读,另一个口只能写。mode选择的是独立工作模式stand_Alone。

 上图中Byte Write Enable 是RAM的最小字节单位,若勾选设置,那RAM的字节单位会设置为设置位数的整数倍,例如设置为9,如下图 Width 选择11个bit为最小单位,那么就会变成18bit。所以根据需要勾选,Port A Depth为需要生成RAM的大小,这里是存存储了一个640x480的图片,色深为12位RGB都是4位。选择初始化文件,这样就基本设置完成了。

 

 最后把俩个端口引出即可,然后就可以调用。A口为写端口,B口为读端口,注意读口的时钟,读出来的数据传给谁,就用谁的时钟。

design_2_wrapper RAM(
    .BRAM_PORTA_addr(buff_addr[18:0]),    
    .BRAM_PORTA_clk(sys_clk),
    .BRAM_PORTA_din(din[11:0]),
    .BRAM_PORTA_en(1'b1),
    .BRAM_PORTA_we(feed_data),
                 
    .BRAM_PORTB_addr(pixel_addr),
    .BRAM_PORTB_clk(pixel_clock),
    .BRAM_PORTB_dout(buff_out),
    .BRAM_PORTB_en(1'b1)
);

  • 0
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值