VHDL补码计数器问题:将std_logic转换为整数

4
基本上,我的问题是:“这个事情不能更简单吗?”,而下面的代码则说明了‘这个事情’是什么:
我想要实现一种类似于'补充'计数器的功能,在VHDL中实现,其基本上会在每一步中反转/补充/否定计数器值,从而为测试提供稍微丰富一些的位模式。当然,我希望这个功能是可综合的(因此计数器值可能被分配到引脚),并且是可移植的代码(即仅实现IEEE库,没有STD_LOGIC_ARITH)。我也不想默认将所有内容都视为无符号(因此我想避免使用STD_LOGIC_UNSIGNED)。
简言之,这个计数器可以描述为:给定初始值C [0],然后在每个时钟周期的值将是:
C[i+1] = not(C[i]) + ( ( C[i]<(Cmax/2) ) ? 0 : 1 )

如果C是16位宽(这将导致无符号的Cmax = 65535和Cmax/2 = 32768),则可以写成以下形式:

C[i+1] = 65535 - C[i] + ( ( C[i]<32768 ) ? 0 : 1 )

这里的技巧在于计数器只应该增加一次 - 如果它同时增加补码和“正常”范围,那么就不会发生任何更改(方程将在两个值之间“振荡”)。
因此,既然检查 C[i] < (Cmax/2) 基本上与检查C的最高位(第15位)相同,我认为我可以轻松使用VHDL实现类似以下的内容:
Y <= not(Y) + Y(15);

哎呀,我对“轻松”这个词的理解完全错误了 :)

首先问题在于上述方程可能会得出65535+1的结果,这种情况下结果需要17位(即溢出);在我的情况下,我只想截断/忽略任何“进位位”。

这导致了使用什么的问题:

  • std_logic_vector有补码not()定义;但它没有加法+定义
  • natural/integer可能内部占用32位,因此它们的位宽并未必定;它们支持算术+,但没有补码not()
  • 我也尝试过unsigned,但遇到了一些问题(记不清是哪些问题了)

当Y是std_logic_vector时才能提取第15位(MSB),在这种情况下,Y(15)是单个std_logic - 但是,它需要转换为integer类型,否则加法+没有定义 :|

所以,我的当前解决方案(如下)首先有两个计数器寄存器的副本;一个是SIGNAL wCntReg : STD_LOGIC_VECTOR(15 DOWNTO 0);另一个是SIGNAL tmp_na : natural。然后:

  • 有两个时钟:一个“主”时钟@50 MHz,另一个是“计数器”时钟:主时钟的16倍频率分频(3.125 MHz)。
  • “计数器”时钟应在下降沿激活计算计数器值
  • 计算通过natural变量执行(它从STD_LOGIC_VECTOR中复制)
  • 显然,只有将std_logic转换为std_logic_vector后,才能将其转换为integer(我很幸运在网上找到了vectorize函数)。

最让人头疼的部分是如何将natural变量值反馈到STD_LOGIC_VECTOR中;我能构造的唯一有效命令是:

wCntReg <= std_logic_vector(to_unsigned(natural'pos(tmp_na), wCntReg'length));

然而,请注意,此命令基本上是“设置”值,该值将在下次运行相同命令时“生效”。因此,它无法在“计数器”时钟进程中运行 - 在下面的代码中,我将其放在更快的“主”时钟进程中。

最后,下面的代码确实有效(在ISE WebPack的行为模拟中通过),但我仍然想知道是否有更简单的方法来解决这个问题。

提前感谢任何答案, 干杯!

 

代码:

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

library IEEE;
  use IEEE.STD_LOGIC_1164.ALL;
  -- use IEEE.STD_LOGIC_ARITH.ALL;
  -- use IEEE.STD_LOGIC_UNSIGNED.ALL;
  use IEEE.NUMERIC_STD.ALL;


ENTITY complement_count_test_tbw IS
END complement_count_test_tbw;

ARCHITECTURE testbench_arch OF complement_count_test_tbw IS

  -- http://www.ingenieurbuero-eschemann.de/downloads/ipicregs/example/vhdl/test/timer_regs_tb.vhd
  -- convert std_logic to std_logic_vector(0 downto 0)
  function vectorize(s: std_logic) return std_logic_vector is
  variable v: std_logic_vector(0 downto 0);
  begin
      v(0) := s;
      return v;
  end;


  -- DECLARE REGISTERS ==========================

  -- 'wires'
  SIGNAL wtCLK : std_logic := '0';

  -- counter register: 16 bit
  SIGNAL wCntReg : STD_LOGIC_VECTOR(15 DOWNTO 0) := (others => 'Z');

  -- temporary 'natural' copy of counter register
  -- http://www.velocityreviews.com/forums/t21700-std_logic_vector-to-unsigned-type-casting.html
  SIGNAL tmp_na : natural;


  -- clock parameters
  constant PERIODN : natural := 20; -- can be real := 20.0;
  constant PERIOD : time := PERIODN * 1 ns;
  constant DUTY_CYCLE : real := 0.5;
  constant OFFSET : time := 100 ns;

  -- freq divisor; with initial values
  constant fdiv : natural := 16;
  SIGNAL fdiv_cnt : natural := 1;
  SIGNAL wfdiv_CLK : std_logic := '0';

