11
\$\begingroup\$

In the comment thread on an answer to this question: Wrong outputs in VHDL entity it was stated:

"With integers you don't have control or access to the internal logic representation in the FPGA, while SLV lets you do tricks like utilizing the carry chain efficiently"

So, in what circumstances have you found it neater to code using a vector of bits representation than using integer s in order to access the internal representation? And what advantages did you measure (in terms of chip area, clock frequency, delay, or otherwise.)?

\$\endgroup\$
  • \$\begingroup\$ I think it's something hard to measure, since apparently it's just a matter of control over low-level implementation. \$\endgroup\$ – clabacchio Mar 20 '12 at 16:54
5
\$\begingroup\$

I've written the code suggested by two other posters in both vector and integer form, taking care to have both versions operate in as similar way as possible.

I compared the results in simulation and then synthesised using Synplify Pro targetting Xilinx Spartan 6. Code samples below are pasted from working code, so you should be able to use them with your favourite synthesiser and see if it behaves the same.


Downcounters

Firstly, the downcounter, as suggested by 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;

Vector architecture:

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;

Integer architecture

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;

Results

Code-wise, the integer one seems preferable to me as it avoid the to_unsigned() calls. Otherwise, not much to choose.

Running it through Synplify Pro with top := 16#7fff_fffe# produces 66 LUTs for the vector version and 64 LUTs for the integer version. Both versions make much use of the carry-chain. Both report clock speeds in excess of 280MHz. The synthesiser is quite capable of establishing good use of the carry chain - I verified visually with the RTL viewer that similar logic is produced with both. Obviously an up-counter with comparator will be bigger, but that'd be the same with both integers and vectors again.


Dividing by 2**n counters

Suggested by 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;

Vector architecture

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;

Integer architecture

You have to jump through some hoops to avoid just using to_unsigned and then picking bits off which would clearly produce the same effect as above:

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;

Results

Code-wise, in this case, the vector version is clearly better!

In terms of synthesis results, for this small example, the integer version (as ajs410 predicted) does produce 3 extra LUTs as part of the comparators, I was too optimistic about the synthesiser, although it is working with an awfully obfuscated piece of code!


Other uses

Vectors are a clear win when you want arithmetic to wrap-around (counters can be done as a single line even):

vec <= vec + 1 when rising_edge(clk);

vs

if int < int'high then 
   int := int + 1;
else
   int := 0;
end if;

although at least it's clear from that code that the author intended a wrap around.


Something I've not used in real-code, but pondered:

The "naturally-wrapping" feature can also be utilised for "computing through overflows". When you know that the output of a chain of additions/subtractions and multiplications is bounded, you don't have to store the high bits of the intermediate calculations as (in 2-s complement) it'll come out "in the wash" by the time you get to the output. I'm told that this paper contains a proof of this, but it looked a bit dense for me to make a quick assessment! Theory of Computer Addition and Overflows - H.L. Garner

Using integers in this situation would cause simulation errors when they wrapped, even though we know they'll unwrap in the end.


And as Philippe pointed out, when you need a number bigger than 2**31 you have no choice but to use vectors.

\$\endgroup\$
  • \$\begingroup\$ In the second code block you have variable c : unsigned(32 downto 0);...isn't c a 33 bit variable then? \$\endgroup\$ – clabacchio Mar 26 '12 at 13:59
  • \$\begingroup\$ @clabacchio : yes, that allows access to the 'carry-bit' to see the wrap-around. \$\endgroup\$ – Martin Thompson Mar 26 '12 at 14:14
5
\$\begingroup\$

When writing VHDL, I highly recommend using std_logic_vector (slv) instead of integer (int) for SIGNALS. (On the other hand, using int for generics, some constants, and some variables can be highly useful.) Simply put, if you declare a signal of type int, or have to specify a range for an integer then you're probably doing something wrong.

The problem with int is that the VHDL programmer has no idea what the internal logic representation of the int is, and so we cannot take advantage of it. For example, if I define an int of range 1 to 10 I have no idea how the compiler encodes those values. Hopefully it would be encoded as 4 bits, but we don't know much beyond that. If you could probe the signals inside the FPGA it might be encoded as "0001" to "1010", or encoded as "0000" to "1001". It's also possible that it is encoded in a way that makes absolutely no sense to us humans.

