本实验的主要内容是介绍如何使用 FPGA内部的RAM以及程序对该RAM数据的读写操作。Vivado软件中提供了RAM的IP核 , 我们只需通过IP核例化一个RAM,根据RAM的读写时序来写入和读取RAM中存储的数据。
首先创建一个名为ram_test的工程,具体的步骤可以参见:Vivado软件的使用——以led的交替闪烁为例。
新工程到下图所示的界面后点击Finish即可完成工程的创建。
工程创建完成后依次按照下图中的序号找到Block Memory Generator双击打开。
弹出如下对话框,在该界面中修改部分参数。
需要修改Component Name和Memory Type这两个部分,修改后如下图所示。
其中 Memory Type 选择的Simple Dual Prot RAM是伪双口RAM,它是最常用的,因为它有两个端口,输入和输出信号是独立的。
Simple Dual Prot RAM模块端口说明如下表。
RAM 的数据写入和读出都是按时钟的上升沿操作的,端口 A 数据写入的时候需要置高wea信号,同时提供地址和要写入的数据。而端口B是不能写入数据的,只能从 RAM 中读出数据,只要提供地址就可以了,一般情况下在下一个周期就可采集到有效的数据。
切换到Port A Options下依次修改数据宽度、深度以及使能管脚,修改后如下图所示。
接着切换到 Port B Options下,将使能管脚 Enable PortType 改为 Always Enable ,也可不修改保持默认Use ENB Pin ,相当于读使能信号,再把 Primitives Output Register取消勾选,其功能是在输出数据上加寄存器,这可以有效改善时序 ,但读出的数据会落后地址两个周期,因此在很多情况下,不用这项功能,保持读出的数据落后地址一个周期即可。
到这里,需要修改的部分已经完成了,其他各选项下的参数保持默认即可,点击OK按钮,弹出如下对话框。
点击Generate就可以生成RAM IP。
编写测试程序来测试RAM的功能,我们向RAM的端口A写入一串连续的数据,且只写一次,并从端口B中读出,使用逻辑分析仪查看数据。
新建名为ram_test的Verilog文件,依次按照下图中标注的序号进行即可。
在新建好的ram_test.v中写入如下代码。
//该代码由正点原子提供
`timescale 1ns / 1ps
module ram_test(
input clk, //50MHz时钟
input rst_n //复位信号,低电平有效
);
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读数据
//产生RAM PORTB读地址
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
r_addr <= 9'd0;
else if (|w_addr) //w_addr位或,不等于0
r_addr <= r_addr+1'b1;
else
r_addr <= 9'd0;
end
//产生RAM PORTA写使能信号
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
wea <= 1'b0;
else
begin
if(&w_addr) //w_addr的bit位全为1,共写入512个数据,写入完成
wea <= 1'b0;
else
wea <= 1'b1; //ram写使能
end
end
//产生RAM PORTA写入的地址及数据
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
w_addr <= 9'd0;
w_data <= 16'd1;
end
else
begin
if(wea) //ram写使能有效
begin
if (&w_addr) //w_addr的bit位全为1,共写入512个数据,写入完成
begin
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
end
end
//实例化RAM
ram_ip ram_ip_inst (
.clka (clk), // input clka
.wea (wea), // input [0 : 0] wea
.addra (w_addr), // input [8 : 0] addra
.dina (w_data), // input [15 : 0] dina
.clkb (clk), // input clkb
.addrb (r_addr), // input [8 : 0] addrb
.doutb (r_data) // output [15 : 0] doutb
);
//实例化ila逻辑分析仪
ila_0 ila_0_inst (
.clk (clk),
.probe0 (r_data),
.probe1 (r_addr)
);
endmodule
其中实例化RAM部分的代码来自ram_ip中的ram_ip.veo文件,不过需要将括号内的参数做一修改。
我们还需要添加ILA,其添加过程可参见:使用Vivado软件进行硬件调试。
探针的数量这里设置为2,即数据和地址两个探针。
这里每个探针的位数按照代码中的分配,r_data是16位,r_addr是9位。
对应的代码如下。
reg [8:0] r_addr; //RAM PORTB读地址
wire [15:0] r_data; //RAM PORTB读数据
点击OK后在弹出的对话框中生成IP,耐心等待其完成!
实例化ila逻辑分析仪部分的代码来自ila_0中的ila_0.veo文件,不过需要将括号内的参数做一修改。
本实验中需要分配管脚的只有时钟信号clk(管脚为U18)和复位信号rst_n(管脚为N15),按照下图中的数字顺序即可完成管脚的分配。
管脚分配完成后Ctrl+S保存,名称与工程名保持一致。
管脚分配的信息在下图圈中的文件中。
Simulator仿真创建文件的过程可参见:Vivado中Simulator仿真软件的使用。
按照下图中的顺序新建TB文件。
在tb_ram_test.v文件中写入如下代码。
//该代码由正点原子提供
`timescale 1ns / 1ps
module vtf_ram_tb;
// Inputs
reg clk;
reg rst_n;
// Instantiate the Unit Under Test (UUT)
ram_test uut (
.clk (clk),
.rst_n (rst_n)
);
initial
begin
// Initialize Inputs
clk = 0;
rst_n = 0;
// Wait 100 ns for global reset to finish
#100;
rst_n = 1;
end
always #10 clk = ~ clk; //20ns一个周期,产生50MHz时钟源
endmodule
保存代码后选择SIMULATION下的Run Simulation,选择第一个行为仿真。
将所有代码中定义的信号拖入到波形仿真窗口,设置仿真时间为1000ns,运行后结果如下图所示。
由仿真结果可知,RAM 的数据写入和读出都是按时钟的上升沿操作的,数据写入的时候需要置高wea信号,在下一个周期就可采集到有效的数据。
连接开发板,点击Generate Bitstream生成比特流文件,将其下载到开发板上。
下载后弹出如下窗口,设置r_addr的初始数值为000,然后点击运行按钮,得到如下波形,将游标移动到红线附近并放大得到下图。
由上图可以看到 r_addr 不断地从0 累加到 1f f,随着 r_addr 的变化,r_data 也在变化,而r_data的数据正是我们写入到RAM中的 512个数据。
这里需要注意 r_addr出现新地址时, r_data对应的数据要延时两个时钟周期才会出现,数据比地址晚两个时钟周期出现,这与仿真的结果是一致的。
以上就是ZYNQ之FPGA 片内RAM读写测试实验的所有内容了,本实验较为综合,包括了前面介绍过的建工程、编写Verilog代码、例化IP核、仿真和硬件调试等内容,注意这些部分在该流程中的顺序和作用。
本文参考资料:正点原子–course_s1_ZYNQ那些事儿-FPGA实验篇V1.06.pdf