You're reading an pre-release version of this documentation.
For the latest stable release version, please have a look at master.

RAM/ROM存储器

要在 SpinalHDL 中创建内存,应使用 Mem 类。它允许您定义内存并向其添加读写端口。

下表显示了如何实例化存储器:

语法

描述

Mem(type : Data, size : Int)

创建随机访问存储器

Mem(type : Data, initialContent : Array[Data])

创建一个 ROM。如果您的目标是 FPGA,因为存储器可以推断为块 RAM,您仍然可以在其上创建写入端口。

备注

如果你想定义一个 ROM,initialContent 数组的元素应该只是字面量(无法做运算,无法改变位宽)。这里有一个例子 here

备注

要给 RAM 初始值,您还可以使用 init 函数。

备注

掩码位宽是可以灵活设定的,您可以根据掩码的宽度将存储器分成位宽相同的多个片段。例如,如果您有一个32位内存字,并提供一个4位掩码,那么它将是一个字节掩码。如果您提供的掩码位数与存储器一个字的位数相同,那么它将是一个位掩码。

备注

在仿真时可以对 Mem 进行操作,请参阅 仿真中加载和存储存储器 部分。

下表显示了如何在存储器上添加访问端口:

语法

描述

返回类型

mem(address) := data

同步写入

mem(x)

异步读取

T

mem.write(
address
data
[enable]
[mask]
)
使用可选掩码进行同步写入。
如果未指定使能(enable)条件,则会自动从调用此函数的条件范围(如when语句等)中推断出条件
mem.readAsync(
address
[readUnderWrite]
)

异步读取,具有可选的写入时读取(read-under-write)策略

T

mem.readSync(
address
[enable]
[readUnderWrite]
[clockCrossing]
)

同步读取,具有可选的使能信号、写入间读取策略、跨时钟域(clockCrossing)模式。

T

mem.readWriteSync(
address
data
enable
write
[mask]
[readUnderWrite]
[clockCrossing]
)
推断读/写端口。
enable && write 满足时写入 data
返回读取的数据,当 enable 为true时读取

T

备注

如果由于某种原因您需要一个未在 Spinal 中实现的特定存储器端口,您始终可以通过为其指定 BlackBox 来抽象您的存储器。

重要

SpinalHDL 中的存储器端口不是推断的,而是明确定义的。您不应使用 VHDL/Verilog 等编码模板来帮助综合工具推断存储器。

下面是一个推断简单双端口 RAM(32 位 * 256)的示例:

val mem = Mem(Bits(32 bits), wordCount = 256)
mem.write(
  enable  = io.writeValid,
  address = io.writeAddress,
  data    = io.writeData
)

io.readData := mem.readSync(
  enable  = io.readValid,
  address = io.readAddress
)

同步使能注意事项

当使能信号用于由 when 等条件块保护的块中时,只会生成用使能信号作为访问条件的电路,也就是说 when 条件将被忽略。

val rom = Mem(Bits(10 bits), 32)
when(cond) {
  io.rdata := rom.readSync(io.addr, io.rdEna)
}

上面的例子中条件 cond 就不详细说明了。最好直接在使能信号中包含条件 cond,如下所示。

io.rdata := rom.readSync(io.addr, io.rdEna & cond)

写入时读取策略

此策略指定在同一周期内对同一地址发生写入时,读取的值将受到怎样的影响。

种类

描述

dontCare

发生这种情况时不用关心读取的值

readFirst

读取操作将得到写入之前的值

writeFirst

读取操作将得到由写入提供的值

重要

生成的 VHDL/Verilog 始终处于 readFirst 模式,该模式与 dontCare 兼容,但与 writeFirst 不兼容。要生成包含此类功能的设计,您需要使能 自动存储器黑盒

混合位宽存储器

您可以使用以下函数指定访问存储器的端口,其位宽为二的幂次:

语法

描述

mem.writeMixedWidth(
address
data
[readUnderWrite]
)

类似于 mem.write

mem.readAsyncMixedWidth(
address
data
[readUnderWrite]
)

类似于 mem.readAsync, 会立即返回值,它驱动以 data 参数的形式传入的信号/对象

mem.readSyncMixedWidth(
address
data
[enable]
[readUnderWrite]
[clockCrossing]
)

mem.readSync 类似,但它不是返回读取值,而是驱动 data 参数给出的信号/对象

mem.readWriteSyncMixedWidth(
address
data
enable
write
[mask]
[readUnderWrite]
[clockCrossing]
)

相当于 mem.readWriteSync

重要

至于写入时读取策略,要使用此功能,您需要启用 自动内存黑盒,因为没有通用的 VHDL/Verilog 语言模板来推断混合位宽存储器。

自动黑盒化

由于使用常规 VHDL/Verilog 不可能推断所有 ram 类型,因此 SpinalHDL 集成了可选的自动黑盒系统。该系统会查看 RTL 网表中存在的所有存储器,并用一个黑盒替换它们。然后生成的代码将依赖第三方IP来提供内存功能,例如写入时读取策略和混合位宽端口。

这是一个如何缺省使能黑盒化存储器的例子:

def main(args: Array[String]) {
  SpinalConfig()
    .addStandardMemBlackboxing(blackboxAll)
    .generateVhdl(new TopLevel)
}

如果标准黑盒工具不足以满足您的设计需求,请毫不犹豫地创建 Github工单。还有一种方法,可以创建您自己的黑盒工具。

黑盒策略

