1、简介
ram 的英文全称是 Random Access Memory,即随机存取存储器, 它可以随时把数据写入任一指定地址的存储单元,也可以随时从任一指定地址中读出数据, 其读写速度是由时钟频率决定的。 ram 主要用来存放程序及程序执行过程中产生的中间数据、 运算结果等。
rom为只读存储器,只能读取数据而不能向里面写入数据。
本次讲解的ram ip核ram指的是bram,即block ram ,通过对这些bram存储器模块进行配置,可以实现ram、移位寄存器、rom以及fifo缓冲器等各种存储器的功能。
bram可以配置成3种ram:
- 单端口ram:只有一个端口,读/写只能通过这一个端口来进行 。
- 伪双端口ram:有两个端口,但是其中一个只能读,另一个只能写 。
- 真双端口ram: 有两个端口,都能进行读和写。
2、单端口ram结构
以单端口ram为例进行讲解,双端口ram的创建大同小异。
ip核配置的ram的框图如下所示:
各个端口的功能描述如下:
dina:ram端口a的写数据信号。
addra:ram端口a的读写地址信号,在单端口ram当中,读地址与写地址公用该地址线。
wea:ram端口a写使能信号,高电平为写,低电平为读。
ena:端口a的使能信号,高电平表示使能端口 a,低电平表示端口 a 被禁止,禁止后端口 a上的读
写操作都会变成无效。另外 ENA 信号是可选的,当取消该使能信号后, RAM 会一直处于有效状态。
rsta:ram端口a的复位信号,可配置成高电平或者电平复位,为可选信号。
regcea: ram 端口 a 输出寄存器使能信号,当 regcea为高电平时, douta 保持最后一次输出
的数据, regcea 同样是一个可选信号。
clka: ram 端口 a的时钟信号。
douta: ram 端口 a 读出的数据。
3、程序设计
先建立一个ip_ram的工程,然后创建ram ip核,在 Vivado 软件的左侧“Flow Navigator”栏中单击“IP Catalog”,然后在下图中搜索“block memory”,如下图所示,双击“ Block Memory Generator”后弹出 IP 核的配置界面。
接下来对 BMG IP 核进行配置,“ Basic”选项页配置界面如下图所示 。按图中配置即可。
接下来切换至“Port A”选项页,设置端口 A 的参数,该页面配置如下:
设置完直接点击ok即可生成ip 核。
查看ram ip核的veo文件,有例化ram ip核的模板,如下所示:
blk_mem_gen_0 your_instance_name (
.clka(clka), // input wire clka
.rsta(rsta), // input wire rsta
.ena(ena), // input wire ena
.wea(wea), // input wire [0 : 0] wea
.addra(addra), // input wire [4 : 0] addra
.dina(dina), // input wire [7 : 0] dina
.douta(douta), // output wire [7 : 0] douta
.rsta_busy(rsta_busy) // output wire rsta_busy
);
接下来创建一个新的设计文件,命名为 ram_rw.v,代码如下:
module ram_rw(
input clk ,
input rst_n ,
input [7:0] r_data , //ram读数据
output reg [7:0] w_data , //ram写数据
output ram_we , //ram读写选择
output reg [4:0] ram_addr , //ram读写地址
output ram_en ); //ram使能信号
//读写计数器
reg [5:0] wr_cnt;
assign ram_en = rst_n;//ram使能
//读写计数器在0-31为写,32到63为读
assign ram_we = (wr_cnt<=6'd31 && ram_en==1)? 1'b1 : 1'b0;
//读写计数器,计数范围为0-63
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
wr_cnt <= 0;
else if(wr_cnt==6'd63)
wr_cnt <= 0;
else
wr_cnt <= wr_cnt + 1;
end
//读写地址信号,范围0-31
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
ram_addr <= 0;
else if(ram_addr==5'd31)
ram_addr <= 0;
else
ram_addr <= ram_addr + 1;
end
//产生ram读写数据
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
w_data <= 0;
else if(wr_cnt <= 6'd31)
w_data <= w_data + 1;
else
w_data <= 0;
end
endmodule
程序中定义了一个读写计数器(wr_cnt),在0-31为向ram中写数据,32-63在ram中读数据,接下来编写一个 ip_ram.v 文件,来实例化创建的 RAM IP 核以及 ram_rw 模块,代码如下:
module ip_ram(
input sys_clk,
input sys_rst);
wire [7:0] r_data;
wire [7:0] w_data;
wire ram_en;
wire ram_we;
wire [4:0] ram_addr;
ram_rw ram_rw_inst(
.clk (sys_clk),
.rst_n (sys_rst),
.r_data (r_data),
.w_data (w_data),
.ram_we (ram_we),
.ram_addr(ram_addr),
.ram_en (ram_en) );
blk_mem_gen_0 blk_mem_gen_1 (
.clka(sys_clk), // input wire clka
.ena(ram_en), // input wire ena
.wea(ram_we), // input wire [0 : 0] wea
.addra(ram_addr), // input wire [4 : 0] addra
.dina(w_data), // input wire [7 : 0] dina
.douta(r_data) // output wire [7 : 0] douta
//.rsta_busy(rsta_busy) // output wire rsta_busy
);
endmodule
程序中例化了 ram_rw 模块和 ram IP 核 blk_mem_gen_0,其中 ram_rw 模块负责产生对 ram IP 核读/写所需的所有数据、地址以和读写使能信号,同时从 ram ip 读出的数据也连接至 ram_rw 模块 。接下来对 RAM IP 核进行仿真,来验证对 RAM 的读写操作是否正确。 tb_ram_ip 仿真文件源代码如下:
`timescale 1ns / 1ps
module tb_ram_ip();
reg sys_clk;
reg sys_rst;
ip_ram ip_ram_inst(
.sys_clk(sys_clk),
.sys_rst(sys_rst)
);
initial begin
sys_clk = 0;
sys_rst = 0;
#10
sys_rst = 1;
#256
$stop;
end
always #5 sys_clk = ~sys_clk;
endmodule
接下来开始仿真,仿真结果如下: