Registers
用 SpinalHDL 创建寄存器与用 VHDL 或 Verilog 创建寄存器非常不同。在SpinalHDL ,没有过程/总是阻塞。寄存器在声明中明确定义。这与传统的事件驱动 HDL 的区别有很大的影响:
- 您可以在相同的范围内分配寄存器和连接,这意味着代码不需要在进程/总是块之间分割。
- 它使一些事情更加灵活(参见函数),并且时钟和重置是分开处理的。
实例化
有4种方法可以实例化一个寄存器:
下面是一个声明某些寄存器的例子:
// UInt register of 4 bits val reg1 = Reg(UInt(4 bit)) // Register that samples reg1 each cycle val reg2 = RegNext(reg1 + 1) // UInt register of 4 bits initialized with 0 when the reset occurs val reg3 = RegInit(U"0000") reg3 := reg2 when(reg2 === 5) { reg3 := 0xF } // Register that samples reg3 when cond is True val reg4 = RegNextWhen(reg3, cond)
上面的代码将推断出以下逻辑:
上面的 reg3示例显示了如何分配 RegInit 寄存器的值。也可以使用相同的语法分配给其他寄存器类型(Reg、 RegNext、 RegNextWhen)。就像组合赋值一样,规则是“最后一次赋值获胜”,但是如果没有完成赋值,寄存器保留其值。
RegNext 是一个基于 Reg 语法构建的抽象。下面两个代码序列是严格等价的:
// Standard way val something = Bool() val value = Reg(Bool()) value := something // Short way val something = Bool() val value = RegNext(something)
Reset value 重置值
除了使用 RegInit (value: Data)语法直接创建具有重置值的寄存器之外,还可以通过调用寄存器上的 init (value: Data)函数来设置重置值。
// UInt register of 4 bits initialized with 0 when the reset occurs val reg1 = Reg(UInt(4 bit)) init(0)
如果有一个包含 Bundle 的寄存器,则可以对 Bundle 的每个元素使用 init 函数。
case class ValidRGB() extends Bundle{ val valid = Bool() val r, g, b = UInt(8 bits) } val reg = Reg(ValidRGB()) reg.valid init(False) // Only the valid if that register bundle will have a reset value.
用于仿真的初始化值
对于在 RTL 中不需要重置值,但是需要初始化值进行模拟(以避免 x 传播)的寄存器,可以通过调用 randBoot ()函数来请求一个随机初始化值。
// UInt register of 4 bits initialized with a random value val reg1 = Reg(UInt(4 bit)) randBoot()
RAM/ROM
语法
要用 SpinalHDL 创建内存,应该使用 Mem 类。它允许您定义一个内存并向其添加读写端口。
下表显示了如何实例化一个内存:
如果你想定义一个 ROM,initialContent 数组的元素应该只是文字值(没有运算符,没有调整大小的函数)。这里有一个例子。要给出 RAM 初始值,还可以使用 init 函数。
下表显示了如何在内存中添加访问端口:
如果出于某种原因,你需要一个在 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 )
读写策略
此策略指定在同一循环中对同一地址进行写操作时,读操作如何受到影响。
生成的 VHDL/Verilog 始终处于 readFirst 模式,该模式与 dontCare 兼容,但与 writeFirst 不兼容。要生成包含这种特性的设计,您需要启用自动内存黑盒。
Mixed-width ram
你可以使用以下函数指定访问内存的端口,其宽度为内存宽度的两分之一的幂:
至于读写策略,要使用这个特性,您需要启用自动内存黑盒,因为没有通用的 VHDL/Verilog 语言模板来推断混合宽度内存。
Automatic blackboxing
由于不可能用常规的 VHDL/Verilog 推断所有的 ram 类型,SpinalHDL 集成了一个可选的自动黑箱系统。这个系统查看你的 RTL 网络列表中的所有内存,并用黑匣子替换它们。然后,生成的代码将依靠第三方 IP 提供内存特性,如写入期间读取策略和混合宽度端口。
下面是一个默认情况下如何启用存储器黑盒的例子:
def main(args: Array[String]) { SpinalConfig() .addStandardMemBlackboxing(blackboxAll) .generateVhdl(new TopLevel) }
Blackboxing policy
你可以使用多种策略来选择你想要黑匣子的内存,以及当黑匣子不可行时该怎么做:
要显式地将内存设置为黑盒,可以使用其 generateAsBlackBox 函数。
val mem = Mem(Rgb(rgbConfig), 1 << 16) mem.generateAsBlackBox()
您还可以通过扩展 MemBlackboxingPolicy 类来定义自己的 blackboxing 策略。
Standard memory blackboxes
下面是 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; 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;
如你所见,blackboxes 有一个技术参数。要设置它,可以在相应的内存上使用 setTechnology 函数。目前有四种可能的技术:
auto
ramBlock
distributedLut
registerFile