当前位置:   article > 正文

【ZYNQ】从入门到秃头07 FPGA 片内 RAM && ROM 读写测试实验_zynq ocm 读写数据测试

zynq ocm 读写数据测试

FPGA 片内RAM读写测试实验

实验原理

Xilinx在 VIVADO 里为我们已经提供了 RAM 的 IP 核 , 我们 只需 通过 IP 核例化一个 R A M 根据 RAM 的读写时序来写入和读取 RAM 中存储的数据 。 实验中会通过 VIVADO 集成的在线逻辑分析仪 ila ,我们可以观察 R A M 的读 写 时序 和从 RAM 中读取的数据。

实验 只用到 了输入的时钟信号和 按键 复位 信号 没有用到 其它 硬件 外设 ,各端口信号的管脚分配如下表所示:
在这里插入图片描述

创建Vivado工程

在添加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的端口定义和时序

在这里插入图片描述
RAM 的数据写入和读出都是按时钟的上升沿操作的,端口 A 数据写入的时候需要置高wea 信号,同时提供地址和要写入的数据。下图为输入写入到 RAM 的时序图。

在这里插入图片描述
而端口B 是不能写入数据的,只能从 RAM 中读出数据,只要提供地址就可以了,一般情况下可以在下一个周期采集 到有效的数据 。
在这里插入图片描述

测试程序编写

Verilog

下面进行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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101

IO约束

在这里插入图片描述

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]

  • 1
  • 2
  • 3
  • 4
  • 5

Testbeach

在这里插入图片描述
通过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
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

simulation仿真

仿真结果如下,从图中可以看出地址 1 写入的数据是 0002 在 下个周期,也就是时刻 2 有效 数据 读出 。
在这里插入图片描述

板上验证

为了能实时看到RAM 中读取的数据值,我们这里添加了 ila 工具来观察 RAM PORTB 的数据信号和地址信号。

添加ILA IP核

IO约束、时序约束这里就不说了,直接快进到添加IP核
在这里插入图片描述
监视两个信号,r_data和r_addr
在这里插入图片描述
注意这两个信号的长度!
在这里插入图片描述

在这里插入图片描述

生成bitstream

生成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 对应的数据要延时两 个时钟周期才会出现,数据比地址出现晚两 个时钟周期,与仿真结果一致。
在这里插入图片描述

FPGA 片内 ROM 读写测试实验

Xilinx在 VIVADO 里为我们已经提供了 ROM 的 IP 核 , 我们 只需 通过 IP 核例化一个 ROM 根据 ROM 的读时序来读取 ROM 中存储的数据 。 实验中会通过 VIVADO 集成的在线逻辑分析仪 ila我们可以观察 ROM 的读时序 和从 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;       //每个数据后面用逗号或者空格或者换行符隔开,最后一个数据后面加分号
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

rom_init.coe 编写完成后保存一下 , 接下去我们开始设计和配置 ROM IP 核 。

添加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 的地址 , 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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56

绑定引脚

############## 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]

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

testbeach

//~ `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

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

仿真

仿真结果如下,符合预期,与RAM 的读取数据 一样 ,数据也是滞后于 地址 一 个周期。
在这里插入图片描述

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/木道寻08/article/detail/906663
推荐阅读
相关标签
  

闽ICP备14008679号