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

猜你喜欢

转载自blog.csdn.net/HeElLose/article/details/131303872
RAM