0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

FPGA学习笔记:ROM IP核的使用方法

CHANBAEK 来源:小小研究生 作者:xxyjs2020 2023-08-22 15:06 次阅读

理论学习

上一篇介绍了常用的锁相环IP,这一节将介绍一种较为常用的 存储类IP核 ——ROM的使用方法。ROM是 只读存储器 (Read-Only Memory),顾名思义,我们只能读出事先存放在固态中的数据,一旦写入不能再修改或删除,断电不丢失。我们知道FPGA只有RAM,因此事实上在 FPGA 中通过 IP 核生成的 ROM 或 RAM掉电内容都会丢失。用 IP 核生成的 ROM 模块只是提前添加了数据文件(.mif 或.hex 格式),在 FPGA 运行时通过数据文件给 ROM 模块初始化,才使得 ROM 模块 “真正”的掉电非易失存储器;也正是这个原因,ROM 模块的内容必须提前在数据文件中写死,无法在电路中修改。

Altera推出的ROM IP核分为两种类型:单端口ROM和双端口ROM。对于单端口ROM提供一个读地址端口和一个读数据端口,只能进行读操作;双端口ROM提供两个读地址端口和两个读数据端口。其中不是每个端口都要用到,调用完IP核后,是可以生成其例化模块的,到时候就可以看到我们需要控制的信号了。

图片

ROM IP核配置

ROM要事先写进去.mif 或.hex 格式,因此我们需要先建一个.mif文件,写入我们想要存储的数据。

File→New→在Memory Files下找到Memory Initialization File-选择容量为256,位宽为8bit→选中表格的行或列右键可以更改进制,默认地址是十进制,存储器是无符号十进制→手动输入数据/复制、粘贴/利用软件自带的功能,直接推译出所有数据。

软件自带的填充功能用法

右键点击任意单元格→Custom Fill Cells→容量为256,如果起始地址为0,结束地址就为255→可以看到表格中从0-255自动填充好,由于位宽8bit,255也不会超→保存为.mif格式

图片

创建好工程之后,还是和上一篇中一样创建IP核,先试单端口

图片

图片

图片

使用写好的数据,浏览文件夹,选择.mif格式,找到刚才保存的.mif文件导入。

图片

和上一篇一样,显示了我们在仿真ROM IP核时需要的Altera仿真库,提示我们单独使用第三方仿真工具时需要添加altera_mf的库

图片

和上一篇一样,选择inst.v实例化文件

图片

双端口有少许不同

图片

1、可以看出有两条地址线和两条数据线了

2、设置定义ROM存储器大小的方式,按字数确定或按比特数确定,用默认就好

3、选择ROM的容量,还是选择256个数据(注意:选择的容量需大于我们需要写入的数据文件的数据量)

4、设置不同端口的位宽是否相同,默认是关闭,即使用相同位宽

5、设置数据位宽,这里的数据位宽设置8bit

6、存储单元类型的选择,按默认选择自动

图片

1、选择单时钟/双时钟:单时钟是用一个时钟控制所有寄存器,双时钟是输入和输出时钟分别控制存储块的数据输入和输出的相关寄存器:时钟A控制端口A的所有寄存器,时钟B控制端口B的所有寄存器。每个端口也支持独立的时钟使能

2、选择是否创建‘rden_a’和‘rden_b’读使能信号

图片

1、选择是否输出‘q_a’和‘q_b’寄存器,选择的话就会使输出延迟一拍

2、选择是否为时钟信号创建使能信号

3、选择是否创建“aclr”异步复位信号

后面的步骤都一样。我们以单端口为例进行设计调用,还是将生成的,qip文件添加到Files下。

设计规划

首先我们ROM的初始化数据是0~255,每隔0.2s从0地址开始往下读取数据显示在数码管上,再利用两个按键信号来读取指定地址的数据(例如按下按键1显示地址为99时的数据,按下按键2显示地址为199时的数据,0-255随意指定)。每按一个按键就读取一个地址的数据显示在数码管上。再次按下按键后,以当前地址继续以0.2s的时间间隔往下读取数据并显示出来。

