当前位置:   article > 正文

小梅哥Xilinx FPGA学习笔记21——IP核之RAM实验_fpga ram ip核

fpga ram ip核

目录

一: RAM 简介

1.1 存储器的分类

二: 单端口ram配置

2.1 单端口 RAM 的框图

2.2 RAM IP 核配置

2.3 RAM 读写模块设计

2.4 顶层模块设计

2.5 仿真测试文件代码

2.6 仿真结果

三:伪双端口配置(小梅哥)

3.1 伪双端口框图

3.2详细配置流程图

3.2 激励文件设计代码

3.3 仿真结果

四:伪双端口配置(正点原子)

4.1 RAM 写模块设计

4.2 RAM 读模块设计

4.3 顶层文件设计

4.4 仿真文件

4.5 仿真结果


: RAM 简介

1.1 存储器的分类

在了解 RAM IP 核之前,我们先来看下存储器的大致分类,如下图所示:

             

       由上图可知,存储器包括随机存储器和只读存储器,随机存储器包括静态 RAM 动态 RAM 。静态 RAM只要有供电,它保存的数据就不会丢失;而动态 RAM 在供电的情况下,还需要根据其要求的时间来对存储 的数据进行刷新,才能保持存储的数据不会丢失。
       静态 RAM 一般包括 单端口 RAM 简单双端口RAM 真双端口 RAM。 静态 RAM 的特点是存储容量相对不是很大,但是读写速度非常高,
       动态 RAM 一般包括 SDRAM DDR SDRAM 。目前 DDR SDRAM 已经从 DDR1 代发展到 DDR5 代了, DDR3 DDR4 SDRAM 是目前非常主流的存储器, 其特 点是存储容量非常大、但是读写速度相比于静态 RAM 会稍低一些
       只读存储器 一般包括 PROM EPROM EEPROM 等,是非易失性的存储器。目前使用率较高的是 EEPROM ,其特点是容量相对较小,存储的一般是器件的配置参数信息,
本次我们学习的 RAM 属于 静态 RAM ,我们重点看下几种静态 RAM 的特性与区别:

不同的特性决定不同的应用场景,在 RAM 的实际应用中,我们一般根据功能需求和带宽需求来选择合适的 RAM 类型,

二: 单端口ram配置

        Vivado 软件自带的 Block Memory Generator IP ,可以用来配置生成 RAM 或者 ROM RAM 是一种随机存取存储器,不仅可以读出存储的数据,同时还支持对存储 的数据进行修改,而 ROM 是一种只读存储器,也就是说,在工作时只能读出数据,而不能写入数据。需要 注意的是,配置生成的 RAM 或者 ROM 使用的都是 FPGA 内部的 BRAM 资源( Block RAM ,即块随机存 储器,是 FPGA 厂商在逻辑资源之外,给 FPGA 加入的专用 RAM 块资源),只不过配置成 ROM 时只用到 了嵌入式 BRAM 的读数据端口。本章我们主要介绍如何将 BMG IP 核配置成 RAM

2.1 单端口 RAM 的框图

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

2.2 RAM IP 核配置

关于具体每一张图中的具体选项的含义可详见正点原子的《领航者ZYNQ 之 FPGA 开发指南》P542

本次实验写入32个数据所以深度为32,数据位宽为8

 

至此,ramIP核配置成功。生成ramIP核文件。

2.3 RAM 读写模块设计

本次实验任务是在1~31个地址中写入1~31个数,然后再在1~31个地址中读取这些数据。

读写波形图如下图所示

