双口 RAM IP 核简介
双口 RAM IP 核有两个端口,它又分为伪双端口 RAM 和真双端口 RAM,伪双端口 RAM 一个端口只能读,另一个端口只能
写,真双端口 RAM 两个端口都可以进行读写操作。同时对存储器进行读写操作时就会用到双端口 RAM,例如有一个 FIFO 存储器,需要同时对其进行数据的写入和读出,这时候就需要一个写端口和一个读端口。
伪双端口 RAM
BMG IP 核配置成简单双端口 RAM 的框图如下图所示:
它有两组独立的端口 A 和 B,其中 A 端口只能提供了 DINA 数据总线,只能进行写操作,B 端口只提供了 DOUTB 数据总线,只能进行读操作,此外它相当于单口 RAM 还多出了以下信号线(在常规使用中一般不会关注这些信号线):
- IINJECTSBITERR:Inject Single-Bit Error 的简写,即注入单 bit 错误,仅适用于 Xilinx Zynq-7000 和 7系列芯片的 ECC 配置。
- IINJECTDBITERR:Inject Double-Bit Error 的简写,即注入双 bit 错误,同样仅适用于 Xilinx Zynq-7000和 7 系列芯片的 ECC 配置。
- SBITERR:Single-Bit Error 的简写,即单 bit 错误,标记内存中存在的单 bit 错误,该错误已在输出总线上自动更正。
- DBITERR:Double-Bit Error 的简写,即双 bit 错误,标记内存中存在双 bit 错误,需要注意的是内置的ECC 解码模块不能自动纠正双 bit 错误。
- RDADDRECC:Read Address for ECC Error output 的简写,即读地址 ECC 错误输出,同样仅适用于Xilinx Zynq-7000 和 7 系列芯片的 ECC 配置。
真双口 RAM
BMG IP 核配置成真双端口RAM 的框图如下图所示:
它有两组独立的端口 A 和 B,且每组端口都可进行读写操作。
不同 RAM 端口对比
通过对比可以发现无论是哪种双端口 RAM,其地址线、时钟线、使能线都有两组,所以双端口 RAM 可以实现在不同时钟域下的读/写,且可以同时对不同的地址进行读/写,这便大大提高了我们数据处理的灵活性。
读写冲突和写写冲突
写-写冲突:当两个端口都试图向同一个地址写数据(即两个端口写使能同时有效且写地址相同)时则会产生写-写冲突,发生写-写冲突后对应内存地址的数据为未知。
读-写冲突:一个端口进行读取数据操作,另一个端口进行写入数据操作,当同时操作到同一地址时就会发生读-写冲突,此时如果:
- 写端口为 READ_FIRST 模式,则读端口读出的数据为写入操作前存储在RAM中的数据。
- 写端口为 WRITE_FIRST 或 NO_CHANGE 模式,则读端口读出的数据为无效。
双口 RAM 配置核生成
打开 BMG IP 核的“Customize IP”窗口
- Vivado 工程左侧“Flow Navigator”栏中的“IP Catalog”
- 在“IP Catalog”窗口的搜索栏中输入“Block Memory”关键字后,出现唯一匹配的“Block Memory Generator”
- 双击“Block Memory Generator”后弹出 IP 核的配置界面
BMG IP 核有关伪双口 RAM 的配置
Block Memory Generator IP核的 IP Catalog 窗口如下,主要包括一个IP名称输入框核4个选项卡,页面依次进行介绍。
- 在“Component Name”一栏可以设置该 IP 元件的名称
- “Basic”选项卡
(1)“lnterface Type(接口模式)”:有两种接口模式可选,分别为 Native(常规)接口和 AXI4 接口。AXI4 模式一般是在处理器中的数据需要和 BRAM 交互时才会使用,当不需要与处理器数据进行交互时一般采用 Native 模式。
(2) “Generate address interface with 32 bits”选项为是否生成位宽为 32 的地址接口,勾选后内存的数据位宽也必须设置成 32 的整数倍,一般不做勾选。
(3) “Memory Type(存储类型)”:有五种类型可选,分别为“Single Port RAM(单端口RAM)”、“Simple Dual Port RAM(伪双端口 RAM)”、“True Dual Port RAM(真双端口 RAM)”、“Single Port ROM(单端口 ROM)”和“Dual Port ROM(双端口 ROM)”,这里以单端口 RAM 为例。
(4) “Common Clock”选项:是否启用同步时钟,Port A 和 Port B 在同一个时钟域时可以勾选也可以不勾选,在不同时钟域时不能勾选,否则跨时钟域会出问题。
(5) “ECC Options(ECC 选项)”:ECC纠错码选项只有在伪双端口 RAM 类型下才可以进行配置。
(6) “Write Enable(写使能)”:可以选择是否使用字节写使能功能,启用后可设置字节大小为 8 位或 9 位,需要注意的是启用后内存的数据位宽必须设置为所选字节大小的整数倍。
(7) “Algorithm Options(算法选项)”:算法选项主要用于决定 BRAM 的拼接的方式,一般在BRAM 深度、宽度较大的时候起作用,有三种算法可选,分别为“Minimum Area(最小面积算法)”、“Low Power(低功耗算法)”和“Fixed Primitives(固定单元算法)” - “Port A Options选项卡,Port A实现数据写入操作。
(1)“Memory Size(内存大小)”:用于指定端口宽度和深度、运行模式和使能端口类型。
- Port A Width;数据位宽,单位 bit,本次实验我们设置成 8,可设置的位宽范围为 1~4608。
- Port A Depth;深度,即 RAM 所能访问的地址范围。这里有一点需要注意,那就是写深度和写位宽的乘积不要超过器件本身的 ram 资源的大小。
- Operating Mode;RAM 运作模式,有三种模式可选,分别是Write First(写优先模式)、 Read First(读优先模式)、No Change(不变模式),单口 RAM 一般选 No Change(不变模式)。
- Enable Port Type;使能端口类型。有两种可选,分别为 Use ENA pin(添加使能端口 A 的信号)和 Always Enabled(取消使能信号,端口 A 一直处于使能状态)。
(2)“Port A Optional Output Register”:用于为 RAM 的输出端添加寄存器,Port A 不支持。
(3)“Port A Output Reset Options”:用于配置端口的复位信号。,Port A 不支持
(4)“READ Address Change A”:用于更改端口的读地址,这个功能只在 UltraScale 设备上使用。
- “Port B Options”选项卡,Port B 只支持读操作。
(1)“Memory Size(内存大小)”:用于指定端口宽度和深度、运行模式和使能端口类型。
- Port B Width;数据位宽,其位宽设置必须与 Port A 数据位宽存在倍数关系,通常情况下保持一致。
- Port B Depth;深度,当 Port B 数据位宽、Port A 数据位宽和深度的值确定后,Port B 深度的值就会自动确定。
- Operating Mode;RAM 运作模式,Port B 不可配置。
- Enable Port Type;使能端口类型。有两种可选,分别为 Use ENA pin(添加使能端口 A 的信号)和 Always Enabled(取消使能信号,端口 B 一直处于使能状态)。
(2)“Port B Optional Output Register”:用于为 RAM 的输出端添加寄存器。其作用是提高 BRAM 的运行频率和改善时序,当然为此付出的代价就是每勾选一个寄存器,输出就会延迟一拍。 - Primitives Output Register 使用 BRAM 内部的寄存器打拍输出。
- Core Output Register 使用 SLICE 的寄存器打拍输出。
- SoftECC Input Register 当使用软 ECC 的时候,用 SLICE 的寄存器打拍。
- REGCEA,当使用 Primitives Output Register 或者 Core Output Register 时,可以用 REGCEA 来使能相应的输出。
(3)“Port B Output Reset Options”:用于配置端口的复位信号。可以添加一个复位信号(RSTA Pin)、配置复位时 RAM 输出总线上的数据值(Output Reset Value)、配置是否复位内置锁存器(Reset Memory Latch)、配置复位信号与时钟使能之间的优先级(Reset Priority)。
(4)“READ Address Change B”:用于更改端口的读地址,这个功能只在 UltraScale 设备上使用。
- “Other Options”选项卡
(1)“Pipeline Stages within Mux”:输出端 Mux 选择器的流水线级数。在大位宽、大深度的 BRAM 拼接场景中会用 MUX 来选择输出地址所对应的数据,勾选该选项后可以使输出数据具有更好的时序。
(2)“Memory Initialization(初始化文件)”:选择是否使用本地初始化文件(.coe 文件)来对存储空间进行初始化。
(3)“Structural/UniSim Simulation Model Options”:用于选择结构仿真模型发生碰撞时生成的警告消息和输出的类型。
(4)“Behavioral Simulation Model Options”:用于关闭仿真时的冲突告警和超出范围告警。 - “Summary”选项卡
该界面显示了配置的存储器的类型,消耗的 BRAM 资源等信息,检查没问题可以直接点击“OK”按钮生成 RAM IP 核。
在弹出的“Generate Output Products”窗口直接点击“Generate”即可。
模块设计
本次实验的用 Xilinx BMG IP 核配置成一个伪装双口 RAM 并对其进行读写操作,系统框图如下:
编写代码
大多数情况下伪双口 RAM 都已经满足使用需求,入跨时钟域传输数据、存储数据、FIFO等,所以这里以伪双口 RAM 为例。
生成伪双口 RAM IP 核
伪双口 RAM IP 核的配置如下:
伪双口 RAM IP读写代码编写
在伪双口 RAM IP读写代码先例化一个伪双口 RAM IP核,在复位完成后开始通过 Port A 周期写入数据,当第一次写入到一半时启动 Port B 进行周期读数据,波形图如下:
module ram_ip_rw(
input sys_clk,
input sys_rst_n
);
//端口 A 时钟
wire clka;
//端口 A 使能,高电平使能
reg ena;
//端口 A 写使能,高电平写入
reg wea;
//端口 A 地址
reg [5:0]addra;
//写入端口 A 的数据
wire [7:0]dina;
//端口 B 时钟
wire clkb;
//端口 B 使能,高电平使能
reg enb;
//端口 B 地址
reg [5:0]addrb;
//端口 B 读出的数据
wire [7:0]doutb;
//端口 A 时钟
assign clka = sys_clk;
//端口 A 使能
always @(posedge sys_clk) begin
if(!sys_rst_n)
ena <= 0;
else
ena <= 1;
end
//端口 A 写使能
always @(posedge sys_clk) begin
if(!sys_rst_n)
wea <= 0;
else
wea <= 1;
end
//端口 A 地址,0~63
always @(posedge sys_clk) begin
if(!sys_rst_n)
addra <= 0;
else if((addra < 63) && (wea == 1))
addra <= addra + 1;
else
addra <= 0;
end
//端口 A 写入数据,从地址0~63依次写入如0~63
assign dina = (wea == 1) ? addra : 0;
//端口 B 时钟
assign clkb = sys_clk;
//端口 B 使能
always @(posedge sys_clk) begin
if(!sys_rst_n)
enb <= 0;
else if(addra == 31)
enb <= 1;
end
//端口 B 地址,0~63
always @(posedge sys_clk) begin
if(!sys_rst_n)
addrb <= 0;
else if((addrb < 63) && (enb == 1))
addrb <= addrb + 1;
else
addrb <= 0;
end
//例化伪双口RAM IP核
blk_mem_gen_0 u_blk_mem_gen_0_inst0(
.clka(clka),
.ena(ena),
.wea(wea),
.addra(addra),
.dina(dina),
.clkb(clkb),
.enb(enb),
.addrb(addrb),
.doutb(doutb)
);
endmodule
仿真激励代码编写
仿真激励代码非常简单,只需要例化 单口 RAM IP读写模块 ,然后产生一个周期时钟即可,完整的代码如下:
module tb_ram_ip_rw(
);
reg sys_clk; //系统时钟
reg sys_rst_n; //系统复位,低电平有效
//信号初始化
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#200
sys_rst_n = 1'b1;
end
//产生时钟
always #20 sys_clk = ~sys_clk;
//例化需要仿真的IP核
ram_ip_rw tb_u_ram_ip_rw_inst0(
.sys_clk(sys_clk), //系统时钟
.sys_rst_n(sys_rst_n) //系统复位,低电平有效
);
endmodule