RAM可以随时把数据写入任一指定地址的存储单元,也可以随时从任一指定地址中读出数据,其读写速度是由时钟频率决定的RAM:随机访问内存
随机访问内存(RAM)相当于PC机上的移动存储,用来存储和保存数据的。在任何时候都可以读写,RAM通常用作操作系统或其他正在运行的程序的临时存储介质(可称作系统内存)。不过,当电源关闭时RAM不能保留数据,如果需要保存数据,就必须把它们写入到一个长期的存储器中(例如硬盘)。正因为如此,有时也将RAM称作"可变存储器"。RAM内存可以进一步分为静态RAM(SRAM)和动态内存(DRAM)两大类。
SRAM:静态随机存取存储器
静态随机存取存储器(Static Random-Access Memory,SRAM)是随机存取存储器的一种。所谓的“静态”,是指这种存储器只要保持通电,里面储存的数据就可以恒常保持。相对之下,动态随机存取存储器(DRAM)里面所储存的数据就需要周期性地更新。然而,当电力供应停止时,SRAM储存的数据还是会消失(被称为volatile memory),这与在断电后还能储存资料的ROM或闪存是不同的。
DRAM:动态随机存取存储器
SRAM不需要刷新电路即能保存它内部存储的数据。而DRAM(Dynamic Random Access Memory)每隔一段时间,要刷新充电一次,否则内部的数据即会消失,因此SRAM具有较高的性能,但是SRAM也有它的缺点,即它的集成度较低,功耗较DRAM大 ,相同容量的DRAM内存可以设计为较小的体积,但是SRAM却需要很大的体积。同样面积的硅片可以做出更大容量的DRAM,因此SRAM显得更贵。。
RAM主要用来存放程序及程序执行过程中产生的中间数据、运算结果等。在资源允许的范围内,允许灵活地配置ram
Vivado软件自带BMG IP核,可以配置成RAM或者ROM(只允许进行读操作)。通过对这些bram存储器模块进行配置,可以实现ram、移位寄存器、rom以及fifo缓冲器等各种存储器的功能。
我们首先进行单端口ram ip的调用实验,明白它的作用和使用方式;再独立进行ram的编写实验
单端口RAM:只有1个端口,共用读写通道,读写不能同时进行
伪双端口RAM:2个通道,分别进行读写
真双端口RAM:2个通道,都可以读写
单端口ip实验
首先调用vivado ip进行单端口实验:
要使用ram ip,要在ip catalog里面搜索ram,并选中block memory generator
修改端口设置
然后就可以通过生成的veo文件的模板调用它
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
);
这里我们要用逻辑分析仪进行分析ram的读写,因为modelsim等仿真软件是看不到相关信号的,所以还要在代码中加入ila ip的调用
配置如下:
读写模块代码:
module ram_wr(
input clk ,
input rst_n ,
input [7:0] r_data , //ram读数据
output reg [7:0] w_data , //ram写数据
output ram_write_sel , //ram读写选择
output reg [4:0] addr , //ram读写地址
output ram_en ); //ram使能信号
//读写计数器
reg [5:0] wr_cnt;
//0-31为写,32到63为读
assign ram_write_sel = (wr_cnt<=6'd31 && ram_en)? 1'b1 : 1'b0;
assign ram_en=rst_n;
//产生ram读写数据
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
w_data <= 0;
else if((wr_cnt <= 6'd31)&&ram_en)
w_data <= + 8'd1;
else
w_data <= 0;
end
//读写计数器,计数范围为0-63
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
wr_cnt <= 0;
else if(wr_cnt==6'd63)
wr_cnt <= 0;
else
wr_cnt <= + 1;
end
//读写地址信号,范围0-31
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
addr <= 0;
else addr<=wr_cnt[4:0];
end
endmodule
顶层模块:
module ip_ram(
input sys_clk,
input sys_rst);
wire [7:0] r_data;
wire [7:0] w_data;
wire ram_en;
wire ram_sel;
wire [4:0] addr;
ram_wr wr_inst(
.clk (sys_clk),
.rst_n (sys_rst),
.r_data (r_data),
.w_data (w_data),
.ram_write_sel (ram_sel),
.addr(addr),
.ram_en (ram_en) );
blk_mem_gen_0 mem_gen_0 (
.clka(sys_clk), // input wire clka
.rsta(sys_rst),
.ena(ram_en), // input wire ena
.wea(ram_sel), // input wire [0 : 0] wea
.addra(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
);
ila_ram test_ram (
.clk(sys_clk), // input wire clk
.probe0(ram_en), // input wire [0:0] probe0
.probe1(ram_sel), // input wire [0:0] probe1
.probe2(addr), // input wire [4:0] probe2
.probe3(w_data), // input wire [7:0] probe3
.probe4(r_data) // input wire [7:0] probe4
);
endmodule
rtl视图
然后将比特流文件下载到开发板上进行调试
由于我们选择了ram ip写优先,所以波形就是这样的,也可以把它调整成为不改变:
这样波形图就更合理了
双端口ram代码编写
这里我们采用三态门进行数据的输入输出,代码编写如下:
module dp_sram
#( parameter ADDR_WIDTH = 4,
parameter DATA_WIDTH = 8,
parameter DEPTH = 2**ADDR_WIDTH )
(/*autoarg*/
// Outputs
// Inputs
input clka, input clkb,
input csen,
inout [DATA_WIDTH-1:0]data_a, inout [DATA_WIDTH-1:0]data_b,
input [ADDR_WIDTH-1:0]addra,input [ADDR_WIDTH-1:0] addrb,
input wrena,input wrenb,
input rdena,input rdenb
);
reg [DATA_WIDTH-1:0] mem[DEPTH-1:0];
reg [DATA_WIDTH - 1 : 0] mid_data_a; //中间缓存的寄存器
reg [DATA_WIDTH - 1 : 0] mid_data_b; //中间缓存的寄存器
assign data_a = (csen &rdena &! wrena)? mid_data_a: 'hz;
assign data_b = (csen & rdenb &! wrenb)? mid_data_b: 'hz;
always @(posedge clka)begin
if(wrena && csen)
mem[addra] <= data_a;
else ;
end
always @(posedge clkb)begin
if(wrenb && csen)
mem[addrb] <= data_b;
else ;
end
always@(posedge clka) begin
if(!wrena && csen)
mid_data_a <= mem[addra];
else ;
end
always@(posedge clkb) begin
if(!wrenb && csen)
mid_data_b <= mem[addrb];
else ;
end
endmodule
这个模块有对应的信号输出,可以用modelsim进行仿真,testbench文件编写如下:
`timescale 1ns / 1ps
// Engineer: Reborn Lee
// Module Name: ram_tb
module ram_tb(
);
parameter ADDR_WIDTH = 4; //地址位宽
parameter DATA_WIDTH = 16; // 数据位宽
parameter DEPTH = 2**ADDR_WIDTH; //ram 列表长度
reg i_clk; //时钟
reg [ADDR_WIDTH - 1 : 0] addr_a; // ram 地址参数
reg [ADDR_WIDTH - 1 : 0] addr_b;
wire [DATA_WIDTH - 1 : 0] data_a; //这个是一个零时变量法,位宽与ram 一样,用于存放缓存的数据
wire [DATA_WIDTH - 1 : 0] data_b;
reg cs; // 这是ram 使能 信号,表示是否操作这个ram
reg wrena; // 写使能
reg wrenb;
reg rdena; // output enable,输出使能时RAM读取的结果才能输出
reg rdenb;
reg [DATA_WIDTH-1:0] tb_data; //这个是一个零时变量法,位宽与ram 一样,用于存放缓存的数据
//generate system clock
initial begin
i_clk = 0;
forever begin
# 5 i_clk = ~i_clk;
end
end
assign data_a = !rdena ? tb_data : 'hz; // 写数据,
assign data_b = !rdenb ? tb_data : 'hz;
// assign data = tb_data ;
initial begin
{cs, wrena,wrenb, addr_a,addr_b, tb_data, rdena,rdenb} = 0; //一开始都置零
repeat (2) @ (posedge i_clk); //先来两个时钟周期
//write test
//每个时钟周期,向 ram 中写一个随机数据,数据写的时候不能读 ,oe=0 输出使能0
for (integer i = 0; i < 2**ADDR_WIDTH/2; i= i+1) begin
repeat (1) @(negedge i_clk)
addr_a = i;
wrena = 1; cs =1;
rdena = 0; tb_data = $random;
end
for (integer i = 2**ADDR_WIDTH/2; i < 2**ADDR_WIDTH; i= i+1) begin
repeat (1) @(negedge i_clk)
addr_b = i;
wrenb = 1; cs =1;
rdenb = 0; tb_data = $random;
end
//read test
//两次读写之间 有两个时钟周期的延时
repeat (2) @ (posedge i_clk);
//每个时钟周期,向 ram 中读一个随机数据,数据读的时候不能写 ,wr=0 写使能 0
for (integer i = 0; i < 2**ADDR_WIDTH/2; i= i+1) begin
repeat (1) @(posedge i_clk) addr_a = i;
wrena = 0; cs = 1; rdena = 1;
end
for (integer i = 2**ADDR_WIDTH/2; i < 2**ADDR_WIDTH; i= i+1) begin
repeat (1) @(posedge i_clk) addr_b = i;
wrenb = 0; cs = 1; rdenb = 1;
end
#20 $finish;
end
//下面是函数调用的接口
dp_sram #(
.ADDR_WIDTH(ADDR_WIDTH),
.DATA_WIDTH(DATA_WIDTH),
.DEPTH(DEPTH)
) dp_sram_inst (
.i_clk (i_clk),
.addr_a (addr_a),
.addr_b (addr_b),
.data_a (data_a),
.data_b (data_b),
.cs (cs),
.wrena (wrena),
.wrenb (wrenb),
.rdena (rdena),
.rdenb (rdenb)
);
endmodule
仿真结果: