How to constrain this array

D

David Perry

Guest
I have a problem with constraining an array.
I have created the array type:

type V_array is array (integer range <>) of std_logic_vector(11 downto 0)

and created an array:

constant V_level_array : V_array := (x"0000",x"0100",x"2000");

so far so good , now the problem:

shared variable Vadc_array : V_array;

Gives me the error "Variable Vadc_array is not constrained", which is to be expected, but I can't figure out how to constrain it.
I want to make it the same size as V_level_array, but I don't want to just put 3 vectors in, I want it to reference the size of V_level_array, but how to do it is eluding me.

Any guidance here would be appreciated.
 
It syntax checks ok now :)
Why are the rules so confusing :(

Ta.
 
Hi,

David Perry wrote:
I have a problem with constraining an array. I have created the array
type:

type V_array is array (integer range <>) of std_logic_vector(11
downto 0)

and created an array:

constant V_level_array : V_array := (x"0000",x"0100",x"2000");

so far so good , now the problem:

shared variable Vadc_array : V_array;

Gives me the error "Variable Vadc_array is not constrained", which is
to be expected, but I can't figure out how to constrain it. I want to
make it the same size as V_level_array, but I don't want to just put
3 vectors in, I want it to reference the size of V_level_array, but
how to do it is eluding me.

Any guidance here would be appreciated.

Haven't tested it, but if you change your declaration to

shared variable Vadc_array : V_array(V_level_array'range);

.... does it work then?

HTH,
Sean
 
David Perry wrote:
It syntax checks ok now :)
Why are the rules so confusing :(

It's not really that confusing if you think about it:

- type V_array is array (integer range <>) of std_logic_vector(11
downto 0)

This defines an unconstrained array with each element being a
12-bit-vector. Unconstrained means that you have not specified how many
elements that array has. This is just like the definition of e.g. the
std_logic_vector type, which per se is also unconstrained.

So basically you now have two unconstrained types, your own V_array and
std_logic_vector, it's just the type of the element that's different.

For std_logic_vector, each element is of type std_logic;
for V_array, each element is of type std_logic_vector(11 downto 0).

But if you now want to create an actual object (signal, variable,
constant...) of one of these types, you have to specify the size of that
object (because in the end these objects may end up as logic or
registers or such, and then their size must be known; also the compiler
may need to reserve memory for these objects, also needs to know their
size for that). You can do that in a number of ways:
- you can explicitly specify the range in parentheses, like you do when
you write std_logic_vector(11 downto 0) or V_array(0 to 2)
- you can implicitly specify it during initialization, which you did here:

constant V_level_array : V_array := (x"0000",x"0100",x"2000");

Since you assigned it an initialization value with three elements, the
compiler now concludes that V_level_array should also hold 3 elements.
You could also in addition have specified it like this to prevent the
compiler from guessing:

constant V_level_array : V_array(0 to 2) := (x"0000",x"0100",x"2000");

Actually, I personally would do the latter, since I don't know what
range the compiler decides it should be. The only info it has is that
V_array should hold 3 elements, but that would also be the case for
ranges like (2 downto 0), (7 downto 5), (0 downto -2). I can only assume
in the LRM there's a default orientation and numbering defined, which I
don't know.

So now we know that in order to create an object (in your case the
shared variable), we need to specify the size it should have. In your
case you want it to be the same size as another object, and fortunately
in VHDL there are so-called attributes you can use to find that out.

- object'range gives you the range (for example "(11 downto 0)") of a
signal, constant or variable (or entity port)
- object'length gives you the total length, e.g. for a
std_logic_vector(11 downto 0) that would be 12
- object'left and object'right give you the left and right boundaries,
which would be 11 and 0, respectively for a std_logic_vector(11 downto 0)

There's a few others, you can look those up. All of these are very
handy, especially if you want to create flexible, re-usable designs.

Imagine you have some arithmetic module that you want to use in several
different projects. But sometimes you need it to work with an input data
width of 8 bit, sometimes 16 and sometimes 32 bits.
You could either create 3 different entities, one each for using 8, 16
and 32 bits as input data width. That's three times the work, and if you
fix bugs, you have to do that in all three. So, instead you could just
use unconstrained input ports, and inside the module have all signal
declarations and such be relative to the port widths, which you can
determine using the abovementioned attributes. That way you can write
one single module that automatically works for all input bit widths. The
actual width is determined by the signal that is connected to the
instance's input ports.

That's one application for what you're trying to do. Probably not what
you had in mind there, but maybe you'll head in that direction later.
 
Wow, comprehensive, thanks :)

It syntax checks ok now :)
Why are the rules so confusing :(

It's not really that confusing if you think about it:

Some of the syntax confuses me. An example, which took me a while to figure out was this:
I have a Fault vector (it's longer than 2 bits but I've shortened it for clarity)
Faults <= EXT_A & EXT_B;
That's fine, it's concatenated using &.
Then I want to output a similar vector to some outputs:
(A, B) <= Faults_mod;
This took me a while to work out!
The documentation I google for seems to only show the most basic things you might want to do.

But if you now want to create an actual object (signal, variable,
constant...) of one of these types, you have to specify the size of that
object (because in the end these objects may end up as logic or
registers or such, and then their size must be known; also the compiler
may need to reserve memory for these objects, also needs to know their
size for that). You can do that in a number of ways:
- you can explicitly specify the range in parentheses, like you do when
you write std_logic_vector(11 downto 0) or V_array(0 to 2)
- you can implicitly specify it during initialization, which you did here:

constant V_level_array : V_array := (x"0000",x"0100",x"2000");

Since you assigned it an initialization value with three elements, the
compiler now concludes that V_level_array should also hold 3 elements.
You could also in addition have specified it like this to prevent the
compiler from guessing:

constant V_level_array : V_array(0 to 2) := (x"0000",x"0100",x"2000");

Actually, I personally would do the latter, since I don't know what
range the compiler decides it should be. The only info it has is that
V_array should hold 3 elements, but that would also be the case for
ranges like (2 downto 0), (7 downto 5), (0 downto -2). I can only assume
in the LRM there's a default orientation and numbering defined, which I
don't know.

I deliberately wanted the compiler to determine the size, I'm using 'range later on to for loop the array. My philosophy was to allow the array size to change if needed without having to change anything else. A bit overkill for the module I've written, but I'm trying to get into good habits :)
(FYI, the 3 elements of the array aren't defined as hex, they are converted from real values and range limited, best practice and all that :) ).

- object'left and object'right give you the left and right boundaries,
which would be 11 and 0, respectively for a std_logic_vector(11 downto 0)

There's a few others, you can look those up. All of these are very
handy, especially if you want to create flexible, re-usable designs.

I keep looking at this:
http://www.csee.umbc.edu/portal/help/VHDL/attribute.html
Been using 'high and 'low, not so sure what the best to use is now.

Imagine you have some arithmetic module that you want to use in several
different projects. But sometimes you need it to work with an input data
width of 8 bit, sometimes 16 and sometimes 32 bits.

Uh-oh, someone mentioned generics to me a while ago, this has a familiar smell to it :)

