Xilinx在 VIVADO 里为我们已经提供了 RAM 的 IP 核 , 我们 只需 通过 IP 核例化一个 R A M 根据 RAM 的读写时序来写入和读取 RAM 中存储的数据 。 实验中会通过 VIVADO 集成的在线逻辑分析仪 ila ,我们可以观察 R A M 的读 写 时序 和从 RAM 中读取的数据。
实验 只用到 了输入的时钟信号和 按键 复位 信号 没有用到 其它 硬件 外设 ,各端口信号的管脚分配如下表所示:
在添加RAM IP 之前先新建一个 ram_test 的工程 , 然后在工程中添加 RAM IP ,方法如下
1) 点击下图中 IP Catalog ,在右侧弹出的界面中搜索 ram ,找到 Block Memory Generator, 双击打开。
2) 将 Component Name 改为 ram_ip, 在 Basic 栏目下,将 Memory Type 改为 Simple Dual ProtRAM ,也就是伪双口 RAM 。 一般来讲 "Simple Dual Port 是最常用的,因为它是两个端口,输入和输出信号独立。
Component Name:设置该 IP核的名称,这里保持默认即可。
Interface Type RAM接口总线。这里保持默认,选择 Native接口类型(标准 RAM接口总线);
Memory Type:存储器类型。可配置成 Single Port RAM(单端口 RAM)、 Simple Dual Port RAM(伪双端口 RAM)、 True Dual Port RAM(真双端口 RAM)、 Single Port ROM(单端口 ROM)和 Dual Port ROM(双端口 ROM)
ECC Options Error Correction Capability,纠错能力选项,单端口 RAM不支持 ECC。
Write Enable:字节写使能选项,勾中后可以单独将数据的某个字节写入 RAM中,这里不使能。
Algorithm Options:算法选项。可选择 Minimum Area(最小面积)、 Low Power(低功耗)和 Fixed Primitives(固定的原语),这里选择默认的 Minimum Area。
3) 切换到 Port A Options 栏目下,将 RAM 位宽 Port A Width 改为 16 也就是数据宽度。 将RAM 深度 Port A Depth 改为 512 深度指的是 RAM 里可以存放多少个数据。 使能管脚Enable Port Type 改为 Always Enable 。
4) 切换到 Port B Options 栏目下, 将 RAM 位宽 Port B Width 改为 16 ,使能管脚 Enable PortType 改为 Always Enable ,当然也可以 Use ENB Pin ,相当于读使能信号。 而 Pri mitivesOutput Register 取消 勾选,其功能是在输出数据加上寄存器,可以有效改善时序 ,但读出的数据会落后 地址两 个周期。 很多情况下,不使能这项功能,保持数据落后地址一个周期。
5) 在 Other Options 栏目 中, 这里不像 ROM 那样需要初始化 RAM 的数据,我们可以在程序中写入,所以 配置默认即可 ,直接点击 OK。
6) 点击 “ 生成 R A M IP 。
RAM 的数据写入和读出都是按时钟的上升沿操作的,端口 A 数据写入的时候需要置高wea 信号,同时提供地址和要写入的数据。下图为输入写入到 RAM 的时序图。
而端口B 是不能写入数据的,只能从 RAM 中读出数据,只要提供地址就可以了,一般情况下可以在下一个周期采集 到有效的数据 。
下面进行RAM 的测试程序的编写,由于测试 RAM 的功能,我们向 RAM 的端口 A 写入一串连续的数据, 只写一次, 并从端口 B 中读出, 使用逻辑分析仪查看数据。 代码如下
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2021/12/21 17:08:49
// 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,
input sys_rst_n
);
reg [8 : 0] w_addr;
reg [15 : 0] w_data;
reg wea;
reg [8 : 0] r_addr;
wire [15 : 0] r_data;
// ************************************************************************
// ** main
// ************************************************************************
// 产生RAM PORTB读地址
always @ (posedge sys_clk or negedge sys_rst_n) begin
if( !sys_rst_n )
r_addr <= 9'd0;
else if( |w_addr )
r_addr <= r_addr + 1'b1;
else
r_addr <= 9'd0;
end
// 产生RAM PORTA写使能信号
always @ (posedge sys_clk or negedge sys_rst_n) begin
if( !sys_rst_n )
wea <= 1'b0;
else begin
if( &w_addr )
wea <= 1'b0;
else
wea <= 1'b1;
end
end
// 产生RAM PORTA写入的地址及数据
always @ (posedge sys_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 ) begin
if( &w_addr ) begin
w_addr <= w_addr;
w_data <= w_data;
end
else begin
w_addr <= w_addr + 1'b1;
w_data <= w_data + 1'b1;
end
end
end
end
//----------- Begin Cut here for INSTANTIATION Template ---// INST_TAG
ram_ip ram_ip_instance (
.clka(sys_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(sys_clk), // input wire clkb
.addrb(r_addr), // input wire [8 : 0] addrb
.doutb(r_data) // output wire [15 : 0] doutb
);
----------- Begin Cut here for INSTANTIATION Template ---// INST_TAG
//ila_0 ila_0_instance (
// .clk(sys_clk), // input wire clk
//
// .probe0(r_addr), // input wire [8:0] probe0
// .probe1(r_data) // input wire [15:0] probe1
//);
endmodule
set_property PACKAGE_PIN U18 [get_ports sys_clk]
set_property IOSTANDARD LVCMOS33 [get_ports sys_clk]
set_property PACKAGE_PIN N15 [get_ports sys_rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports sys_rst_n]
通过VSCode的插件 Verilog Testbeach,生成.v文件对应的testbeach代码,然后稍微修改
//~ `New testbench
`timescale 1ns / 1ps
module tb_ram_test;
// ram_test Parameters
parameter PERIOD = 10;
// ram_test Inputs
reg sys_clk = 0 ;
reg sys_rst_n = 0 ;
// ram_test Outputs
initial
begin
forever #(PERIOD/2) sys_clk=~sys_clk;
end
initial
begin
#(PERIOD*2) sys_rst_n = 1;
end
ram_test u_ram_test (
.sys_clk ( sys_clk ),
.sys_rst_n ( sys_rst_n )
);
initial
begin
sys_clk = 0;
sys_rst_n = 0;
#20
sys_rst_n = 1;
$finish;
end
endmodule
仿真结果如下,从图中可以看出地址 1 写入的数据是 0002 在 下个周期,也就是时刻 2 有效 数据 读出 。
为了能实时看到RAM 中读取的数据值,我们这里添加了 ila 工具来观察 RAM PORTB 的数据信号和地址信号。
IO约束、时序约束这里就不说了,直接快进到添加IP核
监视两个信号,r_data和r_addr
注意这两个信号的长度!
生成bitstream ,并 下载 bit 文件到 FPGA 。接下来我们通过 ila 来观察一下从 RAM 中读出的数据是否为我们初始化的数据。
在Waveform 的窗口 设置 r_addr 地址为 0 作为触发条件, 我们可以看到 r_addr 在不断的从0 累加到 1f f , 随着 r_addr 的变化 , r_data 也在变化 , r_data的数据正是我们 写入到 R A M 中的 512个数据
这里需要注意 r_addr 出现新地址时, r_data 对应的数据要延时两 个时钟周期才会出现,数据比地址出现晚两 个时钟周期,与仿真结果一致。
Xilinx在 VIVADO 里为我们已经提供了 ROM 的 IP 核 , 我们 只需 通过 IP 核例化一个 ROM 根据 ROM 的读时序来读取 ROM 中存储的数据 。 实验中会通过 VIVADO 集成的在线逻辑分析仪 ila我们可以观察 ROM 的读时序 和从 ROM 中读取的数据。
既然是ROM ,那么我们就必须 提前 给它准备好数据,然后在 FPGA 实际运行时,我们直接读取 这些 ROM 中 预存储好的数据就行。
Xilinx FPGA 的片内 ROM 支持初始化数据配置。如 下 图所示,我们可以创建一个名为 rom_init.coe 的文件,注意后缀一定是 “. coe”,前面的名称当然 可以随意起。
ROM初始化文件的内容格式 很简单 , 如 下 图所示。 第一行为定义数据格式 , 16 代表 ROM 的数据格式为 16 进制 。从第 3 行开始到第 34 行,是这个 32*8bit 大小 ROM 的初始化数据。 每行数字后面用逗号,最后一行数字结束用分号。
MEMORY_INITIALIZATION_RADIX=16; //表示ROM内容的数据格式是16进制
MEMORY_INITIALIZATION_VECTOR=
11,
22,
33,
44,
55,
66,
77,
88,
99,
aa,
bb,
cc,
dd,
ee,
ff,
00,
a1,
a2,
a3,
a4,
a5,
a6,
a7,
a8,
b1,
b2,
b3,
b4,
b5,
b6,
b7,
b8; //每个数据后面用逗号或者空格或者换行符隔开,最后一个数据后面加分号
rom_init.coe 编写完成后保存一下 , 接下去我们开始设计和配置 ROM IP 核 。
在添加ROM IP 之前先新建一个 rom_test 的工程 , 然后在工程中添加 ROM IP ,方法如下
1) 点击下图中 IP Catalog ,在右侧弹出的界面中搜索 rom ,找到 Block Memory Generator, 双击打开。
2) 将 Component Name 改为 rom_ip, 在 Basic 栏目下,将 Memory Type 改为 Single Prot ROM 。
3) 切换到 Port A Options 栏目下,将 ROM 位宽 Port A Width 改为 8 ,将 ROM 深度 Port ADepth 改为 32 ,使能管脚 Enable Port Type 改为 Always ,并取消 Pr imitives Output Register
4) 切换到 Other Options 栏目下,勾选 Load Init File ,点击 Browse ,选中之前制作好的 .coe 文件 。
5) 点击 ok ,点击 Generate 生成 ip 核。
ROM的程序设计非常简单 , 在程序中我们只要每个时钟改变 ROM 的地址 , ROM 就会输出当前地址的内部存储数据 ,例化 i la ,用于观察 地址和数据的变化。 ROM IP 的实例化及程序设计如下
`timescale 1ns / 1ps
//
// Company:
// Engineer:
//
// Create Date: 2021/12/23 09:39:48
// Design Name:
// Module Name: rom_ip_alinx
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//
module rom_ip_alinx(
input sys_clk,
input sys_rst_n
);
wire [7 : 0] rom_data;
reg [4 : 0] rom_addr;
always @ (posedge sys_clk or negedge sys_rst_n) begin
if( !sys_rst_n )
rom_addr <= 1'b0;
else
rom_addr <= rom_addr + 1'b1;
end
//----------- Begin Cut here for INSTANTIATION Template ---// INST_TAG
blk_mem_gen_0 rom_ip_inst (
.clka(sys_clk), // input wire clka
.addra(rom_addr), // input wire [4 : 0] addra
.douta(rom_data) // output wire [7 : 0] douta
);
//----------- Begin Cut here for INSTANTIATION Template ---// INST_TAG
ila_0 ila_0_inst (
.clk(sys_clk), // input wire clk
.probe0(rom_addr), // input wire [4:0] probe0
.probe1(rom) // input wire [7:0] probe1
);
endmodule
############## clock and reset define##################
create_clock -period 20.000 [get_ports sys_clk]
set_property IOSTANDARD LVCMOS33 [get_ports sys_clk]
set_property PACKAGE_PIN U18 [get_ports sys_clk]
set_property PACKAGE_PIN N15 [get_ports sys_rst_n]
set_property IOSTANDARD LVCMOS33 [get_ports sys_rst_n]
//~ `New testbench
`timescale 1ns / 1ps
module tb_rom_ip_alinx;
// rom_ip_alinx Parameters
parameter PERIOD = 10;
// rom_ip_alinx Inputs
reg sys_clk = 0 ;
reg sys_rst_n = 0 ;
// rom_ip_alinx Outputs
initial
begin
forever #(PERIOD/2) sys_clk=~sys_clk;
end
initial
begin
#(PERIOD*2) sys_rst_n = 1;
end
rom_ip_alinx u_rom_ip_alinx (
.sys_clk ( sys_clk ),
.sys_rst_n ( sys_rst_n )
);
initial
begin
sys_clk = 0;
sys_rst_n = 0;
#200
sys_rst_n = 1;
$finish;
end
endmodule