写在前面的话
在项目设计中,我们通常需要使用一些固定的数据。如果是使用单片机,那么在数据量比较大的情况下,这些数据就必须存储在外挂的存储芯片中。那么,使用FPGA呢?在数据量不是特别大的情况下,我们可以将这些数据存储到FPGA片内的存储器中,这样既节约了板级成本,又可以保证数据不容易受到外界干扰。那么本节,梦翼师兄和大家一起学习FPGA只读存储器IP核-ROM的设计。
项目需求
设计一个ROM控制器,该控制器负责输出0-255递增的地址数据,将此地址总线连接到ROM地址输入端,查看ROM输出的数据是否正确
操作步骤
由于ROM是只读存储器,也就是说,我们不能对其内部写入外部数据,那么,我们就需要创建一个ROM的数据初始化文件mif文件(mif文件用来存放初始数据)
定义位宽和深度(这里我们选择位宽为8位,深度为256)
填充数据(在这里我们用软件自带的一种的填充数据的方式,填充上0到255,大家自己在项目中应用的时候,应该填充上自己所需要的初始值)。
mif文件建立成功(地址从0到255,数据从0开始,每次增加1,所以此时的mif文件中存放的是0到255)。
在右侧的IP核搜索的编辑区,输入rom,在菜单栏找到并双击rom(在这里我们使用单端口的rom,双端口的rom,自己感兴趣的话,可以自己调用试一试)。
选择语言类型为Verilog,同时为该IP核命名,然后点击【OK】
进入rom的设置向导,设置深度和字节的宽度(rom的深度和字节宽度必须要和建立的mif文件保持一致),然后点击【NEXT】。
把输出端口的寄存器去掉(如果不去掉的话,就会使输出延迟一拍。这里我们不需要延迟这一拍,所以去掉),然后点击【NEXT】
进入如下界面,点击【browse···】(找到我们之前建立的mif文件,添加进来),点击【NEXT】
一直点击【NEXT】,直到出现如下界面,把my_rom_inst.v选择上,然后点击【finish】(完成rom的设置)
顶层架构设计
Rom是只读存储器,需要我们指定地址,它才能输出对应地址的数据。
模块功能介绍
模块名 |
功能描述 |
Rom_control |
ROM控制模块,产生递增的地址信号 |
My_rom |
ROM存储器IP核 |
Rom |
系统顶层模块,负责子模块级联 |
端口和内部连线描述
顶层模块端口介绍
端口名 |
端口说明 |
Clk |
系统时钟输入 |
Rst_n |
系统复位 |
q |
数据输出 |
系统内部连线介绍
连线名 |
连线说明 |
addr |
Rom_control产生的地址信号 |
代码解释
Rom_control模块的代码
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * The module function: 输出有效地址 *****************************************************/ 01 module rom_control ( 02 clk, //系统时钟输入 03 rst_n, //系统复位 04 addr //地址输出 05 ); 06 // 系统输入 07 input clk; //系统时钟输入 08 input rst_n; //系统复位 09 10 output reg [7:0] addr; //地址输出 11 12 // 产生地址电路 13 always @ (posedge clk or negedge rst_n) 14 begin 15 if (!rst_n) 16 addr <= 0; // 复位的时候地址是0; 17 else 18 if (addr < 255) // 让地址在0到255循环 19 addr <= addr + 1; 20 else 21 addr <= 0; 22 end 23 24 endmodule |
本模块只是产生了有效的地址信号,让地址信号在0到255之间循环,用于rom的输入,遍历rom全部存储空间,验证rom是否能够正确地输出对应地址的数据。
Rom模块代码
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * The module function: 顶层连接 *****************************************************/ 01 module rom ( 02 clk, //系统时钟输入 03 rst_n, //系统复位 04 q //有效数据输出 05 ); 06 //系统输入 07 input clk; //系统时钟输入 08 input rst_n; //系统复位 09 //系统输出 10 output [7:0] q; //有效数据输出 11 //定义中间连线 12 wire [7:0] addr; //定义地址信号 13 //实例化rom_control 14 rom_control rom_control ( 15 .clk(clk), //系统时钟输入 16 .rst_n(rst_n), //系统复位 17 .addr(addr) //地址输出 18 ); 19 //IP核--rom的调用 20 my_rom my_rom_inst ( 21 .address ( addr ), // 地址输入 22 .clock ( clk ), // 时钟输入 23 .q ( q ) //有效数据输出 24 ); 25 26 endmodule |
本模块只是用于做连接,没有任何的逻辑。编写完可综合代码之后,查看RTL视图如下:
由RTL视图可以看出,代码综合以后得到的电路和我们设计的系统框图一致,接下来,编写测试代码如下:
/**************************************************** * Engineer : 梦翼师兄 * QQ : 761664056 * The module function: 顶层测试 *****************************************************/ 01 `timescale 1ns/1ps //时间单位和精度定义 02 module rom_tb; 03 04 //系统输入 05 reg clk; //系统时钟输入 06 reg rst_n; //系统复位 07 //系统输出 08 wire [7:0] q; //有效数据输出 09 10 initial begin 11 clk = 1; 12 rst_n = 0; 13 # 200.1 14 rst_n = 1; 15 end 16 17 always # 10 clk = ~clk; //50MHz时钟 18 19 rom rom ( 20 .clk(clk), //系统时钟输入 21 .rst_n(rst_n), //系统复位 22 .q(q) //有效数据输出 23 ); 24 25 endmodule |
仿真分析
当复位信号拉高以后,addr(地址)开始发生变化,q(rom的输出)开始输出有效数据,并且是每一拍都会输出一个有效数据。地址和数据之间存在一拍的延迟是由于rom内部结构导致的。
在地址循环的同时,对应地址的有效数据也循环输出,证明我们的ip核使用正确。