一、各类存储器简介
ROM:只读,只有读接口(读地址、读数据)
RAM:可读可写,有读接口(读地址、读数据)和写接口(写使能、写数据、写地址),默认任何时刻都能读,没有读使能,大小和位宽查手册,需要持续供电才能将数据保存在其中(易失性存储器),断电数据丢失
- 单端口RAM:读写共用一个数据通道,读写不能同时进行
- 伪双端口RAM:两个数据通道,一个用来读一个用来写
- 真双端口RAM:两个数据通道,都可以用来读或写
DRAM:动态随机存取存储器,数据存储在电容器中,通过保持电荷实现数据存储(比如电容器充电和放电分别为1和0),价格低,消耗功率高,最常用作计算机的主存储器,需要不断刷新(由于电容器内部用于分隔导电板的电介质不是完美的绝缘体,电容器易放电导致电荷泄漏),以同步或异步模式运行,存储容量为1GB-16GB
SRAM:静态随机存取存储器,数据存储在晶体管中,通过改变晶体管的导通状态进而改变电平高低,需要恒定的功率流,速度快,用于高速缓存,存储容量为1MB-16MB
SDRAM:同步动态随机存储器(与CPU频率同步、存储阵列需要不断刷新来保证数据不丢失、自由指定地址进行数据读写),通过电容充放电实现数据存储,内部分为多个bank,通过流水线操作提高速度(一个bank处于预充电状态,正在经历访问延迟时,另一个bank可进行读取),存储容量大、速度快、价格低,控制逻辑复杂、对时序要求高
DDR:双倍速率同步动态随机存储器,命令和操作只在时钟的上升沿发生,数据传输在时钟上升沿和下降沿发生,每个时钟周期发送两倍数据
FIFO和RAM的差别:FIFO是先入先出,没有读写地址,只能按顺序读写数据,RAM可以读写任意地址,FIFO常用于数据传输通道中,用于缓存数据,避免数据丢失,RAM常用于存储指令或者中间的数据
二、原理
IP核配置图
信号名称 | 信号功能 |
dina | 端口a的写数据信号 |
addr | 端口a的读写地址信号 |
wea | 端口a的写使能信号,高电平为写,低电平为读 |
ena | 端口a的使能信号,高电平为使能端口a,低电平端口a被禁止,取消此信号后端口a一直有效(可选信号) |
rsta | 端口a的复位信号(可选信号) |
regcea | 端口a输出寄存器使能信号,高电平douta保持最后一次输出(可选信号) |
clka | 端口a的时钟信号 |
douta | 端口a数据输出 |
三、vivado仿真
新建工程:打开vivado-Quick Start-Create Project-Next-输入工程名和位置(注意工程名和位置中都不能出现中文和空格)-勾选Create project subdirectory(为工程在指定存储路径下建立独立的文件夹)-勾选RTL Project-勾选Do not specifysources at this time(此时不定义源文件)-选择FPGA开发板(我用的是xc7k325tfbg676-3)-Next-Finish
创建RAM IP核:Flow Navigator-IP Catalog-Search:block memory-Block Memory Generator
配置IP核:component name(器件名称,默认即可,不用修改)-basic-interface type(接口类型,默认native)-memory type选择single port ram-write enable中取消勾选字节写使能byte write enable-algorithm options-algorithm选最小面积minimum area-ok
port A options-memory size-write width写数据位宽选择8-write depth写深度选择32-operating mode选no change-enable port type选use ena pin-port A optional output registers都不勾选-port A output reset options勾选RSTA pin-output reset value为0-ok
创建模块ram_rw.v:
project manager-add sources-add or create design sources-next-create file-输入文件名ram_rw.v-ok-finish
module ram_rw(
input clk,
input rst_n,//端口复位,为0时复位
input [7:0] r_data,//读数据
output reg [7:0] w_data,//写数据
output ram_we,//写使能
output reg [4:0] ram_addr,//读写地址
output ram_en//端口使能
);
(* DONT_TOUCH = "TRUE" *) reg [5:0] wr_cnt;//读写计数,因为没有规定输入输出,所以加上前缀括号
assign ram_en = rst_n;//端口使能
assign ram_we = (wr_cnt<=6'd31 && ram_en==1)? 1'b1 : 1'b0;//写使能:读写计数器在0-31为写,32-63为读,6'd31为6位四进制数,数值为31
//读写计数信号设计
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
wr_cnt <= 0;//复位时读写计数为0
else if(wr_cnt==6'd63)
wr_cnt <= 0;//读写计数为63,赋值为0,准备重新计数
else
wr_cnt <= wr_cnt + 1;//其他情况,每次计数加1
end
//读写地址信号设计
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
ram_addr <= 0;//复位时读写地址为0
else if(ram_addr==5'd31)
ram_addr <= 0;//读写地址为31,赋值为0,重新开始计数
else
ram_addr <= ram_addr + 1;//其他情况,每次地址加1
end
//写数据信号设计
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
w_data <= 0;//复位时写数据为0
else if(wr_cnt <= 6'd31)
w_data <= w_data + 1;//读写计数在0-31为写
else
w_data <= 0;//其他情况,写数据为0
end
//读数据信号设计
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
r_data <= 0;//复位时读数据为0
else if(wr_cnt >= 6'd32 && wr_cnt <= 6'd63)
r_data <= r_data + 1;//读写计数在32-63为读
else
r_data <= 0;//其他情况,读数据为0
end
endmodule
本部分的功能:对一个数据位宽为8、深度为32的单端口RAM 进行读写操作设计,注意一个always设计一个信号,对于每个信号的设计,要覆盖到其所有情况
例化IP核blk_mem_gen_0及ram_rw 模块:
sources-IP sources-找到.veo文件,查看例化ram IP核的模板
blk_mem_gen_0 your_instance_name (
.clka(clka), // input wire clka
.rsta(rsta), // input wire rsta
.ena(ena), // input wire ena
.wea(wea), // input wire [0 : 0] wea
.addra(addra), // input wire [4 : 0] addra
.dina(dina), // input wire [7 : 0] dina
.douta(douta), // output wire [7 : 0] douta
.rsta_busy(rsta_busy) // output wire rsta_busy
);
创建顶层模块ipram:
module ipram(
input sys_clk,
input sys_rst
);
wire [7:0] r_data;
wire [7:0] w_data;
wire ram_en;
wire ram_we;
wire [4:0] ram_addr;
ram_rw ram_rw_inst(
.clk (sys_clk),//例化本质上就是调用的过程,顶层模块ipram调用读写模块ram_rw,括号内为顶层模块的信号名
.rst_n (sys_rst),
.r_data (r_data),
.w_data (w_data),
.ram_we (ram_we),
.ram_addr(ram_addr),
.ram_en (ram_en) );
blk_mem_gen_0 blk_mem_gen_1 (//顶层模块ipram调用ram IP核blk_mem_gen_0
.clka (sys_clk), // input wire clka
.rsta (sys_rst), // input wire rsta
.ena (ram_en), // input wire ena
.wea (ram_we), // input wire [0 : 0] wea
.addra (ram_addr), // input wire [4 : 0] addra
.dina (w_data), // input wire [7 : 0] dina
.douta (r_data) // output wire [7 : 0] douta
//.rsta_busy(rsta_busy) // output wire rsta_busy顶层模块没有用到此信号
);
endmodule
仿真程序:
module tb_ram_ip(
);
(* DONT_TOUCH = "TRUE" *) reg sys_clk;//输入信号
(* DONT_TOUCH = "TRUE" *) reg sys_rst;
ipram ipram_inst(
.sys_clk(sys_clk),//仿真模块tb_ram_ip调用顶层模块ipram
.sys_rst(sys_rst)
);
initial begin
sys_clk = 0;
sys_rst = 0;
#10//等待复位完成
sys_rst = 1;
#256
$stop;
end
always #5 sys_clk = ~sys_clk;//10ns一个周期,产生100MHz时钟源
endmodule
参考文献:
【Vivado】ram ip核的使用_想学fpga的小猪同学的博客-CSDN博客_vivado移位寄存器FIFO RAM的差异与共同点_IC小鸽的博客-CSDN博客_fifo和ram的区别