图片

一共有5个模块:按键消抖模块(使用两次),ROM控制模块,IP核,数码管动态显示模块,顶层模块。实际需要做的是ROM控制模块,顶层只是实现实例化,其他模块可以直接调用以前的,可能要做一些修改。

刚才建立rom的ip后生成了inst.v实例化文件,由内容可以看出这个模块出输入输出信号名称,顶层模块中关于rom的ip模块的实例化可以直接复制这个

图片

编写代码

ROM 控制模块

读操作是在时钟的上升沿触发的,而我们在调用ROM时是没有生成读使能的,所以在读时钟上升沿只要给相应的地址就能在时钟的上升沿读出该地址内的数据了。我们只需要控制生成读地址即可。现在自定义的ROM IP的用法就是给从地址线输入一个地址,ROM模块从数据线输出地址对应的数据。

图片输入有时钟、复位、两个按键标志信号,中间信号有两个地址标志信号,200ms计数器,输出是8位地址。某一个按键按下时对应的按键标志信号会拉高,当检测到某一个按键标志信号拉高时,对应的地址标志信号会拉高,直至下一个按键被按下。按一次按键是显示规定地址存放的数据,再按同一个按键会在该地址基础上继续显示下一个地址的数据,而按不同的按键就是显示另一个按键规定的地址存放的数据。

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地址
 );


 //parameter define
 parameter CNT_MAX = 9_999_999; //0.2s计数器最大值


 //reg define
 reg addr_flag1 ; //特定地址1标志信号
 reg addr_flag2 ; //特定地址2标志信号
 reg [23:0] cnt_200ms ; //0.2s计数器


 //产生特定地址1标志信号
 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;


 //产生特定地址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;


 //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)
 cnt_200ms <= 24'd0;
 else
 cnt_200ms <= cnt_200ms + 1'b1;


 //让地址从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(addr_flag1 == 1'b1)
 addr <= 8'd99;
 else if(addr_flag2 == 1'b1)
 addr <= 8'd199;
 else if(cnt_200ms == CNT_MAX)
 addr <= addr + 1'b1;


 endmodule

产生addr_flag1:复位有效时addr_flag归0;key2_flag拉高时addr_flag1归0(因为key1与key2是互斥的,同一时间只能亮一个,所以不管key1现在是否按下去,key2一旦按下去就要以key2的地址为准了);key_flag1拉高时addr_flag1取反(因为可能存在连按两次的情况,第一次按addr_flag1从第变高,第二次按就从高变低)。产生addr_flag1同理

200ms计数器:复位有效时归0;计数到最大值时归0;否则自加1

地址输出:复位有效时addr为0;当addr为255且cnt200ms计数为最大值CNT_MAX时,addr为0(因为地址指向255后0.2ms要循环显示地址0对应的数据);当addr_flag1拉高时addr为99;当addr_flag2拉高时addr为199;当计数器计到最大值时addr自加1

顶层模块

实质是几个模块的实例化,需要注意的是key模块使用了两次,要实例化两次,两次实例化的模块名字不能相同

之前的数码管动态显示的模块框图做一下修正

图片

对比一下现在的模块

图片

1、之前的给数码管模块的输入数据是data_gen这个模块的输出产生的,现在的data是rom的IP模块产生的。且这个IP的输出只有8位,而我们之前的设置的数码管模块data是27位,因此要补19个0能保证位数一致且对显示没有影响。还需要修改一处是top_seg_595模块中实例化了data_gen,现在不需要了

2、之前的data_gen的输出信号seg_en与seg_595_dynamic模块的输入信号seg_en相连,用于给数码管显示使能。现在的rom_256x8模块的实例化是系统IP自动生成的,没有提供seg_en信号接口,需要自行设置这个信号为高电平让他使能

顶层模块代码

module rom
(
input wire sys_clk , 
input wire sys_rst_n , 
input wire [1:0] key , 
output wire stcp , 
output wire shcp , 
output wire ds 
);


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


 rom_ctrl rom_ctrl_inst
 (
 .sys_clk (sys_clk ),
 .sys_rst_n (sys_rst_n ), 
 .key1_flag (key1_flag ),
 .key2_flag (key2_flag ),
 .addr (addr )
 );


 key_filter key1_filter_inst
 (
 .sys_clk (sys_clk ), 
 .sys_rst_n (sys_rst_n ), 
 .key_in (key[0] ), 
 .key_flag (key1_flag ) 
 );


 key_filter key2_filter_inst
 (
 .sys_clk (sys_clk ), 
 .sys_rst_n (sys_rst_n ), 
 .key_in (key[1] ), 
 .key_flag (key2_flag ) 
 );


 seg_595_dynamic seg_595_dynamic_inst
 (
 .sys_clk (sys_clk ), 
 .sys_rst_n (sys_rst_n ), 
 .data ({19'd0,rom_data}), 
 .seg_en (1'b1 ), //数码管使能信号,高电平有效
 .stcp (stcp ), //输出数据存储寄时钟
 .shcp (shcp ), //移位寄存器的时钟输入
 .ds (ds )//串行数据输入
 );


 rom_256x8 rom_256x8_inst
 (
 .address (addr ),
 .clock (sys_clk ),
 .q (rom_data )
 );


 endmodule

rom_ctrl模块的实例化没有什么需要特别注意的

key_filter模块需要注意的是这两个模块名不能一样,模块都用的是key_filter,实例化后一个叫key1_filter_inst,一个叫key2_filter_inst。rom顶层模块中定义的Key是一个2位的变量,两个模块中的Key_in就分别接key的高位和低位

数码管动态显示模块:这个模块是第三次使用了,因为之前的data是27位,现在只用了data的其中8位,剩下19位置0

rom_256x8模块的实例化就是rom的ip核生成的inst.v实例化文件

图片

Testbench

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


//wire define
wire stcp;
wire shcp;
wire ds ;
//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[0]
#2000 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]
#2000 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]
#2000 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]
#2000 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]
#2000 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]
#2000 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;


 //---------------rom_inst--------------
 rom rom_inst
 (
 .sys_clk (sys_clk ), //系统时钟,频率50MHz
 .sys_rst_n (sys_rst_n ), //复位信号,低电平有效
 .key (key ), //输入按键信号
 .stcp (stcp ), //输出数据存储寄时钟
 .shcp (shcp ), //移位寄存器的时钟输入
 .ds (ds ) //串行数据输入
 );


 endmodule

初始化:时钟为高电平,复位为低电平,按键都为高电平表示未按下

延迟200ns后复位释放

延迟2000ns后按下按键1但是模拟抖动,抖动中有200ns的按键是按下状态以便识别并拉高flag

再重复模拟按下按键2,2,2,1,1

重新定义参数,缩短仿真时间

rom模块实例化

波形变化

图片

图片

管脚分配

图片

图片

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • FPGA
    +关注

    关注

    1625

    文章

    21664

    浏览量

    601679
  • 锁相环
    +关注

    关注

    35

    文章

    583

    浏览量

    87687
  • 存储器
    +关注

    关注

    38

    文章

    7447

    浏览量

    163586
  • ROM
    ROM
    +关注

    关注

    4

    文章

    562

    浏览量

    85665
  • IP核
    +关注

    关注

    4

    文章

    326

    浏览量

    49420
收藏 人收藏

    评论

    相关推荐

    关于FPGA IP

    对于深入学习使用FPGA的小伙伴们,特别是一些复杂的、大规模的设计应用,适宜的IP核对开发能起到事半功倍的作用。IP的概念与我们sdk里库
    发表于 04-29 21:01

    FPGAIP使用技巧

    的工作原理、使用方法和限制条件。 参数化配置 : 如果IP提供了参数化配置选项,可以根据项目需求进行配置。例如,对于RAM IP
    发表于 05-27 16:13

    FPGA IP的相关问题

    我用的是xinlinx spartan6 FPGA,我想知道它的IPRAM是与FPGA独立的,只是集成在了一起呢,还是占用了FPGA的资源
    发表于 01-10 17:19

    关于ip生成的rom

    用quartus ii 中自带的ip创建了一个rom,并加载了初始的hex数据。当我从rom中读出数据的时候,发现前面两个地址(0000,0001)的输出数据不正确,0002输出数据
    发表于 05-14 14:38

    【锆石A4 FPGA试用体验】IPROM(二)创建ROM IP

    前面建好了mif文件,下面就要创建ROM IP了。首先,我们新建一个工程。菜单栏:Tools --> MegaWizardPlug-InManager ,点击“Next”选择ROM
    发表于 09-25 09:38

    xilinx FPGA的FFT IP的调用

    有没有大神可以提供xilinx FPGA的FFT IP的调用的verilog 的参考程序,最近在学习FFT的IP
    发表于 12-25 17:05

    LabVIEW FPGA CORDIC IP的arctan使用方法

    使用LabVIEW FPGA模块中的CORDIC IP,配置arctan(X/Y)算法,配置完成之后,IP只有一个输入。我参考网上VHD
    发表于 09-10 20:07

    【梦翼师兄今日分享】 只读储存器ROM IP的调取及应用

    和大家一起学习FPGA只读存储器IP-ROM的设计。项目需求设计一个ROM控制器,该控制器负责
    发表于 12-16 17:18

    使用Vivado调用ROM IP

      本例程主要使用Vivado 调用ROM IP,用含有正弦曲线的.coe文件初始化ROM,最终通过仿真实现波形的显示  一、首先建立工程      二、选择芯片的型号  我
    发表于 01-08 17:16

    基于IPFPGA设计方法是什么?

    的分类和特点是什么?基于IPFPGA设计方法是什么?
    发表于 05-08 07:07

    FPGA零基础学习IP CORE 之 ROM设计

    学习FPGA设计方法及设计思想的同时,实操结合各类操作软件,会让你在技术学习道路上无比的顺畅,告别技术学习小BUG卡破脑壳,告别目前忽悠性
    发表于 03-13 15:46

    FPGAIP的生成

    FPGAIP的生成,简单介绍Quartus II生成IP的基本操作,简单实用挺不错的资料
    发表于 11-30 17:36 11次下载

    FPGA学习:使用matlab和ISE 创建并仿真ROM IP

    大家好,又到了每日学习的时间了,今天我们来聊一聊使用matlab和ISE 创建并仿真ROM IP。本人想使用简单的中值滤波进行verilog相关算法的硬件实现,由于HDL设计软件不能
    的头像 发表于 10-25 20:20 3782次阅读
    <b class='flag-5'>FPGA</b><b class='flag-5'>学习</b>:使用matlab和ISE 创建并仿真<b class='flag-5'>ROM</b> <b class='flag-5'>IP</b><b class='flag-5'>核</b>

    FPGA学习笔记:PLL IP使用方法

    IP(Intellectual Property)是知识产权的意思,半导体行业的IP是“用于ASIC或FPGA中的预先设计好的电路功能模块”。一些常用的复杂的功能模块(如FIFO、RAM、FIR
    的头像 发表于 08-22 15:04 4466次阅读
    <b class='flag-5'>FPGA</b><b class='flag-5'>学习</b><b class='flag-5'>笔记</b>:PLL <b class='flag-5'>IP</b><b class='flag-5'>核</b>的<b class='flag-5'>使用方法</b>

    FPGA学习笔记:RAM IP使用方法

    我们知道除了只读存储器外还有随机存取存储器,这一篇将介绍另一种 存储类IP ——RAM的使用方法。RAM是 随机存取存储器 (Random Access Memory),是一个易失性存储器,断电丢失。RAM工作时可以随时从任何
    的头像 发表于 08-29 16:46 3431次阅读
    <b class='flag-5'>FPGA</b><b class='flag-5'>学习</b><b class='flag-5'>笔记</b>:RAM <b class='flag-5'>IP</b><b class='flag-5'>核</b>的<b class='flag-5'>使用方法</b>