Most VHDL designers write ‘something
downto something’ in their code all the time. But what does this
downto actually mean? And what is the difference with
to specify the direction of ranges in VHDL.
downto is descending (going down);
to is ascending (going up).
When used in arrays,
downto corresponds to little endian. This means that the least significant bit is stored at the lowest position. So in
L downto R,
L corresponds to the Most Significant Bit (MSB) and
R to the Least Significant Bit (LSB).
to corresponds to big endian. So in
L to R,
L is the LSB and
R the MSB.
In the VHDL snippet below,
little_endian is initially
big_endian is initially
signal big_endian : std_logic_vector(0 to 7) := ( 0 => '1', others => '0'); signal little_endian : std_logic_vector(7 downto 0) := ( 0 => '1', others => '0');
There are advantages to both
to. However, most VHDL code I have seen, favors
downto. The most important message is to stick to one direction for ranges. If you can, avoid mixing
to because this leads to confusion and bugs.
There are multiple ways to assign values to arrays, with varying degrees of elegance.
If have summarized a few ways below for following declaration:
signal my_array : std_logic_vector(7 downto 0);
-- assign to element my_array(0) <= '0'; -- assign slice my_array(3 downto 0) <= "0001"; -- positional association my_array <= ('0', '0', '0', '0', '0', '0', '0', '1'); -- named association variants my_array <= (0 => '1', others => '0'); my_array <= (0 => '1', 1 => '1', others => '0'); my_array <= (0 | 1 => '1', others => '0'); my_array <= (1 downto 0 => '1', others => '0');
Note that you can not mix positional and named association.
A range is called a null range, when it specifies a empty subset. For descending ranges (
L downto R), this is when
L < R. For ascending ranges (
L to R) this is when
L > R. This sometimes happens for certain function parameters or generics. Your simulator should handle this without problems.
A typical special case is a range with just one element:
signal single_element_array : std_logic_vector(0 downto 0);
Be careful with assignments:
single_element_array <= '0'; --illegal, single_element_array is still an array single_element_array <= ('0'); --also illegal, the optional parentheses do not turn this into an array single_element_array <= (0 => '0'); -- OK single_element_array <= (others => '0'); -- OK single_element_array(0) <= '0'; -- OK
Depending on index type of ranges you can use negative index values to get specific element of array.
type neg_index_array is array (-5 to 5) of integer; variable my_neg_array : neg_index_array; my_neg_array(-5) := 15; my_neg_array(-3 to -1) := 12 & 13 & 14; -- & - concatenation my_neg_array(0 to 1) := my_neg_array(4 to 5); -- illegal, Slice range direction (downto) does not match slice prefix direction (to). my_neg_array(0 to 1) := my_neg_array(3 downto 2);
VHDL also allows you to define unconstrained arrays, which are array declarations where the type of the index values is specified, but not the bounds.
A typical examples is
type std_ulogic_vector is array ( natural range <> ) of std_ulogic;
std_ulogic_vector as an array type with indexes of type natural. The bounds can be specified via object creation or via a subtype.
subtype myarray is std_ulogic_vector(7 downto 0); constant myconst : std_ulogic_vector(7 downto 0) := (others => '0');
The index type of ranges does not need to be numeric, but it has to be discrete. For example: you can specify ranges for enumeration values, and use them to specify arrays.
type myEnum is (a,b,c,d,e); type myArray is array(a to d) of integer; constant myArrayConstant : myArray := (a => 42, others => 0);