在一些工程中我们需要用到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<
下面演示调用上述函数生成两个不同大小的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)
);