1.ROM的介绍
ROM 是只读存储器(Read-Only Memory)的简称,是一种只能读出事先所存数据的固态半导体存储器。其特性是一旦储存资料就无法再将之改变或删除,且资料不会因为电源关闭而消失。而事实上在FPGA 中通过 IP 核生成的 ROM 或 RAM调用的都是 FPGA 内部的 RAM 资源,掉电内容都会丢失(这也很容易解释, FPGA 芯片内部本来就没有掉电非易失存储器单元)。用 IP 核生成的 ROM 模块只是提前添加了数据文件(.coe 格式),在 FPGA 运行时通过数据文件给 ROM 模块初始化,才使得 ROM 模块像个“真正”的掉电非易失存储器;也正是这个原因, ROM 模块的内容必须提前在数据文件中写死,无法在电路中修改。
Xilinx 推出的 ROM IP 核分为两种类型:单端口 ROM(Single-Port Rom)和双端口ROM(Dual-Port ROM)。对于单端口 ROM 提供一个读地址端口和一个读数据端口,只能进行读操作;双端口 ROM 与单端口 ROM 类似,区别是其提供两个读地址端口和两个读数据端口,基本上可以看做两个单口 RAM 拼接而成。
单端口ROM如下图所示:

双端口ROM:

2.IP核配置
关于IP核的配置如下图所示:
首先打开IP选型

然后搜索block

然后双击,我们选择单端口ROM

这里选择8位宽,深度256,总是使能。

定义了一个0-255的.coe文件,然后并添加

在Summary界面可以看到自己的配置

然后点击OK,我们的但端口ROM IP核就创建完成了。
3.IP核的使用
本次实验任务是参考野火征途Pro系列开发指南,设计一个ROM 的初始化数据是 0~255,也就是存入数据0~255。然后每隔 0.2s 我们从 0 地址开始往下读取数据显示在数码管上,我们再利用两个按键信号来读取指定地址的数据,每按一个按键就读取一个地址的数据显示在数码管上。再次按下按键后,以当前地址继续以 0.2s 的时间间隔往下读取数据并显示出来。
3.1实验分析
根据实验任务我们可以知道,本次实验需要使用到的外设有按键以及数码管。按键的话肯定需要消抖模块,数码管的话是需要数码管驱动模块。在按键跟数码管中间的数据传输时通过ROM IP进行传输,所以还需要设计一个rom控制模块。模块框图大致如下:

消抖模块、rom控制模块以及rom IP的系统时钟以及系统复位端口没有画出来,正常都应该有的,这里需要注意一下。
按键消抖模块代码如下:
`timescale 1ns / 1ps
module key_filter(
input sys_clk ,
input sys_rst_n ,
input key ,
output reg key_flag
);
parameter CNT_MAX = 20'd999_999;
reg [19:0] cnt_20ms;
//20ms cnt
always@(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
cnt_20ms <= 20'd0;
else if(key == 1'b1)
cnt_20ms <= 20'd0;
else if(cnt_20ms == CNT_MAX && key == 1'b0)
cnt_20ms <= cnt_20ms;
else
cnt_20ms <= cnt_20ms + 20'd1;
end
//key_flag
always@(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
key_flag <= 1'd0;
else if(cnt_20ms == (CNT_MAX - 20'd1))
key_flag <= 1'd1;
else
key_flag <= 1'd0;
end
消抖设置了一个20ms的计数器,当按键按下时间小于20ms就认为按键在抖动时间,根据消抖模块可以看一下仿真代码及波形图如下:
`timescale 1ns / 1ns
module tb_key_filter(
);
reg sys_clk ;
reg sys_rst_n;
reg key1 ;
reg key2 ;
wire key1_flag;
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
key1 <= 1'b1;
key2 <= 1'b1;
#201
sys_rst_n <= 1'b1;
#200
//key1
key1 <= 1'b0;
#20
key1 <= 1'b1;
#80
key1 <= 1'b0;
#120
key1 <= 1'b1;
#20
key2 <= 1'b0;
#20
key2 <= 1'b1;
#20
key2 <= 1'b0;
#100
key1 <= 1'b0;
#500
key1 <= 1'b1;
#500
key1 <= 1'b0;
#400
key1 <= 1'b1;
end
always #10 sys_clk <= ~sys_clk;
defparam key_filter_inst1.CNT_MAX = 19;
defparam key_filter_inst2.CNT_MAX = 19;
key_filter key_filter_inst1(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.key (key1 ),
.key_flag (key1_flag )
);
key_filter key_filter_inst2(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.key (key2 ),
.key_flag (key2_flag )
);
endmodule

接下来我们开始设计rom控制模块,代码如下:
`timescale 1ns / 1ps
module rom_ctrl(
input sys_clk ,
input sys_rst_n ,
input key1 ,
input key2 ,
output reg [7:0] addr
);
parameter CNT_MAX = 9_999_999; //0.2s计数器
parameter ADDR_MAX = 8'd255; //最大地址
reg key1_en;
reg key2_en;
reg [23:0] cnt_200ms;
//key1_en
always@(posedge sys_clk or negedge sys_rst_n) begin
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;
end
//key2_en
always@(posedge sys_clk or negedge sys_rst_n) begin
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;
end
//0.2s cnt
always@(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
cnt_200ms <= 24'd0;
else if(key1_en == 1'b1 || key2_en == 1'b1 || cnt_200ms == CNT_MAX)
cnt_200ms <= 24'd0;
else
cnt_200ms <= cnt_200ms + 24'd1;
end
//地址
always@(posedge sys_clk or negedge sys_rst_n) begin
if(sys_rst_n == 1'b0)
addr <= 8'd0;
else if(addr == ADDR_MAX && 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 + 8'd1;
end
endmodule
仿真代码:
`timescale 1ns / 1ns
module tb_rom_ctrl();
reg sys_clk ;
reg sys_rst_n;
reg key1 ;
reg key2 ;
wire [7:0] addr ;
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
key1 <= 1'b0;
key2 <= 1'b0;
#201
sys_rst_n <= 1'b1;
#2000
//key1
key1 <= 1'b1;
#20
key1 <= 1'b0;
#2000
key1 <= 1'b1;
#20
key1 <= 1'b0;
#700
//key2
key2 <= 1'b1;
#20
key2 <= 1'b0;
#2500
key2 <= 1'b1;
#20
key2 <= 1'b0;
#500
//key1&key2
key1 <= 1'b1;
#20
key1 <= 1'b0;
#2000
key2 <= 1'b1;
#20
key2 <= 1'b0;
#2000
key2 <= 1'b1;
#20
key2 <= 1'b0;
end
always #10 sys_clk <= ~sys_clk;
defparam rom_ctrl_inst.CNT_MAX = 19;
rom_ctrl rom_ctrl_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n),
.key1 (key1 ),
.key2 (key2 ),
.addr (addr )
);
endmodule

数码管例程就不展示了,大家可以随便使用一个数码管驱动模块,也可以参考野火的数码管例程
最后将这三个模块例化到一起,再进行一个仿真验证:


仿真结果跟我们要实现的任务要求是一致的,说明代码没有问题,这边就不展示上板验证的效果了,下一章介绍RAM的使用。