Vivado综合可以理解多种多样的RAM编写方式,将其映射到分布式RAM或块RAM中。两种实现方法在向RAM写入数据时都是采取同步方式,区别在于从RAM读取数据时,分布式RAM采用异步方式,块RAM采用同步方式。使用RAM_STYLE属性可以强制规定使用哪种方法实现RAM。
Xilinx FPGA的内存接口具有如下特性:
另外目前最新的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种不同的读写同步模式,解决同时读写同一地址的情况,每一个读、写端口都可以配置为:
下面给出三种模式的单端块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