【原创】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本身的和读操作在同时进行,这三个周期的读操作其实是三个输出缓冲器的残存数据,只是在最后三个时钟周期里被吐了出来。