FPGA逻辑设计回顾(13)RAM以及ROM的IP核定制以及关键参数

李锐博恩 2021-02-06 22:29:19 8014

前言

在后面讲解DDR IP的定制之前,这里先介绍下Xilinx的RAM以及ROM IP比较合适,因为都甚为存储性质的东西,有些东西还是可以作为比较的。

RAM中也有一些参数,也有一些延迟,需要注意!因为有时它的特性并非如我们想象的那样,正确的流程应该是先看数据手册,再使用,但是对于很多设计者来说,总感觉这玩意太简单,但越是这样可能越会用错,不注意延迟,可能会导致时序问题,进而导致功能性问题,最后是整个设计的失败。

好了,我们开始吧。

RAM IP的定制

Xilinx的IP定制位置

关于IP的这个最基础的内容,我们在这里只讲一次,可能没用过Xilinx的朋友以及新手朋友们可能需要:

Xilinx的IP核定制均在IP Catalog内:

IP核定制处

Xilinx的RAM IP位于存储元素一类内部,如下图:

RAM IP的分类位置
继续将小分类打开,可见:

小分类

我们的存储老朋友都在,第一个是DDR的控制接口,第二个是使用分布式资源的存储资源,第三个是Block资源的存储元素。

第一个放在后面的文章来讲解,我们先看第三个,Block资源的存储元素,以RAM为例,即BRAM。

Block RAM的定制过程

第一页

双击RAM,进入定制的第一页,第一页我们需要关注的内容如下:

定制第一页

定制的IP的名字只能在定制的时候更改,定制完成之后,不能再次更改,除非你删除自己定制的IP核,再次定制一个新的,那样比较麻烦,因此这里名字要按一个的规则写名字,一般而言,名字易懂即可,例如BRAM_16x1024,表示定制的是一个Block RAM,且位宽为16bit,深度为1024,关键信息一目了然。当然了,如果你的工程里用到了多个这种参数的RAM,你可以在后缀中加入更多的信息,例如功能性的信息,或者标号,即第几个这种RAM。

之后,我们在这一页需要关注的内容就是我们选用什么样的RAM,单端口还是双端口,这看你设计的需求。单端口适合先存储,之后过一段时间再取出,要分时使用,且最重要的是只能使用一个时钟,因为只有一个时钟端口。
对于双端口RAM而言,两边的端口都可以进行读写或存取,但在时序上也要求不能同时操作,例如对同一个地址同时进行读写,双端口的目的在于使用灵活,一端用于写,另一端可以用于读,且可以是不同的时钟域,功能划分清晰。

至于这一页的算法选择,可以关注也可以不关注,每一种选择都各有利弊,看你个人的选择,但我相信在你使用的时候,你可能想不到这一层面,到底我需要哪一种呢?自己也不清楚,还是暂时默认吧,无伤大雅。

第二页

定制第二页,是整个存储器定制的精华,也是我们最需要关注的内容,一般简单选择下就好了,选择满足我们功能的几个选项。
例如宽度与深度,操作模式呢?
可能不太需要吧,我们的使用场景尽量是分时使用,不能说一边写同时读那种吧。(如果你真的需要这种,那就不需要来看这篇博文了)

读写位宽可以不一致,这主要为位宽转换提供了方便,例如存两个数,取一次,就可以将两个数拼接在一起读出,这可以说是一种形式的串并转换了。
使能要不要选呢?
多一个选择多一条路,且这个选择不会对人造成困惑,如果没有特别需求,将其赋值为1即可。
定制第二页

至于输出寄存器放在哪一个位置,这个可以都选择,也可以只选择一个,带来的问题肯定是输出的延迟比较大。
例如我只选择了一个输出寄存器,看看延迟为多少?

直选一个输出寄存器
延迟为2;

选择两个寄存器:

选择两个寄存器
延迟为3。

但是好处在于选了后时序不会比不选差,原语处的寄存器位于原语内,IP核的寄存器位于FPGA的Fabric内。

