FPGA逻辑设计回顾(12)RAM以及ROM的RTL设计及其验证

李锐博恩 2021-01-31 04:24:47 5028

前言

RAM以及ROM在FPGA中的实现大体有两种方式,一种是使用IP核定制,一种是RTL设计。

也许有人会反驳,那原语呢?
我不喜欢讨论这个问题,原语你去使用吗?如果你真的喜欢,请自便。

下面我们讨论这两种实现方式:

  • 首先是RTL的设计,这种方式中,我们重点在于实现逻辑设计。

  • 在IP核的定制中,我们将分别定制一种简单的RAM和ROM的IP核,并讨论它们使用中的一些参数注意事项。(这种方式,下一节讨论)

RAM的RTL设计

RAM的实现分类

在RAM的实现中,我们根据数据是否与时钟同步,分为同步RAM以及异步RAM,如果继续细分地话,我们可以将RAM分为同步读同步写,同步读异步写,异步读,异步写等等组合,但这就没什么意思了,我会给出同步以及异步示例。

同步RAM

我们这里的同步RAM,就是RAM的读写和时钟同步,为了和后面使用IP核定制的方式尽量一致,我们本篇文章统统使用一种位宽,一种深度,端口信号也尽量一致(这包括数量以及命名)。

双端口同步读写
`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Engineer: 李锐博恩
// Create Date: 2021/01/31 02:46:06
// Module Name: dual_ram
//////////////////////////////////////////////////////////////////////////////////

module dual_ram
    #(
        parameter WIDTH = 16,
        parameter DEPTH = 4
    )(
    //a
    input   wire            clka,
    input   wire            rst,
    input   wire            ena,
    input   wire            wea,
    input   wire [DEPTH - 1 : 0]     addra,
    input   wire [WIDTH - 1 : 0]     dina,
    output  reg  [WIDTH - 1 : 0]     douta,
    //b
    input   wire            clkb,
    input   wire            enb,
    input   wire            web,
    input   wire [DEPTH - 1 : 0]     addrb,
    input   wire [WIDTH - 1 : 0]     dinb,
    output  reg  [WIDTH - 1 : 0]     doutb    
    );

    //双端口RAM
    reg [WIDTH - 1 : 0] dual_ram[DEPTH - 1 : 0];

    //写
    integer i;
    always@(posedge clka or posedge rst) begin
        if(rst) begin
            for(i = 0; i <= DEPTH - 1; i = i + 1) begin: initial_a
                dual_ram[i] <= 'd0;
            end
        end
        else if(ena && wea) begin
            dual_ram[addra] <= dina;
        end
        else if(enb && web) begin
            dual_ram[addrb] <= dinb;
        end
        else begin
            //保持
        end
    end

    //a端口读

    always@(posedge clka) begin
        if(ena && ~wea) begin
            douta <= dual_ram[addra];
        end
        else begin
            douta <= 'd0;
        end
    end

    always@(posedge clkb) begin
        if(enb && ~web) begin
            doutb <= dual_ram[addrb];
        end
        else begin
            doutb <= 'd0;
        end
    end

endmodule

这种写法简单易懂,且在平时练习的过程中尽量使用参数化的方式,养成习惯,不要怕麻烦,这样会让你在以后的工作中受益!

验证则尽量简单化:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Engineer: 李锐博恩
// Create Date: 2021/01/31 02:46:06
// Module Name: dual_ram_tb
//////////////////////////////////////////////////////////////////////////////////

module dual_ram_tb(
    );
    parameter WIDTH = 16;
    parameter DEPTH = 4;
    parameter PERIOD_A = 4;
    parameter PERIOD_B = 5;
    reg                         clka;
    reg                         rst;
    reg                         ena;
    reg                         wea;
    reg     [DEPTH - 1 : 0]     addra;
    reg     [WIDTH - 1 : 0]     dina;
    wire    [WIDTH - 1 : 0]     douta;
    //b
    reg                         clkb;
    reg                         enb;
    reg                         web;
    reg     [DEPTH - 1 : 0]     addrb;
    reg     [WIDTH - 1 : 0]     dinb;
    wire    [WIDTH - 1 : 0]     doutb;

    initial begin
        clka = 0;
        forever begin
            # (PERIOD_A/2) clka = ~clka;
        end
    end     

    initial begin
        clkb = 0;
        forever begin
            # (PERIOD_B/2) clkb = ~clkb;
        end
    end

    initial begin
        rst = 1;
        ena = 0;
        enb = 0;
        wea = 0;
        web = 0;
        addra = 0;
        addrb = 0;
        dina = 0;
        dinb = 0; 

        repeat(15);
        @(posedge clka);
        rst = #(0.1 * PERIOD_A) 0;

        //a端口连续写两个数据
        repeat(10);
        @(posedge clka);
        addra = #(0.1 * PERIOD_A) 'd0;
        dina = #(0.1 * PERIOD_A) $random;        
        @(posedge clka);
        ena = #(0.1 * PERIOD_A) 1;
        wea = #(0.1 * PERIOD_A) 1;
        @(posedge clka);
        addra = #(0.1 * PERIOD_A) 'd1;
        dina = #(0.1 * PERIOD_A) $random;
        @(posedge clka);

        //b端口读两个数据

        repeat(10);
        @(posedge clkb);
        addrb = #(0.1 * PERIOD_B) 'd0;        
        @(posedge clkb);
        enb = #(0.1 * PERIOD_B) 1;
        web = #(0.1 * PERIOD_B) 0;

        @(posedge clkb);
        addrb = #(0.1 * PERIOD_B) 'd1;

    end

    dual_ram#(
        .WIDTH ( WIDTH ),
        .DEPTH ( DEPTH )
    )u_dual_ram(
        .clka  ( clka  ),
        .rst   ( rst   ),
        .ena   ( ena   ),
        .wea   ( wea   ),
        .addra ( addra ),
        .dina  ( dina  ),
        .douta ( douta ),
        .clkb  ( clkb  ),
        .enb   ( enb   ),
        .web   ( web   ),
        .addrb ( addrb ),
        .dinb  ( dinb  ),
        .doutb  ( doutb  )
    );

endmodule

如下是仿真时序图:
仿真时序图

RTL原理图:
RTL原理图

综合原理图:

可综合
表明可综合设计。

异步RAM

异步RAM,意思就是不需要时钟同步的RAM,给出RTL设计:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Engineer: 李锐博恩
// Create Date: 2021/01/31 02:46:06
// Module Name: asy_ram
//////////////////////////////////////////////////////////////////////////////////

module asy_ram#(

parameter DATA_WIDTH = 16,
parameter RAM_DEPTH = 4

)(

    //a
    input   wire            ena,
    input   wire            wea,
    input   wire [RAM_DEPTH - 1 : 0]     addra,
    input   wire [DATA_WIDTH - 1 : 0]     dina,
    output  reg  [DATA_WIDTH - 1 : 0]     douta,
    //b
    input   wire            enb,
    input   wire            web,
    input   wire [RAM_DEPTH - 1 : 0]     addrb,
    input   wire [DATA_WIDTH - 1 : 0]     dinb,
    output  reg  [DATA_WIDTH - 1 : 0]     doutb   

    );

//--------------Internal variables---------------- 

reg [DATA_WIDTH-1:0] mem [0:RAM_DEPTH-1];

//initialization

// synopsys_translate_off
integer i;
initial begin
    for(i=0; i < RAM_DEPTH; i = i + 1) begin
        mem[i] = 8'h00;
    end
end
// synopsys_translate_on

//--------------Code Starts Here------------------ 
// Memory Write Block 
// Write Operation : When we_0 = 1, cs_0 = 1
always @ (*)
begin : MEM_WRITE
  if ( ena && wea ) begin
     mem[addra] = dina;
  end 
  else if  (enb && web) begin
     mem[addrb] = dinb;
  end
end

// Memory Read Block 
// Read Operation : When we_0 = 0, oe_0 = 1, cs_0 = 1
always @ (*)
begin : MEM_READ_a
  if (ena && ~wea) begin
    douta = mem[addra]; 
  end else begin
    douta = 0; 
  end
end 

//Second Port of RAM
always @ (*)
begin : MEM_READ_b
  if (enb && ~web) begin
    doutb = mem[addrb]; 
  end else begin
    doutb = 0; 
  end
end 

endmodule

综合后的原理图:
综合后原理图
表明可综合。

仿真就利用同步RAM的仿真,改下例化,时钟只是一个时间尺度,可以不拉出来:

`timescale 1ns / 1ps
//////////////////////////////////////////////////////////////////////////////////
// Engineer: 李锐博恩
// Create Date: 2021/01/31 02:46:06
// Module Name: dual_ram_tb
//////////////////////////////////////////////////////////////////////////////////

