2

我正在尝试编写一个非常简单的模块,其中包含两个整数输入和一个选择输入。When select is 0 output should be sum of inputs and when select is 1 output should be difference of them. 我将使用 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.
4

2 回答 2

2

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

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

错误的原因

IEEE 标准 1076-2008,5.2.3.1:

为所有整数类型预定义了相同的算术运算符(见 9.2)。如果执行这样的操作(特别是隐式转换)不能提供正确的结果(即,如果对应于数学结果的值不是整数类型的值),则为错误。

这与约束范围标量违反其界限的错误相同。这是一个运行时错误。

隐含初始值来自哪里

见 6.4.2.3 信号声明:

在没有显式默认表达式的情况下,为标量子类型的信号或复合信号的每个标量子元素假定隐含默认值,每个标量子元素本身都是标量子类型的信号。标量子类型 T 的信号的隐含默认值定义为由 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_tb_test_cfg.png

alu 在运行时不会生成错误(如 EML 的 Questa/Modelsim)。

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

我们还看到,准确确定发生了什么需要使用测试平台复制问题的能力。

如何处理?

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

你可以做几件事:

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

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

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

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

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

为 a 和 b 提供初始值将消除时间 0 的总线信号变化“交叉”。

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

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

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

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

整数文字是匿名预定义类型的文字,在本标准中称为 Universal_integer。其他整数类型没有文字。然而,对于每个整数类型,都存在一个隐式转换,它将universal_integer 类型的值转换为整数类型的相应值(如果有的话)(见9.3.6)。
...
为所有整数类型预定义了相同的算术运算符(见 9.2)。如果执行这样的操作(特别是隐式转换)不能提供正确的结果(即,如果对应于数学结果的值不是整数类型的值),则为错误。

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

于 2018-01-24T16:12:10.053 回答
0

编辑:看看使用 MCVE 有多容易?(最小完整/可编译的可验证示例)

现在只需添加

 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 而不是普通的等待)

ghdl --version
GHDL 0.35-dev (2017-03-01-252-g90f4fd21-dirty) [Dunoon edition]
使用 GNAT 版本编译:6.3.0
GCC 后端代码生成器,由 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 位)之间的溢出来设置延迟计数器,它默默地通过我尝试过的所有其他工具,产生不正确的计数(并且延迟比要求)。包括综合工具。想象一下:“距离爆炸还有 30 秒”。

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

在您的测试用例中发生的情况是,因为您使用整数,并且没有显式初始化,所以整数被初始化为 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静默溢出产生错误结果是一件坏事,但这似乎是少数人的意见!


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

使用整数的范围子类型。例如,如果您的输入范围为 0 到 100,则您的输出范围为 0 和 200。

例如,

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

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

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

然后,如上所述,总和的适当范围类型避免了溢出的可能性——通过构造正确,避免了许多难以测试的用例。

但是请注意“增长”这个词:您通常无法在设计中无限地传播增加的字宽。但至少您可以控制缩小范围的方式和时间,例如通过明确的超出范围检查、饱和算术或截断/舍入。

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

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

  1. 出于某种原因,您需要访问各个位(std_logic信号),例如移位或逻辑运算
  2. 您需要与std_logic_vector信号交换,例如通过总线。请注意,范围自然可以用作端口,例如寻址内部存储器 - 但您通常需要std_logic_vector外部端口,以便各个位映射到各个引脚。
  3. 您需要克服范围限制Integer和相关类型 - 通常仍然是 32 位。Unsigned可以声明为覆盖任何所需的字宽 - 如果需要,可以为 47 位。
于 2018-01-23T11:26:40.167 回答