VHDL complement counter issues: converting std_logic to integer

sdaau picture sdaau · Jul 15, 2011 · Viewed 9.9k times · Source

Essentially, my question is: "can this not be done any easier?"; and what 'this' is, follows below (code too):

I wanted to have a sort of a 'complement' counter function, implemented in VHDL, which would basically invert/complement/not the counter value in each step, giving slightly richer bit patterns for testing. Of course, I wanted this as synthesizable (so the counter value could be possibly assigned to pins) and portable code (i.e. implemented only IEEE libraries, no STD_LOGIC_ARITH). I also don't want to by default treat all as unsigned (so I'd like to avoid STD_LOGIC_UNSIGNED).

In brief, this counter could be described as: given initial value C[0], then values at each clock tick would be:

C[i+1] = not(C[i]) + ( ( C[i]<(Cmax/2) ) ? 0 : 1 )

... or given C is 16-bit wide (which will result with unsigned Cmax = 65535 and Cmax/2 = 32768), it could also be written as:

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

The trick here is that the counter should only increment once - if it increases for both complementary and 'normal' ranges, then no changes will happen (equation will 'oscillate' between two values).

 

So, given that the check C[i]<(Cmax/2) is basically the same as checking the most significant (15th) bit of C, I thought that I could easily implement something like this in VHDL using something like:

Y <= not(Y) + Y(15);

 

Boy, was I wrong about "easily" :)

First problem is that it is possible the above equation could end up at 65535+1, in which case the result will need 17 bits (i.e. overflow); in my case, I'd simply want to truncate/ignore any 'carry bits'.

This leads into the problem of what to use:

  • std_logic_vector has complement not() defined; but it doesn't have + (addition) defined
  • natural/integer may internally take up 32 bits, and as such bit width for them is not necesarilly specified; they support arithmetic +, but no complement not()
  • I tried unsigned too, had some problems also (cannot remember which)

The 15th (MSB) bit can only be extracted when Y is std_logic_vector, in which case, Y(15) is a single std_logic - but then, it needs to be converted to integer type, because otherwise addition + is not defined :|

So, my current solution (below) first has two copies of the counter register; one is SIGNAL wCntReg : STD_LOGIC_VECTOR(15 DOWNTO 0); the other is SIGNAL tmp_na : natural. Then:

  • There are two clocks: one 'master' @ 50 MHz, the other is the 'counter' clock: the master 16-times frequency divided (3.125 MHz).
  • The 'counter' clock should activate calculation of counter values on falling edge
  • The calculation is performed via natural variable (which copies from the STD_LOGIC_VECTOR one)
  • Apparently, std_logic can only be converted to integer if its converted to std_logic_vector first (I was lucky to find the vectorize function on the net).

The nastiest part here was how to feed back the natural variable value, back to the STD_LOGIC_VECTOR one; the only working command I could construct is:

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

...; however, note that this command basically 'sets' the value, which will be 'effectuated' next time this same command runs. Thus, it cannot run in the 'counter' clock process - in the code below, I have it in the faster 'master' clock process.

Finally, the code below does work (goes through behavioral simulation in ISE WebPack) - but, I'd still like to know if there is a more straightforward way to solve this.

Thanks in advance for any answers, Cheers!

 

The code:

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

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 -----------------------------

Answer

Martin Thompson picture Martin Thompson · Jul 15, 2011

Firstly, full marks for avoiding not use std_logic_arith!

If you define your vectors as unsigned then (outside of a process) all that is required is this:

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

and you can assign cn_plus_1 to a std_logic_vector thus:

wCntReg <= std_logic_vector(cn_plus_1);

Here's a couple of complete examples of VHDL idiomatic ways of doing it:

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;

or in a synchronous process:

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;

Whenever you find yourself converting a lot between std_logic_vectors and integers (or similar), you are usually working with actual numbers. Work with either unsigned/signed vectors or with integers. If you need a vector at the end of it all, convert it one and for all at the end. It's not pleasant to look at necessarily. But first ask if the thing you are sending the value to ought to be using a numerical type of some sort on its interface. Only if it truly is a 'bag-of-bits' is a std_logic_vector the best type. The name wCntReg sound like a count value to me, so it should have a numeric type to my mind.

This code you have:

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

is slightly worse than it needs to be:

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

ought to work fine.

Finally, you comment on this only coming into effect on the next clock tick is how VHDL works - signals are only updated when some time passes (often this is only at the end of a process). If you want to make use of the new value before then, use a variable - they update straight away.

So if your tempna were a variable, you could do the wCntReg assignment straight after.

For completeness: another way around this (which is usually a kludge IME) is to wait for 0 ns; after the signal assignment. This causes some time to pass from the point of view of updates, so all other delta cycles will execute for the current time (including the signal assignment you want to propgate) time will move on (by 0ns!) and a new delta cycle can begin.