IP核之ROM

ROM概述

        ROM 是只读存储器(Read-Only Memory)的简称,是一种只能读出事先所存数据的固态半导体存储器。其特性是一旦储存资料就无法再将之改变或删除,且资料不会因为电源关闭而消失。

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

                                                 单端口ROM接口信号

双端口ROM接口信号

单端口ROM的配置

我们在工程目录下新建一个 ip_core 文件夹,将 IP核保存在该文件夹下并命名为 rom_256x8 rom 是我们调用的 IP 核, 256 是调用的 IP 核容量,8 是调用的 IP 核数据位宽。这里这样命名是为了方便识别我们创建的 IP 核类型及资源量,我们制作的数据文件就是容量为 256 ,数据位宽为 8bit)。

       选择使用的时钟模式,可选择单时钟或双时钟。选择单时钟时用一个时钟控制存储块的所有寄存器,选择双时钟时输入时钟控制地址寄存器,输出时钟控制数据输出寄存器。ROM 模式没有写使能、字节使能和数据输入寄存器。这里我们选择默认选项Single clock(单时钟)。

上图是选择是否输出“q”寄存器。这里我们把输出端口的寄存器去掉(如果不去掉的 话,就会使输出延迟一拍。如果没有特别的需求的话我们是不需要延迟这一拍的).

不勾选时,数据输出会延时地址输入一个时钟周期,勾选时,时钟信号会输入更高的频率,数据输出会延时地址输入两个时钟周期

上图是添加文件路径 

提示了我们单独使用第三方仿真工具时需要添加名为 “altera_mf”的库

双端口ROM的配置

As a number of words”是按字数确定,“AS a number of bits”是按比特数确定。

(注意:选择的容量需大于我们需要写入的数据文件的数据量

 Single clock(单时钟):使用一个时钟控制。

Dual clock use separate input and output clocks (双时钟:使用单独的输入时钟和输出时钟):输入和输出时钟分别控制存储块的数据输入和输出的相关寄存器。
Dual clock use separate clocks for A and B ports (双时钟:端口 A 和端口 B 使用不同的独立时钟):时钟 A 控制端口 A 的所有寄存器,时钟 B 控制端口 B 的所有寄存器。
ROM IP核的使用
实验目标
ROM 内的数据读取出来显示在数码管上。我们 ROM 的初始化数据是 0~255,也就是存入数据
0~255 。然后每隔 0.2s 我们从 0 地址开始往下读取数据显示在数码管上,我们再利用两个按键信号来读取指定地址的数据,每按一个按键就读取一个地址的数据显示在数码管上。再次按下按键后,以当前地址继续以 0.2s 的时间间隔往下读取数据并显示出来。

 ROM IP核模块

 ROM控制模块

ROM 的数据手册可知,读操作是在时钟的上升沿触发的,而我们在调用 ROM 时是没有生成读使能的,所以在 读时钟上升沿我们只要给相应的地址就能在时钟的上升沿读出该地址内的数据了。在该模块我们只需要控制生成读地址即可。

  ROM控制模块输入输出信号     

      ROM 并不是只能从 0 地址开始读取,它能读取指定的任意地址,为了验证这个功能,我们加入两个按键信号,每按一个按键就读出一个指定地址的数据,再次按下就又沿当前地址顺序读取。
      addr_flag1 addr_flag2 : 读 地 址 的 标 志 信 号 。 当 按 下 按 键 1/ 按 键 2
(key1_flag/key2_flag=1)时,让读地址的标志信号为高,再次按下时让读地址的标志信号为低,我们使用一个取反操作即可完成。 当按键 1 按下后, addr_flag1 为高读取地址 99 的数据;当按下按键 2 后, addr_flag2 为高读取地址 199 的数据(地址大家可任意去取,只要在 ROM 的地址范围即可)。这里需要注意的是 因为每次我们只能读取一个地址的信号,所以我们在拉高一个地址标志信号时要将另一个地址标志信号置为 0,这样读取的地址才不会有冲突。
ROM控制模块参考代码(rom_ctrl.v)
module  rom_ctrl
#(
   parameter  CNT_MAX = 24'd9_999_999
)
(
   input  wire          sys_clk  ,
   input  wire          sys_rst_n,
   input  wire          key1     ,     //按键1消抖后有效信号
   input  wire          key2     ,     //按键2消抖后有效信号
   
   output reg   [7:0]  addr            //输出读ROM地址
);

reg   [23:0]  cnt_200ms ;
reg           key1_en;                 //特定地址 1 标志信号
reg           key2_en;                 //特定地址 2 标志信号

//0.2s 循环计数
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 || key1_en == 1'b1 || key2_en == 1'b1)
       cnt_200ms <= 24'd0;
   else 
       cnt_200ms <= cnt_200ms + 1'b1;

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

