Vivado使用技巧(27):RAM编写技巧

Vivado综合可以理解多种多样的RAM编写方式,将其映射到分布式RAM或块RAM中。两种实现方法在向RAM写入数据时都是采取同步方式,区别在于从RAM读取数据时,分布式RAM采用异步方式,块RAM采用同步方式。使用RAM_STYLE属性可以强制规定使用哪种方法实现RAM。

Xilinx FPGA的内存接口具有如下特性:

  • 支持任意大小的深度和数据宽度(综合时会使用一个或多个RAM原语实现);
  • 单端、伪双端、真双端三种模式;
  • 最多可以使用两个写端口;
  • 可以存在多个读端口;
  • 支持写使能信号
  • 块RAM支持RAM使能、数据输出复位、可选的输出寄存器和字节写使能;
  • 每个RAM端口可以由独立的时钟、端口使能、写使能和数据输出复位控制;
  • 按设定的值初始化RAM内容;
  • Vivado综合可以将校验位作为常规数据位,以适应描述的数据宽度。
  • 另外目前最新的UltraScale架构的FPGA中还有专用的UltraRAM资源,这里不做过多介绍。下面给出几个各种实现方式的Verilog示例代码。

    分布式RAM
    下面给出一个异步读模式的双口分布式RAM的示例:
    // 异步读模式的双口分布式RAM
    module rams_dist (
    input clk, we,
    input [5:0]a, dpra,
    input [15:0]di,
    output [15:0]spo, dpo
    );

    reg [15:0] ram [63:0];

    always @(posedge clk)
    if (we) ram[a] <= di;
    assign spo = ram[a];
    assign dpo = ram[dpra];
    endmodule

    单端块RAM
    块RAM支持3种不同的读写同步模式,解决同时读写同一地址的情况,每一个读、写端口都可以配置为:

  • Write-First模式:新内容载入时可以马上被读取;
  • Read-First模式:新内容载入时,先读取旧的内容;
  • No-Change模式:新内容载入时,不读取该地址的内容(即维持之前的值不变);
  • 下面给出三种模式的单端块RAM Verilog示例代码:
    // 数据输出可复位的单端块RAM,Read_first
    module rams_sp_rf_rst (
    input clk, en, we, rst,
    input [9:0]addr,
    input [15:0]di,
    output reg [15:0]dout
    );

    reg [15:0] ram [1023:0];

    always @(posedge clk)
    if (en) begin //块RAM使能
    if (we) ram[addr] <= di; //写使能
    if (rst) dout <= 0; //输出复位
    else dout <= ram[addr];
    end

    endmodule

    // 写优先模式的单端块RAM,Wrist_first
    module rams_sp_wf (
    input clk, en, we,
    input [9:0]addr,
    input [15:0]di,
    output reg [15:0]dout
    );

    reg [15:0] ram [1023:0];

    always @(posedge clk)
    if (en) begin
    if (we) begin
    RAM[addr] <= di;
    dout <= di;
    end
    else dout <= RAM[addr];
    end

    endmodule

    // No-Change模式的单端块RAM
    module rams_sp_wf (
    input clk, en, we,
    input [9:0]addr,
    input [15:0]di,
    output reg [15:0]dout
    );

    reg [15:0] ram [1023:0];

    always @(posedge clk)
    if (en) begin
    if (we) RAM[addr] <= di;
    else dout <= RAM[addr];
    end

    endmodule

    伪双端块RAM
    下面给出伪双端块RAM的Verilog 示例代码:
    // 单时钟控制,伪双端块RAM
    module simple_dual_one_clock (
    input clk,ena,enb,wea,
    input [9:0]addra,addrb,
    input [15:0]dia,
    output reg [15:0] dob
    );

    reg [15:0] ram [1023:0];

    always @(posedge clk) //写
    if (ena)
    if (wea) ram[addra] <= dia;

    always @(posedge clk)
    if (enb) dob <= ram[addrb]; //读

    endmodule

    // 双时钟控制,伪双端块RAM
    module simple_dual_two_clocks (
    input clka,clkb,ena,enb,wea,
    input [9:0] addra,addrb,
    input [15:0]dia,
    output reg [15:0]dob
    );

    reg [15:0] ram [1023:0];

    always @(posedge clka) //写
    if (ena)
    if (wea) ram[addra] <= dia;

    always @(posedge clkb)
    if (enb) dob <= ram[addrb]; //读

    endmodule

    真双端块RAM
    下面给出两个真双端块RAM的Verilog 示例代码:
    // 带有两个写端口的双端块RAM
    module rams_tdp_rf_rf (
    input clka,clkb,ena,enb,wea,web,
    input [9:0] addra,addrb,
    input [15:0]dia,dib,
    output reg [15:0] doa,dob
    );

    reg [15:0] ram [1023:0];

    always @(posedge clka) //端口1
    if (ena) begin
    if (wea) ram[addra] <= dia;
    doa <= ram[addra];
    end

    always @(posedge clkb) //端口2
    if (enb) begin
    if (web) ram[addrb] <= dib;
    dob <= ram[addrb];
    end

    endmodule

    // 带有可选输出寄存器的块RAM
    module rams_pipeline (
    input clk1, clk2, we, en1, en2,
    input [9:0] addr1, addr2,
    input [15:0] di,
    output reg [15:0] res1, res2
    );

    reg [15:0] RAM [1023:0];
    reg [15:0] do1;
    reg [15:0] do2;

    always @(posedge clk1) begin //端口1可读可写
    if (we == 1'b1) RAM[addr1] <= di;
    do1 <= RAM[addr1];
    end

    always @(posedge clk2) //端口2只用于读
    do2 <= RAM[addr2];

    always @(posedge clk1)
    if (en1 == 1'b1) res1 <= do1;

    always @(posedge clk2)
    if (en2 == 1'b1) res2 <= do2;

    endmodule

    初始化RAM内容
    除了上面介绍的一些基本模式外,Xilinx的RAM还包括字节使能模式、非对称RAM(即读写数据的位宽不同)、3D RAM,等后面用到时再做补充。下面介绍一下如何初始化RAM的内容。初始化工作可以在HDL源代码中进行,也可以利用外部数据文件设置。

    比如Verilog中可以使用下面代码将RAM全部初始化为一个值:
    reg [DATA_WIDTH-1:0] ram [DEPTH-1:0];
    integer i;
    initial for (i=0; i < DEPTH; i=i+1) ram[i] = 0;

    或者使用Verilog中的文件读取函数从外部数据文件中获取RAM初始化数据。数据文件必须是ASCII文本文件,每一行表示RAM中的一个地址,因此文件的行数要与RAM的深度对应。文件中数据应用2进制或16进制表示,不能混合使用。除了数据外不能有其它任何内容(包括注释)。一个文件示例如下:
    00001110110000011001111011000110
    00101011001011010101001000100011
    01110100010100011000011100001111
    01000001010000100101001110010100

    对应一个4*32bit的RAM初始化内容。通常将这种格式称之为bit向量格式,与整数格式或hex格式作区别。Verilog代码中使用如下示例调用该文件:
    reg [31:0] ram [0:3];

    initial begin
    $readmemb("ram.data", ram, 0, 3);
    end

    如果文件用16进制定义数据则应使用$readmemh系统任务。下面给出一个直接在HDL中赋值初始化块RAM的完整Verilog示例:
    module rams_sp_rom (
    input clk, we,
    input [5:0]addr,
    input [19:0]di,
    output reg [19:0] dout
    );

    reg [19:0] ram [63:0];

    initial begin
    ram[63] = 20'h0200A; ram[62] = 20'h00300; ram[61] = 20'h08101;
    ram[60] = 20'h04000; ram[59] = 20'h08601; ram[58] = 20'h0233A;
    ram[57] = 20'h00300; ram[56] = 20'h08602; ram[55] = 20'h02310;
    ram[54] = 20'h0203B; ram[53] = 20'h08300; ram[52] = 20'h04002;
    ram[51] = 20'h08201; ram[50] = 20'h00500; ram[49] = 20'h04001;
    ram[48] = 20'h02500; ram[47] = 20'h00340; ram[46] = 20'h00241;
    ram[45] = 20'h04002; ram[44] = 20'h08300; ram[43] = 20'h08201;
    ram[42] = 20'h00500; ram[41] = 20'h08101; ram[40] = 20'h00602;
    ram[39] = 20'h04003; ram[38] = 20'h0241E; ram[37] = 20'h00301;
    ram[36] = 20'h00102; ram[35] = 20'h02122; ram[34] = 20'h02021;
    ram[33] = 20'h00301; ram[32] = 20'h00102; ram[31] = 20'h02222;
    ram[30] = 20'h04001; ram[29] = 20'h00342; ram[28] = 20'h0232B;
    ram[27] = 20'h00900; ram[26] = 20'h00302; ram[25] = 20'h00102;
    ram[24] = 20'h04002; ram[23] = 20'h00900; ram[22] = 20'h08201;
    ram[21] = 20'h02023; ram[20] = 20'h00303; ram[19] = 20'h02433;
    ram[18] = 20'h00301; ram[17] = 20'h04004; ram[16] = 20'h00301;
    ram[15] = 20'h00102; ram[14] = 20'h02137; ram[13] = 20'h02036;
    ram[12] = 20'h00301; ram[11] = 20'h00102; ram[10] = 20'h02237;
    ram[9] = 20'h04004; ram[8] = 20'h00304; ram[7] = 20'h04040;
    ram[6] = 20'h02500; ram[5] = 20'h02500; ram[4] = 20'h02500;
    ram[3] = 20'h0030D; ram[2] = 20'h02341; ram[1] = 20'h08201;
    ram[0] = 20'h0400D;
    end

    always @(posedge clk) begin
    if (we)
    ram[addr] <= di;
    dout <= ram[addr];
    end

    endmodule

    再给出一个用外部文件初始化块RAM的完整Verilog示例:
    module rams_init_file (
    input clk, we,
    input [5:0]addr,
    input [31:0]din,
    output reg [31:0]dout
    );

    reg [31:0] ram [0:63];

    initial begin
    $readmemb("rams_init_file.data",ram);
    end

    always @(posedge clk) begin
    if (we)
    ram[addr] <= din;
    dout <= ram[addr];
    end

    endmodule


    文章来源:FPGADesigner的博客
    *本文由作者授权转发,如需转载请联系作者本人

    最新文章

    最新文章