|
FPGA工程实践中的RAM形式很多,在设计中常用的RAM有单口RAM:SPRAM(single-port RAM)。双口RAM:TPRAM(two-port RAM)和真双口RAM:(dual-port RAM)。在芯片设计中,具体使用哪一种RAM,要根据设计的需要而定,比如读写速度和面积。
以下RAM模型是简化的,实际使用中可能会有一些功耗控制的信号等。
单端口RAM只有一套读写线,读写时分复用,读的时候不能写,写的时候不能读。

SPRAM示意图
其中ena是读写使能信号,addr是RAM地址,data_in_a和data_out_a分别是写入RAM和由RAM读出的数据。
对于双端口RAM来说,读和写是分开的,分别有一套读地址线和写地址线,相对于单端口RAM来说效率更高。但是由于同时可以进行读写,所以要注意同一时间内读写地址不能冲突。我们接下来就主要介绍双端口RAM的功能和简单实现。
双端口RAM分很多种,我们在这里主要介绍伪双端口RAM——TPRAM(two-port RAM)和真双端口RAM——DPRAM(dual-port RAM)。伪双端口RAM是指有两个读写通道,其中一个用来读另一个用来写;真双端口 RAM 指的是有两个读写端口,每个端口都可以独立发起读或者写。
1.伪双端口RAM——TPRAM
该RAM读和写是分开的。分别有一套读地址线和写地址线。在读写TPRAM时,要注意读写地址不能冲突。示意图如下,信号含义参照SPRAM。

TPRAM示意图
其中wea是a通道的写使能信号,enb是b通道的读使能信号。
一个伪双端口RAM,一个通道读,一个通道写,写通道有写数据、写地址和写使能,读通道有读数据、读地址和读使能,再加上输入时钟,构成输入输出的端口。主要代码如下:
module TPRAM (
input clk,
input wea, //enable write signal of channel a
input enb, //enable signal of channel b
input [7:0] addra, //address of channel a
input [7:0] addrb, //address of channle b
input [15:0] data_i_a, //data input of channel a
output reg [15:0] data_o_b //data output of channel b
);
reg [15:0] RAM [255:0]; //DATAWIDTH = 16, DEPTH = 256 = 2^8
always @(posedge clk) begin //write channel
if(wea) begin
RAM[addra] <= data_i_a;
end
end
always @(posedge clk) begin //read channel
if(enb) begin
data_o_b <= RAM[addrb];
end
end
endmodule
RAM读写用两个过程语句块描述,一个通道读,一个通道写。如果是异步的时钟,即读写不是同一时钟的话,可以修改两个过程语句块的敏感事件列表,例如一个改成always@(posedge clkw),另一个改成always@(posedge clkr)即可。
2.真双端口RAM——DPRAM(dual-port RAM)
RAM是真双口RAM,有两套读写地址,可以分别做读写操作。示意图如下,信号含义参照SPRAM。