You could either create 3 different entities, one each for using 8, 16
and 32 bits as input data width. That's three times the work, and if you
fix bugs, you have to do that in all three. So, instead you could just
use unconstrained input ports, and inside the module have all signal
declarations and such be relative to the port widths, which you can
determine using the abovementioned attributes. That way you can write
one single module that automatically works for all input bit widths. The
actual width is determined by the signal that is connected to the
instance's input ports.

I'm sure that will be something I will want to do at some point. I'm moving away from the schematic world where I did have to build multiple sized parts (it wasn't always convenient to just build the biggest and let the compiler remove the excess).

That's one application for what you're trying to do. Probably not what
you had in mind there, but maybe you'll head in that direction later.

A mate of mine handed me a module name and a list of IO and asked me to play with it, it's all a bit simple and specific.

Thanks for the help :)
 
On 7/18/2016 5:17 AM, David Perry wrote:
Wow, comprehensive, thanks :)

It syntax checks ok now :)
Why are the rules so confusing :(

It's not really that confusing if you think about it:

Some of the syntax confuses me. An example, which took me a while to figure out was this:
I have a Fault vector (it's longer than 2 bits but I've shortened it for clarity)
Faults <= EXT_A & EXT_B;
That's fine, it's concatenated using &.
Then I want to output a similar vector to some outputs:
(A, B) <= Faults_mod;
This took me a while to work out!
The documentation I google for seems to only show the most basic things you might want to do.

Aggregates can be PITA to use, but a bit easier in VHDL-2008.


But if you now want to create an actual object (signal, variable,
constant...) of one of these types, you have to specify the size of that
object (because in the end these objects may end up as logic or
registers or such, and then their size must be known; also the compiler
may need to reserve memory for these objects, also needs to know their
size for that). You can do that in a number of ways:
- you can explicitly specify the range in parentheses, like you do when
you write std_logic_vector(11 downto 0) or V_array(0 to 2)
- you can implicitly specify it during initialization, which you did here:

constant V_level_array : V_array := (x"0000",x"0100",x"2000");

Since you assigned it an initialization value with three elements, the
compiler now concludes that V_level_array should also hold 3 elements.
You could also in addition have specified it like this to prevent the
compiler from guessing:

constant V_level_array : V_array(0 to 2) := (x"0000",x"0100",x"2000");

Actually, I personally would do the latter, since I don't know what
range the compiler decides it should be. The only info it has is that
V_array should hold 3 elements, but that would also be the case for
ranges like (2 downto 0), (7 downto 5), (0 downto -2). I can only assume
in the LRM there's a default orientation and numbering defined, which I
don't know.

I deliberately wanted the compiler to determine the size, I'm using 'range later on to for loop the array. My philosophy was to allow the array size to change if needed without having to change anything else. A bit overkill for the module I've written, but I'm trying to get into good habits :)
(FYI, the 3 elements of the array aren't defined as hex, they are converted from real values and range limited, best practice and all that :) ).

- object'left and object'right give you the left and right boundaries,
which would be 11 and 0, respectively for a std_logic_vector(11 downto 0)

There's a few others, you can look those up. All of these are very
handy, especially if you want to create flexible, re-usable designs.

I keep looking at this:
http://www.csee.umbc.edu/portal/help/VHDL/attribute.html
Been using 'high and 'low, not so sure what the best to use is now.

'high and 'low are appropriate often. Once in a while 'left and 'right
are appropriate, other times 'range is best. The advantage of 'high and
'low is it will work for inputs defined using both "downto" and "to" if
you want to create an internal variable of known direction for example.
'range works defining variables that must match the input, also looping
in a direction to match the input. 'left and 'right will match the
input range for a loop as well. It all depends on what you need and how
flexible the code needs to be.

Here I used 'range to define the loop mainly because it is simpler to
type. "'left to 'right" would also have worked. "'left to 'right"
works because that is how the string needs to be interpreted. 'range
works because the string should be converted from the msd to the lsd
which matches the definition of the range whether using "downto" or
'to". Sorry for the line wrap.

-- Convert a string of hex digits to an unsigned vector of
indicated length
function Hex_to_unsigned (HexDigits : string; DigCnt : positive)
return unsigned is
variable temp : natural := 0;
variable unsgnd : unsigned (DigCnt-1 downto 0) := (others => '0');
begin
for I in HexDigits'RANGE loop
temp := temp * 16 + Hex_to_integer (HexDigits (I));
end loop;
return (to_unsigned(temp, DigCnt));
end Hex_to_unsigned;



Imagine you have some arithmetic module that you want to use in several
different projects. But sometimes you need it to work with an input data
width of 8 bit, sometimes 16 and sometimes 32 bits.

Uh-oh, someone mentioned generics to me a while ago, this has a familiar smell to it :)

