基于Xilinx的RAM IP核的使用

1.RAM的介绍

RAM 是随机存取存储器( Random Access Memory)的简称,是一个易失性存储器。RAM 工作时可以随时从任何一个指定的地址写入或读出数据,同时我们还能修改其存储的数据,即写入新的数据,这是 ROM 所并不具备的功能。在 FPGA 中这也是其与 ROM 的最大区别。 ROM 是只读存储器,而 RAM 是可写可读存储器,在我们 FPGA 中使用这两个存储器主要也是要区分这一点,因为这两个存储器使用的都是我们 FPGA 内部的 RAM 资源,不同的是 ROM 是只用到了 RAM 资源的读数据端口。

Xilinx 推出的 RAM IP 核分为两种类型:单端口 RAM 和双端口 RAM。其中双端口RAM 又分为简单双端口 RAM 和真正双端口 RAM。

对于单端口 RAM,读写操作共用一组地址线,读写操作不能同时进行;对于简单双端口 RAM,读操作和写操作有专用地址端口(一个读端口和一个写端口),即写端口只能写不能读,而读端口只能读不能写;对于真正双端口 RAM,有两个地址端口用于读写操作(两个读/写端口),即两个端口都可以进行读写。

下图是单端口RAM模块端口:

简单双端口RAM模块接口图:

真双端口模块接口图:

2.IP核配置

创建RAM IP核,首先搜索block

然后双击,我们选择单端口RAM。

选择位宽为8bit,深度为256;在模式选择中,RAM 读写操作模式共分为三种,非别是 Write First(写优先模式) 、 Read First(读优先模式) 和 No Change(不变模式)。

Write First(写优先模式) : 若我们在在同一个时钟沿下对同一个地址进行读写,则读出的数据为写入的数据。

Read First(读优先模式) : 若我们在在同一个时钟沿下对同一个地址进行读写, 则读出的数据为该地址写入数据前存储的数据。

No Change(不变模式) : 在该模式下不能同时进行读写操作, 输出数据为同时读写操作前输出的数据。

这里我们选择不变模式,主要是为了后面的设计任务。

使能接口直接选择总是使能,当然根据设计需求可以使用使能信号。

然后进入Other Options选项,和ROM一样,这里为了后续代码的实验需求,我们导入一个初始化文件。因为RAM是可以写入数据的,所以这里根据需求而定。

最后点击OK,然后就创建好了我们的单端口RAM IP。

3.IP核的使用

本次RAM IP的使用依然基于野火征途系列RAM IP使用设计一个:按下按键 1 时往 RAM 地址 0~255 里写入数据 0~255;按下按键 2 时读取 RAM 内的数据,从地址 0 开始每隔 0.2s 地址加 1 往下进行读取;再次按下按键 1 时停止读取重新写入数据 0~255;再次按下按键 2 时从头开始读取数据,使用数码管显示。

顺便提一下,如果是入门FPGA的初学者的话,推荐你们可以在小破站看一下野火征途Pro版本的免费视频,反正是免费的白嫖嘛还是可以的,讲的挺细的,这里的文档都是基于野火E10的开发指南来写的,但我没有使用过intel的FPGA芯片,所以例程都是基于AMD家的vivado来开发的。

OK,进入正题,根据任务要求可以知道,我们肯定要使用按键,那么肯定要有消抖模块;然后又是使用数码管显示,那么肯定还是需要数码管驱动模块的,上一小节视频是使用的ROM,本章是RAM,这不是换汤不换药吗?那来个RAM控制模块不久OK了吗。

接下来我们绘制模块图:

我承认模块框图是我复制上一章的,草率了,但是我真的不想话,哭哭~如果初学者的话,一定不要学我这样。

在绘制波形图之前,我们先来找到.veo文件来看一下单端口RAM IP的端口。

ram_256x8 your_instance_name (
  .clka(clka),    // input wire clka
  .wea(wea),      // input wire [0 : 0] wea
  .addra(addra),  // input wire [7 : 0] addra
  .dina(dina),    // input wire [7 : 0] dina
  .douta(douta)  // output wire [7 : 0] douta
);
  1. clka:时钟管脚

  1. wea:这个需要注意一下,在vivado中wea是一个读写使能的信号。当wea拉高时,为写使能;拉低则为读使能。因为单端口RAM IP读写操作是共用一组地址线,所以要特别注意一下。

