摘要:FPGA中的RAM有单端口、双端口和伪双端口之分,本文使用伪双端口。
RAM也具备ROM的功能
RAM与FIFO不同:FIFO具有满和空的判断,FIFO不需对地址进行控制,并且FIFO是先入先出,而RAM根据地址输出。异步时钟域的缓存只要是双口器件都可以完成。
一、单端口RAM(Single-Port RAM)
输入只有一组数据线和一组地址线,只有一个时钟,读写共用地址线。输出只有一个端口。所以单端口RAM的读写操作不能同时进行。当wea拉高时,会将数据写入对应的地址,同时douta输出的数据与此时写入的数据是一致的,因此在读的时候需要重新生成对应的读地址给addra,并且disable掉wea。
二、伪双端口RAM(Simple Dual-Port RAM)
输入有一组数据线,两组地址线,两个时钟。两个输出端口共用有个输出端口。所以一个端口只读,另一个端口只写,但写入和读取的时钟可以不同,且位宽比可以不是1:1。即允许写A的同时读B,且速率可以不同。
三、双端口RAM(True Dual-Port RAM)
输入有两组地址线和两组数据线,两个时钟。输出有两个分别的数据线。所以双口RAM两个端口都分别带有读写端口,可以在没有干扰的情况下进行读写,彼此互不干扰。
四、代码实现
top:
module top(
input clk1,
input resetn1,
input clk2,
input resetn2,
output wire [31:0] temp_doutb
);
reg [31:0] count_ena;
reg [31:0] count_enb;
reg [4:0] addra;
reg [4:0] SPI_data;
reg ena = 0;
reg enb = 0;
reg [4:0] addrb;
wire [4:0] doutb;
always @ (posedge clk1) begin
if(!resetn1)
count_ena <= 32'h0;
else if(count_ena == 32'd200) begin
count_ena <= 32'd200;
ena <= 1'b1;
end
else
count_ena <= count_ena + 1'b1;
end
always @ (posedge clk1) begin
if(!resetn1)
addra <= 5'h0;
else if(ena)
addra <= addra + 1'b1;
end
always @ (posedge clk1) begin
if(!resetn1)
SPI_data <= 32'h0;
else if(ena)
SPI_data <= SPI_data + 1'b1;
end
/
always @ (posedge clk2) begin
if(!resetn1)
addrb <= 5'h0;
else if(enb)
addrb <= addrb + 1'b1;
end
always @ (posedge clk2) begin
if(!resetn2)
count_enb <= 32'h0;
else if(count_enb == 32'd5) begin
count_enb <= 32'd20;
enb <= 1'b1;
end
else if(count_ena == 32'd200)
count_enb <= count_enb + 1'b1;
else
;
end
assign temp_doutb = doutb;
blk_mem_gen_0 u_ram(
.clka(clk1), // input wire clka
.ena(ena), // input wire ena
.wea(1'b1), // input wire [0 : 0] wea
.addra(addra), // input wire [4 : 0] addra
.dina(SPI_data), // input wire [31 : 0] dina
.clkb(clk2), // input wire clkb
.enb(enb), // input wire enb
.addrb(addrb), // input wire [4 : 0] addrb
.doutb(doutb) // output wire [31 : 0] doutb
);
endmodule
tb文件:
module tb_top();
reg clk1 ;
reg resetn1 ;
reg clk2 ;
reg resetn2 ;
wire [31:0] temp_doutb;
always #10 clk1 = ~clk1;
always #10 clk2 = ~clk2;
initial begin
clk1 <= 0;
clk2 <= 0;
resetn1 <= 0;
resetn2 <= 0;
#200
resetn1 <= 1;
resetn2 <= 1;
//#40 $stop;
end
top tb_top(
.clk1 (clk1 ),
.resetn1 (resetn1 ),
.clk2 (clk2 ),
.resetn2 (resetn2 ),
.temp_doutb (temp_doutb)
);
endmodule
仿真图:
代码思路:对wea信号进行拉高,表示模块使能。对于A端口,输入A时钟,输入地址进行简单的累加,输入的数据也进行简单的累加,然后定义一下ena拉高的时间;对于B端口,输入B时钟,输入想读的地址(并不一定要按照顺序来),定义一下enb拉高的时间,拿个寄存器接收输出的值就行了。