赞
踩
RAM(Random Access Memory),随机存储器,是一种用来暂时存储中间数据的存储器,掉电易失。按照类型可分为单口 RAM(Single RAM)和双口 RAM(Dual RAM),其中双口RAM又有简单双口 RAM(Simple-Dual RAM)、真双口 RAM(True-Dual RAM)。在异步FIFO的内部就是一个双口RAM用来存取数据。RAM是最基础的IP,在FPGA和ASIC设计中,会经常调用成熟的RAM。重点是理解RAM的输出输出特性,了解在项目中如何使用RAM,使用什么类型的RAM,以及怎么控制RAM的写入和读出。
注意:
(1)手边有FPGA开发板的同学,可以学习对应公司的IP核手册,并尝试运行对应的例程,完成例程的学习后可以尝试将双口RAM应用到具体的项目实践中。
(2)双口RAM的重点在于读写地址生成及读写控制,比单口RAM更加灵活,可以同时处理读写数据,在数据缓存、图像处理等系统中有广泛应用。
RAM | 要点 |
---|---|
单口RAM | 读写共享相同端口,不可同时读写 |
伪(简单)双口RAM | 读写采用两个端口,可同时读写,但端口固定,一个只能读,另一个只能写 |
真双口RAM | 读写采用两个端口,均可读写 |
单口RAM框图:(From Intel FPGA)
伪双口RAM框图:(From Intel FPGA)
真双口RAM框图:(From Intel FPGA)
双口RAM分为伪双口RAM和真双口RAM。
伪双口RAM,一个端口只读,另一个端口只写,写入和读取的时钟可以不同,位宽比可以不是1:1。
真双口RAM,两个端口都是可读可写,可以在没有干扰的情况下进行读写,彼此互不干扰。
信号 | 方向 | 说明 |
---|---|---|
CLKA | in | 端口A时钟输入 |
WEA | in | 端口A写入使能 |
ENA | in | 端口A使能 |
ADDRA | in | 端口A地址输入 |
DINA | in | 端口A数据输入 |
CLKB | in | 端口B时钟输入 |
ADDRB | in | 端口B地址输入 |
ENB | in | 端口B使能 |
DOUTB | in | 端口B数据输出 |
信号 | 方向 | 说明 |
---|---|---|
DINA | in | 端口A数据输入 |
ADDRA | in | 端口A地址输入 |
WEA | in | 端口A读写使能 |
ENA | in | 端口A使能 |
CLKA | in | 端口A时钟 |
DINB | in | 端口B数据输入 |
ADDRB | in | 端口B地址输入 |
WEB | in | 端口B读写使能 |
ENB | in | 端口B使能 |
CLKB | in | 端口B时钟 |
伪双口RAM:
允许同时端口A写入,端口B读出,且速率可以不同。
真双口RAM:
端口A和端口B的读出和写入相互独立,不受影响,可以对同一地址进行操作,但不能发生冲突!!!
注意点:
FIFO也是一个端口只读,另一个端口只写。,FIFO与伪双口RAM的区别在于,FIFO为先入先出,没有地址线,不能对存储单元寻址;而伪双口RAM两个端口都有地址线,可以对存储单元寻址。
异步时钟域的缓存只要是双口器件都可以完成,但FIFO不需要对地址进行控制,是最方便的。
RAM的数据写入和读出都是同步与时钟上升沿,端口数据写入时,WEA信号需要置高,同时提供地址和要写入的数据。下图为RAM写入数据的时序图:
在读取数据时,提供地址后,数据会在下个周期输出,这里列举伪双口时序,真双口需要控制读写信号。
伪双口RAM将读写分开,资源消耗较低,应用范围较广,这里列举一个测试RAM的示例,向RAM的端口A写入一次数据,并从端口B读出。
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2022/06/12 10:32:29
// Design Name:
// Module Name: ram_test
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module ram_test(
input sys_clk_p , //system clock 200Mhz
input sys_clk_n , //system clock 200Mhz on board
input sys_rst_n //reset signals ,low level effective
);
//----------------------------------------------------------------------------
reg [8:0] w_addr ; //RAM PORTA写地址
reg [15:0] w_data ; //RAM PORTA写数据
reg wea ; //RAM PORTA使能
reg [8:0] r_addr ; //RAM PORTB读地址
wire [15:0] r_data ; //RAM PORTB读数据
wire clk ; //系统时钟
IBUFDS IBUFDS_inst (
.O(clk) , // Buffer output
.I(sys_clk_p) , // Diff_p buffer input (connect directly to toplevel port)
.IB(sys_clk_n) // Diff_n buffer input (connect directly to toplevel port)
);
//产生RAM PORTB读地址
always @(posedge clk or negedge sys_rst_n) begin
if(~sys_rst_n) begin
r_addr <= 9'd0 ;
end
else if (|w_addr) begin //w_addr位或,不等于0 w_addr !== 0
r_addr <= r_addr+1'b1 ;
end
else begin
r_addr <= 9'd0 ;
end
end
//产生RAM PORTA写使能信号
always@(posedge clk or negedge sys_rst_n) begin
if(!sys_rst_n) begin
wea <= 1'b0;
end
else begin
if(&w_addr) //w_addr的bit位全为1,共写入512个数据,写入完成 //不使用w_addr == 9'b1111_11111
wea <= 1'b0;
else
wea <= 1'b1; //ram写使能
end
end
//产生RAM PORTA写入的地址和数据
always @(posedge clk or negedge sys_rst_n) begin
if(~sys_rst_n) begin
w_addr <= 9'd0 ;
w_data <= 16'd1 ;
end
else begin
if(wea == 1'b1) begin //if(wea) begin//ram写使能有效
if(&w_addr) begin //w_addr的bit位全为1,共写入512个数据,写入完成// 通过位于判断有没有写完
w_addr <= w_addr ; //将地址和数据的值保持住,只写一次RAM
w_data <= w_data ;
end
else begin
w_addr <= w_addr + 1'b1;
w_data <= w_data + 1'b1;
end
end
/*else begin
w_addr <= w_addr ;
w_data <= w_data ;
end
*/
end
end
//实例化
//----------- Begin Cut here for INSTANTIATION Template ---// INST_TAG
ram_ip ram_ip_inst (
.clka (clk ), // input wire clka
.wea (wea ), // input wire [0 : 0] wea
.addra (w_addr ), // input wire [8 : 0] addra
.dina (w_data ), // input wire [15 : 0] dina
.clkb (clk ), // input wire clkb
.addrb (r_addr ), // input wire [8 : 0] addrb
.doutb (r_data ) // output wire [15 : 0] doutb
);
// INST_TAG_END ------ End INSTANTIATION Template ---------
//----------- Begin Cut here for INSTANTIATION Template ---// INST_TAG
ila_0 ila_inst (
.clk(clk), // input wire clk
.probe0(r_data), // input wire [15:0] probe0
.probe1(r_addr) // input wire [8:0] probe1
);
// INST_TAG_END ------ End INSTANTIATION Template ---------
endmodule // ram_test
仿真图
地址1写入数据0002,在读取的时候延迟一个周期。
列举之前异步FIFO的code,帮助理解双口RAM的内部工作原理,在实际项目中建议采用IP核。
// -----------------------------------------------------------------------------
// Copyright (c) 2014-2022 All rights reserved
// -----------------------------------------------------------------------------
// Author : hfut904
// File : RAM.v
// Create : 2022-03-04 10:02:58
// Revise : 2022-03-04 17:03:43
// Editor : sublime text4, tab size (4)
// -----------------------------------------------------------------------------
//双口RAM模块
module RAM #(
//------------------------paramter-------------------
parameter FIFO_data_size = 3 ,
parameter FIFO_addr_size = 2
)(
//----------------------port define -----------------
//write clock & reset
input clk_w ,
input rst_w ,
//read clock & reset
input clk_r ,
input rst_r ,
//key signals
input full ,
input empty ,
//enable
input w_en ,
input r_en ,
//wr rd addr
input [FIFO_addr_size-1:0] w_addr ,
input [FIFO_addr_size-1:0] r_addr ,
input [FIFO_data_size-1:0] data_in ,
output reg [FIFO_data_size-1:0] data_out
);
//==============================================================
//------------paramter reg wire ------------------------------
reg [FIFO_data_size-1:0] mem [{FIFO_addr_size{1'b1}}:0 ] ;
integer i ;
/*------------------------------------------------------------------------------
-- wire flag_wr ;
wire flag_rd ;
------------------------------------------------------------------------------*/
//always block
always @(posedge clk_w or negedge rst_w) begin
if(~rst_w) begin
for ( i = 0; i <= FIFO_data_size; i=i+1) begin
mem[i] <= {FIFO_data_size{1'b0}} ;
end
end
else if ((w_en == 1) && (full == 0))begin
mem[w_addr] <= data_in ;
end
else begin
mem[w_addr] <= {FIFO_data_size{1'b0}} ;
end
end
//rd
always @(posedge clk_r or negedge rst_r) begin
if(~rst_r) begin
data_out <= {FIFO_data_size{1'b0}} ; //'d0
end
else if ((r_en == 1) && (empty == 0))begin
data_out <= mem[r_addr] ;
end
else begin
data_out <= {FIFO_data_size{1'b0}} ;
end
end
//assign
/*
assign flag_wr = (w_en == 1) && (full == 0) ;
assign flag_rd = (r_en == 1) && (empty == 0) ;
*/
endmodule
RAM之所以是基础IP,在于现有的数字IC中都是采用冯诺依曼架构,计算产生的中间数据需要存储器完成缓存,再送入下一个计算过程中。初学者需要了解RAM的类型以及使用范围,理解RAM的工作原理,而在实际项目中,多采用IP核,我们需要掌握的就是怎么使用这个IP,了解输入输出信号怎么工作的。
双口RAM四种操作情况:
(1)两个端口不同时对同一地址单元写入数据。(ok)
(2)两个端口同时对同一地址单元读出数据。(ok)
(3)两个端口同时对同一地址单元写入数据。(write error)
(4)两个端口同时对同一地址单元,一个写入数据,一个读取数据。(read error)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。