19_ROM IP 核

1. ROM IP 核简介

1)用 IP 核生成的 ROM 模块只是提前添加了数据文件(.mif 或.hex 格式),在 FPGA 运行时通过数据文件给 ROM 模块初始化,才使得 ROM 模块像个“真正”的掉电非易失存储器;也正是这个原因,ROM 模块的内容必须提前在数据文件中写死,无法在电路中修改。
2)Altera 推出的 ROM IP 核分为两种类型:单端口 ROM 和双端口ROM。对于单端口ROM 提供一个读地址端口和一个读数据端口,只能进行读操作;双端口 ROM 与单端口ROM 类似,区别是其提供两个读地址端口和两个读数据端口,基本上可以看做两个单口RAM 拼接而成。

2. mif 格式文件

ROM 作为只读存储器,在进行 IP 核设置时需要指定初始化文件,即写入存储器中的图片数据,图片要以规定的格式才能正确写入 ROM,这种格式就是 mif 文件。mif 是Quartus 规定的一种文件格式,如下图所示:
在这里插入图片描述

3. 实验目标

我们可以结合我们之前讲的数码管,将 ROM 内的数据读取出来显示在数码管上。我们可以设计这样一个实验:首先我们 ROM 的初始化数据是 0–255,也就是存入数据0~255。然后每隔 0.2s 我们从 0 地址开始往下读取数据显示在数码管上,我们再利用两个按键信号来读取指定地址的数据,每按一个按键就读取一个地址的数据显示在数码管上。再次按下按键后,以当前地址继续以 0.2s 的时间间隔往下读取数据并显示出来。

4. 模块设计

4.1 整体说明

在这里插入图片描述
在这里插入图片描述

4.2 按键消抖模块

在《按键消抖模块的设计与验证》章节中我们对按键消抖模块已经有了详细的讲解,这里我们直接调用即可。

4.3 数码管显示模块

在“数码管的动态显示”章节中我们已经对数码管动态显示模块做了详细的讲解,在此就不再讲解了,在这里我们直接调用这个模块即可。
在这里插入图片描述

4.4 ROM IP 核模块

这里我们调用一个单端口 ROM,完全按单端口 ROM 的配置小节的配置步骤配置来生成一个单端口 ROM IP 核即可。按步骤调用生成完之后打开工程目录下的 ROM IP 核保存位置,如图所示。
在这里插入图片描述
在这里插入图片描述

4.5 ROM 控制模块

在这里插入图片描述

在这里插入图片描述

扫描二维码关注公众号,回复: 15816756 查看本文章

4.6 顶层模块

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

5. ROM 控制模块波形

在这里插入图片描述

5.1 首地址到末尾读取

在这里插入图片描述

5.2 按键 key1

在这里插入图片描述

5.3 按键 key2

在这里插入图片描述

5.4 第一种情况 key1

在这里插入图片描述

5.5 第一种情况 key2

在这里插入图片描述

在这里插入图片描述

6. RTL

6.1 rom_ctrl

module rom_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 //输出读 ROM 地址 0-255
);