Instead we should just use slv instead of int, because then we have control over the encoding and also have direct access to the individual bits. Having direct access is important, as you'll see later.

We could just cast an int to slv whenever we need access to the individual bits, but that gets really messy, very fast. That's like getting the worst of both worlds instead of the best of both worlds. You're code will be difficult for the compiler to optimize, and almost impossible for you to read. I don't recommend this.

So, as I said, with slv you have control over the bit encodings and direct access to the bits. So what can you do with this? I'll show you a couple of examples. Let's say that you need to output a pulse once every 4,294,000,000 clocks. Here's how you would do this with 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;

And the same code using 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;

Most of this code is identical between int and slv, at least in the sense of the size and speed of the resulting logic. Of course one is counting up and the other is counting down, but that's not important for this example.

The difference is in "the important line".

With the int example, this is going to result in a 32-input comparator. With 4-Input LUT's that the Xilinx Spartan-3 uses, this is going to require 11 LUTs and 3 levels of logic. Some compilers might convert this into a subtraction which will use the carry chain and span the equivalent of 32 LUT's but might run faster than 3 levels of logic.

With the slv example, there is no 32-bit comparison so it's "zero LUT's, zero levels of logic". The only penalty is that our counter is one extra bit. Because the additional timing for this extra bit of counter is all in the carry chain, there is "almost zero" additional timing delay.

Of course this is an extreme example, as most people wouldn't be using a 32-bit counter in this way. It does apply to smaller counters, but the difference will be less dramatic although still significant.

This is only one example of how to utilize slv over int to get faster timing. There are many other ways to utilize slv-- it only takes some imagination.

Update: Added stuff to address Martin Thompson's comments about using int with "if (count-1) < 0"

(Note: I assume you meant "if count<0", since that would make it more equivalent to my slv version and remove the need for that extra subtraction.)

Under some circumstances this might generate the intended logic implementation but it is not guaranteed to work all of the time. It will depend on your code and how your compiler encodes the int value.

Depending on your compiler, and how you specify the range of your int, it is entirely possible that an int value of zero does not encode to a bit vector of "0000...0000" when it makes it into the FPGA logic. For your variation to work, it must encode to "0000...0000".

For example, let's say you define an int to have a range of -5 to +5. You are expecting a value of 0 to be encoded into 4 bits like "0000", and +5 as "0101" and -5 as "1011". This is the typical twos-complement encoding scheme.

But don't assume that the compiler is going to use twos-complement. Although unusual, ones-complement could result in "better" logic. Or, the compiler could use a sort of "biased" encoding where -5 is encoded as "0000", 0 as "0101", and +5 as "1010".

If the encoding of the int is "correct" then the compiler will likely infer what to do with the carry bit. But if it is incorrect then the resulting logic will be horrible.

It's possible that using an int in this way could result in reasonable logic size and speed, but it is not a guarantee. Switching to a different compiler (XST to Synopsis for example), or going to an different FPGA architecture could cause the exact wrong thing to happen.

Unsigned/Signed vs. slv is yet another debate. You can thank the U.S. Government committee for giving us so many options in VHDL. :) I use slv because that is the standard for interfacing between modules and cores. Other than that, and some other cases in simulations, I don't think there is a huge benefit to using slv over signed/unsigned. I'm also not sure if signed/unsigned support tri-stated signals.

