一、简介
在上一篇文章中我们对于单端口的RAM调用进行了一个简单的介绍和相关应用,在这篇文章当中我们对于双端口的RAM进行介绍和调用,在调用程度上来说,双端口RAM是单端口RAM的一个进阶。
双端口RAM分类
简单双口 RAM:一组读数据和读地址线,一组写数据和写地址线,能同时进行读和写操作, 但不能同时对同一地址进行读和写操作。
真双口 RAM:有两组读数据线与读地址线,两组写数据线与写地址线;能同时进行两个端口 读,能同时进行两个端口写,也能一个端口读同时另一个端口写。
在文章中我们就以简单双口RAM进行一个介绍。
二、双端口RAM的配置
这里双端口的调用和单端口的基本上是一样的,只不过参数啥的有点不一样。
1、双端口RAM选择
如图所示,在 IP Catalog 的搜索栏中搜索 RAM,然后选择 RAM:2-PORT。(这里工程的创建就不仔细讲了)
2、RAM存放位置的选择
选择自己需要存放的文件目录。
3、RAM类型和存储类型选择
如下图所示,该界面配置的参数有:
方框①:这里就是前面说的简单双口RAM和真双口RAM的选择,因为我们使用的是简单双口RAM,所以选择上面一个选项的一个读和一个写端口就行。
②:这里是数据存放的尺度,分别为字节存放和bit存放,这里根据自身需要进行选择。
然后点击 Next 进入下一界面。
4、数据存放容量、输入输出数据位宽和资源类型的选择
如图所示:
方框①:这里是对于RAM存放数据的深度进行一个选择,也就是存放容量。这里根据需要直接进行选择就行。
方框②:这里就是对于输入输出数据的位宽进行配置,如果想要输入输出数据的位宽不同,那么就可以在这里进行设置,首先就是在选项处进行勾画,然后对于输入输出数据位宽进行一个设置,如果不需要的话这里保持默认不选就行。
方框③:这里就是对于RAM的资源构成进行一个选择,这里默认选择Auto就行,如果有特别需要也可以进行选择。 点击“Next”进入下一界面。
5、时钟数量、读使能信号、以及写入数据位宽控制位的选择
如图所示:
方框①:在这进行时钟数量进行一个选择,分别是单一时钟、读时钟,写始终、输入始终,输出时钟,这里如果没有特殊需求,我们可以直接选择单一时钟就行,如果需要在写和读添加时钟的话就选择第二个选项,输入输出就选择第三个选项。
方框②:这就是对于读数据使用信号的一个配置,如果不需要读数据使能信号的话这里就不选。
方框③:这里就是对于RAM写入数据的位宽进行选择,为高电平有效,如果这个byteena_a信号为高,不管写入数据位宽有多大,最终都只会把低8位读入RAM中。不要求的话也可以不选。 点击“Next”进入下一界面。
6、输出数据的时钟寄存配置
这里和前面一步的时钟数量的选择相互关联,如果在前面时钟选择时只选择一个是话,这里选不选都不影响最后的结果,因为不管是写入还是读出都是在使用同一个系统时钟。
但如果前面的时钟选择了写入和读出分别使用不同的时钟或者输入输出使用不同的时钟,那么这里一定要进行选择。在图中,这个选项的作用就是把rdclok也就是读数据时的时钟和输出数据进行一个连接,这样输出就会按照如数据时钟的变化实时进行数据读取。相反,如果没有进行勾选,读取数据时就会默认根据系统时钟进行一个变化。
这里需要注意一下,如果不注意会极大的影响在仿真时我们对于结果的判断。
7、初始化文件的选择
如下图所示,这里是初始化文件的一个选择,我们有相应的.mif或者.hex等文件,救选择下面一个选项,进行文件的一个选择就行。点击“Next”进入下一界面。
8、仿真库文件的选择
如下图所示,显示的是仿真库文件 altera_mf。无需其它操作,点击“Next”进入下一界面。
9、生成文件的选择
如下图所示,该页面为需要配置生成的文件。勾选 ram_inst.v 即可。ram_bb.v 可勾可不勾选。然后点击 finish。
10、将文件添加到工程中
如下图所示,配置完成,点击“Yes”即可。
三、双端口RAM的调用
1、设计文件的编写
在trl文件夹中新建ip_ram.v文件,如图:
因为我们在前面配置中选择了不同的时钟,所以我们在这里通过pll产生两个不同的时钟给写和读信号。(这里对于pll的调用就不做具体介绍了,不清楚的小伙伴可以参考前面pll调用的文章,这里我们只使用了两个输出时钟,所以在进行pll配置时需要注意一下)。
//---------<模块及端口声名>-------------------------------------------
module ip_ram(
input clk ,
input rst_n ,
input rden ,
input wden ,
input byteena ,
input [6:0] raddress ,
input [6:0] waddress ,
input [15:0] din ,
output [7:0] dout
);
//---------<参数定义>------------------------------------------------
//---------<内部信号定义>--------------------------------------------
wire clk_75m;
wire clk_100m;
pll_1 pll_1_inst (
.areset ( ~rst_n ),
.inclk0 ( clk ),
.c0 ( clk_75m ),
.c1 ( clk_100m ),
.locked ( locked_sig )
);
ram_1 ram_1_inst (
.byteena_a ( byteena ),//读低八位数据
.data ( din ),//写入的数据
.rdaddress ( raddress ),//读数据的地址
.rdclock ( clk_75m ),//读的时钟
.rden ( rden ),//读信号使能
.wraddress ( waddress ),//写数据的地址
.wrclock ( clk_100m ),//写的时钟
.wren ( wden ),//写信号使能
.q ( dout )//输出数据
);
endmodule
2、测试文件的编写
在tb文件夹中新建tb_ram.v文件,如图:
在代码中我们对于写入数据位宽控制信号进行了赋1操作,所以在后面的波形仿真中我们应该可以看到值写入低八位数据。
`timescale 1ns/1ns
module tb_ram();
//激励信号定义
reg clk ;
reg rst_n ;
reg rden ;
reg wden ;
reg byteena ;
reg [6:0] raddress ;
reg [6:0] waddress ;
reg [15:0] din ;
//输出信号定义
wire [7:0] dout ;
//时钟周期参数定义
parameter CLOCK_CYCLE = 20;
//模块例化
ip_ram ip_ram_inst(
/*input */ .clk (clk ),
/*input */ .rst_n (rst_n ),
/*input */ .rden (rden ),
/*input */ .wden (wden ),
/*input */ .byteena (byteena ),
/*input [6:0]*/ .raddress (raddress),
/*input [6:0]*/ .waddress (waddress),
/*input [7:0]*/ .din (din ),
/*output[7:0]*/ .dout (dout )
);
//产生时钟
initial clk = 1'b0;
always #(CLOCK_CYCLE/2) clk = ~clk;
//产生激励
integer i;
initial begin
rst_n = 1'b0;
wden = 1'b0;
rden = 1'b0;
#(CLOCK_CYCLE*20);
rst_n = 1'b1;
byteena =1;
#(20*100);
//写入50个16位数据,使用byteena写入低八位
for (i = 0;i<50;i=i+1) begin
wden = 1'b1;
waddress = 7'(i);
din = 16'($random);
byteena =1;
@(posedge clk);
wden=1'b0;
end
//读50个数据
for (i = 0;i<50;i=i+1) begin
rden = 1'b1;
raddress = 7'(i);
@(posedge clk);
wden=1'b0;
end
//同读同写
for (i = 0;i<5;i=i+1) begin
wden = 1'b1;
waddress = 7'(i);
din = 16'($random);
@(posedge clk);
wden=1'b0;
rden = 1'b1;
raddress = 7'(i);
@(posedge clk);
wden=1'b0;
end
$stop;
end
endmodule
3、波形图仿真
图中我们可以看到最后写入的数据确实是低八位的数据。而最后读取的信号和写入的第八位信号是完全一致的。到这里双端口RAM的调用就结束了。