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

双口RAM简介及Verilog实现

  • 写在前面的话
  • 双口RAM简介
    • 伪双口RAM框图:(Xilinx FPGA)
    • 真双口RAM框图:(Xilinx FPGA)
    • RAM读写时序图
    • 伪双口RAM读写实列
  • 简单的双口RAM的Verilog实现
  • 总结

写在前面的话

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)
数字IC笔面基础,项目常用IP——双口RAM(简介及Verilog实现)_第1张图片
伪双口RAM框图:(From Intel FPGA)
数字IC笔面基础,项目常用IP——双口RAM(简介及Verilog实现)_第2张图片
真双口RAM框图:(From Intel FPGA)
数字IC笔面基础,项目常用IP——双口RAM(简介及Verilog实现)_第3张图片

双口RAM简介

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

伪双口RAM框图:(Xilinx FPGA)

数字IC笔面基础,项目常用IP——双口RAM(简介及Verilog实现)_第4张图片

信号 方向 说明
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数据输出

真双口RAM框图:(Xilinx FPGA)

数字IC笔面基础,项目常用IP——双口RAM(简介及Verilog实现)_第5张图片

信号 方向 说明
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读写时序图

RAM的数据写入和读出都是同步与时钟上升沿,端口数据写入时,WEA信号需要置高,同时提供地址和要写入的数据。下图为RAM写入数据的时序图:
数字IC笔面基础,项目常用IP——双口RAM(简介及Verilog实现)_第6张图片
在读取数据时,提供地址后,数据会在下个周期输出,这里列举伪双口时序,真双口需要控制读写信号。
数字IC笔面基础,项目常用IP——双口RAM(简介及Verilog实现)_第7张图片

伪双口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,在读取的时候延迟一个周期。
数字IC笔面基础,项目常用IP——双口RAM(简介及Verilog实现)_第8张图片

简单的双口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 

总结

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

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

你可能感兴趣的:(数字IC设计,tcp/ip,fpga开发,网络协议)