Yes, generics are great for entities which meed to be flexible over data
widths. For functions the size should be determined using 'range et al
or if necessary with an extra parameter like the way to_unsigned does
when converting an integer (see above example).


You could either create 3 different entities, one each for using 8, 16
and 32 bits as input data width. That's three times the work, and if you
fix bugs, you have to do that in all three. So, instead you could just
use unconstrained input ports, and inside the module have all signal
declarations and such be relative to the port widths, which you can
determine using the abovementioned attributes. That way you can write
one single module that automatically works for all input bit widths. The
actual width is determined by the signal that is connected to the
instance's input ports.

I'm sure that will be something I will want to do at some point. I'm moving away from the schematic world where I did have to build multiple sized parts (it wasn't always convenient to just build the biggest and let the compiler remove the excess).

It can be good to have units that are flexible, but sometimes it just
isn't important and the extra time should be avoided. Certainly you
should know how to do it when needed, so it's good to practice.


That's one application for what you're trying to do. Probably not what
you had in mind there, but maybe you'll head in that direction later.

A mate of mine handed me a module name and a list of IO and asked me to play with it, it's all a bit simple and specific.

Thanks for the help :)

--

Rick C
 
> Aggregates can be PITA to use, but a bit easier in VHDL-2008.

I don't know if my tools support 2008, worth me looking into though.
Annoyingly is seems we can aggregate a vector and a bit to a larger vector quite easily, but not the other way around (you can do it on 2 lines and specify the ranges, but that doesn't look as neat).
 

Welcome to EDABoard.com

Sponsor

Back
Top