BEGIN

  -- initializations of connections:

  -- instances of components, and their wiring (port maps)...
  -- END instances of components, and their wiring (port maps)...


  -- PROCESSES (STATE MACHINES) CODE =========

  -- clock process for generating CLK
  PROCESS
  BEGIN

    WAIT for OFFSET;

    CLOCK_LOOP : LOOP
      wtCLK <= '0';
      -- MUST refresh counter reg here with value of tmp_na
      wCntReg <= std_logic_vector(to_unsigned(natural'pos(tmp_na), wCntReg'length));
      WAIT FOR (PERIOD - (PERIOD * DUTY_CYCLE));
      wtCLK <= '1';
      WAIT FOR (PERIOD * DUTY_CYCLE);
    END LOOP CLOCK_LOOP;
  END PROCESS;

  -- freq divided clock
  freq_divisor: PROCESS(wtCLK)
  BEGIN
    IF rising_edge(wtCLK) THEN -- posedge
      IF fdiv_cnt = fdiv THEN
        -- reset
        fdiv_cnt <= 1 ;
        wfdiv_CLK <= not(wfdiv_CLK);
      ELSE
        fdiv_cnt <= fdiv_cnt + 1;
      END IF;
    END IF;
  END PROCESS freq_divisor;



  -- sim: count
  PROCESS
  BEGIN

    WAIT for 10 ns;

    tmp_na <= 125;

    WAIT for 10 ns;


    TESTCOUNT_LOOP: LOOP

      -- change counter on negedge of freq. divided clock
      WAIT until falling_edge(wfdiv_CLK);

      tmp_na <= to_integer(unsigned(not(wCntReg))) + to_integer(unsigned(vectorize(wCntReg(15))));

      WAIT for 10 ns;

    END LOOP TESTCOUNT_LOOP;

  END PROCESS;

  -- END PROCESSES (STATE MACHINES) CODE =====

-- END IMPLEMENT ENGINE of 'CORE' ===============
END testbench_arch;
-- END ARCHITECTURE -----------------------------

3
如果你想要创造出更多"丰富的测试模式",你有考虑过使用伪随机二进制序列生成器吗? - Chiggs
嗨@Chiggs,谢谢你 - 我不知道PRBS生成器是什么 :) 这听起来确实不错 - 只是在这种情况下,我想要简单的确定性值猜测(PRBS是确定性的,链接注释说,但我不认为我能像OP示例中那样轻松地猜测“下一个值”)。再次感谢 - 干杯! - sdaau
1个回答

6

首先,避免使用std_logic_arith得满分!

如果你将你的向量定义为unsigned,那么(在进程之外)所需的仅是:

  cn_plus_1 <= not cn when cn < halfc else (not cn) + 1;

您可以将cn_plus_1分配给std_logic_vector,如下所示:

wCntReg <= std_logic_vector(cn_plus_1);

这里有几个使用VHDL惯用方式的完整示例:
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
entity funny_counter1 is
    port (
        cn: IN unsigned(15 downto 0);
        cn_plus_1 : out unsigned(15 downto 0));
end entity funny_counter1;

architecture a1 of funny_counter1 is
    constant halfc : unsigned(cn'range) := (cn'high => '1', others => '0');
begin  -- architecture a1
    cn_plus_1 <= not cn when cn < halfc else (not cn) + 1;
end architecture a1;

或者在同步进程中:

library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;

entity funny_counter is
    port (
        clk   : in  std_logic;
        reset : in  std_logic;
        cout  : out unsigned(15 downto 0));
end entity funny_counter;

architecture a1 of funny_counter is
    constant halfc : unsigned(cout'range) := (cout'high => '1', others => '0');
begin
    process (clk) is
        variable c   : unsigned(15 downto 0);
        variable add : integer range 0 to 1;
    begin  -- process
        if rising_edge(clk) then  -- rising clock edge
            if reset = '1' then
                c := (others => '0');
            else
                add := 0;
                if c < halfc then
                    add := 1;
                end if;
                c := (not c) + add;
            end if;
            cout <= c;
        end if;
    end process;
end architecture a1;

每当你发现自己在std_logic_vectorinteger之间频繁转换时(或类似情况),通常是在处理实际数字。应该使用unsigned/signed向量或整数进行操作。如果最终需要一个向量,最好在最后一次转换。这可能不太舒适,但首先询问接收值的事物是否应该在其接口上使用某种数字类型。只有当它确实是“位包”时,std_logic_vector才是最佳类型。名称wCntReg听起来像计数值,所以在我看来应该具有数字类型。
你所拥有的代码:
wCntReg <= std_logic_vector(to_unsigned(natural'pos(tmp_na), wCntReg'length));

比它需要的稍微差一些:

wCntReg <= std_logic_vector(to_unsigned(tmp_na, wCntReg'length));

应该可以正常工作。

最后,你对于这只在下一个时钟周期生效的评论是VHDL的工作方式 - 信号只有在经过一段时间后才会更新(通常只在进程结束时)。如果您想在此之前利用新值,请使用变量 - 它们会立即更新。

因此,如果您的tempna是一个变量,您可以直接进行wCntReg赋值。

为了完整起见:另一种解决此问题的方法(通常是一个临时解决方法)是在信号赋值后wait for 0 ns;。 这会导致从更新的角度来看经过一些时间,因此所有其他增量周期将执行当前时间(包括您想要传播的信号分配),时间将继续推移(0ns!),并且可以开始新的增量周期。


嗨@Martin Thompson,非常感谢您提供的深入而有启发性的答案!只是注意,我在文章中已经声明“没有 std_logic_arith”;而我在代码中保持它被注释的原因恰恰是为了提醒自己避免使用它,以防我偶然在网络上找到使用它的例子:)再次感谢,希望我能给这个回答加上100分:)干杯!! - sdaau
@sdaay - 感谢您的评论 - 对于_arith的参考,我误读了您的问题,现在我明白您非常清楚! 我会相应地调整我的答案 :) - Martin Thompson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接