该页最终的定制:

第二页
输出都是有延迟的,这一点后面的DDR也会有,需要当做常识。

第三页

第三页和第二页是同样的,只不过是定制B端口的内容,没有特别需求,建议保持一致。
B端口

第四页

上一页结束其实关键信息都已经选择,这一页对于RAM来说不太重要了,对于ROM来说可能很重要,因为ROM需要初始化内容, 后面就不写入了。
第四页

第五页

第五页

这一页是总结内容,告诉你的定制耗费了多少资源,输出相对于使能延迟了多少时钟,地址位宽是多少等。

到这里,我们就定制完了一个简单的真双端口BRAM IP核。

点击OK生成IP,之后复制IP核自带的例化模板使用即可。

例化模板
双击Veo文件,即可得到IP模板:

//----------- Begin Cut here for INSTANTIATION Template ---// INST_TAG
bram_16x1024 your_instance_name (
  .clka(clka),    // input wire clka
  .rsta(rsta),    // input wire rsta
  .ena(ena),      // input wire ena
  .wea(wea),      // input wire [0 : 0] wea
  .addra(addra),  // input wire [9 : 0] addra
  .dina(dina),    // input wire [31 : 0] dina
  .douta(douta),  // output wire [31 : 0] douta
  .clkb(clkb),    // input wire clkb
  .rstb(rstb),    // input wire rstb
  .enb(enb),      // input wire enb
  .web(web),      // input wire [0 : 0] web
  .addrb(addrb),  // input wire [9 : 0] addrb
  .dinb(dinb),    // input wire [31 : 0] dinb
  .doutb(doutb)  // output wire [31 : 0] doutb
);
// INST_TAG_END ------ End INSTANTIATION Template ---------

加入到你的设计即可。

Block RAM的延迟讨论

这里的讨论,最主要的还是讨论下什么时候数据可以使用存入的数据而已,以上述定制的IP为例,我们我们令写使能有效6个时钟,即可入6个数据,然后将这6个数据从另外一端读出。

首先将BRAM例化一下:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Engineer: 李锐博恩
// Create Date: 2021/02/06 18:57:57
// Module Name: bram_delay
//////////////////////////////////////////////////////////////////////////////////

module bram_delay(
    input wire clka,
    input wire rsta,
    input wire ena,
    input wire [0 : 0] wea,
    input wire [9 : 0] addra,
    input wire [31 : 0] dina,
    output wire [31 : 0] douta,
    input wire clkb,
    input wire rstb,
    input wire enb,
    input wire [0 : 0] web,
    input wire [9 : 0] addrb,
    input wire [31 : 0] dinb,
    output wire [31 : 0] doutb

    );

    bram_16x1024 inst_bram_16x1024 (
      .clka(clka),    // input wire clka
      .rsta(rsta),    // input wire rsta
      .ena(ena),      // input wire ena
      .wea(wea),      // input wire [0 : 0] wea
      .addra(addra),  // input wire [9 : 0] addra
      .dina(dina),    // input wire [31 : 0] dina
      .douta(douta),  // output wire [31 : 0] douta
      .clkb(clkb),    // input wire clkb
      .rstb(rstb),    // input wire rstb
      .enb(enb),      // input wire enb
      .web(web),      // input wire [0 : 0] web
      .addrb(addrb),  // input wire [9 : 0] addrb
      .dinb(dinb),    // input wire [31 : 0] dinb
      .doutb(doutb)  // output wire [31 : 0] doutb
    );

endmodule

使能的控制放在测试文件里做:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Engineer: 李锐博恩
// Create Date: 2021/02/06 18:57:57
// Module Name: bram_tb
//////////////////////////////////////////////////////////////////////////////////