module dual_ram_tb(
    );
    parameter WIDTH = 16;
    parameter DEPTH = 4;
    parameter PERIOD_A = 4;
    parameter PERIOD_B = 5;
    reg                         clka;
    reg                         rst;
    reg                         ena;
    reg                         wea;
    reg     [DEPTH - 1 : 0]     addra;
    reg     [WIDTH - 1 : 0]     dina;
    wire    [WIDTH - 1 : 0]     douta;
    //b
    reg                         clkb;
    reg                         enb;
    reg                         web;
    reg     [DEPTH - 1 : 0]     addrb;
    reg     [WIDTH - 1 : 0]     dinb;
    wire    [WIDTH - 1 : 0]     doutb;

    initial begin
        clka = 0;
        forever begin
            # (PERIOD_A/2) clka = ~clka;
        end
    end     

    initial begin
        clkb = 0;
        forever begin
            # (PERIOD_B/2) clkb = ~clkb;
        end
    end

    initial begin
        rst = 1;
        ena = 0;
        enb = 0;
        wea = 0;
        web = 0;
        addra = 0;
        addrb = 0;
        dina = 0;
        dinb = 0; 

        repeat(15);
        @(posedge clka);
        rst = #(0.1 * PERIOD_A) 0;

        //a端口连续写两个数据
        repeat(10);
        @(posedge clka);
        addra = #(0.1 * PERIOD_A) 'd0;
        dina = #(0.1 * PERIOD_A) $random;        
        @(posedge clka);
        ena = #(0.1 * PERIOD_A) 1;
        wea = #(0.1 * PERIOD_A) 1;
        @(posedge clka);
        addra = #(0.1 * PERIOD_A) 'd1;
        dina = #(0.1 * PERIOD_A) $random;
        @(posedge clka);

        //b端口读两个数据

        repeat(10);
        @(posedge clkb);
        addrb = #(0.1 * PERIOD_B) 'd0;        
        @(posedge clkb);
        enb = #(0.1 * PERIOD_B) 1;
        web = #(0.1 * PERIOD_B) 0;

        @(posedge clkb);
        addrb = #(0.1 * PERIOD_B) 'd1;

    end

    asy_ram#(
        .DATA_WIDTH ( 16 ),
        .RAM_DEPTH  ( 4 )
    )u_asy_ram(
        .ena        ( ena        ),
        .wea        ( wea        ),
        .addra      ( addra      ),
        .dina       ( dina       ),
        .douta      ( douta      ),
        .enb        ( enb        ),
        .web        ( web        ),
        .addrb      ( addrb      ),
        .dinb       ( dinb       ),
        .doutb      ( doutb      )
    );

