spinal HDL - 09 - 时序逻辑

Registers

用 SpinalHDL 创建寄存器与用 VHDL 或 Verilog 创建寄存器非常不同。在SpinalHDL ,没有过程/总是阻塞。寄存器在声明中明确定义。这与传统的事件驱动 HDL 的区别有很大的影响:

  • 您可以在相同的范围内分配寄存器和连接,这意味着代码不需要在进程/总是块之间分割。

  • 它使一些事情更加灵活(参见函数),并且时钟和重置是分开处理的。

实例化

有4种方法可以实例化一个寄存器:

Syntax语法Description描述
Reg(type : Data)给定类型的寄存器
RegInit(resetValue : Data)重置时使用给定的 resetValue加载的寄存器
RegNext(nextValue : Data)寄存器采样给定的nextValue 每个周期
RegNextWhen(nextValue : Data, cond : Bool)当条件发生时寄存器采样给定的 nextValue

下面是一个声明某些寄存器的例子:

// 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 类。它允许您定义一个内存并向其添加读写端口。

下表显示了如何实例化一个内存:

SyntaxDescription
Mem(type : Data, size : Int)创建一个 RAM
Mem(type : Data, initialContent : Array[Data])创建一个 ROM。如果您的目标是 FPGA,因为内存可以被推断为块内存,那么您仍然可以在它上面创建写入端口。

如果你想定义一个 ROM,initialContent 数组的元素应该只是文字值(没有运算符,没有调整大小的函数)。这里有一个例子。要给出 RAM 初始值,还可以使用 init 函数。

下表显示了如何在内存中添加访问端口:

SyntaxDescriptionReturn
mem(address) := data同步写
mem(x)异步读取T
mem.write(address data [enable] [mask])带有可选掩码的同步写入。如果没有指定 enable,则会从调用此函数的条件作用域自动推断
mem.readAsync(address [readUnderWrite] )使用可选的读取下写策略进行异步读取T
mem.readSync(address [enable] [readUnderWrite] [clockCrossing])具有可选的 enable、read-under-write 策略和 clockCrossing 模式的同步读取T
mem.readWriteSync(address data enable write [mask] [readUnderWrite] [clockCrossing] )推断读/写端口,
当 enable & write 时写入数据。返回读取的数据,当 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
)

读写策略

此策略指定在同一循环中对同一地址进行写操作时,读操作如何受到影响。

KindsDescription
dontCare当事例发生时,不要关心 read 值
readFirst读取将获得旧值 ( 在写入之前 )
writeFirst读取将获得新值 ( 由写入提供 )

生成的 VHDL/Verilog 始终处于 readFirst 模式,该模式与 dontCare 兼容,但与 writeFirst 不兼容。要生成包含这种特性的设计,您需要启用自动内存黑盒。

Mixed-width ram

你可以使用以下函数指定访问内存的端口,其宽度为内存宽度的两分之一的幂:

SyntaxDescription
mem.writeMixedWidth(address
data
[readUnderWrite])
类似于 mem.write
mem.readAsyncMixedWidth(address
data
[readUnderWrite] )
与 mem.readAsync 类似,但它不返回读取值,而是驱动作为数据参数给定的信号/对象
mem.readSyncMixedWidth(address
data
[enable]
[readUnderWrite]
[clockCrossing])
与 mem.readSync 类似,但它不返回读取值,而是驱动作为数据参数给出的信号/对象
mem.readWriteSyncMixedWidth(address
data
enable
write
[mask]
[readUnderWrite]
[clockCrossing])
等价于 mem.readWriteSync

至于读写策略,要使用这个特性,您需要启用自动内存黑盒,因为没有通用的 VHDL/Verilog 语言模板来推断混合宽度内存。

Automatic blackboxing

由于不可能用常规的 VHDL/Verilog 推断所有的 ram 类型,SpinalHDL 集成了一个可选的自动黑箱系统。这个系统查看你的 RTL 网络列表中的所有内存,并用黑匣子替换它们。然后,生成的代码将依靠第三方 IP 提供内存特性,如写入期间读取策略和混合宽度端口。

下面是一个默认情况下如何启用存储器黑盒的例子:

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

Blackboxing policy

你可以使用多种策略来选择你想要黑匣子的内存,以及当黑匣子不可行时该怎么做:

KindsDescription
blackboxAll黑盒所有内存。在不可黑盒的内存上抛出错误。
blackboxAllWhatsYouCan黑匣子所有可黑匣子的内存。
`blackboxRequestedAndUninferable由用户指定的 Blackbox 内存和已知无法推断的内存(mixed-width,…)。在不可黑盒的内存上抛出错误。
blackboxOnlyIfRequested由用户指定的 Blackbox 内存,在不可黑盒的内存上抛出错误。

要显式地将内存设置为黑盒,可以使用其 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