在有关以下问题的答案的评论线程中:VHDL实体中的输出错误:
“使用整数时,您无法控制或访问FPGA中的内部逻辑表示,而SLV则使您可以完成一些技巧,例如有效利用进位链”
因此,在什么情况下您发现使用位表示向量比使用整数 s来访问内部表示更简洁?您测量了哪些优势(在芯片面积,时钟频率,延迟或其他方面)?
在有关以下问题的答案的评论线程中:VHDL实体中的输出错误:
“使用整数时,您无法控制或访问FPGA中的内部逻辑表示,而SLV则使您可以完成一些技巧,例如有效利用进位链”
因此,在什么情况下您发现使用位表示向量比使用整数 s来访问内部表示更简洁?您测量了哪些优势(在芯片面积,时钟频率,延迟或其他方面)?
Answers:
我已经以其他形式vector
和integer
形式编写了其他两个发布者建议的代码,请确保两个版本以尽可能相似的方式运行。
我在仿真中比较了结果,然后使用针对Xilinx Spartan 6的Synplify Pro进行了合成。下面的代码示例是从工作代码中粘贴的,因此您应该可以将它们与您喜欢的合成器一起使用,并查看其行为是否相同。
首先,正如大卫·凯斯纳(David Kessner)所建议的那样:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity downcounter is
generic (top : integer);
port (clk, reset, enable : in std_logic;
tick : out std_logic);
end entity downcounter;
architecture vec of downcounter is
begin
count: process (clk) is
variable c : unsigned(32 downto 0); -- don't inadvertently not allocate enough bits here... eg if "integer" becomes 64 bits wide
begin -- process count
if rising_edge(clk) then
tick <= '0';
if reset = '1' then
c := to_unsigned(top-1, c'length);
elsif enable = '1' then
if c(c'high) = '1' then
tick <= '1';
c := to_unsigned(top-1, c'length);
else
c := c - 1;
end if;
end if;
end if;
end process count;
end architecture vec;
architecture int of downcounter is
begin
count: process (clk) is
variable c : integer;
begin -- process count
if rising_edge(clk) then
tick <= '0';
if reset = '1' then
c := top-1;
elsif enable = '1' then
if c < 0 then
tick <= '1';
c := top-1;
else
c := c - 1;
end if;
end if;
end if;
end process count;
end architecture int;
在代码方面,整数1对我来说似乎更可取,因为它避免了to_unsigned()
调用。否则,没有太多选择。
通过Synplify Pro与它一起运行会top := 16#7fff_fffe#
产生66个LUT(用于该vector
版本)和64个LUT(用于该
integer
版本)。两种版本都充分利用了进位链。两者都报告时钟速度超过280MHz。合成器具有很好的使用进位链的能力-我通过RTL查看器直观地验证了两者产生相似的逻辑。显然,带有比较器的递增计数器会更大,但是整数和向量又将是相同的。
由ajs410建议:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity clkdiv is
port (clk, reset : in std_logic;
clk_2, clk_4, clk_8, clk_16 : buffer std_logic);
end entity clkdiv;
architecture vec of clkdiv is
begin -- architecture a1
process (clk) is
variable count : unsigned(4 downto 0);
begin -- process
if rising_edge(clk) then
if reset = '1' then
count := (others => '0');
else
count := count + 1;
end if;
end if;
clk_2 <= count(0);
clk_4 <= count(1);
clk_8 <= count(2);
clk_16 <= count(3);
end process;
end architecture vec;
您必须跳过一些箍,以避免只使用to_unsigned
然后摘掉明显会产生与上述相同的效果的位:
architecture int of clkdiv is
begin
process (clk) is
variable count : integer := 0;
begin -- process
if rising_edge(clk) then
if reset = '1' then
count := 0;
clk_2 <= '0';
clk_4 <= '0';
clk_8 <= '0';
clk_16 <= '0';
else
if count < 15 then
count := count + 1;
else
count := 0;
end if;
clk_2 <= not clk_2;
for c4 in 0 to 7 loop
if count = 2*c4+1 then
clk_4 <= not clk_4;
end if;
end loop;
for c8 in 0 to 3 loop
if count = 4*c8+1 then
clk_8 <= not clk_8;
end if;
end loop;
for c16 in 0 to 1 loop
if count = 8*c16+1 then
clk_16 <= not clk_16;
end if;
end loop;
end if;
end if;
end process;
end architecture int;
就代码而言,在这种情况下,vector
版本显然更好!
就综合结果而言,对于这个小例子,整数版本(如ajs410所预测的)确实会产生3个额外的LUT作为比较器的一部分,我对综合器感到过于乐观,尽管它正在处理令人费解的代码!
当您希望算术回绕时,向量是一个明显的胜利(计数器甚至可以单行完成):
vec <= vec + 1 when rising_edge(clk);
与
if int < int'high then
int := int + 1;
else
int := 0;
end if;
尽管至少从该代码中可以清楚地看出作者打算进行环绕。
我没有在实代码中使用过的东西,但在思考:
“自然包裹”功能也可以用于“通过溢出进行计算”。当您知道一串加法/减法和乘法的输出是有界的时,您不必存储中间计算的高位,因为(在2 s补码中)它会“在清洗中”出现到输出时。有人告诉我本文包含对此的证明,但是对于我来说,快速评估显得有些密集!计算机增加和溢出理论-HL Garner
integer
在这种情况下使用s会在包装时导致模拟错误,即使我们知道它们最终会解开也是如此。
正如Philippe指出的那样,当您需要大于2 ** 31的数字时,别无选择,只能使用向量。
variable c : unsigned(32 downto 0);
...不是c
33位变量吗?
编写VHDL时,我强烈建议对信号使用std_logic_vector(slv)而不是整数(int)。(另一方面,将int用于泛型,一些常量和一些变量可能非常有用。)简单地说,如果您声明一个int类型的信号,或者必须为整数指定范围,那么您可能正在做有问题。
int的问题在于VHDL程序员不知道int的内部逻辑表示是什么,因此我们无法利用它。例如,如果我定义一个范围为1到10的整数,则我不知道编译器如何编码这些值。希望它会被编码为4位,但是我们对此不了解太多。如果您可以探测FPGA内部的信号,则可以将其编码为“ 0001”至“ 1010”,或编码为“ 0000”至“ 1001”。也可能以对我们人类完全没有意义的方式进行编码。
相反,我们应该只使用slv而不是int,因为这样我们就可以控制编码,并且可以直接访问各个位。拥有直接访问权限很重要,您将在后面看到。
每当需要访问单个位时,我们都可以将int转换为slv,但这确实非常麻烦,非常快。这就像两全其美,而不是两全其美。您的代码将使编译器难以优化,并且几乎无法阅读。我不建议这样做。
因此,正如我所说,使用slv可以控制位编码并直接访问位。那你该怎么办呢?我将向您展示一些示例。假设您需要每4,294,000,000个时钟输出一次脉冲。使用int的方法如下:
signal count :integer range 0 to 4293999999; -- a 32 bit integer
process (clk)
begin
if rising_edge(clk) then
if count = 4293999999 then -- The important line!
count <= 0;
pulse <= '1';
else
count <= count + 1;
pulse <= '0';
end if;
end if;
end process;
和使用slv的相同代码:
use ieee.numeric_std.all;
signal count :std_logic_vector (32 downto 0); -- a 33 bit integer, one extra bit!
process (clk)
begin
if rising_edge(clk) then
if count(count'high)='1' then -- The important line!
count <= std_logic_vector(4293999999-1,count'length);
pulse <= '1';
else
count <= count - 1;
pulse <= '0';
end if;
end if;
end process;
至少就所得逻辑的大小和速度而言,这些代码中的大多数在int和slv之间是相同的。当然,一个正在向上计数,另一个正在向下计数,但这对于本示例而言并不重要。
区别在于“重要方面”。
对于int示例,这将导致一个32输入比较器。通过Xilinx Spartan-3使用的4输入LUT,这将需要11个LUT和3级逻辑。一些编译器可能会将其转换为减法,该减法将使用进位链并跨越等效的32个LUT,但运行速度可能快于3级逻辑。
对于slv示例,由于没有32位比较,所以它是“零LUT,零逻辑电平”。唯一的惩罚是我们的计数器多了一位。因为计数器的这个额外位的额外定时全部在进位链中,所以存在“几乎为零”的额外定时延迟。
当然,这是一个极端的例子,因为大多数人不会以这种方式使用32位计数器。它确实适用于较小的计数器,但是尽管仍然很重要,但差异不会那么明显。
这只是如何在int上使用slv以获得更快的计时的一个示例。利用slv还有许多其他方法,这只需要一点想象力即可。
更新:添加了一些内容来解决Martin Thompson关于使用“ if(count-1)<0”使用int的注释
(注意:我假设您的意思是“ if count <0”,因为那样会使它与我的slv版本更等效,并且不需要进行额外的减法。)
在某些情况下,这可能会生成预期的逻辑实现,但不能保证它一直都在工作。这将取决于您的代码以及编译器如何编码int值。
根据编译器以及如何指定int的范围,将int值零插入到FPGA逻辑中时,很可能不会将其编码为“ 0000 ... 0000”的位向量。为了使您的变体有效,它必须编码为“ 0000 ... 0000”。
例如,假设您将一个int定义为-5到+5。您期望将值0编码为4个位,例如“ 0000”,将+5编码为“ 0101”,将-5编码为“ 1011”。这是典型的二进制补码编码方案。
但是不要假设编译器将使用二进制补码。尽管不寻常,但补码可能会导致“更好”的逻辑。或者,编译器可以使用某种“有偏”编码,其中-5编码为“ 0000”,0编码为“ 0101”,+ 5编码为“ 1010”。
如果int的编码是“正确的”,则编译器可能会推断出如何处理进位。但是,如果它不正确,那么产生的逻辑将是可怕的。
以这种方式使用int可能会导致合理的逻辑大小和速度,但这不是保证。切换到其他编译器(例如,从XST到Synopsis)或切换到其他FPGA架构可能会导致发生完全错误的事情。
Unsigned / Signed vs. slv是另一场辩论。您可以感谢美国政府委员会在VHDL中为我们提供了很多选择。:)我使用slv,因为这是模块和内核之间接口的标准。除此之外,以及仿真中的其他一些情况,我认为使用slv而不是有符号/无符号没有太大的好处。我也不确定有符号/无符号是否支持三态信号。
if (count-1) < 0
我认为合成器将推断进位位并产生与您的slv示例几乎相同的电路。另外,unsigned
这些天我们是否应该使用该类型:)
我的建议是同时尝试这两种方法,然后查看综合,地图和布局布线报告。这些报告将确切告诉您每种方法消耗了多少LUT,它们还将告诉您逻辑可以以最快的速度运行。
我同意大卫·凯斯纳(David Kessner)的观点,即您受制于工具链,没有“正确”的答案。综合是不可思议的事情,知道发生了什么的最好方法是仔细彻底地阅读所产生的报告。Xilinx工具甚至允许您查看FPGA内部,每个LUT的编程方式,进位链的连接方式,交换矩阵如何连接所有LUT的方式等等。
再举一个Kessner先生的方法的生动例子,假设您想在1 / 2、1 / 4、1 / 8、1 / 16等处拥有多个时钟频率。您可以使用一个整数,该整数在每个周期不断计数,然后有多个针对该整数值的比较器,每个比较器输出形成不同的时钟分频。取决于比较器的数量,扇出可能会变得过大而开始消耗额外的LUT,仅用于缓冲。SLV方法只是将向量的每个单独的位作为输出。
一个明显的原因是有符号和无符号允许的值大于32位整数。这是VHDL语言设计中的缺陷,这不是必需的。VHDL的新版本可以解决此问题,要求整数值支持任意大小(类似于Java的BigInt)。
除此之外,我非常想知道基准与矢量相比在整数方面的性能有所不同。
顺便说一句,扬Decaluwe这个写了一个漂亮的文章:这些INTS是为报数造