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 上都有强制注册的输入,并且可以在输出上添加一个额外的可选寄存器。
因此,使用带有注册输入的代码样式代码可能会使模拟符合现实,这通常是一件好事。
多年来我一直在争论这个问题...推断具有同步读取的单个端口 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 上都有强制注册的输入,并且可以在输出上添加一个额外的可选寄存器。 因此,使用带有注册输入的代码样式代码可能会使模拟符合现实,这通常是一件好事。