前言
XILINX UltraRAM的简单介绍,详情见官方文档: ug573-ultrascale-memory-resources.
一、UltraRAM的特点
1、位宽72b,深度4Kb
每块位宽和深度是固定的,不像Block RAM那样可以配置。
但是存储空间更大,每块是288Kb。
2、双端口,单时钟
两个端口使用同一个时钟,不能用于跨时钟域处理。
每个端口可以在每个时钟周期内独立执行一个读操作或一个写操作,但其内部使用的是单端口存储单元。双端口操作是通过在一个单周期内先执行端口A操作,然后执行端口B操作来实现的。
3、在上电和复位期间内存初始化为全0
用户不能自定义初始值
二、UltraRAM的应用场景
图片来自官方介绍:在 UltraScale+ 器件中使用 UltraRAM
三、与Block RAM的比较
四、简单的测试
如何使用UltraRAM:
此处使用第二种方式:XPM
首先在Language Templates中找到XPM_MEMORY
这里选择常用的SDP RAM测试,选中,ctrl+c,ctrl+v。
写一个简单的生成地址和测试数据的module
`timescale 1ns / 1ps
module ram_ctrl
#(
parameter DATA_WIDTH = 72 ,
parameter DATA_DEPTH = 4096
)
(
output reg [DATA_WIDTH - 1 : 0] dina ,
output reg [$clog2(DATA_DEPTH) - 1 : 0] addra ,
output reg [$clog2(DATA_DEPTH) - 1 : 0] addrb ,
output wea ,
input rst_n ,
input clk
);
assign wea = 1'b1;
//输入地址
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
addra <= 'd0;
end
else begin
addra <= addra + 'd1;
end
end
//输入数据
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
dina <= 'd0;
end
else begin
dina <= dina +'b1;
end
end
//输出地址
always @(posedge clk or negedge rst_n) begin
if(!rst_n)begin
addrb <= 'd0;
end
else begin
addrb <= addra;
end
end
endmodule
顶层:
`timescale 1ns / 1ps
module sdp_ram_top
#(
parameter DATA_WIDTH = 72 ,
parameter DATA_DEPTH = 4096
)
(
output [DATA_WIDTH - 1 : 0] doutb ,
input rst_n ,
input clk
);
parameter ADDR_WIDTH = $clog2(DATA_DEPTH);
wire [DATA_WIDTH - 1 : 0] dina ;
wire [ADDR_WIDTH - 1 : 0] addra ;
wire [ADDR_WIDTH - 1 : 0] addrb ;
wire wea ;
wire clka, clkb;
assign clka = clk;
assign clkb = clk;
ram_ctrl #(
.DATA_WIDTH ( DATA_WIDTH ),
.DATA_DEPTH ( DATA_DEPTH )
)
u_ram_ctrl (
.rst_n ( rst_n ),
.clk ( clk ),
.dina ( dina ),
.addra ( addra ),
.addrb ( addrb ),
.wea ( wea )
);
xpm_memory_sdpram #(
.ADDR_WIDTH_A(ADDR_WIDTH), // DECIMAL
.ADDR_WIDTH_B(ADDR_WIDTH), // DECIMAL
.AUTO_SLEEP_TIME(0), // DECIMAL
.BYTE_WRITE_WIDTH_A(DATA_WIDTH), // DECIMAL
.CASCADE_HEIGHT(0), // DECIMAL
.CLOCKING_MODE("common_clock"), // String
.ECC_MODE("no_ecc"), // String
.MEMORY_INIT_FILE("none"), // String
.MEMORY_INIT_PARAM("0"), // String
.MEMORY_OPTIMIZATION("true"), // String
.MEMORY_PRIMITIVE("ultra"), // String
.MEMORY_SIZE(294912), // DECIMAL
.MESSAGE_CONTROL(0), // DECIMAL
.READ_DATA_WIDTH_B(DATA_WIDTH), // DECIMAL
.READ_LATENCY_B(2), // DECIMAL
.READ_RESET_VALUE_B("0"), // String
.RST_MODE_A("SYNC"), // String
.RST_MODE_B("SYNC"), // String
.SIM_ASSERT_CHK(0), // DECIMAL; 0=disable simulation messages, 1=enable simulation messages
.USE_EMBEDDED_CONSTRAINT(0), // DECIMAL
.USE_MEM_INIT(1), // DECIMAL
.WAKEUP_TIME("disable_sleep"), // String
.WRITE_DATA_WIDTH_A(DATA_WIDTH), // DECIMAL
.WRITE_MODE_B("read_first") // String
)
xpm_memory_sdpram_inst (
.dbiterrb( ), // 1-bit output: Status signal to indicate double bit error occurrence
// on the data output of port B.
.doutb(doutb), // READ_DATA_WIDTH_B-bit output: Data output for port B read operations.
.sbiterrb( ), // 1-bit output: Status signal to indicate single bit error occurrence
// on the data output of port B.
.addra(addra), // ADDR_WIDTH_A-bit input: Address for port A write operations.
.addrb(addrb), // ADDR_WIDTH_B-bit input: Address for port B read operations.
.clka(clka), // 1-bit input: Clock signal for port A. Also clocks port B when
// parameter CLOCKING_MODE is "common_clock".
.clkb(clkb), // 1-bit input: Clock signal for port B when parameter CLOCKING_MODE is
// "independent_clock". Unused when parameter CLOCKING_MODE is
// "common_clock".
.dina(dina), // WRITE_DATA_WIDTH_A-bit input: Data input for port A write operations.
.ena(1'b1), // 1-bit input: Memory enable signal for port A. Must be high on clock
// cycles when write operations are initiated. Pipelined internally.
.enb(1'b1), // 1-bit input: Memory enable signal for port B. Must be high on clock
// cycles when read operations are initiated. Pipelined internally.
.injectdbiterra(1'b0), // 1-bit input: Controls double bit error injection on input data when
// ECC enabled (Error injection capability is not available in
// "decode_only" mode).
.injectsbiterra(1'b0), // 1-bit input: Controls single bit error injection on input data when
// ECC enabled (Error injection capability is not available in
// "decode_only" mode).
.regceb(1'b1), // 1-bit input: Clock Enable for the last register stage on the output
// data path.
.rstb(1'b0), // 1-bit input: Reset signal for the final port B output register stage.
// Synchronously resets output port doutb to the value specified by
// parameter READ_RESET_VALUE_B.
.sleep(1'b0), // 1-bit input: sleep signal to enable the dynamic power saving feature.
.wea(wea) // WRITE_DATA_WIDTH_A/BYTE_WRITE_WIDTH_A-bit input: Write enable vector
// for port A input data port dina. 1 bit wide when word-wide writes are
// used. In byte-wide write configurations, each bit controls the
// writing one byte of dina to address addra. For example, to
// synchronously write only bits [15-8] of dina when WRITE_DATA_WIDTH_A
// is 32, wea would be 4'b0010.
);
endmodule
testbench:
`timescale 1ns / 1ps
module tb_sdp_ram_top;
// sdp_ram_top Parameters
parameter PERIOD = 10 ;
parameter DATA_WIDTH = 72 ;
parameter DATA_DEPTH = 4096 ;
// sdp_ram_top Inputs
reg rst_n = 0 ;
reg clk = 0 ;
// sdp_ram_top Outputs
wire [DATA_WIDTH - 1 : 0] doutb ;
initial
begin
forever #(PERIOD/2) clk=~clk;
end
initial
begin
#(PERIOD*2) rst_n = 1;
end
sdp_ram_top #(
.DATA_WIDTH ( DATA_WIDTH ),
.DATA_DEPTH ( DATA_DEPTH )
)
u_sdp_ram_top (
.rst_n ( rst_n ),
.clk ( clk ),
.doutb ( doutb )
);
endmodule
仿真结果:
可以看到数据输出延迟了2个clk,对应例化RAM时的
.READ_LATENCY_B(2),
使用URAM或者BRAM时这个值至少为1,代表输出锁存,为2及以上时代表使用了输出寄存器。
使用DRAM时这个值可以为0,因为DRAM底层为组合逻辑。
综合后可以看到占用的资源:
4K*72的内存仅用了一个URAM。
将
.MEMORY_PRIMITIVE("ultra"),
改为
.MEMORY_PRIMITIVE("block"),
即使用Block RAM实现,综合后:
使用了8个BRAM(72x512x8)。
ila调试:
和仿真结果是一样的。
最后尝试一下给URAM设置初始值
果然不行。