搜索
bottom↓
回复: 0

【正点原子FPGA连载】第十七章IP核之RAM实验

[复制链接]

出0入234汤圆

发表于 2021-1-27 11:03:02 | 显示全部楼层 |阅读模式
1)实验平台:正点原子超越者FPGA开发板
2)  章节摘自【正点原子】超越者之FPGA开发指南
3)购买链接:https://item.taobao.com/item.htm?&id=631660290421
4)全套实验源码+手册+视频下载地址:http://www.openedv.com/docs/boards/fpga/zdyz-chaoyuezhe.html
5)正点原子官方B站:https://space.bilibili.com/394620890
6)正点原子FPGA技术交流群:905624739
QQ群头像.png

100846rel79a9p4uelap24.jpg

100846f1ce1fg14zbg0va4.png

第十七章IP核之RAM实验



RAM的英文全称是Random Access Memory,即随机存取存储器,它可以随时把数据写入任一指定地址的存储单元,也可以随时从任一指定地址中读出数据,其读写速度是由时钟频率决定的。RAM主要用来存放程序及程序执行过程中产生的中间数据、运算结果等。本章将对ISE软件生成的RAM IP核进行读写测试,并向大家介绍Xilinx RAM IP核的使用方法。
本章将详细的讲解如何使用Xilinx Spartan-6的RAM IP核。本章包括以下几个部分:
1717.1  简介
17.2  实验任务
17.3  硬件设计
17.4  程序设计
17.5  下载验证

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

图 17.1.1 单端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读出的数据。
17.2实验任务
本节实验任务是使用RAM IP核配置一个单端口的RAM,然后对RAM进行读写操作。通过在Modelsim仿真器中观察波形是否正确,最后将设计下载到超越者开发板中,并使用Chipscope对其进行在线调试观察。
17.3硬件设计
本章实验只用到了输入的时钟信号和按键复位信号,没有用到其它硬件外设,各端口信号的管脚分配如下表所示:
表17.3.1 IP核之RAM实验管脚分配
1731.png

对应的UCF约束语句如下所示:
  1. NET sys_clk                       TNM_NET = sys_clk_pin;
  2. TIMESPEC TS_sys_clk_pin = PERIOD sys_clk_pin 50000 kHz;

  3. NET sys_clk                       LOC = N8  | IOSTANDARD = "LVCMOS33";
  4. NET sys_rst_n                     LOC = G16 | IOSTANDARD = "LVCMOS33";
复制代码

17.4程序设计
首先在ISE软件中新建一个名为ip_ram的空工程,工程创建完成后,就可以添加RAM IP核了,如下图所示:
172142.png

图 17.4.1 添加IP核

如上图所示选择New Source进入创建新文件界面,如下图所示:

172237.png

图 17.4.2 创建IP核

如上图所示我们选择创建一个IP核,并命名为ip_ram,路径按照默认的即可,点击Next进入IP核选择界面,如下图所示:

172359.png

图 17.4.3 选择IP核

如上图所示直接在搜索栏中输入block,然后会弹出Block Memory Generator,选中它然后点击Next,会进入Summary界面,可以在Summary界面再次检查一下工程名,工程路径等信息,如下图所示:
172529.png

图 17.4.4 Summary界面

直接点击Finish进入RAM IP核的参数配置界面,如下图所示:
172627.png

图 17.4.5 参数配置

如上图所示,在参数配置第一页,只需要选择接口类型。选择Native普通接口,然后点击Next进入参数配置第二页,如下图所示:

172750.png

图 17.4.6 参数配置

参数配置第二页可配置的参数较多其中:
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:算法选项。可选择Minimum Area(最小面积)、Low Power(低功耗)和Fixed Primitives(固定的原语),这里选择默认的Minimum Area。
配置完成后点击Next进入参数配置第三页,如下图所示:
173244.png

图 17.4.7 参数配置

参数配置第三页主要是配置读写数据的位宽、深度、模式和使能。
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。
设置好第三页后点击Next进入下一页参数配置,如下图所示:
173801.png

图 17.4.8 参数配置

参数配置第四页不需要设置,这里点击Next直接进入参数配置第五页,如下图所示:
173900.png

图 17.4.9 参数配置

这里可以选择是否添加复位引脚,我们不添加,然后点击Next进入参数配置第六页,如下图所示:
174005.png

图 17.4.10 参数配置

参数配置第六页就类似Summary界面了,保持默认设置即可,直接点击Generate生成RAM IP核。生成成功后,回到ISE界面,发现工程下出现了IP核,如下图所示:
174151.png


