VHDL:通过同步读取推断单端口 ram 的正确方法

VHDL: Correctly way to infer a single port ram with synchronous read

多年来我一直在争论这个问题...推断具有同步读取的单个端口 ram 的正确原因是什么。

让我们假设我在 VHDL 中推断内存的接口是:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity sram1 is
    generic(
        aw             :integer := 8; --address width of memory
        dw             :integer := 8  --data width of memory
    );
    port(
        --arm clock
        aclk   :in    std_logic;
        aclear :in    std_logic;

        waddr  :in    std_logic_vector(aw-1 downto 0);
        wdata  :in    std_logic_vector(dw-1 downto 0);
        wen    :in    std_logic;

        raddr  :in    std_logic_vector(aw-1 downto 0);
        rdata  :out   std_logic_vector(dw-1 downto 0)        
    );
end entity;

是这样的:1号门

-- I LIKE THIS ONE
architecture rtl of sram1 is    
    constant mem_len :integer := 2**aw;

    type mem_type is array (0 to mem_len-1) of std_logic_vector(dw-1 downto 0);

    signal block_ram : mem_type := (others => (others => '0'));

begin

process(aclk)
begin
    if (rising_edge(aclk)) then
        if (wen = '1') then
            block_ram(to_integer(unsigned(waddr))) <= wdata(dw-1 downto 0);
        end if;     

        -- QUESTION: REGISTERING THE READ DATA (ALL OUTPUT REGISTERED)?
        rdata <= block_ram(to_integer(unsigned(raddr)));        

    end if;
end process;


end architecture;

或者这样:2 号门

-- TEXTBOOKS LIKE THIS ONE
architecture rtl of sram1 is    
    constant mem_len :integer := 2**aw;

    type mem_type is array (0 to mem_len-1) of std_logic_vector(dw-1 downto 0);

    signal block_ram : mem_type := (others => (others => '0'));
    signal raddr_dff : std_logic_vector(aw-1 downto 0);        

begin

process(aclk)
begin
    if (rising_edge(aclk)) then
        if (wen = '1') then
            block_ram(to_integer(unsigned(waddr))) <= wdata(dw-1 downto 0);
        end if;     

        -- QUESTION: REGISTERING THE READ ADDRESS?
        raddr_dff <= raddr;        

    end if;
end process;

-- QUESTION: HOT ADDRESS SELECTION OF DATA
rdata <= block_ram(to_integer(unsigned(raddr_dff)));        

end architecture;

我是第一个版本的粉丝,因为我认为注册 vhdl 模块的所有输出是一种很好的做法。然而,许多教科书将后来的版本列为推断具有同步读取的单端口 ram 的正确方法。

从 Xilinx 或 Altera 综合的角度来看,这真的很重要吗,只要您已经考虑到延迟数据与地址之间的差异(并确定这对您的应用程序无关紧要。)

我的意思是...他们仍然在 FPGA 中为您提供块内存?正确的?

还是一个给你LUTS,另一个给你Block ram?

门 #1 或门 #2 在 FPGA 中哪个会推断出更好的时序和更好的容量?

大家可以看看合成的结果。在综合您的解决方案(默认设置)后,我的 Vivado 会给我以下报告。

第一个解法:

  • BRAM:0.5(来自 60 个块)
  • IO: 34
  • BUFG: 1

原理图是这样的

第二种方案:

  • BRAM:0.5(来自 60 个块)
  • IO: 34
  • BUFG: 1

结果如下:

所以你看到合成将为两个变体生成相同的输出。这取决于您要使用哪一个。我更喜欢第一个变体,因为第二个变体的代码稍微多一些。

差异可能很重要,这实际上取决于您针对的具体家庭。大多数现代 FPGA 都有 block ram 选项,允许它们以任何一种方式运行,但我不会在实践中指望它。

如果我要推断 RAM,我通常会从工具提供的示例设计开始(用户指南几乎总是有一个 "how to infer ram" 部分)。如果针对跨平台(例如:Altera + Xilinx),我会坚持使用 "minimal common supported" 一组功能,合并两个示例设计。

综上所述,我通常会注册 BOTH 地址和数据。这是一个时钟,但它有助于关闭时序,我通常更关心吞吐量与整体延迟。我通常还使用包装函数(例如:My_Simple_Dual_Port_RAM)并使用原语直接实例化低级块 ram,这使得在 FPGA 供应商之间切换变得容易(或换出所需的推断逻辑 if/when)。我只是将模块放在一个目录中(例如:Altera、Lattice、Xilinx)并在项目文件中包含适当的目录。我也对双时钟 FIFO 做同样的事情,你通常 LOT 最好使用库部件而不是尝试构建自己的部件。

不幸的是,综合工具供应商已经制作了 RAM 推理功能,因此他们通常可以识别这两种样式,而不管所讨论的 FPGA 中 RAM 的物理实现如何。 因此,即使您指定了注册输出,syntesis 工具也可能会默默地忽略它并推断出具有注册输入的 RAM。这在功能上并不等同,因此它实际上可能会导致不良行为,特别是在双端口 RAM 的情况下。

为避免此陷阱,您可以添加特定于供应商的属性,告诉综合工具您需要哪种 RAM。

一般来说,大多数 FPGA 在物理 RAM 上都有强制注册的输入,并且可以在输出上添加一个额外的可选寄存器。 因此,使用带有注册输入的代码样式代码可能会使模拟符合现实,这通常是一件好事。