在 VHDL 中定义类型以保存两个整数之和的正确方法

Proper way of defining a type to hold sum of two integer in VHDL

我正在尝试编写一个非常简单的模块,其中包含两个整数输入和一个 select 输入。当 select 为 0 时,输出应为输入之和,当 select 为 1 时,输出应为输入之差。我将使用 GHDL 通过简单的测试平台验证模块。该模块不必是可合成的。

我的第一次尝试如下

entity alu is
  port (
    a   : in  integer;                  -- first input
    b   : in  integer;                  -- second input
    y   : out integer;                  -- result
    sel : in  bit);                     -- if sel = 0 y = a+b,  sel = 1 y = a-b

end entity alu;

architecture behav of alu is

begin  -- architecture behav

  -- purpose: calculate the output
  -- type   : combinational
  -- inputs : a, b, y
  -- outputs: y
  compute: process (sel, a, b) is
  begin  -- process compute
    if sel='0' then
      y <= a + b;
    else
      y <= a - b;
    end if;
  end process compute;

end architecture behav;

问题是 GHDL 给出了溢出错误,因为据我所知,两个整数之和不能放入另一个整数。

如何定义范围足以保留结果的类型?我的第一次尝试如下。但是在那种情况下,我应该为新类型定义“+”和“-”运算符。

type big_int is range 2 * integer'low  to 2 * integer'high;

由于要求的范围比整数宽,我不能使用子类型定义。如果我能够定义子类型,我可以使用为整数定义的“+”和“-”运算符而无需重新定义它们。

编辑 1:

对于那些想知道测试平台和确切错误的人,这里是使用 EMACS vhdl-mode 半自动生成的测试平台。

library ieee;
use ieee.std_logic_1164.all;

-------------------------------------------------------------------------------

entity alu_tb is

end entity alu_tb;

-------------------------------------------------------------------------------

architecture test of alu_tb is

  -- component ports
  signal a   : integer;
  signal b   : integer;
  signal y   : integer;
  signal sel : bit;

  -- clock
  signal Clk : std_logic := '1';

begin  -- architecture test

  -- component instantiation
  DUT: entity work.alu
    port map (
      a   => a,
      b   => b,
      y   => y,
      sel => sel);

  -- clock generation
  Clk <= not Clk after 10 ns;

  -- waveform generation
  WaveGen_Proc: process
  begin
    a <= 24;
    b <= 46;
    sel <= '0';
    wait for 20 ns;

    sel <= '1';
    wait for 20 ns;

    wait;

  end process WaveGen_Proc;



end architecture test;

-------------------------------------------------------------------------------

configuration alu_tb_test_cfg of alu_tb is
  for test
  end for;
end alu_tb_test_cfg;

-------------------------------------------------------------------------------

这是来自 GHDL 的确切错误:

C:\GHDL\bin\ghdl.exe:error: overflow detected
  from: process work.alu(behav).compute at q9_alu.vhd:21
C:\GHDL\bin\ghdl.exe:error: simulation failed

第21行对应

y <= a + b;

在源文件中。

编辑 2:

关于我的 GHDL:

ghdl -v

GHDL 0.35 (tarball) [Dunoon edition]
 Compiled with GNAT Version: GPL 2017 (20170515-63)
 mcode code generator
Written by Tristan Gingold.

Copyright (C) 2003 - 2015 Tristan Gingold.
GHDL is free software, covered by the GNU General Public License.  There is
warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.

编辑:看看使用 MCVE 有多容易? (最小 Complete/Compilable 可验证示例)

现在只需添加

 report "Sel " & bit'image(sel) & ": y is " & integer'image(y) severity Note;

每次等待之后...