图 17.4.11 创建好的RAM IP核

接下来就可以对RAM IP核进行读写操作了。需要创建一个读写模块和一个顶层模块,读写模块顾名思义,顶层模块主要用来例化RAM IP核模块和读写模块。
读写模块代码如下所示:
  1. 1 module ram_rw(
  2. 2      input               clk        ,  //时钟信号
  3. 3      input               rst_n      ,  //复位信号,低电平有效
  4. 4      
  5. 5      output              ram_en     ,  //ram使能信号
  6. 6      output              ram_wea    ,  //ram读写选择
  7. 7      output  reg  [4:0]  ram_addr   ,  //ram读写地址
  8. 8      output  reg  [7:0]  ram_wr_data,  //ram写数据
  9. 9      input        [7:0]  ram_rd_data   //ram读数据        
  10. 10     );
  11. 11
  12. 12 //reg define
  13. 13 reg    [5:0]  rw_cnt ;                //读写控制计数器
  14. 14
  15. 15 //*****************************************************
  16. 16 //**                    main code
  17. 17 //*****************************************************
  18. 18
  19. 19 //控制RAM使能信号
  20. 20 assign ram_en = rst_n;
  21. 21 //rw_cnt计数范围在0~31,写入数据;32~63时,读出数据
  22. 22 assign ram_wea = (rw_cnt <= 6'd31 && ram_en == 1'b1) ? 1'b1 : 1'b0;
  23. 23
  24. 24 //读写控制计数器,计数器范围0~63
  25. 25 always @(posedge clk or negedge rst_n) begin
  26. 26     if(rst_n == 1'b0)
  27. 27         rw_cnt <= 1'b0;   
  28. 28     else if(rw_cnt == 6'd63)
  29. 29         rw_cnt <= 1'b0;
  30. 30     else
  31. 31         rw_cnt <= rw_cnt + 1'b1;   
  32. 32 end  
  33. 33
  34. 34 //产生RAM写数据
  35. 35 always @(posedge clk or negedge rst_n) begin
  36. 36     if(rst_n == 1'b0)
  37. 37         ram_wr_data <= 1'b0;  
  38. 38     else if(rw_cnt <= 6'd31)  //在计数器的0-31范围内,RAM写地址累加
  39. 39         ram_wr_data <= ram_wr_data + 1'b1;
  40. 40     else
  41. 41         ram_wr_data <= 1'b0 ;   
  42. 42 end  
  43. 43
  44. 44 //读写地址信号 范围:0~31
  45. 45 always @(posedge clk or negedge rst_n) begin
  46. 46     if(rst_n == 1'b0)
  47. 47         ram_addr <= 1'b0;
  48. 48     else if(ram_addr == 5'd31)
  49. 49         ram_addr <= 1'b0;
  50. 50     else   
  51. 51         ram_addr <= ram_addr + 1'b1;
  52. 52 end
  53. 53
  54. 63 endmodule
复制代码

模块中定义了一个读写控制计数器(rw_cnt),当计数范围在0~31之间时,向ram中写入数据;当计数范围在32~63之间时,从ram中读出数据。
接下来我们再来看一下顶层模块代码,如下所示:
  1. 1  module ip_ram(
  2. 2              input sys_clk,
  3. 3       input sys_rst_n
  4. 4  
  5. 5      );
  6. 6  
  7. 7  wire         ena     ;
  8. 8  wire         wea     ;
  9. 9  wire [4 : 0] addra   ;
  10. 10 wire [7 : 0] dina    ;
  11. 11 wire [7 : 0] douta   ;
  12. 12
  13. 13 ram u_ram (
  14. 14  .clka       (sys_clk), // input clka
  15. 15  .ena        (ena    ), // input ena
  16. 16  .wea        (wea    ), // input [0 : 0] wea
  17. 17  .addra      (addra  ), // input [4 : 0] addra
  18. 18  .dina       (dina   ), // input [7 : 0] dina
  19. 19  .douta      (douta  ) // output [7 : 0] douta
  20. 20 );
  21. 21
  22. 22 ram_rw u_ram_rw (
  23. 23     .clk         (sys_clk    ),
  24. 24     .rst_n       (sys_rst_n  ),
  25. 25     .ram_en      (ena        ),
  26. 26     .ram_wea     (wea        ),
  27. 27     .ram_addr    (addra      ),
  28. 28     .ram_wr_data (dina        )
  29. 29     );
  30. 30  
  31. 31 endmodule   
复制代码

当代码全部写好后,会发现编译过不了报错,因为所有的逻辑都是在FPGA内部进行,内部产生数据,内部读写,对外界没有产生任何影响,软件会给我们报错。这个时候可以定义一些冗余输出端口,把想要观察的信号赋值给冗余输出端口,这样既方便我们仿真和在线调试,编译也不会再报错,如下图所示:
176915.png

图 17.4.12 添加冗余输出端口

我们可以看到我将想要观察的信号全部重新定义了一遍加上后缀为_tb,并把它们作为输出端口,然后把想要观察的信号赋值给这些输出端口,这样软件就不会报错了。
接下来我们先添加一个仿真文件(Testbench文件),如下所示:
  1. 1  `timescale 1ns / 1ps
  2. 2  module ip_ram_tb;
  3. 3  
  4. 4   // Inputs
  5. 5   reg sys_clk;
  6. 6   reg sys_rst_n;
  7. 7  
  8. 8   // Outputs
  9. 9   wire ena_tb;
  10. 10  wire wea_tb;
  11. 11  wire [4:0] addra_tb;
  12. 12  wire [7:0] dina_tb;
  13. 13  wire [7:0] douta_tb;
  14. 14
  15. 15  // Instantiate the Unit Under Test (UUT)
  16. 16  ip_ram uut (
  17. 17      .sys_clk    (sys_clk    )   ,
  18. 18      .sys_rst_n  (sys_rst_n  )   ,
  19. 19      .ena_tb     (ena_tb     )   ,
  20. 20      .wea_tb     (wea_tb     )   ,
  21. 21      .addra_tb   (addra_tb   )   ,
  22. 22      .dina_tb    (dina_tb    )   ,
  23. 23      .douta_tb   (douta_tb   )
  24. 24  );
  25. 25
  26. 26  initial begin
  27. 27      // Initialize Inputs
  28. 28      sys_clk = 0;
  29. 29      sys_rst_n = 0;
  30. 30
  31. 31      // Wait 100 ns for global reset to finish
  32. 32      #100;
  33. 33      sys_rst_n=1;
  34. 34      // Add stimulus here
  35. 35
  36. 36  end
  37. 37  always #10 sys_clk=~sys_clk;  
  38. 38 endmodule
复制代码

仿真文件写好后,点击Modelsim联调按钮,打开Modelsim仿真软件,可以看到仿真波形如下图所示:
177982.png

图 17.4.13 仿真波形

从上图中可以看到随着wea_tb(读写切换)的不断变换,读数据和写数据交替进行,下面我们把波形图放大一点,看看细节,主要是观察写入数据和读出数据是否一致,如下图所示:
178126.png

图 17.4.14 仿真波形细节图

从上图中可以RAM的地址是从0~31跳变,写入的数据和读出的数据也是一致的,说明代码功能是符合预期的。
仿真通过后就,给工程添加UCF引脚约束,如下所示:
  1. NET sys_clk                       TNM_NET = sys_clk_pin;
  2. TIMESPEC TS_sys_clk_pin = PERIOD sys_clk_pin 50000 kHz;

  3. NET sys_clk                       LOC = N8  | IOSTANDARD = "LVCMOS33";
  4. NET sys_rst_n                     LOC = G16 | IOSTANDARD = "LVCMOS33";
复制代码

编译通过后,添加Chipscope的逻辑分析仪IP核,然后生成bit流文件,下载到板子中去,使用Chipscope工具(Chipscope的使用请参考软件篇)进行在线调试,看看工程在板子上运行是否正确。
17.5下载验证
首先将下载器与超越者底板上的JTAG接口连接,下载器另外一端与电脑连接,连接开发板的电源。
178677.png

图 17.5.1 硬件连接

打开电源开关,然后回到ISE界面,打开Chipscope工具,加载生成好的CDC文件,下载到板子中去,得到波形图,如下图所示:
178800.png

图 17.5.2 在线调试波形

可以看到Chipscope抓取到的波形与仿真波形相同,RAM地址从0~31跳变,读数据和写数据交替、进行且读写数据一样,所有结果都是符合预期的,这就说明本次RAM IP核实验成功了。

阿莫论坛20周年了!感谢大家的支持与爱护!!

月入3000的是反美的。收入3万是亲美的。收入30万是移民美国的。收入300万是取得绿卡后回国,教唆那些3000来反美的!
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-4-25 05:59

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表