什么时候使用VECTOR表示形式和INTEGER形式更整洁?


11

在有关以下问题的答案的评论线程中:VHDL实体中的输出错误

“使用整数时,您无法控制或访问FPGA中的内部逻辑表示,而SLV则使您可以完成一些技巧,例如有效利用进位链”

因此,在什么情况下您发现使用表示向量比使用整数 s来访问内部表示更简洁?您测量了哪些优势(在芯片面积,时钟频率,延迟或其他方面)?


我认为这很难衡量,因为显然这只是对底层实现的控制问题。
clabacchio

Answers:


5

我已经以其他形式vectorinteger形式编写了其他两个发布者建议的代码,请确保两个版本以尽可能相似的方式运行。

我在仿真中比较了结果,然后使用针对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查看器直观地验证了两者产生相似的逻辑。显然,带有比较器的递增计数器会更大,但是整数和向量又将是相同的。


除以2 ** n个计数器

由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);...不是c33位变量吗?
clabacchio

@clabacchio:是的,这允许访问“进位”以查看环绕。
马丁·汤普森

5

编写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而不是有符号/无符号没有太大的好处。我也不确定有符号/无符号是否支持三态信号。


4
大卫,这些代码片段并不等效。一个从零递增到任意数(使用昂贵的比较运算符);另一个从任意数倒数到零。您可以使用整数或向量来编写这两种算法,并且对任意数进行计数都会得到不好的结果,而对零进行计数则会得到好的结果。请注意,如果软件工程师需要从热循环中挤出更多性能,他们将倒数为零。
菲利普(Philippe)

1
像菲利普一样,我不认为这是一个有效的比较。如果将整数示例倒数并使用,if (count-1) < 0我认为合成器将推断进位位并产生与您的slv示例几乎相同的电路。另外,unsigned这些天我们是否应该使用该类型:)
马丁·汤普森

2
@DavidKessner,您当然提供了一个透彻且合理的答案,您已获得我的+1。我不得不问……为什么您担心整个设计的优化?将精力集中在需要它的代码区域上或者集中于接口点(实体端口)的SLV以获得兼容性,这会更好吗?我知道,在我的大多数设计中,只要符合时序并适合该部件,我就不会特别在意LUT的使用是否最少。如果我有特别严格的约束,我当然会更了解最佳设计,但通常不会。
akohlsmith 2012年

2
我对这个答案的投票数感到有些惊讶。@ bit_vector @无疑是用于建模和优化微体系结构的正确抽象层,但是我普遍认为,对于信号和端口,通常推荐使用“ integer @”之类的“高级”类型。由于缺少抽象知识,我已经看到了足够多的卷积代码和难以理解的代码,无法了解这些功能所提供的价值,如果不得不将它们抛在脑后,那将是非常可悲的。
trondd

2
@david出色的讲话。的确,从许多方面来说,与软件开发相比,我们仍处于中世纪时期,但是根据我在Quartus集成综合和Synplify中的经验,我认为事情没有那么糟糕。它们非常有能力处理很多事情,例如寄存器重定时和其他优化,这些改进可以在保持可读性的同时提高性能。我怀疑大多数针对的是几个工具链和设备,但是对于您的情况,我知道对最小公分母的要求:-)。
trondd

2

我的建议是同时尝试这两种方法,然后查看综合,地图和布局布线报告。这些报告将确切告诉您每种方法消耗了多少LUT,它们还将告诉您逻辑可以以最快的速度运行。

我同意大卫·凯斯纳(David Kessner)的观点,即您受制于工具链,没有“正确”的答案。综合是不可思议的事情,知道发生了什么的最好方法是仔细彻底地阅读所产生的报告。Xilinx工具甚至允许您查看FPGA内部,每个LUT的编程方式,进位链的连接方式,交换矩阵如何连接所有LUT的方式等等。

再举一个Kessner先生的方法的生动例子,假设您想在1 / 2、1 / 4、1 / 8、1 / 16等处拥有多个时钟频率。您可以使用一个整数,该整数在每个周期不断计数,然后有多个针对该整数值的比较器,每个比较器输出形成不同的时钟分频。取决于比较器的数量,扇出可能会变得过大而开始消耗额外的LUT,仅用于缓冲。SLV方法只是将向量的每个单独的位作为输出。


1

一个明显的原因是有符号和无符号允许的值大于32位整数。这是VHDL语言设计中的缺陷,这不是必需的。VHDL的新版本可以解决此问题,要求整数值支持任意大小(类似于Java的BigInt)。

除此之外,我非常想知道基准与矢量相比在整数方面的性能有所不同。

顺便说一句,扬Decaluwe这个写了一个漂亮的文章:这些INTS是为报数造


感谢Philippe(尽管那不是“更好地访问内部表示形式”的应用程序,这是我真正追求的...)
Martin Thompson

那篇文章不错,但是完全忽略了底层实现以及由此产生的逻辑速度和大小。我同意Decaluwe所说的大部分内容,但是他对合成的结果一无所获。有时合成的结果无关紧要,有时却很重要。所以这是一个判断电话。

1
@David,我同意Jan不会详细介绍综合工具对整数的反应。但是,不,这不是一个判断电话。您可以测量综合结果并确定给定综合工具的结果。我认为OP意味着他的问题是我们要产生表现出性能差异(如果有)的代码片段和综合结果所面临的挑战。
菲利普(Philippe)

@Philippe不,我的意思是,如果您根本不关心综合结果,这是一个判断电话。并非综合结果本身就是判断的依据。

@DavidKessner好的。我误解了。
菲利普(Philippe)2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.