目录
前言:
本人使用的是野火家Xilinx Spartan6系列开发板及配套教程,写博客记录自己的学习。
开发软件:ise14.7 仿真:modelsim 10.5
一、ROM IP核
1.简介
ROM 是只读存储器(Read-Only Memory)的简称,是一种只能读出预先锁存数据的固态半导体存储器。其特性是一旦储存资料就无法再改变或删除,且资料不会因为电源关闭而消失。
ROM IP核是在 FPGA 中通过 IP 核生成的,调用的都是 FPGA 内部的 RAM 资源,掉电内容都会丢失(因为FPGA 芯片内部没有掉电非易失存储器单元)。所以要让 ROM 模块像个掉电非易失存储器要提前添加了数据文件(.coe 格式),让其上电就可以初始化。需注意不同的FPGA生产厂商后缀名是不一样的,Xilinx系列开发板是 .coe而Altera是 .mif。
Xilinx 推出的 ROM IP 核分为两种类型:单端口 ROM(Single-Port Rom)和双端口ROM(Dual-Port ROM),常用的是单端口。单端口 ROM 提供一个读地址端口和一个读数据端口,只能进行读操作;双端口 ROM 与单端口 ROM 类似,区别是其提供两个读地址端口和两个读数据端口,可看做两个单口 RAM 拼接而成。在使用ADDDRA与CLKA并使能ENA的情况下,ROM模块可以工作。
2.创建立初始化文件
第一行MEMORY_INITIALIZATION_RADIX=10; 是定义数据的格式,其中 10表示数据格式为 10进制,也可将数据格式定义为二、八、十六进制。 后面 MEMORY_INITIALIZATION_VECTOR= 是ROM 的初始化数据。
将文本格式(.txt)改为.coe就完成了初始化文件的创建。
3.配置
由于我已经配置完了,方便大家看我把教程提供的配置过程贴出来。
1.新建工程后,添加ROM IP核
2.选择我们需要生成的 IP 核
3.对前面过程 IP 核存储空间等信息的确认
4. 其中1 显示的是配置的 IP 核的输入输出接口框图; 2 框中是配置接口类型选择“本地”即可;3 框的“Datasheet”是下载 Xilinx 的官方 ROM & RAM 核数据手册,这个感兴趣可以看看。
5. 其中1选择 IP 核类型,选择“Single Port Rom”单端口 ROM。
在2中选择用于实现内存的算法,其中 Minimum Area 为最小面积算法; Low Power 为低功耗算法; Fixed Primitives 为固定单元算法;这里按默认选择Minimum Area 即可。
6. 1框中可设置存储数据的位宽和深度, “Read Width”是设置数据位宽,设置为 8 位; Read Depth 是设置数据深度,设置为 256;这样我们设置的 ROM ip核最大能存储的数据即为 256 x 8bit。
2 框是选择启用类型,可以选择“Use ENA Pin” 添加 ENA 脚,利用该信号去使能端口的读写和复位, 这里我们省去麻烦不添加该信号, 默认选择“Always Enabled”始终启用即可。
7. 其中1是加载数据文件,就是前面创建的 ROM 初始化文件,勾选上“ Load Init File”点击 Browse 进行添加.coe 初始化文件。注意如果文件地址显示为红色这说明文件格式或者地址等有误,可以点击show查看。
8. 框中选项是选择是否创建复位信号,这里按默认不勾选即可。
9. 最后一步点击 Generate 完成单端口 ROM IP 核的生成。
配置完成会生成如下 .xco 文件
4.调用
实验目标:读出存储在ROM IP核中的数据。
4.1 整体设计
4.2 编写rtl代码:
打开工程目录下的 ROM IP 核保存位置,找到 rom.veo文件。
打开rom.veo文件,为了将ip核信号与顶层模块中信号连接,将红框内容复制到顶层模块中。
`timescale 1ns/1ns
module rom_top(
input wire sys_clk ,
input wire sys_rst_n ,
output wire [7:0] douta
);
reg [7:0] addra;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
addra <= 8'd0;
else if(addra == 8'd4)
addra <= 8'd0;
else
addra <= addra + 1'b1;
//调用IP核
rom rom_inst(
.clka (sys_clk),
.addra(addra),
.douta(douta)
);
endmodule
4.3 仿真验证
代码:
`timescale 1ns / 1ns
module tb_rom_top();
reg sys_clk;
reg sys_rst_n;
wire [7:0] douta;
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~ sys_clk;
rom_top rom_top_inst(
.sys_clk (sys_clk),
.sys_rst_n (sys_rst_n),
.douta (douta )
);
endmodule
仿真结果:
由仿真结果来看,我们可以知道两点一、读取数据是按照先寻址的方式,二、观察地址8’d0可知,先存入的数据会被后来数据覆盖。
二、RAM IP核
1.简介
RAM 是随机存取存储器( Random Access Memory)的简称,是一个易失性存储器。RAM 工作时可以随时从任何一个指定的地址写入或读出数据,这是其与 ROM 的最大区别。 ROM 是只读存储器,而 RAM 是可写可读存储器。在FPGA中ROM本质只用到了RAM 资源的读数据端口。
Xilinx 推出的 RAM IP 核分为两种类型:单端口RAM 和双端口RAM。其中双端口RAM 又分为简单双端口 RAM 和真正双端口 RAM。对于单端口RAM,读写操作共用一组地址线,读写操作不能同时进行。单端口RAM各管脚作用:
DINA:写入数据位
DOUTA:读出数据位
ADDRA:读或写数据时地址位,读写不可同时进行
WEA:写使能端,高为写入数据,低为读出数据
ENA:读写信号使能端,高ROM读写功能有效,低禁止读或写
RSTA:复位端
REGCEA:读出寄存器使能端,当REGCEA为高电平时,DOUTA保持最后一次输出的数据。
CLKA: 时钟信号
2.配置
RAM配置过程中,输入的数据可以随时输入的,故不用事先创建初始化文件。下面详细介绍操作模式的意思。
操作模式:RAM 读写操作模式共分为三种。
Write First(写优先模式) : 若我们在同一个时钟沿下对同一个地址进行读写,则读出的数据为写入的数据。这里使用常规的写优先模式。
Read First(读优先模式) : 若我们在同一个时钟沿下对同一个地址进行读写, 则读出的数据为该地址写入数据前存储的数据。
No Change(不变模式) : 在该模式下不能同时进行读写操作, 输出数据为同时读写操作前输出的数据。
配置完成产生.xco文件
3.调用
实验目标:先写入数据,再读出数据
3.1 整体设计
3.2 rtl代码
`timescale 1ns/1ns
module ram
#(
parameter CNT_MAX = 25'd999_999 //20ms
)
(
input wire sys_clk ,
input wire sys_rst_n ,
output wire [7:0] douta
);
reg wea ; //写使能
reg [24:0] cnt_20ms ;
reg [3:0] cnt ;
reg [7:0] addr ; //地址线
reg [7:0] dina ; //写数据
//计时20ms
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_20ms <= 25'b0;
else if (cnt_20ms == CNT_MAX)
cnt_20ms <= 25'b0;
else
cnt_20ms <= cnt_20ms + 1'b1;
//cnt:以20ms为单位,计0-8个数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 4'b0;
else if(cnt == 4'd9 && cnt_20ms == CNT_MAX)
cnt <= 4'd0;
else if(cnt_20ms == CNT_MAX)
cnt <= cnt + 1'b1;
//addr:地址
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
addr <= 8'd0;
else if(cnt < 4'd4 && cnt_20ms == CNT_MAX)
addr <= addr + 8'd1;
else if ((cnt > 4'd4 && cnt < 4'd9) && cnt_20ms == CNT_MAX)
addr <= addr - 8'd1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
wea <= 1'b0;
else if (cnt == 4'd4 && cnt_20ms == CNT_MAX)
wea <= 1'b0;
else if (cnt < 4'd5)
wea <= 1'b1; //存入数
else
wea <= 1'b0; //读取数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
dina <= 8'd2;
else if (dina == 8'd10 && cnt_20ms == CNT_MAX )
dina <= 8'd2;
else if (cnt_20ms == CNT_MAX)
dina <= dina + 8'd2;
//调用ip核 RAM是建立的RAM ip核
ram_ip ram_ip_inst
(
.addra (addr ),
.clka (sys_clk ),
.dina (dina ),
.wea (wea ),
.douta (douta )
);
endmodule
3.3 仿真验证
仿真代码:
`timescale 1ns/1ns
module ram
#(
parameter CNT_MAX = 25'd999_999 //20ms
)
(
input wire sys_clk ,
input wire sys_rst_n ,
output wire [7:0] douta
);
reg wea ; //写使能
reg [24:0] cnt_20ms ;
reg [3:0] cnt ;
reg [7:0] addr ; //地址线
reg [7:0] dina ; //写数据
//计时20ms
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_20ms <= 25'b0;
else if (cnt_20ms == CNT_MAX)
cnt_20ms <= 25'b0;
else
cnt_20ms <= cnt_20ms + 1'b1;
//cnt:以20ms为单位,计0-8个数
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt <= 4'b0;
else if(cnt == 4'd9 && cnt_20ms == CNT_MAX)
cnt <= 4'd0;
else if(cnt_20ms == CNT_MAX)
cnt <= cnt + 1'b1;
//addr:地址
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
addr <= 8'd0;
else if(cnt < 4'd4 && cnt_20ms == CNT_MAX)
addr <= addr + 8'd1;
else if ((cnt > 4'd4 && cnt < 4'd9) && cnt_20ms == CNT_MAX) //addr数据维持
addr <= addr - 8'd1;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
wea <= 1'b0;
else if (cnt == 4'd4 && cnt_20ms == CNT_MAX) //占用一个脉冲,使wea严格对齐cnt。cnt为0-4是wea为高,5—9为低
wea <= 1'b0;
else if (cnt < 4'd5)
wea <= 1'b1; //存入数
else
wea <= 1'b0; //读取数据
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
dina <= 8'd2;
else if (dina == 8'd10 && cnt_20ms == CNT_MAX )
dina <= 8'd2;
else if (cnt_20ms == CNT_MAX)
dina <= dina + 8'd2;
//调用ip核 RAM是建立的RAM ip核
ram_ip ram_ip_inst
(
.addra (addr ),
.clka (sys_clk ),
.dina (dina ),
.wea (wea ),
.douta (douta )
);
endmodule
仿真结果:
由仿真结果说明,当wea为低电平时,写数据功能失效,此时按照地址读数据。