Vivado18.3-IP核-RAM 学习笔记

本内容学习自领航者ZYNQ 之FPGA开发指南 V1.0

1.RAM IP核简介

Xilinx 7 系列器件内部的块RAM 全部是真双端口RAM(True Dual-Port ram,TDP),这两个端口都可以独立地对块RAN 进行读/写。但其也可以被配置为伪双端口RAM(Simple Dual-Port ram,SDP)(有两个端口,但是其中一个只能读,另一个只能写)或单端口RAM(只有一个端口,读/写只能通过这一个端口来进行)。单端口RAM 只有一组数据总线、地址总线、时钟信号以及其他控制信号,而双端口RAM 具有两组数据总线、地址总线、时钟信号以及其他控制信号。

Xilinx 7 系列器件内部的真双端口块RAM 的端口框图如下图所示。

Vivado18.3-IP核-RAM 学习笔记_第1张图片

可以看到,其中包括了Port A 和Port B,我们在接下来的实验中,会例化一个单端口RAM 的IP 核,其只会用到Port A,如图中的红色方框所示。下面是Port A 中的端口描述,Port B 的端口功能和Port A 是一模一样的:
DI A:数据输入总线,用于写操作。
DO A:数据输出总线,用于读操作。
ADDR A:地址总线。
WE A:写使能信号,为高代表写操作,为低代表读操作。
EN A:端口A 的使能引脚,为高时使能端口A,若端口A 的使能引脚被禁止,则端口A 上的读/写操作都会变为无效。使能极性是可配置的,默认情况下高使能。
CLK A:端口A 的时钟信号。

2.RAM IP核的使用。

首先创建一个名为ip_ram 的工程,然后我们创建ram IP 核。在Vivado 软件的左侧“Flow Navigator”栏中单击“IP Catalog”,“IP Catalog”按钮以及单击后弹出的“IP Catalog”窗口如下图所示。

Vivado18.3-IP核-RAM 学习笔记_第2张图片

在“IP Catalog”窗口中,依次展开“Memories & Storage Elements”——“RAMs & ROMs & BRAM”,然后双击“Block Memory Generator”,如下图所示。

Vivado18.3-IP核-RAM 学习笔记_第3张图片

双击“Block Memory Generator”后弹出“Customize IP”窗口,如下图所示。

Vivado18.3-IP核-RAM 学习笔记_第4张图片

最上面的“Component Name”一栏设置该IP 元件的名称,这里保持默认即可。在第一个“Basic”选项卡中,“Memory Type”选项用于选择要实现的存储器类型,本次实验选择默认的“Single Port RAM”,其他的设置保持默认即可,如下图所示。

Vivado18.3-IP核-RAM 学习笔记_第5张图片

接下来是“Port A”选项卡,该选项卡用于设置Port A 的参数。“Write Width”选项用于设置写端口的宽度,以bit 为单位,这里我们设置宽度为一个字节即8 位。“Read Width”选项用于设置读端口的宽度,也以bit 为单位,这里我们设置成和写端口一样的宽度即8 位。“Write Depth”选项用于设置写端口所能访问的地址范围的大小,这里我们设置成32,即其所能访问的地址范围为0-31,对应的地址线刚好是5 位。“Read Depth”选项与“Write Depth”选项同理,我们同样也设置成32。

另外,需要注意的是,下面的“Primitives Output Register”默认是选中状态的,此选项的作用是打开块RAM 内部的位于输出数据总线之后的输出流水线寄存器,虽然在一般设计中为了改善时序性能会保持此选项的默认勾选状态,但是这会使得块RAM 输出的数据延迟一拍,这不利于我们在Vivado 的ILA 调试窗口中直观清晰地观察信号;而且在本实验中我们仅仅是把块RAM 的数据输出总线连接到了ILA 的探针端口上来进行观察,除此之外数据输出总线没有别的负载,不会带来难以满足的时序路径。

其它选项保持默认即可,如下图所示。

Vivado18.3-IP核-RAM 学习笔记_第6张图片

“Other Options”选项卡主要是一些其他的设置,在本实验中同样用不到,保持默认即可,如下图所示。

Vivado18.3-IP核-RAM 学习笔记_第7张图片

最后的“Summary”选项卡是对前面所有配置的一个总结,在这里我们直接点击“OK”按钮即可,如下图所示。

Vivado18.3-IP核-RAM 学习笔记_第8张图片

接着就弹出了“Genarate Output Products”窗口,我们直接点击“Generate”即可,如下图所示。

Vivado18.3-IP核-RAM 学习笔记_第9张图片

之后我们就可以在“Design Run”窗口的“Out-of-Context Module Runs”一栏中出现了该IP 核对应的run“blk_mem_gen_0_synth_1”,其综合过程独立于顶层设计的综合,所以在我们可以看到其正在综合,如下图所示。

Vivado18.3-IP核-RAM 学习笔记_第10张图片

在其Out-of-Context 综合的过程中,我们就可以进行RTL 编码了。首先打开IP 核的例化模板,在“Source”窗口中的“IP Sources”选项卡中,依次用鼠标单击展开“IP”-“blk_mem_gen_0”-“Instantitation Template”,我们可以看到“blk_mem_gen_0.veo”文件,它是由IP 核自动生成的只读的verilog 例化模板文件,双击就可以打开它,如下图所示。

