理论学习
上一篇介绍了常用的锁相环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实例化文件
双端口有少许不同
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模块实例化
波形变化
管脚分配
全部0条评论
快来发表一下你的评论吧 !