ghdl(在每次等待(时间)报告后添加以下语句后:

alu_tb.vhd:43:5:@20ns:(report note): Sel '0': y is 70
alu_tb.vhd:47:5:@40ns:(report note): Sel '1': y is -22

(并且永远不会终止,因为 TB 时钟保持 运行。使用 --std=08 编译并调用 std.env.Stop 而不是普通的 Wait)

ghdl --version
GHDL 0.35-dev (2017-03-01-252-g90f4fd21-dirty) [Dunoon edition]
Compiled with GNAT Version: 6.3.0
GCC back-end code generator Written by Tristan Gingold.

我想知道你的 ghdl 是从哪里得到的,它是哪个版本?

github.com/ghdl/ghdl 买一个更新的。 (好的,您使用的是 0.35,见下文)

ghdl 是最符合(VHDL 标准)的模拟器之一,现在支持 VHDL-2008 的一个很好的子集。

下面是原回答;它的信息通常有用,但不是问题中的具体问题。


再次编辑,现在我们有了工具版本信息。

GHDL 可以将 VHDL 编译为 3 种不同的代码生成后端; gcc、llvm 和它自己的 JIT 代码生成器 mcode。各有优缺点。

GCC 可以生成高度优化的代码,并且可以利用 gcov 工具进行代码覆盖率分析。

LLVM(也被 Clang 编译器使用)也提供了良好的代码性能,而且它的重量比 GCC 轻得多。

Mcode 是一个更简单的代码生成器,所以它生成代码的速度很快,但代码本身有点慢(根据我的经验,少于 2:1 所以还是很不错的)。但它是为 VHDL 量身定制的,因此它比其他语言更彻底地实现了运行时检查。

我看到它捕获了真正的错误 - 时间计算(64 位)和整数(32 位)之间的溢出以设置延迟计数器,它静默地通过我尝试过的所有其他工具,生成不正确的计数(并且延迟比要求的要短得多)。包括合成工具。想象一下:"You have 30 seconds until detonation".

在我尝试过的工具中,只有 ghdl (mcode) 捕获了这个错误。

在你的测试用例中发生的事情是,因为你使用 Integer,并且没有明确初始化,所以 Integers 被初始化为 Integer'Left,或 -2^^31。添加其中的 2 个会溢出。

将 A 或 B(或两者)之一初始化为 0,您的代码也可以使用 ghdl mcode 版本运行。

  signal a   : integer := 0;

不要相信我的话。测试完上述修复后,试试这个实验。

a <= -2**31;
b <= 46;

现在加法应该可以了,但是减法应该会遇到溢出错误或产生一个大的负数。 任何其他结果都是不可接受的。

ghdl (gcc):此测试失败:第二个值为正值

ghdl -r alu_tb
alu_tb.vhd:43:5:@20ns:(report note): Sel '0': y is -2147483602
alu_tb.vhd:47:5:@40ns:(report note): Sel '1': y is 2147483602

ghdl (mcode) : 正确报错

ghdl -r alu_tb
alu_tb.vhd:43:5:@20ns:(report note): Sel '0': y is -2147483602
ghdl:error: overflow detected
  from: process work.alu(behav).compute at alu.vhd:23
ghdl:error: simulation failed

我很想看看其他模拟器的结果,例如对另一个答案的更新(目前已删除)。有些人可能会超出标准要求并提供 64 位整数,但我不知道。

IMO 静默溢出产生错误结果是一件坏事,但这似乎是少数人的意见!


好的,我将扩展我的评论:

Use ranged subtypes of integer. For example if your inputs are ranged 0 to 100 your output is bounded by 0 and 200.

例如,

subtype in_type is natural range 0 to 100;
subtype sum_type is natural range 0 to 200;

子类型的一个特征是它们的默认值,例如intype, is the first value in the range, i.e. 0 above, rather than -2**31, the default value for整数`。

因此,范围子类型明确避免了 user1155120 在他的评论中指出的问题,这是您溢出的实际来源。

然后,一个适当范围的求和类型,如上所述,避免了溢出的可能性 - 通过构造纠正,避免了大量难以测试的用例。

但请注意“增长”一词:您通常无法承受在设计中无限远地传播不断增加的字宽。但至少你可以控制缩小的方式和时间,例如通过明确的超出范围检查,或饱和算法,或 truncation/rounding.

综合中 Integer 没有任何问题,但范围子类型具有允许综合生成正确大小的硬件的额外优势(此处为 7 位输入、8 位输出,而不是可能的 32 位) .

那么你什么时候想使用numeric_std.signed(或unsigned)?三个主要原因:

  1. 出于某种原因,您需要访问各个位(std_logic 信号),例如移位或逻辑运算
  2. 您需要与 std_logic_vector 信号互换,例如穿过一辆公共汽车。请注意,ranged naturals 可以用作端口,例如寻址内部存储器 - 但您通常需要 std_logic_vector 在外部端口,以便各个位映射到各个引脚。
  3. 您需要克服 Integer 和相关类型的范围限制 - 通常仍然是 32 位。 Unsigned 可以声明为覆盖任何所需的字宽 - 如果需要,可以为 47 位。

使用测试台提供的 sel = '0' 隐式初始值添加发生在第一个模拟周期中导致错误 - ghdl 在这里实际上是正确的。

溢出错误是由a和b的隐式初始值引起的,它们是整数'LEFT,就像sel的值来自bit'LEFT。

错误原因

IEEE 标准 1076-2008,5.2.3.1:

The same arithmetic operators are predefined for all integer types (see 9.2). It is an error if the execution of such an operation (in particular, an implicit conversion) cannot deliver the correct result (that is, if the value corresponding to the mathematical result is not a value of the integer type).

这与约束范围标量越界的错误相同。这是一个 运行 时间错误。

隐式初始值的来源

参见 6.4.2.3 信号声明:

In the absence of an explicit default expression, an implicit default value is assumed for a signal of a scalar subtype or for each scalar subelement of a composite signal, each of which is itself a signal of a scalar subtype. The implicit default value for a signal of a scalar subtype T is defined to be that given by T'LEFT.

整数的 T'LEFT 在包 std.standard (16.3) 中针对整数类型和位类型(均为标量)指定。整数类型声明为上升范围,最大负数(整数'低)是整数'LEFT 值。 (另见 5.2.3.2 预定义整数类型)。

并非所有模拟器都能捕捉到该错误

对于 ghdl-0.36dev 的 llvm 后端版本

/usr/local/bin/ghdl -a alu_tb.vhdl
/usr/local/bin/ghdl -e alu_tb_test_cfg
/usr/local/bin/ghdl -r alu_tb_test_cfg --stop-time=70ns   --wave=alu_tb_test_cfg.ghw
./alu_tb_test_cfg:info: simulation stopped by --stop-time

这给了我们:

alu 在 运行 时不产生错误(像 EML 的 Questa/Modelsim)。

测试不同的 ghdl 版本表明 ghdl 的 mcode 后端版本执行错误检查,而其他版本不执行。

我们还看到,要准确确定发生了什么,需要能够使用测试平台重现问题。

如何处理?

有些模拟器可以捕捉到错误,有些则不能,这是一个可移植性问题。

您可以做几件事:

  • 为 a 和 b 提供不会导致溢出的初始值。

  • 在出现不会溢出的操作数值之前,不允许发生会溢出的操作。在这种情况下,您可以将 sel 的初始值设置为“1”。一个负数从自身减去是零,包括当 a 和 b 都是整数'LEFT.

测试台中的一个简单修复方法是更改​​ sel 的声明以包含初始值“1”,这将调用减法:

  signal sel : bit := '1';  -- CHANGED, added initial value for subtraction

进行此更改会在 ghdl 的 mcode 版本中产生与我们在 llvm 版本中看到的几乎相同的波形(在时间零时从“1”到“0”的转换是您可以看到的唯一区别) .

为 a 和 b 提供初始值将消除时间 0 处的总线信号变化 'crosses'。

对于纯行为模型,您可以添加另一个条件,要求在添加或减去之前发生模拟循环。您还可以使用其他形式的敏感度列表(使用等待语句)并等待 0 ns;在 a 和 b 上有有效值之前不要继续。

时钟系统自然会阻塞操作,直到时钟的上升沿(您可以在第一个模拟周期中停止发生)。

声明一个更大的整数怎么样?

您不能声明一个新的整数类型,其范围边界不能用包含在最大整数类型范围内的值表示。唯一的预定义整数类型是整数类型(5.2.3.2 预定义整数类型)。此限制基于 5.2.3.1:

Integer literals are the literals of an anonymous predefined type that is called universal_integer in this standard. Other integer types have no literals. However, for each integer type there exists an implicit conversion that converts a value of type universal_integer into the corresponding value (if any) of the integer type (see 9.3.6).
...
The same arithmetic operators are predefined for all integer types (see 9.2). It is an error if the execution of such an operation (in particular, an implicit conversion) cannot deliver the correct result (that is, if the value corresponding to the mathematical result is not a value of the integer type).

这也不是治愈方法。是什么阻止某人使用新类型执行操作并以同样的方式被咬?