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

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/FPGADesigner/article/details/82117562

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

猜你喜欢

转载自blog.csdn.net/FPGADesigner/article/details/82117562
今日推荐