您可以使用多种策略来选择要黑盒的内存以及黑盒不可行时要执行的操作:

种类

描述

blackboxAll

黑盒化所有存储器。
Throw an error on unblackboxable memory.

blackboxAllWhatsYouCan

Blackbox every memory that is replaceable.

blackboxRequestedAndUninferable

用户指定的黑盒存储器和已知不可推断的存储器(混合位宽,…)。
Throw an error on unblackboxable memory.

blackboxOnlyIfRequested

Blackbox memory specified by the user.
Throw an error on unblackboxable memory.

blackboxByteEnables

Blackbox every memory which use write port with byte mask.
Useful because synthesis tool don’t support an unified way to infer byte mask in verilog/VHDL.
Throw an error on unblackboxable memory.

blackboxOnlyIfRequested

Blackbox memory specified by the user.
Throw an error on unblackboxable memory.

要显式地将存储器设置为黑盒,您可以使用其 generateAsBlackBox 函数。

val mem = Mem(Rgb(rgbConfig), 1 << 16)
mem.generateAsBlackBox()

你可以通过继承 MemBlackboxingPolicy 类定义你自己的黑盒化策略。

标准存储器黑盒

下面显示的是 SpinalHDL 中使用的标准黑盒的 VHDL 定义:

-- Simple asynchronous dual port (1 write port, 1 read port)
component Ram_1w_1ra is
  generic(
    wordCount : integer;
    wordWidth : integer;
    technology : string;
    readUnderWrite : string;
    wrAddressWidth : integer;
    wrDataWidth : integer;
    wrMaskWidth : integer;
    wrMaskEnable : boolean;
    rdAddressWidth : integer;
    rdDataWidth : integer
  );
  port(
    clk : in std_logic;
    wr_en : in std_logic;
    wr_mask : in std_logic_vector;
    wr_addr : in unsigned;
    wr_data : in std_logic_vector;
    rd_addr : in unsigned;
    rd_data : out std_logic_vector
  );
end component;

-- Simple synchronous dual port (1 write port, 1 read port)
component Ram_1w_1rs is
  generic(
    wordCount : integer;
    wordWidth : integer;
    clockCrossing : boolean;
    technology : string;
    readUnderWrite : string;
    wrAddressWidth : integer;
    wrDataWidth : integer;
    wrMaskWidth : integer;
    wrMaskEnable : boolean;
    rdAddressWidth : integer;
    rdDataWidth : integer;
    rdLatency : integer -- Cycles between the rd_en and the actual value on rd_data ports. It will be set to 1, unless the you added the MemReadBufferPhase to the SpinalConfig and the ram can merge a register on the read data path.
  );
  port(
    wr_clk : in std_logic;
    wr_en : in std_logic;
    wr_mask : in std_logic_vector;
    wr_addr : in unsigned;
    wr_data : in std_logic_vector;
    rd_clk : in std_logic;
    rd_en : in std_logic;
    rd_addr : in unsigned;
    rd_dataEn : in std_logic; -- Only used if rdLatency > 1, drive the enable of rd_data flip flops
    rd_data : out std_logic_vector
  );
end component;

-- Single port (1 readWrite port)
component Ram_1wrs is
  generic(
    wordCount : integer;
    wordWidth : integer;
    readUnderWrite : string;
    technology : string
  );
  port(
    clk : in std_logic;
    en : in std_logic;
    wr : in std_logic;
    addr : in unsigned;
    wrData : in std_logic_vector;
    rdData : out std_logic_vector
  );
end component;

--True dual port (2 readWrite port)
component Ram_2wrs is
  generic(
    wordCount : integer;
    wordWidth : integer;
    clockCrossing : boolean;
    technology : string;
    portA_readUnderWrite : string;
    portA_addressWidth : integer;
    portA_dataWidth : integer;
    portA_maskWidth : integer;
    portA_maskEnable : boolean;
    portB_readUnderWrite : string;
    portB_addressWidth : integer;
    portB_dataWidth : integer;
    portB_maskWidth : integer;
    portB_maskEnable : boolean
  );
  port(
    portA_clk : in std_logic;
    portA_en : in std_logic;
    portA_wr : in std_logic;
    portA_mask : in std_logic_vector;
    portA_addr : in unsigned;
    portA_wrData : in std_logic_vector;
    portA_rdData : out std_logic_vector;
    portB_clk : in std_logic;
    portB_en : in std_logic;
    portB_wr : in std_logic;
    portB_mask : in std_logic_vector;
    portB_addr : in unsigned;
    portB_wrData : in std_logic_vector;
    portB_rdData : out std_logic_vector
  );
end component;

正如你所看到的,黑盒有一个技术参数。要设置它,您可以在相应的内存上使用 setTechnology 函数。目前有4种可能的技术:

  • auto

  • ramBlock

  • distributedLut

  • registerFile

如果已为您的设备供应商配置了 SpinalConfig#setDevice(Device) ,则黑盒化可以插入 HDL 属性。

生成的 HDL 属性可能如下所示:

(* ram_style = "distributed" *)
(* ramsyle = "no_rw_check" *)

SpinalHDL 尝试支持知名供应商和设备提供的许多常见存储器类型,但是这是一个不断变化的领域,并且该领域的项目要求可能非常具体。

如果这对您的设计流程很重要,请检查输出 HDL 是否有预期的属性/代码,同时查阅供应商的平台文档。

HDL 属性也可以使用 addAttribute() addAttribute 机制手动添加。