## RAM/ROM ### 一、语义(Syntax) 用`Mem`类在SpinalHDL中创建memory, 它允许你定义memory并增加读写端口。 以下表格展示了如何例化memory: | 语句 | 描述 | | :------------------------------------------: | :----------------------------------------------------------------------: | | Mem(type: Data, size: Int) | 创建RAM | | Mem(type: Data, initialContent: Array[Data]) | 创建ROM, 如果目标是FPGA, 因为memory会被推断成ram模块, 故也可以创建写端口 | > 备注:如果你想定义ROM, `initialContent`阵列的元素应该是纯粹的值(没有操作符, 没有改变尺寸函数), 在例子里有Sinus rom的例子。 > 备注:给RAM初始化时, 也可以用`init`函数。 > 备注:写mask宽度是灵活的, 可以把mem字拆分成和mask宽度一样的片段。例如如果你有32 bits的mem字并提供了4 bits的mask, 那么这就是字节(byte)mask。如果你提供32 bits的mask, 那这就是bit mask。 以下表格展示了如何给memory增加端口: | 语句 | 描述 | 返回类型 | | :------------------------------------------------------------------------------------------------------------- | :---------------------------------------------------------------------------------: | :------: | | mem(address):=data | 同步写 | | | mem(x) | 异步读 | T | | mem.write(
address
data
[enable]
[mask]
) | 用可选择的mask同步写。如果没有给定enable, 它会自动从被引用的条件区域里推断 | | | mem.readAsync(
address
[readUnderWrite]
) | 基于可选择的读下写原则异步读 | T | | mem.readSync(
address
[enable]
[readUnderWrite]
[clockCrossing]
) | 用可选择的enable, 读下写原则和`clockCrossing`模式同步写 | T | | mem.readWriteSync(
address
data
enable
write
[mask]
[readUnderWrite]
[clockCrossing]
) | 推断读写端口。当`enable && write`时`data`被写入。返回读数据, 当`enable`为真时触发读 | T | > 备注:如果处于某些原因你需要指定非SpinalHDL实现的memory端口, 你可以通过给memory指定一个黑盒来抽象它。 > 重要:SpinalHDL的memory端口不是推断得到的, 而是清晰地定义出的。你不能用像VHDL/Verilog一样的代码模板帮助综合工具推断出memory。 以下是一个推断出双端口ram(32 bits*256)的例子: ```Scala 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 ) ``` Verilog: ```Verilog module MyTopLevel ( input io_writeValid, input [7:0] io_writeAddress, input [31:0] io_writeData, input io_readValid, input [7:0] io_readAddress, output [31:0] io_readData, input clk, input reset ); reg [31:0] _zz_mem_port1; reg [31:0] mem [0:255]; always @(posedge clk) begin if(io_writeValid) begin mem[io_writeAddress] <= io_writeData; end end always @(posedge clk) begin if(io_readValid) begin _zz_mem_port1 <= mem[io_readAddress]; end end assign io_readData = _zz_mem_port1; endmodule ``` ### 二、同步使能quirk(Synchronous enable quirk) 如果在条件模块中使用使能信号, 如同`when`, 只有使能信号会作为访存条件生成, `when`条件被忽视。 ```Scala val rom = Mem(Bits(10 bits), 32) when(cond) { io.rdata := rom.readSync(io.addr, io.rdEna) } ``` 在上述例子中, 条件`cond`不会在生成后的RTL中描述, 在使能信号中包含条件`cond`的方法如下: ```Scala io.rdata := rom.readSync(io.addr, io.rdEna & cond) ``` ### 三、读下写原则(Read-under-write policy) 这个规则指定了当在读和写发生在同一周期同一地址时, 写会如何影响。 | 类别 | 描述 | | :----------: | :----------------------: | | `dontCare` | 当情况发生时不关心读的值 | | `readFirst` | 读会取到旧(写前)的值 | | `writeFirst` | 读会得到新(写后)的值 | > 重要:生成的VHDL/Verilog总是`readFirst`模式, 会兼容`dontCare`但是不兼容`writeFirst`。为了产生有这个特点的电路, 需要使能*自动化mem黑盒* ### 四、混合宽度ram(Mixed-width ram) 你可以指定访存mem的端口的位宽, 这个位宽是mem位宽的一部分, 是二的幂次方, 函数如下: | 语句 | 描述 | | :----------------------------------------------------------------------------------------------------------------------: | :----------------------------------------------------------------------: | | 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` | > 重要:对于读下写机制, 为了使用这个特点你需要使能*自动化mem黑盒*, 因为没有一般性的VHDL/Verilog代码模板推断ram的混合位宽 ### 五、自动化mem黑盒(Automatic blackboxing) 因为用传统VHDL/Verilog推断所有ram类型是不可能的, SpinalHDL集成了可选择的自动化黑盒(automatic blackboxing system)系统。这个系统会检查你的RTL网表中所有出现了的mem并把他们用黑盒替代。之后产生的代码会根据第三方IP提供mem的特性, 例如读时写(read-during-write)机制和混合带宽端口。 以下例子说明了如何默认开启mem黑盒: ```Scala def main(args: Array[String]) { SpinalConfig() .addStandardMemBlackboxing(blackboxAll) .generateVhdl(new TopLevel) } ``` 如果对于你的设计, 标准黑盒工具还不够用, 不要犹豫去创建一个*Github issue*。这里还有一种你创建自己黑盒工具的方法。 1. 黑盒机制(Blackboxing policy) 有多种机制供你使用, 你可以选择你想要把哪个mem变成黑盒, 并且当黑盒不可用时要做什么: | 种类 | 描述 | | :-------------------------------: | :--------------------------------------------------------------: | | `blackboxAll` | 把所有mem变成黑盒。对于无法黑盒化的mem报错 | | `blackboxAllWhatsYouCan` | 把所有可黑盒化的mem变成黑盒 | | `blackboxRequestedAndUninferable` | 把用户给定的和已知可被推断的mem变成黑盒。对于无法黑盒化的mem报错 | | `blackboxOnlyIfRequested` | 用户指定哪些mem变成黑盒。对于无法黑盒化的mem报错 | 为了清晰地把mem设置成黑盒, 你可以用`generateAsBlackBox`函数 ```Scala val mem = Mem(Rgb(rgbConfig), 1 << 16) mem.generateAsBlackBox() ``` 你也可以通过拓展`MemBlackboxingPolicy`类定义你自己的黑盒机制。 2. 标准mem黑盒(Standard memory blackboxed) 以下展示的是SpinalHDL中用的标准黑盒的VHDL定义: ```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; rdEnEnable : boolean ); 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_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; ``` 如你所见, 黑盒有一个技术参数。你可以在对应的mem上用`setTechnology`函数来设置它。当前有4中可用的技术: + `auto` + `ramBlock` + `distributedLut` + `registerFile`