20_IP 核之 RAM
1. RAM IP 核简介
2. 实验目标
同样的我们可以设计一个与 ROM 小节一样的例子,只不过是 ROM 是初始化数据文件,而我们这里是由我们自己写入。具体功能如下:按下按键 1 时往 RAM 地址 0–255 里写入数据 0-255;按下按键 2 时读取 RAM 内的数据,从地址 0 开始每隔 0.2s 地址加 1 往下进行读取;再次按下按键 1 时停止读取重新写入数据 0~255;再次按下按键 2 时从头开始读取数据。
3. 模块设计
3.1 整体说明
3.2 按键消抖模块
在《按键消抖模块的设计与验证》章节中我们对按键消抖模块已经有了详细的讲解,这里我们直接调用即可。
3.3 数码管显示模块
在“数码管的动态显示”章节中我们已经对数码管动态显示模块做了详细的讲解,在此就不再讲解了,在这里我们直接调用这个模块即可。需要注意的是该模块下还有子模块我们没有在整体框图中画出,该模块框图如图 27-111 所示。
3.4 RAM IP 核模块
这里我们调用一个单端口 RAM,完全按单端口 RAM 的配置小节的步骤配置来生成一个单端口 RAM IP 核即可。按步骤调用生成完之后打开工程目录下的 RAM IP 核保存位置,如下图所示:
1)aclr:异步清零信号,高电平有效。该清零信号只能清楚输出端口的数据,并不会清除存储器内部存储的内容。
2)address:地址线,位宽为 8bit。各信号的位宽可打开图 27-112 中的“ram_256x8.v”文件进行查看。由于我们调用的 RAM 为单口 RAM,所以只有一组地址写。
3)clock:读写时钟。
4)data:写入 RAM 的数据,位宽为 8bit。
5)rd_en:读使能信号,高电平有效。该信号在配置时刻可选择不生成。
6)wr_en:写使能信号,高电平有效。在 RAM 中,该信号固定存在。
7)q:读出 RAM 中的数据,位宽也是 8bit。
其中“q”为输出信号,其余信号都为该模块的输入信号,需要我们产生输入。RAM 控制模块。
3.5 RAM 控制模块
3.6 顶层模块
4. RAM 控制模块波形
4.1 写操作
4.2 读操作
4.3 读操作时按下了写操作的按键
4.4 正在进行数据的读操作,又按下了数据读操作的按钮
5. RTL
5.1 ram_ctrl
module ram_ctrl
(
input wire sys_clk , //系统时钟,频率 50MHz
input wire sys_rst_n , //复位信号,低有效
input wire key1_flag , //按键 1 消抖后有效信号 作为写标志信号
input wire key2_flag , //按键 2 消抖后有效信号 作为读标志信号
output reg [7:0] addr, //输出读写 RAM 地址 0-255
output wire [7:0] wr_data, //输出写 RAM 数据
output reg wr_en , //输出写 RAM 使能,高点平有效
output reg rd_en //输出读 RAM 使能,高电平有效
);
/*
0.2s(200ms)计数器
如果我们一个时钟读取的地址就变化一次,
也就是说我们读取的数据在一个时钟(20ns)
变化一次再输出数据给数码管显示,
这样的话我们显示的数据就是 20ns 变化一次,这是我们肉眼难以捕捉的
所以所以在这里我们设计让地址每 0.2s 变化一次,
这样读出的数据就是 0.2s 变化一次,我们肉眼就能很清
晰的看到我们读出的数据变化了。所以这里我们先生成一个 0.2s 的计数器
*/
parameter CNT_MAX = 9_999_999; //0.2s 计数器最大值
reg [23:0] cnt_200ms ; //0.2s 计数器
// wr_data 输出写 RAM 数据
// wr_data写的数据我们规定与 产生的地址一样
// 当wr_en写使能有效时候,用组合逻辑电路,因为是实时赋值的没有打一拍, 无效时赋值为0
assign wr_data = (wr_en == 1'b1) ? addr : 8'd0;
//wr_en:产生写 RAM 使能信号
// 观察 we_en 电平在合适拉高拉低
// 1. 按键 1 消抖后有效信号 作为写标志信号为高电平 wr_en 拉高
// 2. 255写完 wr_en 拉低
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(key1_flag == 1'b1)
wr_en <= 1'b1;
//rd_en:产生读 RAM 使能信号
// 1. 拉高:写使能信号为0 读使能信号产生
// 2.当读的时候,写使能拉高,立刻拉低读使能
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_en <= 1'b0;
else if(key2_flag == 1'b1 && wr_en == 1'b0)
rd_en <= 1'b1;
else if(key1_flag == 1'b1)
rd_en <= 1'b0;
else
rd_en <= rd_en;
//0.2s 循环计
// 1. 当读信号来的时候 开始赋值为0 或者到最大数255 为0
// 2. 然后rd_en == 1'b1 读使能为1 开始加
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 || key2_flag == 1'b1)
cnt_200ms <= 24'd0;
else if(rd_en == 1'b1)
cnt_200ms <= cnt_200ms + 1'b1;
//写使能有效时,
// 1. add为0 :
// 1)读写 信拉高,2) 写使能有效 并且到255 3) cnt_200ms 到最大值,并且到255
// 1. add为0 :
// 1)读使能有效并且到最大值0.2s 2)写使能有效
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
addr <= 8'd0;
else if((addr == 8'd255 && cnt_200ms == CNT_MAX) ||
(addr == 8'd255 && wr_en == 1'b1) ||
(key2_flag == 1'b1) || (key1_flag == 1'b1))
addr <= 8'd0;
else if((wr_en == 1'b1) || (rd_en == 1'b1 && cnt_200ms == CNT_MAX))
addr <= addr + 1'b1;
endmodule
5.2 ram
module ram
(
input wire sys_clk , //系统时钟,频率 50MHz
input wire sys_rst_n , //复位信号,低电平有效
input wire [1:0] key , //输入按键信号
output wire stcp , //输出数据存储器时钟
output wire shcp , //移位寄存器的时钟输入
output wire ds , //串行数据输入
output wire oe //输出使能信号
);
wire wr_en ; //写使能
wire rd_en ; //读使能
wire [7:0] addr ; //地址线
wire [7:0] wr_data ; //写数据
wire [7:0] rd_data ; //读出 RAM 数据
wire key1_flag ; //按键 1 消抖信号
wire key2_flag ; //按键 2 消抖信号
ram_ctrl ram_ctrl_inst
(
.sys_clk (sys_clk ), //系统时钟,频率 50MHz
.sys_rst_n (sys_rst_n ), //复位信号,低有效
.key1_flag (key1_flag ), //按键 1 消抖后有效信号,作为写标志信号
.key2_flag (key2_flag ), //按键 2 消抖后有效信号,作为读标志信号
.wr_en (wr_en ), //输出写 RAM 使能,高点平有效
.rd_en (rd_en ), //输出读 RAM 使能,高电平有效
.addr (addr ), //输出读写 RAM 地址
.wr_data (wr_data ) //输出写 RAM 数据
);
key_filter key1_filter_inst
(
.sys_clk (sys_clk ), //系统时钟 50Mhz
.sys_rst_n (sys_rst_n ), //全局复位
.key_in (key[0] ), //按键输入信号
.key_flag (key1_flag ) //key_flag 为 1 时表示消抖后检测到按键被按下
//key_flag 为 0 时表示没有检测到按键被按下
);
//----------------key2_filter_inst----------------
key_filter key2_filter_inst
(
.sys_clk (sys_clk ), //系统时钟 50Mhz
.sys_rst_n (sys_rst_n ), //全局复位
.key_in (key[1] ), //按键输入信号
.key_flag (key2_flag ) //key_flag 为 1 时表示消抖后检测到按键被按下
//key_flag 为 0 时表示没有检测到按键被按下
);
seg_595_dynamic seg_595_dynamic_inst
(
.sys_clk (sys_clk ), //系统时钟,频率 50MHz
.sys_rst_n (sys_rst_n ), //复位信号,低有效
.data ({12'd0,rd_data} ), //数码管要显示的值
.point (0 ), //小数点显示,高电平有效
.seg_en (1'b1 ), //数码管使能信号,高电平有效
.sign (0 ), //符号位,高电平显示负号
.stcp (stcp ), //输出数据存储寄时钟
.shcp (shcp ), //移位寄存器的时钟输入
.ds (ds ), //串行数据输入
.oe (oe ) //输出使能信号
);
//---------------rom_256x8_inst--------------
ram_256_8 ram_256_8_inst
(
.aclr (~sys_rst_n ), //异步清零信号
.address (addr ), //读写地址线
.clock (sys_clk ), //使用系统时钟作为读写时钟
.data (wr_data ), //输入写入 RAM 的数据
.rden (rd_en ), //读 RAM 使能
.wren (wr_en ), //写 RAM 使能
.q (rd_data ) //输出读 RAM 数据
);
endmodule
6. testbench
6.1 tb_ram_ctrl
`timescale 1ns/1ns
module tb_ram_ctrl();
//因为 testbench 不对外进行信号的输入输出,只是自己产生
//激励信号提供给内部实例化待测 RTL 模块使用,所以端口列表
//中没有内容,只是列出“()”,当然可以将“()”省略,括号
//后有个“;”不要忘记
//要在 initial 块和 always 块中被赋值的变量一定要是 reg 型
//在 testbench 中待测试 RTL 模块的输入永远是 reg 型变量
//输出信号,我们直接观察,也不用在任何地方进行赋值
//所以是 wire 型变量(在 testbench 中待测试 RTL 模块的输出永远是 wire 型变量)
reg sys_clk ; //系统时钟,频率 50MHz
reg sys_rst_n ; //复位信号,低有效
reg key1_flag ; //按键 1 消抖后有效信号
reg key2_flag ; //按键 2 消抖后有效信号
//输出信号,我们直接观察,也不用在任何地方进行赋值
//所以是 wire 型变量(在 testbench 中待测试 RTL 模块的输出永远是 wire 型变量)
wire [7:0] addr; //输出读写 RAM 地址 0-255
wire [7:0] wr_data;//输出写 RAM 数据
wire wr_en ; //输出写 RAM 使能,高点平有效
wire rd_en ; //输出读 RAM 使能,高电平有效
//初始化值在没有特殊要求的情况下给 0 或 1 都可以。如果不赋初值,仿真时信号
//会显示为不定态(ModelSim 中的波形显示红色)
initial
//initial 只在通电执行一次
//在仿真中 begin...end 块中的内容都是顺序执行的,
//在没有延时的情况下几乎没有差别,看上去是同时执行的,
//如果有延时才能表达的比较明了;
//而在 rtl 代码中 begin...end 相当于括号的作用, begin...end 在 Testbench 中的用法及意义(区别 -----------------------------------------------------)
//在同一个 always 块中给多个变量赋值的时候要加上
begin
sys_clk = 1'b1; //时钟信号的初始化为 1,且使用“=”赋值,
//其他信号的赋值都是用“<=”
sys_rst_n <= 1'b0; //因为低电平复位,所以复位信号的初始化为 0
key1_flag <= 1'b0;
key2_flag <= 1'b0;
#20 //延时20ns
sys_rst_n <= 1'b1; //初始化 20ns 后,复位释放,因为是低电平复位
// key1_flag 作为写标志信号
// key2_flag 作为读标志信号
//读
#1000
key2_flag <= 1'b1;
#20
key2_flag <= 1'b0;
//写
#60000
key1_flag <= 1'b1;
#20
key1_flag <= 1'b0;
//读
//注意读的时候,注意 要仿真写完才能够读
#6000
key2_flag <= 1'b1;
#20
key2_flag <= 1'b0;
//读
#6000
key2_flag <= 1'b1;
#20
key2_flag <= 1'b0;
end
//产生时钟信号
always #10 sys_clk = ~sys_clk;
defparam ram_ctrl_inst.CNT_MAX = 10;
ram_ctrl ram_ctrl_inst
(
//前面的“in1”表示被实例化模块中的信号,后面的“in1”表示实例化该模块并要和这个
//模块的该信号相连接的信号(可以取名不同,一般取名相同,方便连接和观察)
//“.”可以理解为将这两个信号连接在一起
.sys_clk(sys_clk),
.sys_rst_n(sys_rst_n),
.key1_flag(key1_flag) , //按键 1 消抖后有效信号
.key2_flag(key2_flag) , //按键 2 消抖后有效信号
.addr(addr) , //输出读 ROM 地址 0-255
.wr_data(wr_data),//输出写 RAM 数据
.wr_en(wr_en) , //输出写 RAM 使能,高点平有效
.rd_en(rd_en) //输出读 RAM 使能,高电平有效
);
endmodule
6.2 tb_ram
`timescale 1ns/1ns
module tb_ram();
// wire define
wire stcp;
wire shcp;
wire ds ;
wire oe ;
//reg define
reg sys_clk ;
reg sys_rst_n ;
reg [1:0] key ;
//对 sys_clk,sys_rst 赋初值,并模拟按键抖动
initial
begin
sys_clk = 1'b1 ;
sys_rst_n <= 1'b0 ;
key <= 2'b11;
#200 sys_rst_n <= 1'b1 ;
//按下按键 key[1]
#2000000 key[1] <= 1'b0;//按下按键
#20 key[1] <= 1'b1;//模拟抖动
#20 key[1] <= 1'b0;//模拟抖动
#20 key[1] <= 1'b1;//模拟抖动
#20 key[1] <= 1'b0;//模拟抖动
#200 key[1] <= 1'b1;//松开按键
#20 key[1] <= 1'b0;//模拟抖动
#20 key[1] <= 1'b1;//模拟抖动
#20 key[1] <= 1'b0;//模拟抖动
#20 key[1] <= 1'b1;//模拟抖动
//按下按键 key[0]
#2000000 key[0] <= 1'b0;//按下按键
#20 key[0] <= 1'b1;//模拟抖动
#20 key[0] <= 1'b0;//模拟抖动
#20 key[0] <= 1'b1;//模拟抖动
#20 key[0] <= 1'b0;//模拟抖动
#200 key[0] <= 1'b1;//松开按键
#20 key[0] <= 1'b0;//模拟抖动
#20 key[0] <= 1'b1;//模拟抖动
#20 key[0] <= 1'b0;//模拟抖动
#20 key[0] <= 1'b1;//模拟抖动
//按下按键 key[1]
#2000000 key[1] <= 1'b0;//按下按键
#20 key[1] <= 1'b1;//模拟抖动
#20 key[1] <= 1'b0;//模拟抖动
#20 key[1] <= 1'b1;//模拟抖动
#20 key[1] <= 1'b0;//模拟抖动
#200 key[1] <= 1'b1;//松开按键
#20 key[1] <= 1'b0;//模拟抖动
#20 key[1] <= 1'b1;//模拟抖动
#20 key[1] <= 1'b0;//模拟抖动
#20 key[1] <= 1'b1;//模拟抖动
//按下按键 key[1]
#2000000 key[1] <= 1'b0;//按下按键
#20 key[1] <= 1'b1;//模拟抖动
#20 key[1] <= 1'b0;//模拟抖动
#20 key[1] <= 1'b1;//模拟抖动
#20 key[1] <= 1'b0;//模拟抖动
#200 key[1] <= 1'b1;//松开按键
#20 key[1] <= 1'b0;//模拟抖动
#20 key[1] <= 1'b1;//模拟抖动
#20 key[1] <= 1'b0;//模拟抖动
#20 key[1] <= 1'b1;//模拟抖动
//按下按键 key[0]
#2000000 key[0] <= 1'b0;//按下按键
#20 key[0] <= 1'b1;//模拟抖动
#20 key[0] <= 1'b0;//模拟抖动
#20 key[0] <= 1'b1;//模拟抖动
#20 key[0] <= 1'b0;//模拟抖动
#200 key[0] <= 1'b1;//松开按键
#20 key[0] <= 1'b0;//模拟抖动
#20 key[0] <= 1'b1;//模拟抖动
#20 key[0] <= 1'b0;//模拟抖动
#20 key[0] <= 1'b1;//模拟抖动
//按下按键 key[1]
#2000000 key[1] <= 1'b0;//按下按键
#20 key[1] <= 1'b1;//模拟抖动
#20 key[1] <= 1'b0;//模拟抖动
#20 key[1] <= 1'b1;//模拟抖动
#20 key[1] <= 1'b0;//模拟抖动
#200 key[1] <= 1'b1;//松开按键
#20 key[1] <= 1'b0;//模拟抖动
#20 key[1] <= 1'b1;//模拟抖动
#20 key[1] <= 1'b0;//模拟抖动
#20 key[1] <= 1'b1;//模拟抖动
end
//sys_clk:模拟系统时钟,每 10ns 电平取反一次,周期为 20ns,频率为 50Mhz
always #10 sys_clk = ~sys_clk;
//重新定义参数值,缩短仿真时间仿真
defparam ram_inst.key1_filter_inst.CNT_MAX = 5 ;
defparam ram_inst.key2_filter_inst.CNT_MAX = 5 ;
defparam ram_inst.ram_ctrl_inst.CNT_MAX = 99;
//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//
//---------------ram_inst--------------
ram ram_inst
(
.sys_clk (sys_clk ), //系统时钟,频率 50MHz
.sys_rst_n (sys_rst_n ), //复位信号,低电平有效
.key (key ), //输入按键信号
.stcp (stcp ), //输出数据存储寄时钟
.shcp (shcp ), //移位寄存器的时钟输入
.ds (ds ), //串行数据输入
.oe (oe ) //输出使能信号
);
endmodule