当前位置:   article > 正文

数字IC笔面基础,项目常用IP——双口RAM(简介及Verilog实现)_真双口ram和伪双口ram

真双口ram和伪双口ram

写在前面的话

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。
伪双口RAM,一个端口只读,另一个端口只写,写入和读取的时钟可以不同,位宽比可以不是1:1。
真双口RAM,两个端口都是可读可写,可以在没有干扰的情况下进行读写,彼此互不干扰。

伪双口RAM框图:(Xilinx FPGA)

在这里插入图片描述

信号方向说明
CLKAin端口A时钟输入
WEAin端口A写入使能
ENAin端口A使能
ADDRAin端口A地址输入
DINAin端口A数据输入
CLKBin端口B时钟输入
ADDRBin端口B地址输入
ENBin端口B使能
DOUTBin端口B数据输出

真双口RAM框图:(Xilinx FPGA)

在这里插入图片描述

信号方向说明
DINAin端口A数据输入
ADDRAin端口A地址输入
WEAin端口A读写使能
ENAin端口A使能
CLKAin端口A时钟
DINBin端口B数据输入
ADDRBin端口B地址输入
WEBin端口B读写使能
ENBin端口B使能
CLKBin端口B时钟

伪双口RAM:
允许同时端口A写入,端口B读出,且速率可以不同。

真双口RAM:
端口A和端口B的读出和写入相互独立,不受影响,可以对同一地址进行操作,但不能发生冲突!!!

注意点:
FIFO也是一个端口只读,另一个端口只写。,FIFO与伪双口RAM的区别在于,FIFO为先入先出,没有地址线,不能对存储单元寻址;而伪双口RAM两个端口都有地址线,可以对存储单元寻址。

异步时钟域的缓存只要是双口器件都可以完成,但FIFO不需要对地址进行控制,是最方便的。

RAM读写时序图

RAM的数据写入和读出都是同步与时钟上升沿,端口数据写入时,WEA信号需要置高,同时提供地址和要写入的数据。下图为RAM写入数据的时序图:
在这里插入图片描述
在读取数据时,提供地址后,数据会在下个周期输出,这里列举伪双口时序,真双口需要控制读写信号。
在这里插入图片描述

伪双口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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123

仿真图
地址1写入数据0002,在读取的时候延迟一个周期。
在这里插入图片描述

简单的双口RAM的Verilog实现

列举之前异步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 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92

总结

RAM之所以是基础IP,在于现有的数字IC中都是采用冯诺依曼架构,计算产生的中间数据需要存储器完成缓存,再送入下一个计算过程中。初学者需要了解RAM的类型以及使用范围,理解RAM的工作原理,而在实际项目中,多采用IP核,我们需要掌握的就是怎么使用这个IP,了解输入输出信号怎么工作的。

双口RAM四种操作情况:
(1)两个端口不同时对同一地址单元写入数据。(ok)
(2)两个端口同时对同一地址单元读出数据。(ok)
(3)两个端口同时对同一地址单元写入数据。(write error)
(4)两个端口同时对同一地址单元,一个写入数据,一个读取数据。(read error)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/Cpp五条/article/detail/586020
推荐阅读
相关标签
  

闽ICP备14008679号