/*
	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 计数器

reg addr_flag1 ; //特定地址 1 标志信号
reg addr_flag2 ; //特定地址 2 标志信号

/*
	addr_flag1 、 addr_flag2 : 读 地 址 的 标 志 信 号 。 当 按 下 按 键 1/ 按 键 2
	(key1_flag/key2_flag=1)时,让读地址的标志信号为高,再次按下时让读地址的标志信号
	为低,我们使用一个取反操作即可完成。
*/
//产生特定地址 1 标志信号
// 注意!!!!!!!!!  信号的归0 一般都是在复位之后
always@(posedge sys_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		addr_flag1 <= 1'b0;
	else if(key2_flag == 1'b1)
		addr_flag1 <= 1'b0;
	else if(key1_flag == 1'b1)
		addr_flag1 <= ~addr_flag1;
	else 
		addr_flag1 <= addr_flag1;


//产生特定地址 2 标志信号
always@(posedge sys_clk or negedge sys_rst_n)
	if(sys_rst_n == 1'b0)
		addr_flag2 <= 1'b0;
	else if(key1_flag == 1'b1)
		addr_flag2 <= 1'b0;
	else if(key2_flag == 1'b1)
		addr_flag2 <= ~addr_flag2;
	else 
		addr_flag2 <= addr_flag2;

// //0.2s 循环计数
/*
	每来一个时钟上升沿计数器加一,当加到最大值
	CNT_MAX(9999999)时,计数器清零开始下一轮的计数。
	0~9999999 即 10000000 个系统
	时钟,即为 0.2s(10000000*20ns=0.2s)。
	同时当我们使用按键跳转读某个地址的数据时我
	们也让计数器为 0,让其停止计数。
*/
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 || addr_flag1 == 1'b1 ||  addr_flag2 == 1'b1)
		cnt_200ms <= 24'd0;
	else 
		cnt_200ms <= cnt_200ms + 24'd1;
/*
	当 0.2s 计数器产生完之后,每检测到计数器计到最大值时我们就让地址加一,
	这样我们就能每 0.2s 依次读出 ROM 里的数据了。
	这里需要说明的是 ROM 并不是只能从 0 地址开始读取,它能读取指定的任意地址,
	为了验证这个功能,
	我们加入两个按键信号,每按一个按键就读出一个指定地址的数据,
	再次按下就又沿当前地址顺序读取。

*/
//让地址从 0~255 循环,其中两个按键控制两个特定地址的跳转
//都是先判断等于 0 的条件
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'd0;
	else if(addr_flag1 == 1'b1)
		addr <= 8'd99;
	else if(addr_flag2 == 1'b1)
		addr <= 8'd199;
	else if(cnt_200ms == CNT_MAX)
		addr <= addr + 8'd1;
	else	 
		addr <= addr;


endmodule

6.2 rom

module rom
(
	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 define
wire [7:0] addr ; //地址线
wire [7:0] rom_data ; //读出 ROM 数据
wire key1_flag ; //按键 1 消抖信号
wire key2_flag ; //按键 2 消抖信号

//----------------rom_ctrl_inst----------------
rom_ctrl rom_ctrl_inst
(
	  .sys_clk (sys_clk ), //系统时钟,频率 50MHz
      .sys_rst_n (sys_rst_n ), //复位信号,低有效
      .key1_flag (key1_flag ), //按键 1 消抖后有效信号
      .key2_flag (key2_flag ), //按键 2 消抖后有效信号
 
      .addr (addr ) //输出读 ROM 地址
);
//----------------key1_filter_inst--------------
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_inst-------------
seg_595_dynamic seg_595_dynamic_inst
(
       .sys_clk (sys_clk ), //系统时钟,频率 50MHz
       .sys_rst_n (sys_rst_n ), //复位信号,低有效
       .data ({
    
    12'd0,rom_data}), //数码管要显示的值
       .point (0 ), //小数点显示,高电平有效
       .seg_en (1'b1 ), //数码管使能信号,高电平有效
       .sign (0 ), //符号位,高电平显示负号
       
       .stcp (stcp ), //输出数据存储寄时钟
       .shcp (shcp ), //移位寄存器的时钟输入
       .ds (ds ), //串行数据输入
       .oe (oe ) //输出使能信号
);

//----------------rom_256x8_inst---------------
rom_256_8 rom_256_8_inst
(
       .address (addr ),
       .clock (sys_clk ),
       .q (rom_data )
);

endmodule

7. testbench

7.1 rom_ctrl

`timescale 1ns/1ns
module tb_rom_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 ; //输出读 ROM 地址 0-255

//初始化值在没有特殊要求的情况下给 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;
		#30 //延时30ns
		sys_rst_n <= 1'b1; //初始化 20ns 后,复位释放,因为是低电平复位	
		
		#700_000
		key1_flag <= 1'b1;
		#20
		key1_flag <= 1'b0;
		#20000
		key1_flag <= 1'b1;
		#20
		key1_flag <= 1'b0;
		
		#600000
		key2_flag <= 1'b1;
		#20
		key2_flag <= 1'b0;
		#20000
		key2_flag <= 1'b1;
		#20
		key2_flag <= 1'b0;
		
		#600000
		key1_flag <= 1'b1;
		#20
		key1_flag <= 1'b0;
		#20000
		key2_flag <= 1'b1;
		#20
		key2_flag <= 1'b0;
		#20000
		key2_flag <= 1'b1;
		#20
		key2_flag <= 1'b0;
		
	end

//产生时钟信号
always #10 sys_clk = ~sys_clk;

defparam rom_ctrl_inst.CNT_MAX = 24'd99;

rom_ctrl rom_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
	
);

endmodule						 


7.2 rom

`timescale 1ns/1ns
module tb_rom();

//********************************************************************//
//****************** Parameter and Internal Signal *******************//
//********************************************************************//

//wire define
wire stcp;
wire shcp;
wire ds;
wire oe;

//reg define
reg sys_clk;
reg sys_rst_n;
reg [1:0] key;

 //********************************************************************//
 //***************************** Main Code ****************************//
 //********************************************************************//

//对 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[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[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[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;//模拟抖动
	end

//sys_clk:模拟系统时钟,每 10ns 电平取反一次,周期为 20ns,频率为 50Mhz
always #10 sys_clk = ~sys_clk;

//重新定义参数值,缩短仿真时间仿真
defparam rom_inst.key1_filter_inst.CNT_MAX = 5 ;
defparam rom_inst.key2_filter_inst.CNT_MAX = 5 ;
defparam rom_inst.rom_ctrl_inst.CNT_MAX = 99;

//********************************************************************//
//*************************** Instantiation **************************//
//********************************************************************//

//---------------rom_inst--------------
rom rom_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/131295817