【原创】Xilinx 的 RAM IP核调用与仿真(一)

Xilinx 的 RAM IP核分为三种:单口RAM(Single Port RAM)、伪双口RAM(Simple Dual Port RAM) 和真双口RAM(True Dual Port RAM)。

 上图为单口RAM;拥有一组地址线、一组读数据线和一组写数据线,且读写操作共用同一个时钟,在同一时刻要么写数据要么读数据。

上图为伪双口RAM;拥有一组写数据线和一组读数据线,且两个数据总线各配一组地址总线 ,读和写端各有一个时钟(类似于FIFO),内存空间共享,可以同时读写。

上图为真双口RAM;有两个Port,每个Port都有一套读写总线、地址总线和同步时钟;真双口RAM相当于把两个单口RAM合并到一起,但地址空间是共享的,可以通过两个Port同时写、同时读或者一个读一个写。 

一、IP核配置

本文是Xilinx RAM IP核调用与仿真的第一部分,仅讲解单口RAM的配置和仿真

首先在 IP Catalog 中寻找 Block Memory ,打开后如下图所示,在 Basic 中的 Memory Type 中选择 Single Port RAM 。

点击 Port A Options  ,出现如下图所示界面,Write Width 是数据总线位宽,这里设置为8位,Write Depth 是存储深度,这里设置为1024,即开辟1KB的 Block RAM 作为单口RAM使用(实际上是使用了一块18K的BRAM,因为一块BRAM最小为18K)。Operating Mode 是RAM的操作模式,有保持、读优先和写优先 三种模式,这里选择 No Change(保持),意思是在写操作时数据输出总线的值一直保持最后一次读取的值。Enable Port Type 选择 Always Enabled ,表示一直使能RAM的功能。Port A Options Output Registers 是在读数据输出后面接一个寄存器打一拍,其中 Primitives Output Register 是使用BRAM内部自带的缓冲器打拍,这个缓冲器的数据打入触发器的时间延迟比较大,在高速设计中可能会引起时序违例,优点是在低速应用中充分应用BRAM内部资源,节约LUT资源;其中 Core Output Register 是使用LUT搭建的缓冲器,这个缓冲器的延迟较小,适合高速设计中使用;由于RAM IP核的自身设计结构问题,RAM数据输出总是迟于地址一个读时钟周期,即输出数据在RAM内部就被打了一拍,如果上述两个缓冲器勾了一个,那输出数据就是打两拍,如果两个缓冲器都勾上了,那最后输出数据就是打三拍,在后面的仿真中可以看见他们的不同。

 点击 Other Options ,可以看到一个 Coe File 和 Fill Remaining Memory Locations ,其中 Coe File  是类似于Altera中的mif文件一样的用途,用于初始化RAM中的初始数据,如果不需要初始化RAM中的数据或者都想初始化成0,那么可以勾选下面的 Fill Remaining Memory Locations  ,代表将所有未被初始化的RAM空间都设定为后面输入框中的值(十六进制表示)。

点击 Summary ,可以看到本RAM的配置参数和IP核对BRAM资源的消耗情况,点击 OK ,生成IP核文件。

 

 二、Vivado 仿真

先贴出testbench代码:

`timescale 1ns / 1ns
//
// Company: Xidian University
// Engineer: Xu Mingwei
// 
// Create Date: 2020/11/13 22:44:07
// Design Name: ram_test_tb
// Module Name: ram_test_tb
// Project Name: ram_test
// Target Devices: XC7K325T
// Tool Versions: Vivado 2019.2
// Description: None
// 
// Dependencies: 
// 
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
// 
//


module ram_test_tb;

reg			clk 		;		// 同步时钟
reg			wea		;		// 读写控制
reg  [9:0]		addr 		;		// 地址总线
reg  [7:0]		data_in 	;		// 数据输入
wire [7:0]		data_out	;		// 数据输出


// tb初始化
initial begin
	clk 		= 0		;
	wea 		= 1		;
	addr 		= 10'b0 	;
	data_in 	= 8'd255	;
// clk生成
	#10
	forever
		#1 clk = ~clk;
end



// RAM读写
always @(posedge clk) begin
	if( addr == 10'd1023 )begin
		addr <= 10'b0;
		data_in <= 8'd255;
		if( wea == 1'b1 )
			wea <= 1'b0;
		else
			wea <= 1'b1;
	end
	else begin
		if( wea == 1'b1 )begin
			addr <= addr + 1'b1;
			data_in <= data_in - 1'b1;
		end
		else if( wea == 1'b0 )begin
			addr <= addr + 1'b1;
		end
	end
end



// SPRAM 例化
	blk_mem_gen_0 	U_blk_mem_gen_0
	(
	  .clka		( clk 		)	,
	  .wea		( wea 		)	,
	  .addra	( addr 		)	,
	  .dina		( data_in 	)	,
	  .douta	( data_out 	)	
	);



endmodule

1、不勾选输出缓冲器 

下图为写操作,循环地从255写到0(地址0的数据是255),总共写入1024字节的数据

下图为无输出缓冲的读操作,可以看见虽然没有输出缓冲器,但输出数据仍然延迟一个读时钟周期,这是IP核内部结构的作用结果。

 

 2、勾选一个输出缓冲器

下图为勾选了一个输出缓冲器的仿真图,可以看见输出数据被打了两拍。

3、两个输出缓冲器全都勾选 

下图为勾选两个输出缓冲器的读操作,可以看见输出数据被打了三拍,输出数据分别经过了BRAM和LUT构成的buffer。

下图为勾选两个输出缓冲器的写操作和读操作转换的中间过程,可以看到,最后一个读出的数据0相对于wea(读写控制线)的跳变沿被延了三个时钟周期,从而导致当控制线已经拉高(处于写操作状态)时还能输出三个数据,即读出和写入存在三个时钟周期的共存状态,但实际上这并不是写操作RAM本身的和读操作在同时进行,这三个周期的读操作其实是三个输出缓冲器的残存数据,只是在最后三个时钟周期里被吐了出来。

猜你喜欢

转载自blog.csdn.net/qq_40807206/article/details/109685727
RAM