DPRAM示意图
真双端口RAM,每个通道都有通道使能、输入数据、输出数据、数据地址、写使能、通道时钟六个端口,所以真双端口RAM共有12个端口。主要代码如下:
module DPRAM (
input clka,
input clkb,
input ena, //enable signal of channel a
input enb, //enable signal of channel b
input wea, //enable write signal of channel a
input web, //enable write signal of channel b
input [7:0] addra, //address of channel a
input [7:0] addrb, //address of channle b
input [15:0] data_i_a, //data input of channel a
input [15:0] data_i_b, //data input of channel b
output reg [15:0] data_o_a, //data output of channel a
output reg [15:0] data_o_b //data output of channel b
);
reg [15:0] RAM [255:0]; //DATAWIDTH = 16, DEPTH = 256 = 2^8
always @(posedge clka) begin
if(ena) begin
if(wea) begin
RAM[addra] <= data_i_a;
end
data_o_a <= RAM[addra];
end
end
always @(posedge clkb) begin
if(enb) begin
if(wea) begin
RAM[addrb] <= data_i_b;
end
data_o_b <= RAM[addrb];
end
end
endmodule
对真双端口RAM的一个通道来说,当通道使能时,如果写使能,则把值写入到RAM的addra地址,并读出RAM位于addra地址的旧的值。如果写不使能,则读出RAM位于addra地址的数据。
以上就是两个模块的简单实现,我们再通过两个简单的testbench文件测试分别模块功能。这里由于用Mac仿真和查看波形,所以写了额外的波形生成文件代码,可参考如下链接:
1.TPRAM:
`timescale 1ns/100ps
`include &#34;TPRAM.v&#34;
module real_dual_port_ram_tb ();
reg clk,wea,enb ;
reg [7:0] addra,addrb ;
reg [15:0] data_i_a ;
wire [15:0] data_o_b ;
always #5 clk = ~clk;
// instantiation
TPRAM inst_TPRAM(
.clk(clk),
.wea(wea),
.enb(enb),
.addra(addra),
.addrb(addrb),
.data_i_a(data_i_a),
.data_o_b(data_o_b)
);
initial begin
clk <= 0;
#25
wea<=1;enb<=1;addra<=8&#39;d0;addrb<=8&#39;d0;data_i_a<=16&#39;d9;
#20
wea<=1;enb<=1;addra<=8&#39;d1;addrb<=8&#39;d1;data_i_a<=16&#39;d2;
#20
wea<=1;enb<=1;addra<=8&#39;d2;addrb<=8&#39;d2;data_i_a<=16&#39;d7;
#20
wea<=1;enb<=1;addra<=8&#39;d3;addrb<=8&#39;d3;data_i_a<=16&#39;d7;
end
/*iverilog */
initial begin
$dumpfile(&#34;waveform_TPRAM.vcd&#34;);//generate waveform file
$dumpvars;//or $dumpvars;
#200 //simulation time
$finish;
end
/*iverilog */
endmodule
测试结果如下:

TPRAM仿真波形图
之所以开始输出数据会出现红线,是因为RAM的初始数值是不确定的,所以在每次切换地址后,读取出来的RAM对应地址的旧的值是不确定的,所以是红线。过一个周期后,改地址被写入新的值,新的值被读出。仿真结果是符合预期目标的。
2.DPRAM:
`timescale 1ns/100ps
`include &#34;DPRAM.v&#34;
module real_dual_port_ram_tb ();
reg clka,clkb,ena,enb,wea,web ;
reg [7:0] addra,addrb ;
reg [15:0] data_i_a,data_i_b ;
wire [15:0] data_o_a,data_o_b ;
always #5 clka = ~clka;
always #5 clkb = ~clkb;
// instantiation
DPRAM inst_DPRAM(
.clka(clka),
.clkb(clkb),
.ena(ena),
.enb(enb),
.wea(wea),
.web(web),
.addra(addra),
.addrb(addrb),
.data_i_a(data_i_a),
.data_i_b(data_i_b),
.data_o_a(data_o_a),
.data_o_b(data_o_b)
);
initial begin
clka <= 0;
clkb <= 0;
#25
ena<=1;enb<=1;wea<=1;web<=1;addra<=8&#39;d0;addrb<=8&#39;d1;data_i_a<=16&#39;d9;data_i_b<=16&#39;d10;
#20
ena<=1;enb<=1;wea<=1;web<=1;addra<=8&#39;d2;addrb<=8&#39;d3;data_i_a<=16&#39;d7;data_i_b<=16&#39;d7;
#20
ena<=1;enb<=1;wea<=1;web<=1;addra<=8&#39;d4;addrb<=8&#39;d5;data_i_a<=16&#39;d6;data_i_b<=16&#39;d8;
#20
ena<=1;enb<=1;wea<=1;web<=1;addra<=8&#39;d2;addrb<=8&#39;d3;data_i_a<=16&#39;d0;data_i_b<=16&#39;d0;
end
/*iverilog */
initial begin
$dumpfile(&#34;waveform_DPRAM.vcd&#34;);//generate waveform file
$dumpvars;//or $dumpvars;
#200 //simulation time
$finish;
end
/*iverilog */
endmodule
测试结果如下:

DPRAM仿真波形图
仿真结果也是符合预期目标的。
由此,我们介绍了简单的单双端口RAM定义、分类及伪双口RAM和真双口RAM的简单实现。当然,目前的vivado等verilog IDE中已经有了很多成熟的RAM IP可直接调用,上述代码可供初学者参考学习RAM的定义和功能。
参考链接:
https://www.bilibili.com/read/cv16270030/
https://blog.csdn.net/re_call/article/details/105225731 |
|