RAM 读写模块设计代码(代码的输入是RAMIP核的输出,代码的输出是RAMIP核的输入)

  1. module ram_rw(
  2. input clk, //系统时钟,50MHz
  3. input reset_n, //系统复位按键,低电平有效
  4. input [7 : 0] ram_rd_data, //ram读数据
  5. output reg ram_en, //ram端口使能信号,高有效
  6. output wire ram_we, //ram读写使能信号,1为写,0为读
  7. output reg [4 : 0] ram_addr, //ram读写地址
  8. output reg [7 : 0] ram_wr_data //ram写数据
  9. );
  10. reg [5:0] rw_cnt ; //读写控制计数器
  11. always @(posedge clk or negedge reset_n)
  12. if(!reset_n)
  13. ram_en <= 0;
  14. else
  15. ram_en <= 1;
  16. assign ram_we = (rw_cnt <= 6'd31 && ram_en == 1'b1) ? 1'b1 : 1'b0;//组合逻辑实现会和ram_en同步
  17. //时序逻辑am_we实现会使得其不在ram_en信号拉高的同时立马拉高,会晚半拍
  18. /*always @(posedge clk or negedge reset_n)
  19. if(!reset_n)
  20. ram_we <= 0;
  21. else if(ram_en && rw_cnt <= 31)
  22. ram_we <= 1;
  23. else
  24. ram_we <= 0;
  25. */
  26. //读写控制计数器,计数器范围 0~63
  27. always @(posedge clk or negedge reset_n)
  28. if(!reset_n)
  29. rw_cnt <= 0;
  30. else if(ram_en)begin
  31. if(rw_cnt >= 63 )
  32. rw_cnt <= 0;
  33. else
  34. rw_cnt <= rw_cnt + 1;
  35. end
  36. else
  37. rw_cnt <= 0;
  38. //读写地址信号 范围:0~31
  39. always @(posedge clk or negedge reset_n) begin
  40. if(!reset_n)
  41. ram_addr <= 0;
  42. else if(ram_addr == 5'd31 && ram_en)
  43. ram_addr <= 5'b0;
  44. else if (ram_en)
  45. ram_addr <= ram_addr + 1'b1;
  46. else
  47. ram_addr <= 5'b0;
  48. end
  49. //在 WE 拉高期间产生 RAM 写数据,变化范围是 0~31
  50. always @(posedge clk or negedge reset_n) begin
  51. if(!reset_n)
  52. ram_wr_data <= 8'b0;
  53. else if(ram_wr_data < 8'd31 && ram_we)
  54. ram_wr_data <= ram_wr_data + 1'b1;
  55. else
  56. ram_wr_data <= 8'b0 ;
  57. end
  58. endmodule

2.4 顶层模块设计

设计代码

  1. module zdyz_ip_ram(
  2. input clk,
  3. input reset_n
  4. );
  5. wire ram_en;
  6. wire ram_we;
  7. wire [4 : 0] ram_addr;
  8. wire [7 : 0] ram_wr_data ;
  9. wire [7 : 0] ram_rd_data;
  10. //例化ram 读写模块
  11. ram_rw ram_rw(
  12. . clk(clk), //系统时钟,50MHz
  13. . reset_n(reset_n), //系统复位按键,低电平有效
  14. . ram_rd_data(ram_rd_data), //ram读数据
  15. . ram_en(ram_en), //ram端口使能信号,高有效
  16. . ram_we(ram_we), //ram读写使能信号,1为写,0为读
  17. . ram_addr(ram_addr), //ram读写地址
  18. . ram_wr_data(ram_wr_data) //ram写数据
  19. );
  20. blk_mem_gen_0 blk_mem_gen_0 (
  21. .clka(clk), // input wire clka
  22. .ena(ram_en), // input wire ena
  23. .wea(ram_we), // input wire [0 : 0] wea
  24. .addra(ram_addr), // input wire [4 : 0] addra
  25. .dina(ram_wr_data), // input wire [7 : 0] dina
  26. .douta(ram_rd_data) // output wire [7 : 0] douta
  27. );
  28. endmodule

2.5 仿真测试文件代码

  1. `timescale 1ns / 1ps
  2. module zdyz_ip_ram_tb();
  3. reg clk;
  4. reg reset_n;
  5. initial begin
  6. clk = 1'b0;
  7. reset_n = 1'b0;
  8. #200
  9. reset_n = 1'b1;
  10. end
  11. //产生时钟
  12. always #20 clk = ~clk;
  13. zdyz_ip_ram zdyz_ip_ram(
  14. .clk(clk),
  15. .reset_n(reset_n)
  16. );
  17. endmodule

2.6 仿真结果

仿真通过

三:伪双端口配置(小梅哥)

关于具体每一张图中的具体选项的含义可详见小梅哥的《基于HDL的FPGA逻辑设计与验证教程》P314

3.1 伪双端口框图

与单端口 RAM 不同的是,伪双端口 RAM 输入有两路时钟信号 CLKA/CLKB;独立的两组地址信号ADDRA/ADDRB;Port A 仅提供 DINA 写数据总线,作为数据的写入口;Port B 仅提供数据读的功能,读出的数据为 DOUTB。 关于各个引脚说明可参考小梅哥或者正点原子文档教程。

3.2详细配置流程图

选择 Block Memory Generator 双击鼠标进入到 RAM IP 配置界面。

端口类型的选择,Xilinx 的很多 IP 一般都有提供两种接口,一种是常规接口,一种是 AXI 接口,这里选择选择常规接口 Native

这里我们选择简单双端口 RAMSimple Dual Port RAM

ECC 全称是 Error Correction Capability,是在简单双端口 RAM 类型下的一种纠错功能,具体该功能的详细说明,可以查看 IP 手册,这里选择 NO ECC

写数据字节使能,如果勾选,写使能信号会根据写数据的字节数生成对应的 bit 数据,1 个字节对应 1bit 写使能,这里字节的大小可以设置为 8 9,当这里选择后,输入输出的数据的位宽就必须是 8 9 的整数倍,这里我们需要一个位宽为 8bit RAM,这里勾选 Write Enable 并设置字节大小为 8bit

这里我们保持默认的最小面积选项即可。

RAM 数据位宽和深度设置,这个根据实际应用需求进行设置,这里设置数据位宽 8bit,深度 256

这里选择NO Change(其他选项的具体说明可参考文档教程)

端口使能信号类型设置,一个是一直使能,一个是通过一个 ENA 信号管脚控制,这里选择 Always Enable

由于我们前面选择的是简单双端口 RAM,对于端口 A 只能进行数据的写入,没有数据的输出,所有关于端口 A 的数据输出的相关配置是不可配置的。

端口 B 数据位宽和内存深度的设置,这里设置位宽为8,深度会自动根据你选择的位宽进行设置。

端口 B 操作模式不可设置,由于在简单双端口 RAM 下端口 B 只能进行读 操作,不能进行写操作,所以这里不可设置,在真双单口 RAM 下,这里是可进 行设置的。端口使能就设置为 Always Enable ,让端口 B 一直使能。
          端口 B 输出寄存器配置,这里可以看下 RAM 内部结构图,可以很清楚的看到 Primitives Output Register 是结构中的 1 处的寄存器, Core Output Register 是结 构图中 2 出的寄存器。 REGCEB Pin 是寄存器使能管脚,如果勾选,会有一个寄 存器使能控制管脚用于控制寄存器的使能,如果不勾选寄存器就一直使能状态, 这里就不勾选。要得到更好的性能,将这里的两个寄存器都勾选。

端口 B 输出置位/复位设置,这里不创建置位/复位端口,需注意这里置位/复位并不复位 RAM 中的数据而是只复位寄存器上的值。

其他设置,这里不对 RAM 进行初始化, 关于仿真设置就保持默认即可。

3.2 激励文件设计代码

       为了测试简单双端口 RAM ,可以通过实际写入一些数据再读取部分数据的 方式来验证双端口 RAM 读写是否正常。添加并新建 tb 文件命名为 ram_tb.v 。编 tb 代码,具体 tb 代码实现的是在地址从 0~16 上写入数据为从 255 减至 240 延时一段时间后读地址为 0~16 上的数据。
  1. `timescale 1ns / 1ps
  2. module xmg_ram_ip_tb();
  3. reg clka ;
  4. reg clkb ;
  5. reg wea ;
  6. reg [7 : 0] addra ;
  7. reg [7 : 0] dina ;
  8. reg [7 : 0] addrb ;
  9. wire [7 : 0] doutb ;
  10. integer i;//integer类型用于表示整数值。在FPGA设计中,integer类型通常用于计数器、延时器等电路中。作用:用于表示整数。
  11. blk_mem_gen_0 blk_mem_gen_0 (
  12. .clka(clka), // input wire clka
  13. .wea(wea), // input wire [0 : 0] wea
  14. .addra(addra), // input wire [7 : 0] addra
  15. .dina(dina), // input wire [7 : 0] dina
  16. .clkb(clkb), // input wire clkb
  17. .addrb(addrb), // input wire [7 : 0] addrb
  18. .doutb(doutb) // output wire [7 : 0] doutb
  19. );
  20. initial clka = 1;
  21. always #10 clka = ~clka;
  22. initial clkb = 1;
  23. always #10 clkb = ~clkb;
  24. initial begin
  25. wea=0;
  26. addra=0;
  27. dina=0;
  28. addrb=0; //255
  29. #201;
  30. wea = 1;
  31. for (i=0;i<=15;i=i+1)begin
  32. dina=255-i;//写入数据
  33. addra = i;//选择地址
  34. #20;
  35. end
  36. wea=0;
  37. #1;
  38. for (i=0;i<=15;i=i+1)begin
  39. addrb=i;//读取相应地址的数据
  40. #40;
  41. end
  42. #200;
  43. $stop;
  44. end
  45. endmodule

3.3 仿真结果

至此伪双端口RAM配置以及设计仿真完毕。

四:伪双端口配置(正点原子)

由于伪双端口配置较为常用,所以再次再次重复编写一下伪双端口实例。
详细配置方案可参考正点原子文档教程《 领航者ZYNQ 之 FPGA 开发指南》P564。
本节任务是在0~63个地址依次写入0~63个数,然后在写到一半数据(32个数据)时就开始从地址0~63依次读数据

4.1 RAM 写模块设计

模块框图:

模块代码

  1. module zdyz_ram_wr(
  2. input clk , //时钟信号
  3. input reset_n , //复位信号,低电平有效
  4. //RAM 写端口操作
  5. output ram_wr_we , //ram 写使能
  6. output reg ram_wr_en , //端口使能
  7. output reg [5:0] ram_wr_addr , //ram 写地址
  8. output [7:0] ram_wr_data ,//ram 写数据
  9. output reg rd_flag //读启动信号
  10. );
  11. //ram_wr_we 为高电平表示写数据
  12. assign ram_wr_we = ram_wr_en;
  13. //写数据与写地址相同,因位宽不等,所以高位补 0
  14. assign ram_wr_data = {2'b0,ram_wr_addr};
  15. //控制 RAM 使能信号
  16. always @(posedge clk or negedge reset_n)
  17. if(!reset_n)
  18. ram_wr_en <= 1'b0;
  19. else
  20. ram_wr_en <= 1'b1;
  21. //写地址信号 范围:0~63
  22. always @(posedge clk or negedge reset_n)
  23. if(!reset_n)
  24. ram_wr_addr <= 0;
  25. else if(ram_wr_en && ram_wr_addr < 63)
  26. ram_wr_addr <= ram_wr_addr + 1;
  27. else
  28. ram_wr_addr <= 0;
  29. //当写入 32 个数据(0~31)后,拉高读启动信号
  30. always @(posedge clk or negedge reset_n)
  31. if(!reset_n)
  32. rd_flag <= 0;
  33. else if(ram_wr_addr == 31)
  34. rd_flag <= 1;
  35. else
  36. rd_flag <= rd_flag;
  37. endmodule

4.2 RAM 读模块设计

模块框图:
模块代码
  1. module zdyz_ram_rd(
  2. input clk , //时钟信号
  3. input reset_n , //复位信号,低电平有效
  4. //RAM 读端口操作
  5. input rd_flag , //读启动标志
  6. input [7:0] ram_rd_data ,//ram 读数据
  7. output wire ram_rd_en , //端口使能
  8. output reg [5:0] ram_rd_addr //ram 读地址
  9. );
  10. assign ram_rd_en = rd_flag;
  11. //读地址信号 范围:0~63
  12. always @(posedge clk or negedge reset_n)
  13. if(!reset_n)
  14. ram_rd_addr <= 0;
  15. else if(rd_flag && ram_rd_addr < 63)
  16. ram_rd_addr <= ram_rd_addr + 1;
  17. else
  18. ram_rd_addr <= 0;
  19. endmodule

4.3 顶层文件设计

代码设计
  1. module zdyz_ip_2port_ram(
  2. input clk , //系统时钟
  3. input reset_n //系统复位,低电平有效
  4. );
  5. wire ram_wr_we;
  6. wire ram_wr_en;
  7. wire [5:0]ram_wr_addr; //ram 写地址
  8. wire [7:0]ram_wr_data; //ram 写数据
  9. wire [5:0]ram_rd_addr; //ram 读地址
  10. wire [7:0]ram_rd_data; //ram 读数据
  11. wire rd_flag; //读启动标志
  12. wire ram_rd_en;
  13. //RAM 写模块例化
  14. zdyz_ram_wr zdyz_ram_wr(
  15. .clk (clk) , //时钟信号
  16. .reset_n (reset_n) , //复位信号,低电平有效
  17. .ram_wr_we (ram_wr_we) , //ram 写使能
  18. .ram_wr_en (ram_wr_en) , //端口使能
  19. .ram_wr_addr (ram_wr_addr) , //ram 写地址
  20. .ram_wr_data (ram_wr_data) , //ram 写数据
  21. .rd_flag (rd_flag) //读启动信号
  22. );
  23. //RAM 读模块例化
  24. zdyz_ram_rd zdyz_ram_rd(
  25. .clk (clk) ,//时钟信号
  26. .reset_n (reset_n) ,//复位信号,低电平有效
  27. .rd_flag (rd_flag) ,//读启动标志
  28. .ram_rd_data (ram_rd_data) ,//ram 读数据
  29. .ram_rd_en (ram_rd_en) ,//端口使能
  30. .ram_rd_addr (ram_rd_addr) //ram 读地址
  31. );
  32. //RAM IP核例化
  33. zdyz_blk_mem_gen_2 your_instance_name (
  34. .clka(clk), // input wire clka
  35. .ena(ram_wr_en), // input wire ena
  36. .wea(ram_wr_we), // input wire [0 : 0] wea
  37. .addra(ram_wr_addr), // input wire [5 : 0] addra
  38. .dina(ram_wr_data), // input wire [7 : 0] dina
  39. .clkb(clk), // input wire clkb
  40. .enb(ram_rd_en), // input wire enb
  41. .addrb(ram_rd_addr), // input wire [5 : 0] addrb
  42. .doutb(ram_rd_data) // output wire [7 : 0] doutb
  43. );
  44. endmodule

4.4 仿真文件

  1. `timescale 1ns / 1ps
  2. module zdyz_ip_2port_ram_tb();
  3. //parameter define
  4. parameter CLK_PERIOD = 20; //时钟周期 20ns
  5. //reg define
  6. reg clk;
  7. reg reset_n;
  8. //信号初始化
  9. initial begin
  10. clk = 1'b0;
  11. reset_n = 1'b0;
  12. #200
  13. reset_n = 1'b1;
  14. end
  15. //产生时钟
  16. always #(CLK_PERIOD/2) clk = ~clk;
  17. zdyz_ip_2port_ram zdyz_ip_2port_ram(
  18. .clk (clk ),
  19. .reset_n(reset_n)
  20. );
  21. endmodule

4.5 仿真结果

至此RAM IP核配置以及仿真验证已经结束。
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/菜鸟追梦旅行/article/detail/464985
推荐阅读
相关标签
  

闽ICP备14008679号