Vivado18.3-IP核-RAM 学习笔记_第11张图片

接下来我们就可以进行RTL 编码了。ip_ram.v 源文件的代码如下:

`timescale 1ns / 1ps
module ip_ram(
	input sys_clk , //系统时钟
	input sys_rst_n //系统复位,低电平有效
);
//wire define
	wire ram_en ; //RAM 使能
	wire ram_wr_H_rd_L ; //ram 读写选择信号
	wire [4:0] ram_addr ; //ram 读写地址
	wire [7:0] ram_wr_data ; //ram 写数据
	wire [7:0] ram_rd_data ; //ram 读数据
//*****************************************************
//** main code
//*****************************************************
//ram 读写模块
ram_rw u_ram_rw(
.clk ( sys_clk ),
.rst_n ( sys_rst_n ),
.ram_en ( ram_en ),
.ram_wr_H_rd_L ( ram_wr_H_rd_L ),
.ram_addr ( ram_addr ),
.ram_wr_data ( ram_wr_data ),
.ram_rd_data ( ram_rd_data )
);

//ram ip 核
blk_mem_gen_0 blk_mem_gen_0 (
.clka ( sys_clk ), // input wire clka
.ena ( ram_en ), // input wire ena
.wea ( ram_wr_H_rd_L ), // input wire [0 : 0] wea
.addra ( ram_addr ), // input wire [4 : 0] addra
.dina ( ram_wr_data ), // input wire [7 : 0] dina
.douta ( ram_rd_data ) // output wire [7 : 0] douta
);

endmodule

程序中例化了ram_rw 模块和ram IP 核blk_mem_gen_0,其中ram_rw 模块负责产生对ram IP 核读/写所需的所有数据总线、地址总线以及控制信号,同时从ram IP 读出的数据也被送进ram_rw 模块。ram_rw.v 源文件的代码如下:

`timescale 1ns / 1ps

module ram_rw(
	input clk , //时钟信号
	input rst_n , //复位信号,低电平有效
	output reg ram_en , //ram 使能信号
	output reg ram_wr_H_rd_L , //ram 读写选择
	output reg [4:0] ram_addr , //ram 读写地址
	output reg [7:0] ram_wr_data , //ram 写数据
	input [7:0] ram_rd_data //ram 读数据
);
//reg define
reg [5:0] rw_cnt ; //读写控制计数器
//*****************************************************
//** main code
//*****************************************************
//设置RAM 使能信号
always @(posedge clk ) begin
if(rst_n == 1'b0)
ram_en <= 1'b0;
else
ram_en <= 1'b1;
end

//读写控制计数器,计数器范围0~63
always @(posedge clk ) begin
if(rst_n == 1'b0)
rw_cnt <= 6'd0;
else if(rw_cnt == 6'd63)
rw_cnt <= 6'd0;
else
rw_cnt <= rw_cnt + 6'd1;
end

//设置RAM 写数据
always @(posedge clk ) begin
if(rst_n == 1'b0)
ram_wr_data <= 8'd0;
else if( (rw_cnt <= 6'd31) && ram_en ) //在计数器的0-31 范围内,是写操作
//写数据的值不断加1
ram_wr_data <= ram_wr_data + 8'd1;
else
ram_wr_data <= ram_wr_data ;
end

//设置RAM 读写选择信号
always @(posedge clk ) begin
if(rst_n == 1'b0)
ram_wr_H_rd_L <= 1'b1 ;
else if( rw_cnt <= 6'd31 ) //在计数器的0-31 范围内,是写操作
ram_wr_H_rd_L <= 1'b1 ;
else //在计数器的32-63 范围内,是读操作
ram_wr_H_rd_L <= 1'b0 ;
end

//设置RAM 读写地址
always @(posedge clk ) begin
if(rst_n == 1'b0)
ram_addr <= 5'd0;
else
ram_addr <= rw_cnt[4:0]; //地址直接取计数器的低5 位
end
//例化ILA
ila_0 ila_0 (
.clk(clk), // input wire clk
.probe0(ram_en), // input wire [0:0] probe0
.probe1(ram_wr_H_rd_L), // input wire [0:0] probe1
.probe2(rw_cnt), // input wire [5:0] probe2
.probe3(ram_addr), // input wire [4:0] probe3
.probe4(ram_wr_data), // input wire [7:0] probe4
.probe5(ram_rd_data) // input wire [7:0] probe5
);

endmodule

对块RAM 的写数据总线、读/写地址总线以及写使能信号,都是直接从rw_cnt 派生出来的。复位后,ram_en 信号被打开,块RAM 被使能,rw_cnt 持续不断地计数,同时读写操作在rw_cnt 的节拍下不断地进行。每写完32 个数据后,就转到读操作,把刚刚写的32 个数据读出来,读完这32 个数据后,就再继续写数据。写数据不断地从0 累加到255,并继续累加环回至0,循环往复。我们在Vivado 中创建了一个ILA 调试的IP 核,添加了6 个调试探针,并在ram_rw.v 源文件中例化了它,且连接到了ram_rw.v 源文件中的所有内部信号,以便观察。
我们对代码进行仿真,TestBench 中只要送出时钟的复位信号即可,得到的仿真波形图如下图所示:

Vivado18.3-IP核-RAM 学习笔记_第12张图片

你可能感兴趣的:(FPGA,Vivado,ZYNQ)