而对于简单双端口 RAM,读操作和写操作有专用地址端口(一个读端口和一个写端口),即写端口只能写不能读,而读端口只能读不能写;对于真正双端口 RAM,有两个地址端口用于读写操作(两个读/写端口),即两个端口都可以进行读写。

  1. addra:为读写地址线

  1. dina:为写数据

  1. douta: 为读数据

本次使用的IP管脚就这些,下面根据IP的管脚来绘制波形图。

这里需要说明一下,当读写使能为低时,前面讲过了IP是进行读操作的。所以在本次的设计中,初始化状态也应该是一直在进行读操作的。

根据我们的波形图,可以很简单的编写一下ram_ctrl模块的代码。

module ram_ctrl(
    input               sys_clk     ,
    input               sys_rst_n   ,
    
    input               wr_flag     ,
    input               rd_flag     ,
    
    output  reg         wea         ,      //写数据
    output  reg [7:0]   addra       ,
    output      [7:0]   wr_data     ,
    output      [7:0]   rd_data
    );
    
parameter   CNT_MAX = 24'd9_999_999;    

reg [23:0]  cnt_200ms;



//cnt_200ms
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        cnt_200ms <= 24'd0;
    else if((cnt_200ms == CNT_MAX) || (wr_flag == 1'b1) || (rd_flag == 1'b1))
        cnt_200ms <= 24'd0;
    else if(wea == 1'b0)
        cnt_200ms <= cnt_200ms + 24'd1;
end

//wea 高为写,低为读
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        wea <= 1'b0;
    else if((addra == 8'd255 && cnt_200ms == CNT_MAX) || rd_flag == 1'b1)
        wea <= 1'b0;
    else if(wr_flag == 1'b1)
        wea <= 1'b1;
end



//addra
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        addra <= 8'd0;
    else if((addra == 8'd255 && cnt_200ms == CNT_MAX) || (wr_flag == 1'b1) || (rd_flag == 1'b1))
        addra <= 8'd0;
    else if((wea == 1'b0 && cnt_200ms == CNT_MAX) || (wea == 1'b1 && cnt_200ms == CNT_MAX))
        addra <= addra + 8'd1;
end

assign  wr_data = (wea == 1'b1) ? addra : 8'd0;


//ram
ram_256x8 ram_256x8_inst (
  .clka     (sys_clk),  // input wire clka
  
  .wea      (wea),      // input wire [0 : 0] wea
  .addra    (addra),    // input wire [7 : 0] addra
  .dina     (wr_data),     // input wire [7 : 0] dina
  
  .douta    (rd_data)     // output wire [7 : 0] douta
);      
        
endmodule

仿真代码:

`timescale 1ns / 1ns

module tb_ctrl_ram();

reg               sys_clk     ;
reg               sys_rst_n   ;
                     
reg               wr_flag     ;
reg               rd_flag     ;
                                
wire              wea         ;
wire      [7:0]   addra       ;
wire      [7:0]   dina     ;
wire      [7:0]   douta     ;

initial begin
    sys_clk = 1'b1;
    sys_rst_n <= 1'b0;
    wr_flag <= 1'b0;
    rd_flag <= 1'b0;
    #201
    sys_rst_n <= 1'b1;
    #1000
    //rd_flag
    rd_flag <= 1'b1;
    #20
    rd_flag <= 1'b0;
    #60_000
    //wr_flag
    wr_flag <= 1'b1;
    #20
    wr_flag <= 1'b0;
    #60_000
    //rd_flag
    rd_flag <= 1'b1;
    #20
    rd_flag <= 1'b0;
    #60_000
    //wr_flag
    wr_flag <= 1'b1;
    #20
    wr_flag <= 1'b0;
    #70_000
    //rd_flag
    rd_flag <= 1'b1;
    #20
    rd_flag <= 1'b0;
end

always #10 sys_clk <= ~sys_clk;

defparam    ram_ctrl_inst.CNT_MAX = 9;


ram_ctrl ram_ctrl_inst(
    .sys_clk     (sys_clk  ),
    .sys_rst_n   (sys_rst_n),
   
    .wr_flag     (wr_flag),
    .rd_flag     (rd_flag),
    
    .wea         (wea)    , //写数据
    .addra       (addra)  ,
    .wr_data     (dina)   ,
    .rd_data     (douta)

    );

endmodule

然后我们来看一下波形,首先是开始读的初始化数据:

然后再看一下按下写信号之后的波形:

最后就是将几个模块统一例化到顶层,然后约束管脚然后上板验证,这里就不演示了。

  • 1
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伊藤诚诚诚诚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值