\$\endgroup\$
  • 4
    \$\begingroup\$ David, those code fragments are not equivalent. One counts up from zero to an arbitrary number (with an expensive comparison operator); the other counts down to zero from an arbitrary number. You can write both algorithms with either integers or vectors, and you will get bad results when counting towards an arbitrary number and good results counting towards zero. Note that software engineers would also count down to zero if they need to squeeze out a little more performance out of a hot loop. \$\endgroup\$ – Philippe Mar 12 '12 at 15:57
  • 1
    \$\begingroup\$ Like Philippe, I'm not convinced that this is a valid comparison. If the integer example counted down and used if (count-1) < 0 I'd think the synthesiser will infer the carry out bit and produce much the same circuit as your slv example. Also, shouldn't we be using the unsigned type these days :) \$\endgroup\$ – Martin Thompson Mar 12 '12 at 16:22
  • 2
    \$\begingroup\$ @DavidKessner you have certainly provided a THOROUGH and well reasoned answer, you've got my +1. I have to ask though... why are you worried about optimization throughout the design? Wouldn't it be better to focus your efforts on the areas of code that require it or to focus on SLVs for interface points (entity ports) for compatibility? I know that in most of my designs I don't particularly care that LUT use is minimized, as long as it meets timing and fits the part. If I have particularly tight constraints I'd certainly be more conscious of optimal design, but not as a general rule. \$\endgroup\$ – akohlsmith Mar 23 '12 at 21:07
  • 2
    \$\begingroup\$ I am a bit surpriced by the number of up-votes on this answer. @bit_vector@ is certainly the correct abstraction level for modeling and optimizing micro-architectures, but a general recommendation agains "high-level" types such as @integer@ for signals and port is something I find strange. I've seen enough convoluted and unreadable code due to the lack of abstraction to know the value these features provide, and would be very sad if I had to leave them behind. \$\endgroup\$ – trondd Feb 20 '13 at 19:46
  • 2
    \$\begingroup\$ @david Excellent remarks. It's true we're still in the medieval age compared to software development in many ways, but from my experience with Quartus integrated synthesis and Synplify I don't think things are that bad. They are quite capable of handling lots of things like register retiming and other optimizations that improves performance while maintaining readability. I doubt the majority is targeting several toolchains and devices, but for your case I understand the requirement for the least common denominator :-). \$\endgroup\$ – trondd Feb 21 '13 at 19:15
2
+75
\$\begingroup\$

My advice is to try both, and then look at the synthesis, map, and place-and-route reports. These reports will tell you exactly how many LUTs each approach is consuming, they will also tell you the maximum speed at which the logic can operate.

I agree with David Kessner that you're at the mercy of your toolchain, and there is no "right" answer. Synthesis is black magic and the best way to know what happened is to carefully and thoroughly read the reports that are produced. Xilinx tools even allow you to see inside the FPGA, right down to how each LUT is programmed, how the carry chain is connected, how the switch fabric connects all the LUTs, etc.

For another dramatic example of Mr. Kessner's approach, imagine that you want to have multiple clock frequencies at 1/2, 1/4, 1/8, 1/16, etc. You could use an integer that constantly counts up every cycle, and then have multiple comparators against that integer value, with each comparator output forming a different clock division. Depending on the number of comparators, the fanout could become unreasonably large and start consuming extra LUTs just for buffering. The SLV approach would just take each individual bit of the vector as an output.

\$\endgroup\$
1
\$\begingroup\$

One obvious reason is that signed and unsigned allow larger values than the 32 bit integer. That is a flaw in the VHDL language design, which is not essential. A new version of VHDL could fix that, requiring integer values to support arbitrary size (akin to Java's BigInt).

Other than that, I'm very interested to hear about benchmarks that perform differently for integer as compared to vectors.

BTW, Jan Decaluwe wrote a nice essay on this: These Ints are made for Countin'

\$\endgroup\$
  • \$\begingroup\$ Thanks Philippe (although that's not a "better through access to the internal representation" application, which is what I'm really after...) \$\endgroup\$ – Martin Thompson Mar 12 '12 at 16:24
  • \$\begingroup\$ That essay is nice, but completely ignores the underlying implementation and resulting logic speed and size. I agree with most of what Decaluwe says, but he doesn't say anything about the results of synthesis. Sometimes the results of synthesis don't matter, and sometimes they do. So it's a judgement call. \$\endgroup\$ – user3624 Mar 12 '12 at 17:16
  • 1
    \$\begingroup\$ @David, I agree that Jan does not go into full detail on how synthesis tools react to integers. But, no it is not a judgment call. You can measure the synthesis results and determine the results of your given synthesis tool. I think the OP meant his question as a challenge for us to produce code fragments and synthesis results that demonstrate a difference (if any) in performance. \$\endgroup\$ – Philippe Mar 12 '12 at 21:48
  • \$\begingroup\$ @Philippe No, I meant that it's a judgement call on if you care at all about the synthesis results at all. Not that the synthesis results themselves are a judgement call. \$\endgroup\$ – user3624 Mar 13 '12 at 0:05
  • \$\begingroup\$ @DavidKessner OK. I misunderstood. \$\endgroup\$ – Philippe Mar 13 '12 at 8:17

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.