//让地址从 0~255 循环,其中两个按键控制两个特定地址的跳转
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(key1_en == 1'b1)
       addr <= 8'd99;
   else if(key2_en == 1'b1)
       addr <=8'd199;
   else if(cnt_200ms == CNT_MAX)
       addr <= addr + 1'b1;

endmodule

顶层模块

rom 顶层模块主要是对各个子功能模块的实例化,以及对应信号的连接

 

rom 顶层模块主要是对各个子功能模块的实例化,以及对应信号的连接,代码编写较为容易,无需波形图的绘制
module rom
(
   input  wire    sys_clk,
   input  wire    sys_rst_n,
   input  wire    key1     ,
   input  wire    key2     ,
   
   output wire    ds,
   output wire    oe,
   output wire    shcp,
   output wire    stcp
);

wire           key1_flag;  //按键 1 消抖信号
wire           key2_flag;  //按键 2 消抖信号
wire  [7:0]    addr;
wire  [7:0]    data;       //读出 ROM 数据

key_filter
#(
    .CNT_MAX (20'd999_999)
)
key_filter_inst1
(
   .sys_clk  (sys_clk),
   .sys_rst_n(sys_rst_n),
   .key_in   (key1),
   
   .key_flag (key1_flag)     //key_flag 为 1 时表示消抖后检测到按键被按下
);

key_filter
#(
    .CNT_MAX (20'd999_999)
)
key_filter_inst2
(
   .sys_clk  (sys_clk),
   .sys_rst_n(sys_rst_n),
   .key_in   (key2),
   
   .key_flag (key2_flag)     //key_flag 为 1 时表示消抖后检测到按键被按下
);

rom_ctrl
#(
   .CNT_MAX (24'd9_999_999)
)
rom_ctrl_inst
(
   .sys_clk  (sys_clk),
   .sys_rst_n(sys_rst_n),
   .key1     (key1_flag),      //按键 1 消抖后有效信号 
   .key2     (key2_flag),      //按键 2 消抖后有效信号
   
   .addr     (addr)            //输出读 ROM 地址
);

rom_8x256	rom_8x256_inst 
(
	.address ( addr ),
	.clock ( sys_clk ),
	.q ( data )
	);
	
seg_595_dynamic seg_595_dynamic
(
   .sys_clk  (sys_clk),
   .sys_rst_n(sys_rst_n),
   .data     ({12'b0,data}),
   .point    (6'b000_000),      //小数点显示,高电平有效
   .sign     (1'b0),            //符号位,高电平显示负号
   .seg_en   (1'b1),            //数码管使能信号,高电平有效
  
   .ds       (ds  ),       //串行数据输入
   .oe       (oe  ),       //输出使能信号
   .shcp     (shcp),       //移位寄存器的时钟输入
   .stcp     (stcp)        //输出数据存储寄时钟
);

endmodule

 RTL视图     

 ROM IP核仿真

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

reg   sys_clk;
reg   sys_rst_n;
reg   key1;
reg   key2;

wire  ds;
wire  oe;
wire  shcp;
wire  stcp;

initial
     begin
	    sys_clk = 1'b1;
		sys_rst_n   <= 1'b0;
		key1  <= 1'b1;
		key2  <= 1'b1;
		#20
		sys_rst_n   <= 1'b1;
		#700000                   //仿真时是100个时钟周期,一个时钟周期20ns,也就是一个
                                  //数据的显示时间是100*20=2000ns,
                           //256个数据显示时间是256*2000=512000ns,故延时的时间需要大于此值

//key1
		key1  <= 1'b0;
		#20
		key1  <= 1'b1;
		#20
		key1  <= 1'b0;
		#20
		key1  <= 1'b1;
		#20
		key1  <= 1'b0;
		#200                           //以上是模拟按键前抖动,以下是模拟按键后抖动
		key1  <= 1'b1;
		#20
		key1  <= 1'b0;
		#20
		key1  <= 1'b1;
		#20
		key1  <= 1'b0;
		#20
		key1  <= 1'b1;
//key2
        #20000
		key2  <= 1'b0;
		#20
		key2  <= 1'b1;
		#20
		key2  <= 1'b0;
		#20
		key2  <= 1'b1;
		#20
		key2  <= 1'b0;
		#200                          //以上是模拟按键前抖动,以下是模拟按键后抖动
		key2  <= 1'b1;
		#20
		key2  <= 1'b0;
		#20
		key2  <= 1'b1;
		#20
		key2  <= 1'b0;
		#20
		key2  <= 1'b1;
//key2
        #20000
		key2  <= 1'b0;
		#20
		key2  <= 1'b1;
		#20
		key2  <= 1'b0;
		#20
		key2  <= 1'b1;
		#20
		key2  <= 1'b0;
		#200                         //以上是模拟按键前抖动,以下是模拟按键后抖动
		key2  <= 1'b1;
		#20
		key2  <= 1'b0;
		#20
		key2  <= 1'b1;
		#20
		key2  <= 1'b0;
		#20
		key2  <= 1'b1;
	 end
	 
always #10 sys_clk = ~sys_clk;
//defparam   rom_inst.key_filter_indt1.CNT_MAX = 9;

rom  rom_inst
(
   .sys_clk  (sys_clk  ),
   .sys_rst_n(sys_rst_n),
   .key1     (key1     ),
   .key2     (key2     ),
  
   .ds       (ds       ),
   .oe       (oe       ),
   .shcp     (shcp     ),
   .stcp     (stcp     )
);

endmodule

接下来仿真验证

  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值