endmodule

仿真波形图:

异步仿真

ROM的RTL设计

ROM的设计就更简单了,不用考虑写,一次性写入,剩下的都是读的问题了。

给出RTL设计:

module Rom_RTL(
    input [7:0] address , // Address input
    output [7:0] data    , // Data output
    input read_en , // Read Enable 
    input ce        // Chip Enable
    );
    reg [7:0] mem [0:255] ;  

    assign data = (ce && read_en) ? mem[address] : 8'b0;

    initial begin
      $readmemb("F:/Prj_blog/vivado_csdn/prj_mem/prj_mem.srcs/sources_1/new/memory.list", mem); // memory_list is memory file
    end

endmodule

仿真平台:


`timescale 1ns / 1ps

module rom_using_file_tb;
 reg [7:0] address;
 reg read_en, ce;
 wire [7:0] data;
 integer i;

 initial begin
   address = 0;
   read_en = 0;
   ce      = 0;
   //#10 $monitor ("address = %h, data = %h, read_en = %b, ce = %b", address, data, read_en, ce);
   for (i = 0; i < 256; i = i + 1 )begin
     #5 
     address = i;
     read_en = 1;
     ce = 1;
     #5
     read_en = 0;
     ce = 0;
     address = 0;
   end
 end

Rom_RTL u_Rom_RTL(
    .address  ( address  ),
    .data     ( data     ),
    .read_en  ( read_en  ),
    .ce       ( ce       )
);

endmodule

仿真波形

由于,memory.list文件内容是0,1,2,3,...
list内容
因此,仿真内容也符合预期。

给出综合后的原理图:

综合后原理图

证明可综合!

最后,大家可能会有疑问?说ROM的设计中用到了一个系统函数:readmemb,这东西能综合?

其实,这还真是要取决于综合工具,我找出了一个解释:

Altera的“推荐的HDL编码样式”指南包括示例10-31(第10-38页),该示例演示了从中推断出的ROM $readmemb(如下所示):

module dual_port_rom (
   input [(addr_width-1):0] addr_a, addr_b,
   input clk, 
   output reg [(data_width-1):0] q_a, q_b
);
   parameter data_width = 8;
   parameter addr_width = 8;
   reg [data_width-1:0] rom[2**addr_width-1:0];
   initial // Read the memory contents in the file
           // dual_port_rom_init.txt. 
   begin
      $readmemb("dual_port_rom_init.txt", rom);
   end
   always @ (posedge clk)
   begin
      q_a <= rom[addr_a];
      q_b <= rom[addr_b];
   end
endmodule

同样,Xilinx的XST用户指南指出:

该readmemb和readmemh系统任务可以用来初始化块存储器。有关更多信息,请参见:

从外部文件初始化RAM的示例

使用readmemb二进制和readmemh十六进制表示。为了避免XST和模拟器行为之间可能的差异,Xilinx®建议您在这些系统任务中使用索引参数。请参见以下编码示例。

$readmemb("rams_20c.data",ram, 0, 7);

因此,对于存储器的初始化,这样做是没问题的。

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

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

举报反馈

举报类型

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

详细说明

审核成功

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

审核失败

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

小包子的红包

恭喜发财,大吉大利

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

    易百纳技术社区