时间:2024-07-30 来源:网络搜集 关于我们 0
上一篇介绍了常用的锁相环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核后,是可以生成其例化模块的,到时候就可以看到我们需要控制的信号了。
File→New→在Memory Files下找到Memory Initialization File-选择容量为256,位宽为8bit→选中表格的行或列右键可以更改进制,默认地址是十进制,存储器是无符号十进制→手动输入数据/复制、粘贴/利用软件自带的功能,直接推译出所有数据。
软件自带的填充功能用法
右键点击任意单元格→Custom Fill Cells→容量为256,如果起始地址为0,结束地址就为255→可以看到表格中从0-255自动填充好,由于位宽8bit,255也不会超→保存为.mif格式
使用写好的数据,浏览文件夹,选择.mif格式,找到刚才保存的.mif文件导入。
和上一篇一样,显示了我们在仿真ROM IP核时需要的Altera仿真库,提示我们单独使用第三方仿真工具时需要添加altera_mf的库
和上一篇一样,选择inst.v实例化文件
双端口有少许不同
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的时间间隔往下读取数据并显示出来。刚才建立rom的ip后生成了inst.v实例化文件,由内容可以看出这个模块出输入输出信号名称,顶层模块中关于rom的ip模块的实例化可以直接复制这个
编写代码
ROM 控制模块读操作是在时钟的上升沿触发的,而我们在调用ROM时是没有生成读使能的,所以在读时钟上升沿只要给相应的地址就能在时钟的上升沿读出该地址内的数据了。我们只需要控制生成读地址即可。现在自定义的ROM IP的用法就是给从地址线输入一个地址,ROM模块从数据线输出地址对应的数据。输入有时钟、复位、两个按键标志信号,中间信号有两个地址标志信号,200ms计数器,输出是8位地址。某一个按键按下时对应的按键标志信号会拉高,当检测到某一个按键标志信号拉高时,对应的地址标志信号会拉高,直至下一个按键被按下。按一次按键是显示规定地址存放的数据,再按同一个按键会在该地址基础上继续显示下一个地址的数据,而按不同的按键就是显示另一个按键规定的地址存放的数据。
地址输出:复位有效时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 ({19d0,rom_data}), .seg_en (1b1 ), //数码管使能信号,高电平有效 .stcp (stcp ), //输出数据存储寄时钟 .shcp (shcp ), //移位寄存器的时钟输入 .ds (ds )//串行数据输入 ); rom_256x8 rom_256x8_inst ( .address (addr ), .clock (sys_clk ), .q (rom_data ) ); endmodulerom_ctrl模块的实例化没有什么需要特别注意的
key_filter模块需要注意的是这两个模块名不能一样,模块都用的是key_filter,实例化后一个叫key1_filter_inst,一个叫key2_filter_inst。rom顶层模块中定义的Key是一个2位的变量,两个模块中的Key_in就分别接key的高位和低位数码管动态显示模块:这个模块是第三次使用了,因为之前的data是27位,现在只用了data的其中8位,剩下19位置0rom_256x8模块的实例化就是rom的ip核生成的inst.v实例化文件Testbench
`timescale 1ns/1nsmodule tb_rom();//wire definewire stcp;wire shcp;wire ds ;//reg definereg sys_clk ;reg sys_rst_n ;reg [1:0] key ;//对sys_clk,sys_rst赋初值,并模拟按键抖动initialbeginsys_clk = 1b1 ;sys_rst_n <= 1b0 ;key <= 2b11;#200 sys_rst_n <= 1b1 ;//按下按键key[0]#2000 key[0] <= 1b0;//按下按键#20 key[0] <= 1b1;//模拟抖动#20 key[0] <= 1b0;//模拟抖动#20 key[0] <= 1b1;//模拟抖动#20 key[0] <= 1b0;//模拟抖动#200 key[0] <= 1b1;//松开按键#20 key[0] <= 1b0;//模拟抖动#20 key[0] <= 1b1;//模拟抖动#20 key[0] <= 1b0;//模拟抖动#20 key[0] <= 1b1;//模拟抖动//按下按键key[1]#2000 key[1] <= 1b0;//按下按键#20 key[1] <= 1b1;//模拟抖动#20 key[1] <= 1b0;//模拟抖动#20 key[1] <= 1b1;//模拟抖动#20 key[1] <= 1b0;//模拟抖动#200 key[1] <= 1b1;//松开按键#20 key[1] <= 1b0;//模拟抖动#20 key[1] <= 1b1;//模拟抖动#20 key[1] <= 1b0;//模拟抖动#20 key[1] <= 1b1;//模拟抖动//按下按键key[1]#2000 key[1] <= 1b0;//按下按键#20 key[1] <= 1b1;//模拟抖动#20 key[1] <= 1b0;//模拟抖动#20 key[1] <= 1b1;//模拟抖动#20 key[1] <= 1b0;//模拟抖动#200 key[1] <= 1b1;//松开按键#20 key[1] <= 1b0;//模拟抖动#20 key[1] <= 1b1;//模拟抖动#20 key[1] <= 1b0;//模拟抖动#20 key[1] <= 1b1;//模拟抖动//按下按键key[1]#2000 key[1] <= 1b0;//按下按键#20 key[1] <= 1b1;//模拟抖动#20 key[1] <= 1b0;//模拟抖动#20 key[1] <= 1b1;//模拟抖动#20 key[1] <= 1b0;//模拟抖动#200 key[1] <= 1b1;//松开按键#20 key[1] <= 1b0;//模拟抖动#20 key[1] <= 1b1;//模拟抖动#20 key[1] <= 1b0;//模拟抖动#20 key[1] <= 1b1;//模拟抖动//按下按键key[0]#2000 key[0] <= 1b0;//按下按键#20 key[0] <= 1b1;//模拟抖动#20 key[0] <= 1b0;//模拟抖动#20 key[0] <= 1b1;//模拟抖动#20 key[0] <= 1b0;//模拟抖动#200 key[0] <= 1b1;//松开按键#20 key[0] <= 1b0;//模拟抖动#20 key[0] <= 1b1;//模拟抖动#20 key[0] <= 1b0;//模拟抖动#20 key[0] <= 1b1;//模拟抖动//按下按键key[0]#2000 key[0] <= 1b0;//按下按键#20 key[0] <= 1b1;//模拟抖动#20 key[0] <= 1b0;//模拟抖动#20 key[0] <= 1b1;//模拟抖动#20 key[0] <= 1b0;//模拟抖动#200 key[0] <= 1b1;//松开按键#20 key[0] <= 1b0;//模拟抖动#20 key[0] <= 1b1;//模拟抖动#20 key[0] <= 1b0;//模拟抖动#20 key[0] <= 1b1;//模拟抖动end//sys_clk:模拟系统时钟,每10ns电平取反一次,周期为20ns,频率为50MHzalways #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模块实例化
波形变化管脚分配
全编译后上板验证