• FPGA片内RAM读写测试实验



    前言

    本节讲述一下 FPGA 片内 RAM 的仿真与测试,我们也知道 RAM 是随机存储器,顾名思义是一种存储数据的一种模块,说到随机呢,也就是我们可以任意的访问它里面的一些地址空间里面的数据。

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


    一、创建及配置工程

    1、创建工程

    新建一个 ram_test 的工程
    在这里插入图片描述

    2、添加 RAM IP核

    ①、 点击下图中 IP Catalog ,在右侧弹出的界面中搜索 ram ,找到 Block Memory Generator,双击打开。
    在这里插入图片描述
    ②、Basic 选项栏按照下图进行配置
    在这里插入图片描述

    • Interface Type:接口类型,选择 Native
      • 在 XILINX 的 FPGA 上想要实现一个存储器,一般有两种方式:第一种是自己使用 FPGA 的逻辑资源自己设计;第二种是使用 XILINX 专用的 Block Memory Generator(BMG)。针对 BRAM 的资源形式,XILINX 提供了两种接口类型:Native 和 AXI4。这两种核的主要特点如下表。
        在这里插入图片描述
    • Memory Type:存储器类型,选择 Simple Dual Port RAM
      • Native 端口的内存类型共有五种,区别如下表在这里插入图片描述
      • Simple Dual Port RAM 的写时序如下
        • 当写使能有效时(高电平),在时钟的上升沿将指定数据写入到指定地址 在这里插入图片描述
      • Simple Dual Port RAM 的读时序如下
        • 读的时候是有一定延时的,如果在同一个时钟下面我们去采集这个有效的输出数据的时候,我们是需要在下一个周期才能采集到它的一个有效的数据
          在这里插入图片描述

    ③、Port A Option 选项栏按照下图进行配置
    按照这样设置,也就是说可以存储 512 个 16 位数据
    在这里插入图片描述

    • Port A Width(数据宽度):端口 A 的宽度,这里我们设置 16 位宽
    • Port A Depth(RAM 里可以存放多少个数据):端口 A 的深度,我们这里设置 512
    • Enable Port Type:Always Enable,保持一直使能,这样我们就不需要去控制它了

    ④、Port B Option 选项栏按照下图进行配置
    在这里插入图片描述

    • Port B Width(数据宽度):端口 B 的宽度,这里我们设置 16 位宽
    • Enable Port Type:Always Enable(端口 B 的使能保持一直开启)
    • Primitives Output Register(基元输出寄存器):取消勾选,其功能是在输出数据加上寄存器,可以有效改善时序 ,但读出的数据会落后地址两个周期。 很多情况下,不使能这项功能,保持数据落后地址一个周期。

    关于其它更多的设置可以参照 XILINX 的官方文档《Block Memory Generator v8.4 LogiCORE IP Product Guide

    ⑤、Other Options 保持默认配置即可
    在这里插入图片描述

    • Load Init File 是我们可以初始化一些文件放在 RAM 里面,也就是说 FPGA 上电之后会把你预先存好的这些数据加载到 RAM 里面

    ⑥、点击 OK,再点击 Generate,生成 RAM IP
    在这里插入图片描述

    3、添加 ILA IP 核

    ①、点击左侧 PROJECT MANAGER 栏 –> IP Catalog 或者菜单栏下 Window –> IP Catalog 然后在右侧出现的 IP Catalog 窗口下搜索 ILA,双击选择 Debug 下的 ILA 进行 IP 配置操作步骤如下图所示
    在这里插入图片描述
    ②、General Option 添加两个探针去采集我们读的地址和数据,采样数据的长度我们设置大一些,如下图所示
    在这里插入图片描述
    ③、Probe_Ports(0…7) 中PROBE0 用来采集9 位地址,PROBE1 用来采集 16 位数据,如下图所示
    在这里插入图片描述
    ④、点击 OK,再点击 Generate,生成 ILA IP
    在这里插入图片描述

    二、程序编写

    编写程序之前我们先介绍下我们涉及到哪些信号

    Simple Dual Port RAM 模块端口的说明如下:

    信号名称方向说明
    clkain端口 A 时钟输入
    weain端口 A 使能
    addrain端口 A 地址输入
    dinain端口 A 数据输入
    clkbin端口 B 时钟输入
    addrbin端口 B 地址输入
    doutbout端口 B 数据输出

    RAM 的数据写入和读出都是按时钟的上升沿操作的,端口 A 数据写入的时候需要置高 wea 信号,同时提供地址和要写入的数据。下图为输入写入到 RAM 的时序图。
    在这里插入图片描述
    而端口 B 是不能写入数据的,只能从 RAM 中读出数据,只要提供地址就可以了,一般情况下可以在下一个周期采集 到有效的数据 。
    在这里插入图片描述

    1、新建测试程序

    新建 ram_test.v 源文件并将下面的程序块拷贝过去
    ram_test.v

    `timescale 1ns / 1ps
    
    module ram_test(
        input clk,					// 50 MHz 时钟
        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 读数据
    
    // ************************************************************************
    // ** main
    // ************************************************************************
    
    // 产生RAM PORTB读地址,读的地址 + 1,模拟写的地址滞后一个周期
    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 ) begin				// ram 写使能有效
                if( &w_addr ) begin		// w_addr 的 bit 位全为 1,共写入 512 个数据,写入完成
                    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_instance (
      .clka(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(clk		),    		// input wire clkb
      .addrb(r_addr		),  	// input wire [8 : 0] addrb
      .doutb(r_data		)  		// output wire [15 : 0] doutb
    );
    
    // 实例化 ila 逻辑分析仪
    ila_0 ila_0_instance (
    	.clk(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

    在这里插入图片描述

    2、新建仿真文件

    新建 vtf_ram_tb.v 仿真文件并将下面的程序块拷贝过去
    vtf_ram_tb.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 100ns for global reset to finish
    	#100;
    		rst_n = 1;
    end
    
    always #10 clk = ~clk;	// 20ns 一个周期,产生 50MHz 时钟源
    
    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

    在这里插入图片描述

    三、进行仿真

    ①、点击 Run Simulation -> Run Behavioral Simulation,进行仿真
    在这里插入图片描述
    ②、将 RAM 里面相关信号拖拽进观察窗口
    (clocka 和 clockb 用的就是系统时钟,因此这里无需拖入到观察窗口)
    在这里插入图片描述
    ③、设置个 200us 跑一下
    在这里插入图片描述
    ④、分析波形

    开始的波形信息
    可以看到读地址是滞后于写地址的一个时钟周期的,读出的数据是滞后于写地址的一个时钟周期的
    在这里插入图片描述
    结束的波形信息
    我们这里把这些信号以 10 进制方式进行查看
    可以看到最后写的一个地址是 511,写入的内容是 512,最后读的一个地址是 511,读取的内容是 512
    在这里插入图片描述
    可以发现我们的写只写第一,而读一直在进行。

    四、下载到 FPGA

    1、引脚约束及时序约束

    rst_n -> T11
    clk -> U18

    新建引脚约束文件 ram.xdc,将下面语句拷贝过去并保存

    set_property PACKAGE_PIN T11 [get_ports rst_n]
    set_property IOSTANDARD LVCMOS33 [get_ports rst_n]
    set_property PACKAGE_PIN U18 [get_ports clk]
    set_property IOSTANDARD LVCMOS33 [get_ports clk]
    
    create_clock -period 20.000 -name clk -waveform {0.000 10.000} [get_ports clk]
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    在这里插入图片描述

    2、生成比特文件

    点击 “Generate Bitstream”,直接生成 bit 文件
    在这里插入图片描述

    3、下载程序

    连接上 JTAG 以及电源线,将板子上电,下载程序
    在这里插入图片描述

    4、逻辑分析仪分析波形

    以 10 进制方式查看读取的内存及数据,设置触发条件为 “读取的地址为 0”,可以看到读取地址 0 的数据为 1,读取地址 511 时的数据为 512,可见满足了预期结果。
    在这里插入图片描述

    五、资源自取

    FPGA片内RAM读写测试实验


    我的qq:2442391036,欢迎交流!


  • 相关阅读:
    接口测试实战工具如何选择?这6个工具首选(建议收藏)
    基于区块链的协作流程数据共享与访问控制
    【Qt系列】QtableWidget表格列宽自适应表格大小
    【已解决】c++如何从0配置ffmpeg
    LiteIDE主题定制教程
    docker- harbor私有仓库部署与管理
    LeetCode 2351. 第一个出现两次的字母
    binlog 和 redolog 有什么区别
    【深度学习】(Multi-Head)Self-Attention (多头)自注意力机制 + Pytorch代码实现
    二进制搭建及高可用 Kubernetes v1.20
  • 原文地址:https://blog.csdn.net/qq_41839588/article/details/133199557