• IP核之RAM实验


            RAM 的英文全称是 Random Access Memory,即随机存取存储器,它可以随时把数据写入任一指定地址的存储单元,也可以随时从任一指定地址中读出数据,其读写速度是由时钟频率决定的。RAM 主要用来存放程序及程序执行过程中产生的中间数据、运算结果等。本章我们将对 Vivado 软件生成的 RAM IP 核进行读写测试,并向大家介绍 Xilinx RAM IP 核的使用方法。

    RAM IP 核简介

            Xilinx 7 系列器件具有嵌入式存储器结构,满足了设计对片上存储器的需求。嵌入式存储器结构由一列列 BRAM(块 RAM)存储器模块组成,通过对这些 BRAM 存储器模块进行配置,可以实现各种存储器的功能,例如:RAM、移位寄存器、ROM 以及 FIFO 缓冲器。
            Vivado 软件自带了 BMG IP 核(Block Memory Generator,块 RAM 生成器),可以配置成 RAM 或者ROM。这两者的区别是 RAM 是一种随机存取存储器,不仅仅可以存储数据,同时支持对存储的数据进行修改;而 ROM 是一种只读存储器,也就是说,在正常工作时只能读出数据,而不能写入数据。需要注意的是,配置成 RAM 或者 ROM 使用的资源都是 FPGA 内部的 BRAM,只不过配置成 ROM 时只用到了嵌入式 BRAM 的读数据端口。本章我们主要介绍通过 BRAM IP 核配置成 RAM 的使用方法。
            Xilinx 7 系列器件内部的 BRAM 全部是真双端口 RAM(True Dual-Port ram,TDP),这两个端口都可以独立地对 BRAM 进行读/写。但也可以被配置成伪双端口 RAM(Simple Dual-Port ram,SDP)(有两个端口,但是其中一个只能读,另一个只能写)或单端口 RAM(只有一个端口,读/写只能通过这一个端口来进行)。单端口 RAM 只有一组数据总线、地址总线、时钟信号以及其他控制信号,而双端口 RAM 具有两 组数据总线、地址总线、时钟信号以及其他控制信号。有关 BRAM 的更详细的介绍,请读者参阅 Xilinx 官方的手册文档“UG473,7 Series FPGAs Memory Resources User Guide”。
            单端口 RAM 类型和双端口 RAM 类型在操作上都是一样的,我们只要学会了单端口 RAM 的使用,那么学习双端口 RAM 的读写操作也是非常容易的。本章我们以配置成单端口 RAM 为例进行讲解。
            BMG IP 核配置成单端口 RAM 的框图如下图所示。

    单端口 RAM 框图

             各个端口的功能描述如下:

            DINA:RAM 端口 A 写数据信号。

            ADDRA:RAM 端口 A 读写地址信号,对于单端口 RAM 来说,读地址和写地址共用同该地址线。

            WEA: RAM 端口 A 写使能信号,高电平表示向 RAM 中写入数据,低电平表示从 RAM 中读出数据。
            ENA:端口 A 的使能信号,高电平表示使能端口 A ,低电平表示端口 A 被禁止,禁止后端口 A 上的读写操作都会变成无效。另外 ENA 信号是可选的,当取消该使能信号后, RAM 会一直处于有效状态。
            RSTA RAM 端口 A 复位信号,可配置成高电平或者低电平复位,该复位信号是一个可选信号。
            
            REGCEA: RAM 端口 A 输出寄存器使能信号,当 REGCEA 为高电平时, DOUTA 保持最后一次输出的数据,REGCEA 同样是一个可选信号。
            
            CLKA: RAM 端口 A 的时钟信号。
            DOUTA: RAM 端口 A 读出的数据。

    实验任务

            本节实验任务是使用 Xilinx BMG IP 核,配置成一个单端口的 RAM,然后对 RAM 进行读写操作,通 过在 Vivado 自带的仿真器中观察波形是否正确,最后将设计下载到领航者 Zynq 开发板中,并使用 ILA 对其进行在线调试观察。

    硬件设计

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

    IP核之RAM实验管脚分配

             对应的 XDC 约束语句如下所示:

    1. set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
    2. set_property -dict {PACKAGE_PIN J15 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]

    程序设计

            首先在 Vivado 软件中创建一个名为 ip_ram 的工程,工程创建完成后,在 Vivado 软件的左侧“ Flow Navigator”栏中单击“ IP Catalog ”,如下图所示。

    点击“IP Catalog”

            在“IP Catalog” 窗口的搜索框中输入“ Block Memory ”,出现唯一匹配的“ Block Memory Generator ”,如下图所示(图中出现的两个 IP 核为同一个 BMG IP 核)。

    搜索框中输入“Block Memory”

            双击“Block Memory Generator ”后弹出 IP 核的配置界面,接下来对 BMG IP 核进行配置,“ Basic ”选项页配置界面如下图所示。

    “Basic”选项页配置

             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 ),这里选择 Single Port RAM ,即配置成单端口 RAM
            ECC Options:Error Correction Capability,纠错能力选项,单端口 RAM 不支持 ECC。
            Write Enable:字节写使能选项,勾中后可以单独将数据的某个字节写入 RAM 中,这里不使能。
            Algorithm Options:算法选项。可选择 Minimum Area (最小面积)、 Low Power (低功耗)和 Fixed Primitives (固定的原语),这里选择默认的 Minimum Area
            接下来切换至“Port A ”选项页,设置端口 A 的参数,该页面配置如下:

    “Port A Options”选项页配置

            Write Width:端口 A 写数据位宽,单位 Bit,这里设置成 8

            Read Width:端口 A 读数据位宽,一般和写数据位宽保持一致,设置成 8
            Write Depth:写深度,这里设置成 32 ,即 RAM 所能访问的地址范围为 0-31
            Read Depth:读深度,默认和写深度保持一致。
            Operating Mode: RAM 读写操作模式。共分为三种模式,分别是 Write First (写优先模式)、 Read First (读优先模式)和 No Change (不变模式)。写优先模式指数据先写入 RAM 中,然后在下一个时钟输出该 数据;读优先模式指数据先写入 RAM 中,同时输出 RAM 中同地址的上一次数据;不变模式指读写分开操 作,不能同时进行读写,这里选择 No Change 模式。
            Enable Port Type:使能端口类型。 Use ENA pin (添加使能端口 A 信号); Always Enabled (取消使能信号,端口 A 一直处于使能状态),这里选择默认的 Use ENA pin
            Port A Optional Output Register:端口 A 输出寄存器选项。其中“Primitives Output Register”默认是选中状态,作用是打开 BRAM 内部位于输出数据总线之后的输出流水线寄存器,虽然在一般设计中为了改善时序性能会保持此选项的默认勾选状态,但是这会使得 BRAM 输出的数据延迟一拍,这不利于我们在 Vivado的ILA调试窗口中直观清晰地观察信号;而且在本实验中我们仅仅是把BRAM的数据输出总线连接到了ILA的探针端口上来进行观察,除此之外数据输出总线没有别的负载,不会带来难以满足的时序路径,因此这里取消勾选。
            
            Port A Output Reset Options: RAM 复位信号选项,这里不添加复位信号,保持默认即可。
            另外,需要注意的是,下面的“Primitives Output Register ”默认是选中状态的,此选项的作用是打开块 RAM 内部的位于输出数据总线之后的输出流水线寄存器,虽然在一般设计中为了改善时序性能会保持此选 项的默认勾选状态,但是这会使得块 RAM 输出的数据延迟一拍,这不利于我们在 Vivado ILA 调试窗口 中直观清晰地观察信号;而且在本实验中我们仅仅是把块 RAM 的数据输出总线连接到了 ILA 的探针端口 上来进行观察,除此之外数据输出总线没有别的负载,不会带来难以满足的时序路径。
            
            接下来的“Other Options ”选项页用于设置 RAM 的初始值等,本次实验不需要设置,直接保持默认即可。
            最后一个是“Summary ”选项页,该页面显示了存储器的类型,消耗的 BRAM 资源等,我们直接点击 “OK ”按钮完成 BMG IP 核的配置,如下图所示:

    “Summary”选项页

            接下来会弹出询问是否在工程目录下创建存放 IP 核的文件,我们点击“ OK ”按钮即可。
            紧接着会弹出“Genarate Output Products ”窗口,我们直接点击“ Generate ”,如下图所示。

    “Genarate Output Products”窗口

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

    “blk_mem_gen_0_synth_1”run

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

    “blk_mem_gen_0.veo”文件

             接下来我们创建一个新的设计文件,命名为 ram_rw.v,代码如下:

    1. module ram_rw(
    2. input clk , //时钟信号
    3. input rst_n , //复位信号,低电平有效
    4. output ram_en , //ram使能信号
    5. output ram_wea , //ram读写选择
    6. output reg [4:0] ram_addr , //ram读写地址
    7. output reg [7:0] ram_wr_data, //ram写数据
    8. input [7:0] ram_rd_data //ram读数据
    9. );
    10. //reg define
    11. reg [5:0] rw_cnt ; //读写控制计数器
    12. //*****************************************************
    13. //** main code
    14. //*****************************************************
    15. //控制RAM使能信号
    16. assign ram_en = rst_n;
    17. //rw_cnt计数范围在0~31,写入数据;32~63时,读出数据
    18. assign ram_wea = (rw_cnt <= 6'd31 && ram_en == 1'b1) ? 1'b1 : 1'b0;
    19. //读写控制计数器,计数器范围0~63
    20. always @(posedge clk or negedge rst_n) begin
    21. if(rst_n == 1'b0)
    22. rw_cnt <= 1'b0;
    23. else if(rw_cnt == 6'd63)
    24. rw_cnt <= 1'b0;
    25. else
    26. rw_cnt <= rw_cnt + 1'b1;
    27. end
    28. //产生RAM写数据
    29. always @(posedge clk or negedge rst_n) begin
    30. if(rst_n == 1'b0)
    31. ram_wr_data <= 1'b0;
    32. else if(rw_cnt <= 6'd31) //在计数器的0-31范围内,RAM写地址累加
    33. ram_wr_data <= ram_wr_data + 1'b1;
    34. else
    35. ram_wr_data <= 1'b0 ;
    36. end
    37. //读写地址信号 范围:0~31
    38. always @(posedge clk or negedge rst_n) begin
    39. if(rst_n == 1'b0)
    40. ram_addr <= 1'b0;
    41. else if(ram_addr == 5'd31)
    42. ram_addr <= 1'b0;
    43. else
    44. ram_addr <= ram_addr + 1'b1;
    45. end
    46. ila_0 your_instance_name (
    47. .clk(clk), // input wire clk
    48. .probe0(ram_en), // input wire [0:0] probe0
    49. .probe1(ram_wea), // input wire [0:0] probe1
    50. .probe2(ram_addr), // input wire [0:0] probe2
    51. .probe3(ram_wr_data),// input wire [4:0] probe3
    52. .probe4(ram_rd_data) // input wire [7:0] probe4
    53. );
    54. endmodule

            模块中定义了一个读写控制计数器(rw_cnt),当计数范围在 0~31 之间时,向 ram 中写入数据;当计数范围在 32~63 之间时,从 ram 中读出数据。

    接下来我们设计一个 verilog 文件来实例化创建的 RAM IP 核以及 ram_rw 模块,文件名为 ip_ram.v,编写的 verilog 代码如下。

    1. module ip_ram(
    2. input sys_clk , //系统时钟
    3. input sys_rst_n //系统复位,低电平有效
    4. );
    5. //wire define
    6. wire ram_en ; //RAM使能
    7. wire ram_wea ; //ram读写使能信号,高电平写入,低电平读出
    8. wire [4:0] ram_addr ; //ram读写地址
    9. wire [7:0] ram_wr_data ; //ram写数据
    10. wire [7:0] ram_rd_data ; //ram读数据
    11. //*****************************************************
    12. //** main code
    13. //*****************************************************
    14. //ram读写模块
    15. ram_rw u_ram_rw(
    16. .clk (sys_clk ),
    17. .rst_n (sys_rst_n ),
    18. //RAM
    19. .ram_en (ram_en ),
    20. .ram_wea (ram_wea ),
    21. .ram_addr (ram_addr ),
    22. .ram_wr_data (ram_wr_data ),
    23. .ram_rd_data (ram_rd_data )
    24. );
    25. //ram ip核
    26. blk_mem_gen_0 blk_mem_gen_0 (
    27. .clka (sys_clk ), // input wire clka
    28. .ena (ram_en ), // input wire ena
    29. .wea (ram_wea ), // input wire [0 : 0] wea
    30. .addra (ram_addr ), // input wire [4 : 0] addra
    31. .dina (ram_wr_data ), // input wire [7 : 0] dina
    32. .douta (ram_rd_data ) // output wire [7 : 0] douta
    33. );
    34. endmodule
            程序中例化了 ram_rw 模块和 ram IP blk_mem_gen_0 ,其中 ram_rw 模块负责产生对 ram IP 核读 / 写所 需的所有数据、地址以和读写使能信号,同时从 ram IP 读出的数据也连接至 ram_rw 模块。
            接下来对 RAM IP 核进行仿真,来验证对 RAM 的读写操作是否正确。 tb_ip_ram 仿真文件源代码如下:
    1. `timescale 1ns / 1ps
    2. module tb_ip_ram();
    3. reg sys_clk;
    4. reg sys_rst_n;
    5. always #10 sys_clk = ~sys_clk;
    6. initial begin
    7. sys_clk = 1'b0;
    8. sys_rst_n = 1'b0;
    9. #200
    10. sys_rst_n = 1'b1;
    11. end
    12. ip_ram u_ip_ram(
    13. .sys_clk (sys_clk ),
    14. .sys_rst_n (sys_rst_n )
    15. );
    16. endmodule

            接下来就可以开始仿真了,仿真过程这里不再赘述,仿真波形图如下图所示。

    RAM 写操作波形图

             上图为 RAM 的写操作仿真波形图,由上图可知,ram_wea 信号拉高,说明此时是对 ram 进行写操作。ram_wea 信号拉高之后,地址和数据都是从 0 开始累加,也就说当 ram 地址为 0 时,写入的数据也是 0;当 ram 地址为 1 时,写入的数据也是 1,我们总共向 ram 中写入 32 个数据。

            RAM 读操作仿真波形图如下图所示:

    RAM 读操作波形图

            由上图可知,ram_wea 信号拉低,说明此时是对 ram 进行读操作。 ram_wea 信号拉低之后, ram_addr 从 0 开始增加,也就是说从 ram 的地址 0 开始读数据; ram 中读出的数据 ram_rd_data 在延时一个时钟周期 之后,开始输出数据,输出的数据为 0 1 2…… ,和我们写入的值是相等的, 也就是说,我们创建的 RAM IP 核从仿真结果上来看是正确的。
            接下来添加 ILA IP 核,将 ram_en ram_wea ram_addr ram_wr_data ram_rd_data 信号添加至观察 列表中,添加 ILA IP 核的方法这里不再赘述。
            最后为工程添加 IO 管脚约束,对应的 XDC 约束语句如下所示:
    1. set_property -dict {PACKAGE_PIN U18 IOSTANDARD LVCMOS33} [get_ports sys_clk]
    2. set_property -dict {PACKAGE_PIN J15 IOSTANDARD LVCMOS33} [get_ports sys_rst_n]

     下载验证

            编译工程并生成比特流.bit 文件。将下载器一端连接电脑,另一端与开发板上的 JTAG 下载口连接,连 接电源线,并打开开发板的电源开关。
            点击 Vivado 左侧“ Flow Navigator ”窗口最下面的“ Open Hardware Manager ”,此时 Vivado 软件识别 到下载器,点击“Hardware” 窗口中“ Progam Device ”下载程序,在弹出的界面中选择“ Program ”下载程 序。
            RAM 写操作在 ILA 中观察的波形如下图所示:

    RAM 写操作 ILA 波形图

            ram_wea 信号拉高之后,地址和数据都是从 0 开始累加,也就说当 ram 地址为 0 时,写入的数据也是 0 ; 当 ram 地址为 1 时,写入的数据也是 1 。我们可以发现,上图中的数据变化和在 Vivado 仿真的波形是一致的。
            RAM 读操作在 ILA 中观察的波形如下图所示:

    RAM 读操作 ILA 波形图

            ram_wea(读使能)信号拉低之后,ram_addr 从 0 开始增加,也就是说从 ram 的地址 0 开始读数据;ram中读出的数据 ram_rd_data 在延时一个时钟周期之后,开始输出数据,输出的数据为 0,1,2……,和我们写入的值是相等的。我们可以发现,上图中的数据变化同样和 Vivado 仿真的波形是一致的。本次实验的 IP核之 RAM 读写实验验证成功。

    ip_ram

  • 相关阅读:
    海康威视-下载的录像视频浏览器播放问题
    与六年测试工程师促膝长谈,他分享的这些让我对软件测试工作有了全新的认知~
    系统移植部署开发阶段
    Go 理解零值
    Kerberos协议详解
    C# 文件监听FileSystemWatcher
    解决git clone报错RPC failed; curl 56 GnuTLS recv error (-9)
    【腾讯云云上实验室-向量数据库】探索腾讯云向量数据库:全方位管理与高效利用多维向量数据的引领者
    HCIA数据通信——交换机(Vlan间的通信与安全)
    代码随想录刷题】Day15 二叉树02------延伸题目练习
  • 原文地址:https://blog.csdn.net/weixin_52804129/article/details/126217201