module bram_tb(

    );

    reg clk;
    reg rst;

    reg wea;
    reg [9 : 0] addra;
    reg [31 : 0] dina;
    wire [31 : 0] douta;

    reg enb;
    reg web;
    reg [9 : 0] addrb;
    reg [31 : 0] dinb;
    wire [31 : 0] doutb;

    localparam PERIOD_CLK = 5;

    initial begin
        clk = 0;
        forever begin
            #(PERIOD_CLK/2) clk = ~clk;
        end
    end

    initial begin
        rst = 1'd1;
        wea = 1'd0;
        dina = 32'd0;
        addra = 10'd0;

        web = 1'd1;
        dinb = 32'd0;
        addrb = 10'd0;
        enb = 1'd0;

//写6个时钟的数据
        #(15*PERIOD_CLK) rst = #(0.1) 1'd0;
        #(6*PERIOD_CLK);
        @(posedge clk);
            wea = #(0.1*PERIOD_CLK) 1'd1;
            dina = #(0.1*PERIOD_CLK) 32'h66;
        #(5 * PERIOD_CLK) begin
            wea = #(0.1*PERIOD_CLK) 1'd0;
        end

//b端口读取6个数据
        #(15*PERIOD_CLK);
        @(posedge clk);
        enb = #(0.1*PERIOD_CLK) 1'd1;
        web = #(0.1 * PERIOD_CLK) 1'd0;
        repeat(5) begin
            @(posedge clk);
            addrb = #( 0.1*PERIOD_CLK )addrb + 10'd1;
        end

    end

    always @(posedge clk or posedge rst) begin
        if(rst) begin
            dina <= #(0.1) 32'd0;
            addra <= #(0.1) 10'd0;
        end
        else if(wea)begin
            dina <= #(0.1) dina + 32'd1;
            addra <= #(0.1) addra + 10'd1;
        end
    end

    bram_delay u_bram_delay(
        .clka  ( clk  ),
        .rsta  ( rst  ),
        .ena   ( 1'd1   ),
        .wea   ( wea   ),
        .addra ( addra ),
        .dina  ( dina  ),
        .douta ( douta ),
        .clkb  ( clk  ),
        .rstb  ( rst  ),
        .enb   ( enb   ),
        .web   ( web   ),
        .addrb ( addrb ),
        .dinb  ( dinb  ),
        .doutb  ( doutb  )
    );

endmodule

可见,我们的写使能持续了6个时钟:
写入
这个效果靠的是下面这段测试代码实现:

//写6个时钟的数据
        #(15*PERIOD_CLK) rst = #(0.1) 1'd0;
        #(6*PERIOD_CLK);
        @(posedge clk); //wea的第一个有效周期
            wea = #(0.1*PERIOD_CLK) 1'd1;
            dina = #(0.1*PERIOD_CLK) 32'h66;
        repeat(5) begin //wea的后5个有效周期
            @(posedge clk);
            wea = #(0.1*PERIOD_CLK) 1'd1;
        end
        @(posedge clk);
        wea = #(0.1*PERIOD_CLK)1'd0;

读6个数据:

取数据
从给地址开始,延迟3拍出数据,这与定制的时候看到的延迟数为3一致。

ROM IP核的定制

关于ROM IP核的定制,没什么好说的,和RAM类似,但有一点就是需要进行初始化,初始化时初始化的数据使用COE文件来提供,而这个COE文件的格式为:

memory_initialization_radix = 16;

memory_initialization_vector =

23f4 0721 11ff ABe1 0001 1 0A 0 23f4 0721 11ff ABe1 0001 1 0A 0

即正常的文件加上两个开头,一个表明初始化数据的进制:memory_initialization_radix;
另一个表明初始化数据:memory_initialization_vector;
提供一个例子:

memory_initialization_radix = 2;
memory_initialization_vector =
00000000
00000001
00000010
00000011

总结

以上就是这篇文章的所有内容,很简单,主要是提供一个清晰地认识,为以后类似的存储器件作为对比,同时理解参数的延迟很重要,这对逻辑设计的时序十分关键。

声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
红包 94 8 评论 打赏
评论
0个
内容存在敏感词
手气红包
    易百纳技术社区暂无数据
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
李锐博恩
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区