RAM IP核简介
RAM
是随机存取存储器(
Random Access Memory
)的简称,是一个易失性存储器。RAM 工作时可以随时从任何一个指定的地址写入或读出数据,同时我们还能修改其存储的数据,即写入新的数据,这是 ROM
所并不具备的功能。
Altera
推出的
RAM IP
核分为两种类型:单端口
RAM
和双端口
RAM
。
RAM
又分为简单双端口
RAM
和真正双端口
RAM:
-
单端口 RAM ,读写操作共用一组地址线,读写操作不能同时进行
- 简单双端口 RAM,读操作和写操作有专用地址端口(一个读端口和一个写端口),即写端口只能写不能读,而读端口只能读不能写
- 真正双端口 RAM,有两个地址端口用于读写操作(两个读/写端口),即两个端口都可以进
行读写。
单端口
RAM
接口信号
简单双端口
RAM
接口信号
真正双端口 RAM 接口信号
RAM IP核配置
单端口RAM的配置
第二幅图是选择是否创建“
aclr
”异步复位信号以及是否创建“
rden
”读使能信号,大家可根据实际的设计需求进行勾选,这里我们把它们都勾选上。后面EDA、Summary都一样。
后面图是选择某个地址即将被写入数据时读该地址的数据输出类型:有 Don’t Care
(不关心)、
New Data
(写入的新数据)和 Old Data(原有数据),我们保持默认的
New Data
即可,也就是说,某个地址将被写入新数据时,同时进行读操作会读出新的数据
简单双口RAM的配置
其中第一行是简单双口 RAM
,第二行是真双口 RAM
,这里我们先选择简单双口RAM 为大家讲解。第三行是设置 ROM
存储器大小的方式,“
As a number of words
”是按字数确定,“AS a number of bits
”是按比特数确定。我们默认选择按字数确定。
真双端口 RAM 的配置
RAM IP核使用
按下按键
1
时往
RAM
地址
0~255
里写入数据
0~255
;按下按键
2
时读取
RAM
内的数据,从地址 0
开始每隔
0.2s
地址加
1
往下进行读取;再次按下按键
1
时停止读取重新写入数据 0~255
;再次按下按键
2
时从头开始读取数据。
RAM 控制模块
当按下按键 1 时(key1_flag=1)说明要开始往 RAM 里写数据了。RAM 的写时序为:当 RAM 写时钟上升沿采到写使能为高时,就能将该上升沿采到的数据写入该上升沿采到的地址中。所以要想往 RAM 里写数据必须有时钟,写使能,地址,写数据信号。
wr_en
:写
RAM
使能信号,高有效,当其为高电平时才能往
RAM
里写数据。所以当检测到按键 1
消抖信号有效时,我们拉高写使能,开始往
RAM
里写数据。
addr
:当写使能信号为高时,我们需要给写入
RAM
的地址才能往地址里写入数据。这里我们从 0
地址开始写入,一个时钟上升沿写一个地址,依次一直写到最后一个地址255。当写完最后一个地址时拉低写使能信号,停止写入。
wr_data
:写入
RAM
的数据。当使能和地址信号都有了,再加上地址就能往
RAM
里写入数据了。这里我们让数据和地址相等即可。即往地址里写入数据 0~255.
读操作与写操作的时序是一样的。都是时钟上升沿触发。RAM
的读时序为:当
RAM读时钟上升沿采到读使能为高时,就能读出该上升沿采到的地址中的数据。若是我们配置IP 核时没有生成
RAM
读使能,那么
RAM
就能直接读出读时钟上升沿采到的地址中的数据。
rd_en
:读
RAM
使能信号,高有效,当其为高电平时才能读出
RAM
里的数据。当检测到按键 2
消抖信号有效时,我们拉高读使能信号,开始读出
RAM
里的数据。由于我们读出的数据要显示在数码管上,如果读的太快,我们肉眼是看不出来的。所以这里我们设计每 0.2s
读一个地址。这样我们就能很清晰的看到我们读出的数据了
当
0.2s
计数器产生完之后,每检测到计数器计到最大值我们就让地址加一,这样我们就能每 0.2s
依次读出
RAM
里的数据了。当读完最后一个地址的数据时,我们让地址归
0重新开始读取,依次循环。
若我们在往
RAM
里写数据时,按下按键
2
(读按键)将不会读取
RAM
内的数据,即写使能为低时按下按键 2
(读按键)才会拉高读使能。若我们在往
RAM
里写数据时,按下按键 1
(写键),那么我们就将地址归
0
,从
0
地址开始重新写入。
RAM控制模块参考代码(ram_ctrl.v)
module ram_ctrl
(
input wire sys_clk,
input wire sys_rst_n,
input wire wr_flag,
input wire rd_flag,
output reg wr_en, //输出写 RAM 使能,高点平有效
output reg [7:0] addr, //输出读写 RAM 地址
output wire [7:0] wr_data, //输出写 RAM 使能,高点平有效
output reg rd_en //输出读 RAM 使能,高电平有效
);
parameter CNT_MAX =24'd9_999_999;
reg [23:0] cnt_200ms;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_200ms <= 24'd0;
else if((cnt_200ms == CNT_MAX) || (wr_flag == 1'b1) || (rd_flag == 1'b1))
cnt_200ms <= 24'd0;
else if(rd_en == 1'b1)
cnt_200ms <= cnt_200ms + 1'b1;
//wr_en:产生写 RAM 使能信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
wr_en <= 1'b0;
else if(addr == 8'd255)
wr_en <= 1'b0;
else if(wr_flag == 1'b1)
wr_en <= 1'b1;
//写使能有效时,
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
addr <= 8'd0;
else if((addr == 8'd255&&wr_en == 1'b1) || (addr == 8'd255&&cnt_200ms == CNT_MAX)
|| (wr_flag == 1'b1) || (rd_flag == 1'b1))
addr <= 8'd0;
else if((wr_en == 1'b1) || (rd_en ==1'b1 && cnt_200ms == CNT_MAX))
addr <= addr + 1'b1;
//让写入的数据等于地址数,即写入数据 0~255
assign wr_data = (wr_en == 1'b1) ? addr : 8'd0;
//rd_en:产生读 RAM 使能信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_en <= 1'b0;
else if(wr_flag == 1'b1)
rd_en <= 1'b0;
else if(rd_flag == 1'b1 && wr_en == 1'b0)
rd_en <= 1'b1;
else
rd_en <= rd_en;
endmodule
RAM顶层代码模块参考代码(ram.v)
module ram
(
input wire sys_clk,
input wire sys_rst_n,
input wire wr_key,
input wire rd_key,
output wire ds,
output wire oe,
output wire shcp,
output wire stcp
);
wire wr_flag;
wire rd_flag;
wire wr_en ;
wire [7:0] addr ;
wire [7:0] wr_data;
wire rd_en ;
wire [7:0] out_data;
key_filter
#(
.CNT_MAX (20'd999_999)
)
key_filter_wr_inst
(
.sys_clk (sys_clk),
.sys_rst_n(sys_rst_n),
.key_in (wr_key),
.key_flag (wr_flag)
);
key_filter
#(
.CNT_MAX (20'd999_999)
)
key_filter_rd_inst
(
.sys_clk (sys_clk),
.sys_rst_n(sys_rst_n),
.key_in (rd_key),
.key_flag (rd_flag)
);
ram_ctrl ram_ctrl_inst
(
.sys_clk (sys_clk ),
.sys_rst_n(sys_rst_n),
.wr_flag (wr_flag ),
.rd_flag (rd_flag ),
.wr_en (wr_en ),
.addr (addr ),
.wr_data (wr_data),
.rd_en (rd_en )
);
ram_8x256_one ram_8x256_one_inst
(
.aclr ( ~sys_rst_n ),
.address ( addr ),
.clock ( sys_clk ),
.data ( wr_data ),
.rden ( rd_en ),
.wren ( wr_en ),
.q ( out_data )
);
seg_595_dynamic seg_595_dynamic_inst
(
.sys_clk (sys_clk ),
.sys_rst_n(sys_rst_n ),
.data ({12'b0,out_data} ),
.point (6'b000_000 ),
.sign (1'b0 ),
.seg_en (1'b1 ),
.ds (ds ),
.oe (oe ),
.shcp (shcp),
.stcp (stcp)
);
endmodule
RTL视图
RAM IP 核仿真
`timescale 1ns/1ns
module tb_ram();
reg sys_clk;
reg sys_rst_n;
reg wr_key;
reg rd_key;
wire ds ;
wire oe ;
wire shcp ;
wire stcp ;
initial
begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
wr_key = 1'b1;
rd_key = 1'b1;
#20
sys_rst_n <= 1'b1;
#10000
//rd_key
#20
rd_key =1'b1;
#20
rd_key =1'b0;
#20
rd_key =1'b1;
#20
rd_key =1'b0;
#200
rd_key =1'b1;
#20
rd_key =1'b0;
#20
rd_key =1'b1;
#20
rd_key =1'b0;
#20
rd_key =1'b1;
#200000
//wr_key
#20
wr_key =1'b1;
#20
wr_key =1'b0;
#20
wr_key =1'b1;
#20
wr_key =1'b0;
#200
wr_key =1'b1;
#20
wr_key =1'b0;
#20
wr_key =1'b1;
#20
wr_key =1'b0;
#20
wr_key =1'b1;
#10000
//rd_key
#20
rd_key =1'b1;
#20
rd_key =1'b0;
#20
rd_key =1'b1;
#20
rd_key =1'b0;
#200
rd_key =1'b1;
#20
rd_key =1'b0;
#20
rd_key =1'b1;
#20
rd_key =1'b0;
#20
rd_key =1'b1;
#200000
//rd_key
#20
rd_key =1'b1;
#20
rd_key =1'b0;
#20
rd_key =1'b1;
#20
rd_key =1'b0;
#200
rd_key =1'b1;
#20
rd_key =1'b0;
#20
rd_key =1'b1;
#20
rd_key =1'b0;
#20
rd_key =1'b1;
end
//sys_clk:模拟系统时钟,每 10ns 电平取反一次,周期为 20ns,频率为 50MHz
always #10 sys_clk = ~sys_clk;
//重新定义参数值,缩短仿真时间仿真
defparam ram_inst.key_filter_wr_inst.CNT_MAX = 9;
defparam ram_inst.key_filter_rd_inst.CNT_MAX = 9;
defparam ram_inst.ram_ctrl_inst.CNT_MAX = 99;
ram ram_inst
(
.sys_clk (sys_clk ),
.sys_rst_n(sys_rst_n),
.wr_key (wr_key ),
.rd_key (rd_key ),
.ds (ds ),
.oe (oe ),
.shcp (shcp),
.stcp (stcp)
);
endmodule